mirror of https://github.com/citusdata/citus.git
Merge branch 'citusdata:main' into sqlancer-test-gha
commit
24708600eb
|
@ -6,16 +6,19 @@ orbs:
|
|||
parameters:
|
||||
image_suffix:
|
||||
type: string
|
||||
default: '-vbab548a'
|
||||
default: '-v87fd773'
|
||||
pg14_version:
|
||||
type: string
|
||||
default: '14.8'
|
||||
default: '14.9'
|
||||
pg15_version:
|
||||
type: string
|
||||
default: '15.3'
|
||||
default: '15.4'
|
||||
pg16_version:
|
||||
type: string
|
||||
default: '16.0'
|
||||
upgrade_pg_versions:
|
||||
type: string
|
||||
default: '14.8-15.3'
|
||||
default: '14.9-15.4-16.0'
|
||||
style_checker_tools_version:
|
||||
type: string
|
||||
default: '0.8.18'
|
||||
|
@ -722,6 +725,10 @@ workflows:
|
|||
name: build-15
|
||||
pg_major: 15
|
||||
image_tag: '<< pipeline.parameters.pg15_version >>'
|
||||
- build:
|
||||
name: build-16
|
||||
pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.pg16_version >>'
|
||||
|
||||
- check-style
|
||||
- check-sql-snapshots
|
||||
|
@ -872,6 +879,79 @@ workflows:
|
|||
image: citus/failtester
|
||||
make: check-failure
|
||||
|
||||
- test-citus: &test-citus-16
|
||||
name: 'test-16_check-split'
|
||||
make: check-split
|
||||
pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.pg16_version >>'
|
||||
requires: [build-16]
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-enterprise'
|
||||
make: check-enterprise
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-enterprise-isolation'
|
||||
make: check-enterprise-isolation
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-enterprise-isolation-logicalrep-1'
|
||||
make: check-enterprise-isolation-logicalrep-1
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-enterprise-isolation-logicalrep-2'
|
||||
make: check-enterprise-isolation-logicalrep-2
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-enterprise-isolation-logicalrep-3'
|
||||
make: check-enterprise-isolation-logicalrep-3
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-enterprise-failure'
|
||||
image: citus/failtester
|
||||
make: check-enterprise-failure
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-multi'
|
||||
make: check-multi
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-multi-1'
|
||||
make: check-multi-1
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-mx'
|
||||
make: check-multi-mx
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-vanilla'
|
||||
make: check-vanilla
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-isolation'
|
||||
make: check-isolation
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-operations'
|
||||
make: check-operations
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-follower-cluster'
|
||||
make: check-follower-cluster
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-columnar'
|
||||
make: check-columnar
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-columnar-isolation'
|
||||
make: check-columnar-isolation
|
||||
- test-citus:
|
||||
<<: *test-citus-16
|
||||
name: 'test-16_check-failure'
|
||||
image: citus/failtester
|
||||
make: check-failure
|
||||
|
||||
- test-pytest:
|
||||
name: 'test-14_pytest'
|
||||
pg_major: 14
|
||||
|
@ -884,6 +964,12 @@ workflows:
|
|||
image_tag: '<< pipeline.parameters.pg15_version >>'
|
||||
requires: [build-15]
|
||||
|
||||
- test-pytest:
|
||||
name: 'test-16_pytest'
|
||||
pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.pg16_version >>'
|
||||
requires: [build-16]
|
||||
|
||||
- tap-test-citus:
|
||||
name: 'test-15_tap-cdc'
|
||||
suite: cdc
|
||||
|
@ -891,6 +977,13 @@ workflows:
|
|||
image_tag: '<< pipeline.parameters.pg15_version >>'
|
||||
requires: [build-15]
|
||||
|
||||
- tap-test-citus:
|
||||
name: 'test-16_tap-cdc'
|
||||
suite: cdc
|
||||
pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.pg16_version >>'
|
||||
requires: [build-16]
|
||||
|
||||
- test-arbitrary-configs:
|
||||
name: 'test-14_check-arbitrary-configs'
|
||||
pg_major: 14
|
||||
|
@ -903,6 +996,12 @@ workflows:
|
|||
image_tag: '<< pipeline.parameters.pg15_version >>'
|
||||
requires: [build-15]
|
||||
|
||||
- test-arbitrary-configs:
|
||||
name: 'test-16_check-arbitrary-configs'
|
||||
pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.pg16_version >>'
|
||||
requires: [build-16]
|
||||
|
||||
- test-query-generator:
|
||||
name: 'test-14_check-query-generator'
|
||||
pg_major: 14
|
||||
|
@ -915,6 +1014,12 @@ workflows:
|
|||
image_tag: '<< pipeline.parameters.pg15_version >>'
|
||||
requires: [build-15]
|
||||
|
||||
- test-query-generator:
|
||||
name: 'test-16_check-query-generator'
|
||||
pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.pg16_version >>'
|
||||
requires: [build-16]
|
||||
|
||||
- test-pg-upgrade:
|
||||
name: 'test-14-15_check-pg-upgrade'
|
||||
old_pg_major: 14
|
||||
|
@ -922,6 +1027,20 @@ workflows:
|
|||
image_tag: '<< pipeline.parameters.upgrade_pg_versions >>'
|
||||
requires: [build-14, build-15]
|
||||
|
||||
- test-pg-upgrade:
|
||||
name: 'test-15-16_check-pg-upgrade'
|
||||
old_pg_major: 15
|
||||
new_pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.upgrade_pg_versions >>'
|
||||
requires: [build-15, build-16]
|
||||
|
||||
- test-pg-upgrade:
|
||||
name: 'test-14-16_check-pg-upgrade'
|
||||
old_pg_major: 14
|
||||
new_pg_major: 16
|
||||
image_tag: '<< pipeline.parameters.upgrade_pg_versions >>'
|
||||
requires: [build-14, build-16]
|
||||
|
||||
- test-citus-upgrade:
|
||||
name: test-14_check-citus-upgrade
|
||||
pg_major: 14
|
||||
|
@ -968,7 +1087,28 @@ workflows:
|
|||
- test-15_check-split
|
||||
- test-15_check-arbitrary-configs
|
||||
- test-15_check-query-generator
|
||||
- test-16_check-multi
|
||||
- test-16_check-multi-1
|
||||
- test-16_check-mx
|
||||
- test-16_check-vanilla
|
||||
- test-16_check-isolation
|
||||
- test-16_check-operations
|
||||
- test-16_check-follower-cluster
|
||||
- test-16_check-columnar
|
||||
- test-16_check-columnar-isolation
|
||||
- test-16_check-failure
|
||||
- test-16_check-enterprise
|
||||
- test-16_check-enterprise-isolation
|
||||
- test-16_check-enterprise-isolation-logicalrep-1
|
||||
- test-16_check-enterprise-isolation-logicalrep-2
|
||||
- test-16_check-enterprise-isolation-logicalrep-3
|
||||
- test-16_check-enterprise-failure
|
||||
- test-16_check-split
|
||||
- test-16_check-arbitrary-configs
|
||||
- test-16_check-query-generator
|
||||
- test-14-15_check-pg-upgrade
|
||||
- test-15-16_check-pg-upgrade
|
||||
- test-14-16_check-pg-upgrade
|
||||
- test-14_check-citus-upgrade
|
||||
|
||||
- ch_benchmark:
|
||||
|
|
|
@ -28,6 +28,7 @@ src/backend/distributed/utils/citus_outfuncs.c -citus-style
|
|||
src/backend/distributed/deparser/ruleutils_13.c -citus-style
|
||||
src/backend/distributed/deparser/ruleutils_14.c -citus-style
|
||||
src/backend/distributed/deparser/ruleutils_15.c -citus-style
|
||||
src/backend/distributed/deparser/ruleutils_16.c -citus-style
|
||||
src/backend/distributed/commands/index_pg_source.c -citus-style
|
||||
|
||||
src/include/distributed/citus_nodes.h -citus-style
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Function to get the OS version
|
||||
get_rpm_os_version() {
|
||||
if [[ -f /etc/centos-release ]]; then
|
||||
cat /etc/centos-release | awk '{print $4}'
|
||||
elif [[ -f /etc/oracle-release ]]; then
|
||||
cat /etc/oracle-release | awk '{print $5}'
|
||||
else
|
||||
echo "Unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
package_type=${1}
|
||||
|
||||
# Since $HOME is set in GH_Actions as /github/home, pyenv fails to create virtualenvs.
|
||||
|
@ -10,11 +25,24 @@ pyenv versions
|
|||
pyenv virtualenv ${PACKAGING_PYTHON_VERSION} packaging_env
|
||||
pyenv activate packaging_env
|
||||
|
||||
git clone -b v0.8.24 --depth=1 https://github.com/citusdata/tools.git tools
|
||||
git clone -b v0.8.27 --depth=1 https://github.com/citusdata/tools.git tools
|
||||
python3 -m pip install -r tools/packaging_automation/requirements.txt
|
||||
|
||||
|
||||
echo "Package type: ${package_type}"
|
||||
echo "OS version: $(get_rpm_os_version)"
|
||||
|
||||
# if os version is centos 7 or oracle linux 7, then remove urllib3 with pip uninstall and install urllib3<2.0.0 with pip install
|
||||
if [[ ${package_type} == "rpm" && $(get_rpm_os_version) == 7* ]]; then
|
||||
python3 -m pip uninstall -y urllib3
|
||||
python3 -m pip install 'urllib3<2'
|
||||
fi
|
||||
|
||||
python3 -m tools.packaging_automation.validate_build_output --output_file output.log \
|
||||
--ignore_file .github/packaging/packaging_ignore.yml \
|
||||
--package_type ${package_type}
|
||||
pyenv deactivate
|
||||
# Set $HOME back to /github/home
|
||||
export HOME=${GITHUB_HOME}
|
||||
|
||||
# Print the output to the console
|
||||
|
|
|
@ -6,6 +6,10 @@ on:
|
|||
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
get_postgres_versions_from_file:
|
||||
|
@ -43,7 +47,7 @@ jobs:
|
|||
- oraclelinux-7
|
||||
- oraclelinux-8
|
||||
- centos-7
|
||||
- centos-8
|
||||
- almalinux-8
|
||||
- almalinux-9
|
||||
POSTGRES_VERSION: ${{ fromJson(needs.get_postgres_versions_from_file.outputs.pg_versions) }}
|
||||
|
||||
|
@ -73,8 +77,18 @@ jobs:
|
|||
|
||||
- name: Make
|
||||
run: |
|
||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
make CFLAGS="-Wno-missing-braces" -sj$(cat /proc/cpuinfo | grep "core id" | wc -l) 2>&1 | tee -a output.log
|
||||
|
||||
# Check the exit code of the make command
|
||||
make_exit_code=${PIPESTATUS[0]}
|
||||
|
||||
# If the make command returned a non-zero exit code, exit with the same code
|
||||
if [[ $make_exit_code -ne 0 ]]; then
|
||||
echo "make command failed with exit code $make_exit_code"
|
||||
exit $make_exit_code
|
||||
fi
|
||||
|
||||
- name: Make install
|
||||
run: |
|
||||
make CFLAGS="-Wno-missing-braces" install 2>&1 | tee -a output.log
|
||||
|
@ -109,10 +123,8 @@ jobs:
|
|||
- debian-buster-all
|
||||
- debian-bookworm-all
|
||||
- debian-bullseye-all
|
||||
- ubuntu-bionic-all
|
||||
- ubuntu-focal-all
|
||||
- ubuntu-jammy-all
|
||||
- ubuntu-kinetic-all
|
||||
|
||||
POSTGRES_VERSION: ${{ fromJson(needs.get_postgres_versions_from_file.outputs.pg_versions) }}
|
||||
|
||||
|
@ -141,9 +153,22 @@ jobs:
|
|||
make clean
|
||||
|
||||
- name: Make
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
make -sj$(cat /proc/cpuinfo | grep "core id" | wc -l) 2>&1 | tee -a output.log
|
||||
|
||||
# Check the exit code of the make command
|
||||
make_exit_code=${PIPESTATUS[0]}
|
||||
|
||||
# If the make command returned a non-zero exit code, exit with the same code
|
||||
if [[ $make_exit_code -ne 0 ]]; then
|
||||
echo "make command failed with exit code $make_exit_code"
|
||||
exit $make_exit_code
|
||||
fi
|
||||
|
||||
|
||||
- name: Make install
|
||||
run: |
|
||||
make install 2>&1 | tee -a output.log
|
||||
|
|
139
CHANGELOG.md
139
CHANGELOG.md
|
@ -1,3 +1,142 @@
|
|||
### citus v12.1.0 (September 12, 2023) ###
|
||||
|
||||
* Adds support for PostgreSQL 16.0 (#7173)
|
||||
|
||||
* Add `citus_schema_move()` function which moves tables within a
|
||||
distributed schema to another node (#7180)
|
||||
|
||||
* Adds `citus_pause_node()` UDF that allows pausing the node with given id
|
||||
(#7089)
|
||||
|
||||
* Makes sure to enforce shard level colocation with the GUC
|
||||
`citus.enable_non_colocated_router_query_pushdown` (#7076)
|
||||
|
||||
* Allows creating reference / distributed-schema tables from local tables added
|
||||
to metadata and that use identity columns (#7131)
|
||||
|
||||
* Propagates `BUFFER_USAGE_LIMIT` option in `VACUUM` and `ANALYZE` (#7114)
|
||||
|
||||
* Propagates `PROCESS_MAIN`, `SKIP_DATABASE_STATS`, `ONLY_DATABASE_STATS`
|
||||
options in `VACUUM` (#7114)
|
||||
|
||||
* Propagates `GENERIC_PLAN` option in `EXPLAIN` (#7141)
|
||||
|
||||
* Propagates "rules" option in `CREATE COLLATION` (#7185)
|
||||
|
||||
* Propagates `GRANT`/ `REVOKE` for database privileges (#7109)
|
||||
|
||||
* Adds TRUNCATE trigger support on Citus foreign tables (#7170)
|
||||
|
||||
* Removes `pg_send_cancellation` (#7135)
|
||||
|
||||
* Prevents unnecessarily pulling the data into coordinator for some
|
||||
`INSERT .. SELECT` queries that target a single-shard group (#7077)
|
||||
|
||||
* Makes sure that rebalancer throws an error if replication factor is greater
|
||||
than the shard allowed node count. Also makes sure to avoid moving a shard
|
||||
to a node that it already exists on. (#7074)
|
||||
|
||||
* Fixes a bug that may appear during 2PC recovery when there are multiple
|
||||
databases (#7174)
|
||||
|
||||
* Fixes a bug that could cause `COPY` logic to skip data in case of
|
||||
out-of-memory (#7152)
|
||||
|
||||
* Fixes a bug that causes an unexpected error when adding a column with
|
||||
a `NULL` constraint (#7093)
|
||||
|
||||
* Fixes `PROCESS_TOAST` default value to `true` (#7122)
|
||||
|
||||
* Improves the error thrown when there is datatype mismatch in `MERGE ON`
|
||||
(#7081)
|
||||
|
||||
### citus v12.0.0 (July 11, 2023) ###
|
||||
|
||||
* Adds support for schema-based sharding.
|
||||
While `citus.enable_schema_based_sharding` GUC allows sharding the database
|
||||
based on newly created schemas, `citus_schema_distribute()` allows doing so
|
||||
for the existing schemas. Distributed schemas used for sharding the database
|
||||
can be listed by using the view `citus_schemas`, monitored by using the view
|
||||
`citus_stat_schemas`, and undistributed by using the udf
|
||||
`citus_schema_undistribute()`
|
||||
(#6866, #6979, #6933, #6936 and many others)
|
||||
|
||||
* Supports MERGE command across non-colocated distributed tables/subqueries,
|
||||
reference tables and joins on non-distribution columns (#6927)
|
||||
|
||||
* Drops PG13 Support (#7002, #7007)
|
||||
|
||||
* Changes default rebalance strategy to by_disk_size (#7033)
|
||||
|
||||
* Changes by_disk_size rebalance strategy to have a base size (#7035)
|
||||
|
||||
* Improves citus_tables view performance (#7018)
|
||||
|
||||
* Improves tenant monitoring performance (#6868)
|
||||
|
||||
* Introduces the GUC `citus.stat_tenants_untracked_sample_rate` for sampling in
|
||||
tenant monitoring (#7026)
|
||||
|
||||
* Adds CPU usage to citus_stat_tenants (#6844)
|
||||
|
||||
* Propagates `ALTER SCHEMA .. OWNER TO ..` commands to worker (#6987)
|
||||
|
||||
* Allows `ADD COLUMN` in command string with other commands (#7032)
|
||||
|
||||
* Allows `DROP CONSTRAINT` in command string with other commands (#7012)
|
||||
|
||||
* Makes sure to properly handle index storage options for `ADD CONSTRAINT
|
||||
`/ COLUMN commands (#7032)
|
||||
|
||||
* Makes sure to properly handle `IF NOT EXISTS` for `ADD COLUMN` commands
|
||||
(#7032)
|
||||
|
||||
* Allows using generated identity column based on int/smallint when creating
|
||||
a distributed table with the limitation of not being able perform DMLs on
|
||||
identity columns from worker nodes (#7008)
|
||||
|
||||
* Supports custom cast from / to timestamptz in time partition management UDFs
|
||||
(#6923)
|
||||
|
||||
* Optimizes pushdown planner on memory and cpu (#6945)
|
||||
|
||||
* Changes citus_shard_sizes view's table_name column to shard_id (#7003)
|
||||
|
||||
* The GUC search_path is now reported when it is updated (#6983)
|
||||
|
||||
* Disables citus.enable_non_colocated_router_query_pushdown GUC by default to
|
||||
ensure generating a consistent distributed plan for the queries that
|
||||
reference non-colocated distributed tables (#6909)
|
||||
|
||||
* Disallows MERGE with filters that prune down to zero shards (#6946)
|
||||
|
||||
* Makes sure to take `shouldhaveshards` setting into account for a node when
|
||||
planning rebalance steps (#6887)
|
||||
|
||||
* Improves the compatibility with other extension by forwarding to existing
|
||||
emit_log_hook in our log hook (#6877)
|
||||
|
||||
* Fixes wrong result when using `NOT MATCHED` with MERGE command (#6943)
|
||||
|
||||
* Fixes querying the view `citus_shard_sizes` when there are too many shards
|
||||
(#7018)
|
||||
|
||||
* Fixes a bug related to type casts from other types to text/varchar (#6391)
|
||||
|
||||
* Fixes propagating `CREATE SCHEMA AUTHORIZATION ..` with no schema name
|
||||
(#7015)
|
||||
|
||||
* Fixes an error when creating a FOREIGN KEY without a name referencing a schema
|
||||
qualified table (#6986)
|
||||
|
||||
* Fixes a rare bug which mostly happens with queries that contain both outer
|
||||
join and where clauses (#6857)
|
||||
|
||||
* Fixes a bug related to propagation of schemas when pg_dist_node is empty
|
||||
(#6900)
|
||||
|
||||
* Fixes a crash when a query is locally executed with explain analyze (#6892)
|
||||
|
||||
### citus v11.3.0 (May 2, 2023) ###
|
||||
|
||||
* Introduces CDC implementation for Citus using logical replication
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# Microsoft Open Source Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
|
||||
Resources:
|
||||
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
|
16
Makefile
16
Makefile
|
@ -11,7 +11,7 @@ endif
|
|||
|
||||
include Makefile.global
|
||||
|
||||
all: extension pg_send_cancellation
|
||||
all: extension
|
||||
|
||||
|
||||
# build columnar only
|
||||
|
@ -40,22 +40,14 @@ clean-full:
|
|||
|
||||
install-downgrades:
|
||||
$(MAKE) -C src/backend/distributed/ install-downgrades
|
||||
install-all: install-headers install-pg_send_cancellation
|
||||
install-all: install-headers
|
||||
$(MAKE) -C src/backend/columnar/ install-all
|
||||
$(MAKE) -C src/backend/distributed/ install-all
|
||||
|
||||
# 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
|
||||
install: install-extension install-headers
|
||||
clean: clean-extension
|
||||
|
||||
# apply or check style
|
||||
reindent:
|
||||
|
|
56
README.md
56
README.md
|
@ -1,4 +1,4 @@
|
|||
| **<br/>The Citus database is 100% open source.<br/><img width=1000/><br/>Learn what's new in the [Citus 11.3 release blog](https://www.citusdata.com/blog/2023/05/05/whats-new-in-citus-11-3-multi-tenant-saas/) and the [Citus Updates page](https://www.citusdata.com/updates/).<br/><br/>**|
|
||||
| **<br/>The Citus database is 100% open source.<br/><img width=1000/><br/>Learn what's new in the [Citus 12.0 release blog](https://www.citusdata.com/blog/2023/07/18/citus-12-schema-based-sharding-comes-to-postgres/) and the [Citus Updates page](https://www.citusdata.com/updates/).<br/><br/>**|
|
||||
|---|
|
||||
<br/>
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
|||
|
||||
[](https://docs.citusdata.com/)
|
||||
[](https://stackoverflow.com/questions/tagged/citus)
|
||||
[Slack](https://citus-public.slack.com/)
|
||||
[](https://slack.citusdata.com/)
|
||||
[](https://app.codecov.io/gh/citusdata/citus)
|
||||
[](https://twitter.com/intent/follow?screen_name=citusdata)
|
||||
|
||||
|
@ -38,6 +38,7 @@ Since Citus is an extension to Postgres, you can use Citus with the latest Postg
|
|||
- [Why Citus?](#why-citus)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Using Citus](#using-citus)
|
||||
- [Schema-based sharding](#schema-based-sharding)
|
||||
- [Setting up with High Availability](#setting-up-with-high-availability)
|
||||
- [Documentation](#documentation)
|
||||
- [Architecture](#architecture)
|
||||
|
@ -94,14 +95,14 @@ Install packages on Ubuntu / Debian:
|
|||
```bash
|
||||
curl https://install.citusdata.com/community/deb.sh > add-citus-repo.sh
|
||||
sudo bash add-citus-repo.sh
|
||||
sudo apt-get -y install postgresql-15-citus-11.3
|
||||
sudo apt-get -y install postgresql-15-citus-12.0
|
||||
```
|
||||
|
||||
Install packages on CentOS / Red Hat:
|
||||
```bash
|
||||
curl https://install.citusdata.com/community/rpm.sh > add-citus-repo.sh
|
||||
sudo bash add-citus-repo.sh
|
||||
sudo yum install -y citus113_15
|
||||
sudo yum install -y citus120_15
|
||||
```
|
||||
|
||||
To add Citus to your local PostgreSQL database, add the following to `postgresql.conf`:
|
||||
|
@ -347,6 +348,45 @@ When using columnar storage, you should only load data in batch using `COPY` or
|
|||
|
||||
To learn more about columnar storage, check out the [columnar storage README](https://github.com/citusdata/citus/blob/master/src/backend/columnar/README.md).
|
||||
|
||||
## Schema-based sharding
|
||||
|
||||
Available since Citus 12.0, [schema-based sharding](https://docs.citusdata.com/en/stable/get_started/concepts.html#schema-based-sharding) is the shared database, separate schema model, the schema becomes the logical shard within the database. Multi-tenant apps can a use a schema per tenant to easily shard along the tenant dimension. Query changes are not required and the application usually only needs a small modification to set the proper search_path when switching tenants. Schema-based sharding is an ideal solution for microservices, and for ISVs deploying applications that cannot undergo the changes required to onboard row-based sharding.
|
||||
|
||||
### Creating distributed schemas
|
||||
|
||||
You can turn an existing schema into a distributed schema by calling `citus_schema_distribute`:
|
||||
|
||||
```sql
|
||||
SELECT citus_schema_distribute('user_service');
|
||||
```
|
||||
|
||||
Alternatively, you can set `citus.enable_schema_based_sharding` to have all newly created schemas be automatically converted into distributed schemas:
|
||||
|
||||
```sql
|
||||
SET citus.enable_schema_based_sharding TO ON;
|
||||
|
||||
CREATE SCHEMA AUTHORIZATION user_service;
|
||||
CREATE SCHEMA AUTHORIZATION time_service;
|
||||
CREATE SCHEMA AUTHORIZATION ping_service;
|
||||
```
|
||||
|
||||
### Running queries
|
||||
|
||||
Queries will be properly routed to schemas based on `search_path` or by explicitly using the schema name in the query.
|
||||
|
||||
For [microservices](https://docs.citusdata.com/en/stable/get_started/tutorial_microservices.html) you would create a USER per service matching the schema name, hence the default `search_path` would contain the schema name. When connected the user queries would be automatically routed and no changes to the microservice would be required.
|
||||
|
||||
```sql
|
||||
CREATE USER user_service;
|
||||
CREATE SCHEMA AUTHORIZATION user_service;
|
||||
```
|
||||
|
||||
For typical multi-tenant applications, you would set the search path to the tenant schema name in your application:
|
||||
|
||||
```sql
|
||||
SET search_path = tenant_name, public;
|
||||
```
|
||||
|
||||
## Setting up with High Availability
|
||||
|
||||
One of the most popular high availability solutions for PostgreSQL, [Patroni 3.0](https://github.com/zalando/patroni), has [first class support for Citus 10.0 and above](https://patroni.readthedocs.io/en/latest/citus.html#citus), additionally since Citus 11.2 ships with improvements for smoother node switchover in Patroni.
|
||||
|
@ -414,6 +454,8 @@ Citus is uniquely capable of scaling both analytical and transactional workloads
|
|||
|
||||
Example multi-tenant SaaS users: [Copper](https://www.citusdata.com/customers/copper), [Salesloft](https://fivetran.com/case-studies/replicating-sharded-databases-a-case-study-of-salesloft-citus-data-and-fivetran), [ConvertFlow](https://www.citusdata.com/customers/convertflow)
|
||||
|
||||
- **[Microservices](https://docs.citusdata.com/en/stable/get_started/tutorial_microservices.html)**: Citus supports schema based sharding, which allows distributing regular database schemas across many machines. This sharding methodology fits nicely with typical Microservices architecture, where storage is fully owned by the service hence can’t share the same schema definition with other tenants. Citus allows distributing horizontally scalable state across services, solving one of the [main problems](https://stackoverflow.blog/2020/11/23/the-macro-problem-with-microservices/) of microservices.
|
||||
|
||||
- **Geospatial**:
|
||||
Because of the powerful [PostGIS](https://postgis.net/) extension to Postgres that adds support for geographic objects into Postgres, many people run spatial/GIS applications on top of Postgres. And since spatial location information has become part of our daily life, well, there are more geospatial applications than ever. When your Postgres database needs to scale out to handle an increased workload, Citus is a good fit.
|
||||
|
||||
|
@ -431,6 +473,12 @@ Citus is uniquely capable of scaling both analytical and transactional workloads
|
|||
|
||||
Citus is built on and of open source, and we welcome your contributions. The [CONTRIBUTING.md](CONTRIBUTING.md) file explains how to get started developing the Citus extension itself and our code quality guidelines.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Stay Connected
|
||||
|
||||
- **Twitter**: Follow us [@citusdata](https://twitter.com/citusdata) to track the latest posts & updates on what’s happening.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for Citus 12.0devel.
|
||||
# Generated by GNU Autoconf 2.69 for Citus 12.2devel.
|
||||
#
|
||||
#
|
||||
# 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='12.0devel'
|
||||
PACKAGE_STRING='Citus 12.0devel'
|
||||
PACKAGE_VERSION='12.2devel'
|
||||
PACKAGE_STRING='Citus 12.2devel'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
|
@ -1262,7 +1262,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 12.0devel to adapt to many kinds of systems.
|
||||
\`configure' configures Citus 12.2devel to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
|
@ -1324,7 +1324,7 @@ fi
|
|||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of Citus 12.0devel:";;
|
||||
short | recursive ) echo "Configuration of Citus 12.2devel:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
|
@ -1429,7 +1429,7 @@ fi
|
|||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
Citus configure 12.0devel
|
||||
Citus configure 12.2devel
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
|
@ -1912,7 +1912,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 12.0devel, which was
|
||||
It was created by Citus $as_me 12.2devel, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
|
@ -2588,7 +2588,7 @@ fi
|
|||
if test "$with_pg_version_check" = no; then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: building against PostgreSQL $version_num (skipped compatibility check)" >&5
|
||||
$as_echo "$as_me: building against PostgreSQL $version_num (skipped compatibility check)" >&6;}
|
||||
elif test "$version_num" != '14' -a "$version_num" != '15'; then
|
||||
elif test "$version_num" != '14' -a "$version_num" != '15' -a "$version_num" != '16'; then
|
||||
as_fn_error $? "Citus is not compatible with the detected PostgreSQL version ${version_num}." "$LINENO" 5
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: building against PostgreSQL $version_num" >&5
|
||||
|
@ -5393,7 +5393,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 12.0devel, which was
|
||||
This file was extended by Citus $as_me 12.2devel, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
|
@ -5455,7 +5455,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 12.0devel
|
||||
Citus config.status 12.2devel
|
||||
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], [12.0devel])
|
||||
AC_INIT([Citus], [12.2devel])
|
||||
AC_COPYRIGHT([Copyright (c) Citus Data, Inc.])
|
||||
|
||||
# we'll need sed and awk for some of the version commands
|
||||
|
@ -80,7 +80,7 @@ AC_SUBST(with_pg_version_check)
|
|||
|
||||
if test "$with_pg_version_check" = no; then
|
||||
AC_MSG_NOTICE([building against PostgreSQL $version_num (skipped compatibility check)])
|
||||
elif test "$version_num" != '14' -a "$version_num" != '15'; then
|
||||
elif test "$version_num" != '14' -a "$version_num" != '15' -a "$version_num" != '16'; then
|
||||
AC_MSG_ERROR([Citus is not compatible with the detected PostgreSQL version ${version_num}.])
|
||||
else
|
||||
AC_MSG_NOTICE([building against PostgreSQL $version_num])
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Columnar extension
|
||||
comment = 'Citus Columnar extension'
|
||||
default_version = '11.3-1'
|
||||
default_version = '12.2-1'
|
||||
module_pathname = '$libdir/citus_columnar'
|
||||
relocatable = false
|
||||
schema = pg_catalog
|
||||
|
|
|
@ -18,11 +18,16 @@
|
|||
#include "lib/stringinfo.h"
|
||||
|
||||
#include "columnar/columnar_compression.h"
|
||||
#include "distributed/pg_version_constants.h"
|
||||
|
||||
#if HAVE_CITUS_LIBLZ4
|
||||
#include <lz4.h>
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "varatt.h"
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBZSTD
|
||||
#include <zstd.h>
|
||||
#endif
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
#include "optimizer/paths.h"
|
||||
#include "optimizer/plancat.h"
|
||||
#include "optimizer/restrictinfo.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parsetree.h"
|
||||
#endif
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/relcache.h"
|
||||
|
@ -127,6 +131,9 @@ static List * set_deparse_context_planstate(List *dpcontext, Node *node,
|
|||
/* other helpers */
|
||||
static List * ColumnarVarNeeded(ColumnarScanState *columnarScanState);
|
||||
static Bitmapset * ColumnarAttrNeeded(ScanState *ss);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
static Bitmapset * fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns);
|
||||
#endif
|
||||
|
||||
/* saved hook value in case of unload */
|
||||
static set_rel_pathlist_hook_type PreviousSetRelPathlistHook = NULL;
|
||||
|
@ -535,7 +542,7 @@ ColumnarIndexScanAdditionalCost(PlannerInfo *root, RelOptInfo *rel,
|
|||
* "anti-correlated" (-1) since both help us avoiding from reading the
|
||||
* same stripe again and again.
|
||||
*/
|
||||
double absIndexCorrelation = Abs(indexCorrelation);
|
||||
double absIndexCorrelation = float_abs(indexCorrelation);
|
||||
|
||||
/*
|
||||
* To estimate the number of stripes that we need to read, we do linear
|
||||
|
@ -654,7 +661,7 @@ CheckVarStats(PlannerInfo *root, Var *var, Oid sortop, float4 *absVarCorrelation
|
|||
* If the Var is not highly correlated, then the chunk's min/max bounds
|
||||
* will be nearly useless.
|
||||
*/
|
||||
if (Abs(varCorrelation) < ColumnarQualPushdownCorrelationThreshold)
|
||||
if (float_abs(varCorrelation) < ColumnarQualPushdownCorrelationThreshold)
|
||||
{
|
||||
if (absVarCorrelation)
|
||||
{
|
||||
|
@ -662,7 +669,7 @@ CheckVarStats(PlannerInfo *root, Var *var, Oid sortop, float4 *absVarCorrelation
|
|||
* Report absVarCorrelation if caller wants to know why given
|
||||
* var is rejected.
|
||||
*/
|
||||
*absVarCorrelation = Abs(varCorrelation);
|
||||
*absVarCorrelation = float_abs(varCorrelation);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1371,7 +1378,43 @@ AddColumnarScanPath(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte,
|
|||
cpath->custom_private = list_make2(NIL, NIL);
|
||||
}
|
||||
|
||||
int numberOfColumnsRead = bms_num_members(rte->selectedCols);
|
||||
int numberOfColumnsRead = 0;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
if (rte->perminfoindex > 0)
|
||||
{
|
||||
/*
|
||||
* If perminfoindex > 0, that means that this relation's permission info
|
||||
* is directly found in the list of rteperminfos of the Query(root->parse)
|
||||
* So, all we have to do here is retrieve that info.
|
||||
*/
|
||||
RTEPermissionInfo *perminfo = getRTEPermissionInfo(root->parse->rteperminfos,
|
||||
rte);
|
||||
numberOfColumnsRead = bms_num_members(perminfo->selectedCols);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* If perminfoindex = 0, that means we are skipping the check for permission info
|
||||
* for this relation, which means that it's either a partition or an inheritance child.
|
||||
* In these cases, we need to access the permission info of the top parent of this relation.
|
||||
* After thorough checking, we found that the index of the top parent pointing to the correct
|
||||
* range table entry in Query's range tables (root->parse->rtable) is found under
|
||||
* RelOptInfo rel->top_parent->relid.
|
||||
* For reference, check expand_partitioned_rtentry and expand_inherited_rtentry PG functions
|
||||
*/
|
||||
Assert(rel->top_parent);
|
||||
RangeTblEntry *parent_rte = rt_fetch(rel->top_parent->relid, root->parse->rtable);
|
||||
RTEPermissionInfo *perminfo = getRTEPermissionInfo(root->parse->rteperminfos,
|
||||
parent_rte);
|
||||
numberOfColumnsRead = bms_num_members(fixup_inherited_columns(perminfo->relid,
|
||||
rte->relid,
|
||||
perminfo->
|
||||
selectedCols));
|
||||
}
|
||||
#else
|
||||
numberOfColumnsRead = bms_num_members(rte->selectedCols);
|
||||
#endif
|
||||
|
||||
int numberOfClausesPushed = list_length(allClauses);
|
||||
|
||||
CostColumnarScan(root, rel, rte->relid, cpath, numberOfColumnsRead,
|
||||
|
@ -1391,6 +1434,69 @@ AddColumnarScanPath(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte,
|
|||
}
|
||||
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/*
|
||||
* fixup_inherited_columns
|
||||
*
|
||||
* Exact function Copied from PG16 as it's static.
|
||||
*
|
||||
* When user is querying on a table with children, it implicitly accesses
|
||||
* child tables also. So, we also need to check security label of child
|
||||
* tables and columns, but there is no guarantee attribute numbers are
|
||||
* same between the parent and children.
|
||||
* It returns a bitmapset which contains attribute number of the child
|
||||
* table based on the given bitmapset of the parent.
|
||||
*/
|
||||
static Bitmapset *
|
||||
fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
|
||||
{
|
||||
Bitmapset *result = NULL;
|
||||
|
||||
/*
|
||||
* obviously, no need to do anything here
|
||||
*/
|
||||
if (parentId == childId)
|
||||
{
|
||||
return columns;
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
while ((index = bms_next_member(columns, index)) >= 0)
|
||||
{
|
||||
/* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
|
||||
AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
|
||||
|
||||
/*
|
||||
* whole-row-reference shall be fixed-up later
|
||||
*/
|
||||
if (attno == InvalidAttrNumber)
|
||||
{
|
||||
result = bms_add_member(result, index);
|
||||
continue;
|
||||
}
|
||||
|
||||
char *attname = get_attname(parentId, attno, false);
|
||||
attno = get_attnum(childId, attname);
|
||||
if (attno == InvalidAttrNumber)
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for attribute %s of relation %u",
|
||||
attname, childId);
|
||||
}
|
||||
|
||||
result = bms_add_member(result,
|
||||
attno - FirstLowInvalidHeapAttributeNumber);
|
||||
|
||||
pfree(attname);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* CostColumnarScan calculates the cost of scanning the columnar table. The
|
||||
* cost is estimated by using all stripe metadata to estimate based on the
|
||||
|
@ -1435,7 +1541,8 @@ ColumnarPerStripeScanCost(RelOptInfo *rel, Oid relationId, int numberOfColumnsRe
|
|||
ereport(ERROR, (errmsg("could not open relation with OID %u", relationId)));
|
||||
}
|
||||
|
||||
List *stripeList = StripesForRelfilenode(relation->rd_node);
|
||||
List *stripeList = StripesForRelfilelocator(RelationPhysicalIdentifier_compat(
|
||||
relation));
|
||||
RelationClose(relation);
|
||||
|
||||
uint32 maxColumnCount = 0;
|
||||
|
@ -1492,7 +1599,8 @@ ColumnarTableStripeCount(Oid relationId)
|
|||
ereport(ERROR, (errmsg("could not open relation with OID %u", relationId)));
|
||||
}
|
||||
|
||||
List *stripeList = StripesForRelfilenode(relation->rd_node);
|
||||
List *stripeList = StripesForRelfilelocator(RelationPhysicalIdentifier_compat(
|
||||
relation));
|
||||
int stripeCount = list_length(stripeList);
|
||||
RelationClose(relation);
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@
|
|||
#include "miscadmin.h"
|
||||
#include "nodes/execnodes.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "parser/parse_relation.h"
|
||||
#endif
|
||||
#include "port.h"
|
||||
#include "storage/fd.h"
|
||||
#include "storage/lmgr.h"
|
||||
|
@ -57,7 +60,12 @@
|
|||
#include "utils/memutils.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/rel.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "storage/relfilelocator.h"
|
||||
#include "utils/relfilenumbermap.h"
|
||||
#else
|
||||
#include "utils/relfilenodemap.h"
|
||||
#endif
|
||||
|
||||
#define COLUMNAR_RELOPTION_NAMESPACE "columnar"
|
||||
#define SLOW_METADATA_ACCESS_WARNING \
|
||||
|
@ -112,7 +120,7 @@ static Oid ColumnarChunkGroupRelationId(void);
|
|||
static Oid ColumnarChunkIndexRelationId(void);
|
||||
static Oid ColumnarChunkGroupIndexRelationId(void);
|
||||
static Oid ColumnarNamespaceId(void);
|
||||
static uint64 LookupStorageId(RelFileNode relfilenode);
|
||||
static uint64 LookupStorageId(RelFileLocator relfilelocator);
|
||||
static uint64 GetHighestUsedRowNumber(uint64 storageId);
|
||||
static void DeleteStorageFromColumnarMetadataTable(Oid metadataTableId,
|
||||
AttrNumber storageIdAtrrNumber,
|
||||
|
@ -591,14 +599,15 @@ ReadColumnarOptions(Oid regclass, ColumnarOptions *options)
|
|||
* of columnar.chunk.
|
||||
*/
|
||||
void
|
||||
SaveStripeSkipList(RelFileNode relfilenode, uint64 stripe, StripeSkipList *chunkList,
|
||||
SaveStripeSkipList(RelFileLocator relfilelocator, uint64 stripe,
|
||||
StripeSkipList *chunkList,
|
||||
TupleDesc tupleDescriptor)
|
||||
{
|
||||
uint32 columnIndex = 0;
|
||||
uint32 chunkIndex = 0;
|
||||
uint32 columnCount = chunkList->columnCount;
|
||||
|
||||
uint64 storageId = LookupStorageId(relfilenode);
|
||||
uint64 storageId = LookupStorageId(relfilelocator);
|
||||
Oid columnarChunkOid = ColumnarChunkRelationId();
|
||||
Relation columnarChunk = table_open(columnarChunkOid, RowExclusiveLock);
|
||||
ModifyState *modifyState = StartModifyRelation(columnarChunk);
|
||||
|
@ -657,10 +666,10 @@ SaveStripeSkipList(RelFileNode relfilenode, uint64 stripe, StripeSkipList *chunk
|
|||
* SaveChunkGroups saves the metadata for given chunk groups in columnar.chunk_group.
|
||||
*/
|
||||
void
|
||||
SaveChunkGroups(RelFileNode relfilenode, uint64 stripe,
|
||||
SaveChunkGroups(RelFileLocator relfilelocator, uint64 stripe,
|
||||
List *chunkGroupRowCounts)
|
||||
{
|
||||
uint64 storageId = LookupStorageId(relfilenode);
|
||||
uint64 storageId = LookupStorageId(relfilelocator);
|
||||
Oid columnarChunkGroupOid = ColumnarChunkGroupRelationId();
|
||||
Relation columnarChunkGroup = table_open(columnarChunkGroupOid, RowExclusiveLock);
|
||||
ModifyState *modifyState = StartModifyRelation(columnarChunkGroup);
|
||||
|
@ -693,7 +702,8 @@ SaveChunkGroups(RelFileNode relfilenode, uint64 stripe,
|
|||
* ReadStripeSkipList fetches chunk metadata for a given stripe.
|
||||
*/
|
||||
StripeSkipList *
|
||||
ReadStripeSkipList(RelFileNode relfilenode, uint64 stripe, TupleDesc tupleDescriptor,
|
||||
ReadStripeSkipList(RelFileLocator relfilelocator, uint64 stripe,
|
||||
TupleDesc tupleDescriptor,
|
||||
uint32 chunkCount, Snapshot snapshot)
|
||||
{
|
||||
int32 columnIndex = 0;
|
||||
|
@ -701,15 +711,15 @@ ReadStripeSkipList(RelFileNode relfilenode, uint64 stripe, TupleDesc tupleDescri
|
|||
uint32 columnCount = tupleDescriptor->natts;
|
||||
ScanKeyData scanKey[2];
|
||||
|
||||
uint64 storageId = LookupStorageId(relfilenode);
|
||||
uint64 storageId = LookupStorageId(relfilelocator);
|
||||
|
||||
Oid columnarChunkOid = ColumnarChunkRelationId();
|
||||
Relation columnarChunk = table_open(columnarChunkOid, AccessShareLock);
|
||||
|
||||
ScanKeyInit(&scanKey[0], Anum_columnar_chunk_storageid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, UInt64GetDatum(storageId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(storageId));
|
||||
ScanKeyInit(&scanKey[1], Anum_columnar_chunk_stripe,
|
||||
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(stripe));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(stripe));
|
||||
|
||||
Oid indexId = ColumnarChunkIndexRelationId();
|
||||
bool indexOk = OidIsValid(indexId);
|
||||
|
@ -915,7 +925,7 @@ StripeMetadataLookupRowNumber(Relation relation, uint64 rowNumber, Snapshot snap
|
|||
uint64 storageId = ColumnarStorageGetStorageId(relation, false);
|
||||
ScanKeyData scanKey[2];
|
||||
ScanKeyInit(&scanKey[0], Anum_columnar_stripe_storageid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(storageId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(storageId));
|
||||
|
||||
StrategyNumber strategyNumber = InvalidStrategy;
|
||||
RegProcedure procedure = InvalidOid;
|
||||
|
@ -930,7 +940,7 @@ StripeMetadataLookupRowNumber(Relation relation, uint64 rowNumber, Snapshot snap
|
|||
procedure = F_INT8GT;
|
||||
}
|
||||
ScanKeyInit(&scanKey[1], Anum_columnar_stripe_first_row_number,
|
||||
strategyNumber, procedure, UInt64GetDatum(rowNumber));
|
||||
strategyNumber, procedure, Int64GetDatum(rowNumber));
|
||||
|
||||
Relation columnarStripes = table_open(ColumnarStripeRelationId(), AccessShareLock);
|
||||
|
||||
|
@ -1081,7 +1091,7 @@ FindStripeWithHighestRowNumber(Relation relation, Snapshot snapshot)
|
|||
uint64 storageId = ColumnarStorageGetStorageId(relation, false);
|
||||
ScanKeyData scanKey[1];
|
||||
ScanKeyInit(&scanKey[0], Anum_columnar_stripe_storageid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(storageId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(storageId));
|
||||
|
||||
Relation columnarStripes = table_open(ColumnarStripeRelationId(), AccessShareLock);
|
||||
|
||||
|
@ -1143,9 +1153,9 @@ ReadChunkGroupRowCounts(uint64 storageId, uint64 stripe, uint32 chunkGroupCount,
|
|||
|
||||
ScanKeyData scanKey[2];
|
||||
ScanKeyInit(&scanKey[0], Anum_columnar_chunkgroup_storageid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, UInt64GetDatum(storageId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(storageId));
|
||||
ScanKeyInit(&scanKey[1], Anum_columnar_chunkgroup_stripe,
|
||||
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(stripe));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(stripe));
|
||||
|
||||
Oid indexId = ColumnarChunkGroupIndexRelationId();
|
||||
bool indexOk = OidIsValid(indexId);
|
||||
|
@ -1235,13 +1245,13 @@ InsertEmptyStripeMetadataRow(uint64 storageId, uint64 stripeId, uint32 columnCou
|
|||
|
||||
|
||||
/*
|
||||
* StripesForRelfilenode returns a list of StripeMetadata for stripes
|
||||
* StripesForRelfilelocator returns a list of StripeMetadata for stripes
|
||||
* of the given relfilenode.
|
||||
*/
|
||||
List *
|
||||
StripesForRelfilenode(RelFileNode relfilenode)
|
||||
StripesForRelfilelocator(RelFileLocator relfilelocator)
|
||||
{
|
||||
uint64 storageId = LookupStorageId(relfilenode);
|
||||
uint64 storageId = LookupStorageId(relfilelocator);
|
||||
|
||||
return ReadDataFileStripeList(storageId, GetTransactionSnapshot());
|
||||
}
|
||||
|
@ -1256,9 +1266,9 @@ StripesForRelfilenode(RelFileNode relfilenode)
|
|||
* returns 0.
|
||||
*/
|
||||
uint64
|
||||
GetHighestUsedAddress(RelFileNode relfilenode)
|
||||
GetHighestUsedAddress(RelFileLocator relfilelocator)
|
||||
{
|
||||
uint64 storageId = LookupStorageId(relfilenode);
|
||||
uint64 storageId = LookupStorageId(relfilelocator);
|
||||
|
||||
uint64 highestUsedAddress = 0;
|
||||
uint64 highestUsedId = 0;
|
||||
|
@ -1372,9 +1382,9 @@ UpdateStripeMetadataRow(uint64 storageId, uint64 stripeId, bool *update,
|
|||
|
||||
ScanKeyData scanKey[2];
|
||||
ScanKeyInit(&scanKey[0], Anum_columnar_stripe_storageid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(storageId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(storageId));
|
||||
ScanKeyInit(&scanKey[1], Anum_columnar_stripe_stripe,
|
||||
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(stripeId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(stripeId));
|
||||
|
||||
Oid columnarStripesOid = ColumnarStripeRelationId();
|
||||
|
||||
|
@ -1451,7 +1461,7 @@ ReadDataFileStripeList(uint64 storageId, Snapshot snapshot)
|
|||
HeapTuple heapTuple;
|
||||
|
||||
ScanKeyInit(&scanKey[0], Anum_columnar_stripe_storageid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(storageId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(storageId));
|
||||
|
||||
Oid columnarStripesOid = ColumnarStripeRelationId();
|
||||
|
||||
|
@ -1539,7 +1549,7 @@ BuildStripeMetadata(Relation columnarStripes, HeapTuple heapTuple)
|
|||
* metadata tables.
|
||||
*/
|
||||
void
|
||||
DeleteMetadataRows(RelFileNode relfilenode)
|
||||
DeleteMetadataRows(RelFileLocator relfilelocator)
|
||||
{
|
||||
/*
|
||||
* During a restore for binary upgrade, metadata tables and indexes may or
|
||||
|
@ -1550,7 +1560,7 @@ DeleteMetadataRows(RelFileNode relfilenode)
|
|||
return;
|
||||
}
|
||||
|
||||
uint64 storageId = LookupStorageId(relfilenode);
|
||||
uint64 storageId = LookupStorageId(relfilelocator);
|
||||
|
||||
DeleteStorageFromColumnarMetadataTable(ColumnarStripeRelationId(),
|
||||
Anum_columnar_stripe_storageid,
|
||||
|
@ -1578,7 +1588,7 @@ DeleteStorageFromColumnarMetadataTable(Oid metadataTableId,
|
|||
{
|
||||
ScanKeyData scanKey[1];
|
||||
ScanKeyInit(&scanKey[0], storageIdAtrrNumber, BTEqualStrategyNumber,
|
||||
F_INT8EQ, UInt64GetDatum(storageId));
|
||||
F_INT8EQ, Int64GetDatum(storageId));
|
||||
|
||||
Relation metadataTable = try_relation_open(metadataTableId, AccessShareLock);
|
||||
if (metadataTable == NULL)
|
||||
|
@ -1713,7 +1723,14 @@ create_estate_for_relation(Relation rel)
|
|||
rte->relid = RelationGetRelid(rel);
|
||||
rte->relkind = rel->rd_rel->relkind;
|
||||
rte->rellockmode = AccessShareLock;
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
List *perminfos = NIL;
|
||||
addRTEPermissionInfo(&perminfos, rte);
|
||||
ExecInitRangeTable(estate, list_make1(rte), perminfos);
|
||||
#else
|
||||
ExecInitRangeTable(estate, list_make1(rte));
|
||||
#endif
|
||||
|
||||
estate->es_output_cid = GetCurrentCommandId(true);
|
||||
|
||||
|
@ -1917,10 +1934,11 @@ ColumnarNamespaceId(void)
|
|||
* false if the relation doesn't have a meta page yet.
|
||||
*/
|
||||
static uint64
|
||||
LookupStorageId(RelFileNode relfilenode)
|
||||
LookupStorageId(RelFileLocator relfilelocator)
|
||||
{
|
||||
Oid relationId = RelidByRelfilenode(relfilenode.spcNode,
|
||||
relfilenode.relNode);
|
||||
Oid relationId = RelidByRelfilenumber(RelationTablespace_compat(relfilelocator),
|
||||
RelationPhysicalIdentifierNumber_compat(
|
||||
relfilelocator));
|
||||
|
||||
Relation relation = relation_open(relationId, AccessShareLock);
|
||||
uint64 storageId = ColumnarStorageGetStorageId(relation, false);
|
||||
|
@ -1951,7 +1969,7 @@ columnar_relation_storageid(PG_FUNCTION_ARGS)
|
|||
Oid relationId = PG_GETARG_OID(0);
|
||||
Relation relation = relation_open(relationId, AccessShareLock);
|
||||
|
||||
if (!pg_class_ownercheck(relationId, GetUserId()))
|
||||
if (!object_ownercheck(RelationRelationId, relationId, GetUserId()))
|
||||
{
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE,
|
||||
get_rel_name(relationId));
|
||||
|
|
|
@ -254,8 +254,9 @@ ColumnarReadFlushPendingWrites(ColumnarReadState *readState)
|
|||
{
|
||||
Assert(!readState->snapshotRegisteredByUs);
|
||||
|
||||
Oid relfilenode = readState->relation->rd_node.relNode;
|
||||
FlushWriteStateForRelfilenode(relfilenode, GetCurrentSubTransactionId());
|
||||
RelFileNumber relfilenumber = RelationPhysicalIdentifierNumber_compat(
|
||||
RelationPhysicalIdentifier_compat(readState->relation));
|
||||
FlushWriteStateForRelfilenumber(relfilenumber, GetCurrentSubTransactionId());
|
||||
|
||||
if (readState->snapshot == InvalidSnapshot || !IsMVCCSnapshot(readState->snapshot))
|
||||
{
|
||||
|
@ -984,7 +985,8 @@ ColumnarTableRowCount(Relation relation)
|
|||
{
|
||||
ListCell *stripeMetadataCell = NULL;
|
||||
uint64 totalRowCount = 0;
|
||||
List *stripeList = StripesForRelfilenode(relation->rd_node);
|
||||
List *stripeList = StripesForRelfilelocator(RelationPhysicalIdentifier_compat(
|
||||
relation));
|
||||
|
||||
foreach(stripeMetadataCell, stripeList)
|
||||
{
|
||||
|
@ -1012,7 +1014,8 @@ LoadFilteredStripeBuffers(Relation relation, StripeMetadata *stripeMetadata,
|
|||
|
||||
bool *projectedColumnMask = ProjectedColumnMask(columnCount, projectedColumnList);
|
||||
|
||||
StripeSkipList *stripeSkipList = ReadStripeSkipList(relation->rd_node,
|
||||
StripeSkipList *stripeSkipList = ReadStripeSkipList(RelationPhysicalIdentifier_compat(
|
||||
relation),
|
||||
stripeMetadata->id,
|
||||
tupleDescriptor,
|
||||
stripeMetadata->chunkCount,
|
||||
|
|
|
@ -169,7 +169,11 @@ ColumnarStorageInit(SMgrRelation srel, uint64 storageId)
|
|||
}
|
||||
|
||||
/* create two pages */
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
PGIOAlignedBlock block;
|
||||
#else
|
||||
PGAlignedBlock block;
|
||||
#endif
|
||||
Page page = block.data;
|
||||
|
||||
/* write metapage */
|
||||
|
@ -188,7 +192,7 @@ ColumnarStorageInit(SMgrRelation srel, uint64 storageId)
|
|||
(char *) &metapage, sizeof(ColumnarMetapage));
|
||||
phdr->pd_lower += sizeof(ColumnarMetapage);
|
||||
|
||||
log_newpage(&srel->smgr_rnode.node, MAIN_FORKNUM,
|
||||
log_newpage(RelationPhysicalIdentifierBackend_compat(&srel), MAIN_FORKNUM,
|
||||
COLUMNAR_METAPAGE_BLOCKNO, page, true);
|
||||
PageSetChecksumInplace(page, COLUMNAR_METAPAGE_BLOCKNO);
|
||||
smgrextend(srel, MAIN_FORKNUM, COLUMNAR_METAPAGE_BLOCKNO, page, true);
|
||||
|
@ -196,7 +200,7 @@ ColumnarStorageInit(SMgrRelation srel, uint64 storageId)
|
|||
/* write empty page */
|
||||
PageInit(page, BLCKSZ, 0);
|
||||
|
||||
log_newpage(&srel->smgr_rnode.node, MAIN_FORKNUM,
|
||||
log_newpage(RelationPhysicalIdentifierBackend_compat(&srel), MAIN_FORKNUM,
|
||||
COLUMNAR_EMPTY_BLOCKNO, page, true);
|
||||
PageSetChecksumInplace(page, COLUMNAR_EMPTY_BLOCKNO);
|
||||
smgrextend(srel, MAIN_FORKNUM, COLUMNAR_EMPTY_BLOCKNO, page, true);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "executor/executor.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "optimizer/plancat.h"
|
||||
#include "pg_version_compat.h"
|
||||
#include "pgstat.h"
|
||||
#include "safe_lib.h"
|
||||
#include "storage/bufmgr.h"
|
||||
|
@ -206,7 +207,8 @@ columnar_beginscan_extended(Relation relation, Snapshot snapshot,
|
|||
uint32 flags, Bitmapset *attr_needed, List *scanQual)
|
||||
{
|
||||
CheckCitusColumnarVersion(ERROR);
|
||||
Oid relfilenode = relation->rd_node.relNode;
|
||||
RelFileNumber relfilenumber = RelationPhysicalIdentifierNumber_compat(
|
||||
RelationPhysicalIdentifier_compat(relation));
|
||||
|
||||
/*
|
||||
* A memory context to use for scan-wide data, including the lazily
|
||||
|
@ -236,7 +238,7 @@ columnar_beginscan_extended(Relation relation, Snapshot snapshot,
|
|||
scan->scanQual = copyObject(scanQual);
|
||||
scan->scanContext = scanContext;
|
||||
|
||||
if (PendingWritesInUpperTransactions(relfilenode, GetCurrentSubTransactionId()))
|
||||
if (PendingWritesInUpperTransactions(relfilenumber, GetCurrentSubTransactionId()))
|
||||
{
|
||||
elog(ERROR,
|
||||
"cannot read from table when there is unflushed data in upper transactions");
|
||||
|
@ -432,8 +434,9 @@ columnar_index_fetch_begin(Relation rel)
|
|||
{
|
||||
CheckCitusColumnarVersion(ERROR);
|
||||
|
||||
Oid relfilenode = rel->rd_node.relNode;
|
||||
if (PendingWritesInUpperTransactions(relfilenode, GetCurrentSubTransactionId()))
|
||||
RelFileNumber relfilenumber = RelationPhysicalIdentifierNumber_compat(
|
||||
RelationPhysicalIdentifier_compat(rel));
|
||||
if (PendingWritesInUpperTransactions(relfilenumber, GetCurrentSubTransactionId()))
|
||||
{
|
||||
/* XXX: maybe we can just flush the data and continue */
|
||||
elog(ERROR, "cannot read from index when there is unflushed data in "
|
||||
|
@ -815,7 +818,7 @@ static TM_Result
|
|||
columnar_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot,
|
||||
CommandId cid, Snapshot snapshot, Snapshot crosscheck,
|
||||
bool wait, TM_FailureData *tmfd,
|
||||
LockTupleMode *lockmode, bool *update_indexes)
|
||||
LockTupleMode *lockmode, TU_UpdateIndexes *update_indexes)
|
||||
{
|
||||
elog(ERROR, "columnar_tuple_update not implemented");
|
||||
}
|
||||
|
@ -841,11 +844,11 @@ columnar_finish_bulk_insert(Relation relation, int options)
|
|||
|
||||
|
||||
static void
|
||||
columnar_relation_set_new_filenode(Relation rel,
|
||||
const RelFileNode *newrnode,
|
||||
char persistence,
|
||||
TransactionId *freezeXid,
|
||||
MultiXactId *minmulti)
|
||||
columnar_relation_set_new_filelocator(Relation rel,
|
||||
const RelFileLocator *newrlocator,
|
||||
char persistence,
|
||||
TransactionId *freezeXid,
|
||||
MultiXactId *minmulti)
|
||||
{
|
||||
CheckCitusColumnarVersion(ERROR);
|
||||
|
||||
|
@ -861,16 +864,19 @@ columnar_relation_set_new_filenode(Relation rel,
|
|||
* state. If they are equal, this is a new relation object and we don't
|
||||
* need to clean anything.
|
||||
*/
|
||||
if (rel->rd_node.relNode != newrnode->relNode)
|
||||
if (RelationPhysicalIdentifierNumber_compat(RelationPhysicalIdentifier_compat(rel)) !=
|
||||
RelationPhysicalIdentifierNumberPtr_compat(newrlocator))
|
||||
{
|
||||
MarkRelfilenodeDropped(rel->rd_node.relNode, GetCurrentSubTransactionId());
|
||||
MarkRelfilenumberDropped(RelationPhysicalIdentifierNumber_compat(
|
||||
RelationPhysicalIdentifier_compat(rel)),
|
||||
GetCurrentSubTransactionId());
|
||||
|
||||
DeleteMetadataRows(rel->rd_node);
|
||||
DeleteMetadataRows(RelationPhysicalIdentifier_compat(rel));
|
||||
}
|
||||
|
||||
*freezeXid = RecentXmin;
|
||||
*minmulti = GetOldestMultiXactId();
|
||||
SMgrRelation srel = RelationCreateStorage_compat(*newrnode, persistence, true);
|
||||
SMgrRelation srel = RelationCreateStorage_compat(*newrlocator, persistence, true);
|
||||
|
||||
ColumnarStorageInit(srel, ColumnarMetadataNewStorageId());
|
||||
InitColumnarOptions(rel->rd_id);
|
||||
|
@ -885,12 +891,12 @@ static void
|
|||
columnar_relation_nontransactional_truncate(Relation rel)
|
||||
{
|
||||
CheckCitusColumnarVersion(ERROR);
|
||||
RelFileNode relfilenode = rel->rd_node;
|
||||
RelFileLocator relfilelocator = RelationPhysicalIdentifier_compat(rel);
|
||||
|
||||
NonTransactionDropWriteState(relfilenode.relNode);
|
||||
NonTransactionDropWriteState(RelationPhysicalIdentifierNumber_compat(relfilelocator));
|
||||
|
||||
/* Delete old relfilenode metadata */
|
||||
DeleteMetadataRows(relfilenode);
|
||||
DeleteMetadataRows(relfilelocator);
|
||||
|
||||
/*
|
||||
* No need to set new relfilenode, since the table was created in this
|
||||
|
@ -907,7 +913,7 @@ columnar_relation_nontransactional_truncate(Relation rel)
|
|||
|
||||
|
||||
static void
|
||||
columnar_relation_copy_data(Relation rel, const RelFileNode *newrnode)
|
||||
columnar_relation_copy_data(Relation rel, const RelFileLocator *newrnode)
|
||||
{
|
||||
elog(ERROR, "columnar_relation_copy_data not implemented");
|
||||
}
|
||||
|
@ -953,7 +959,8 @@ columnar_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
|
|||
ColumnarOptions columnarOptions = { 0 };
|
||||
ReadColumnarOptions(OldHeap->rd_id, &columnarOptions);
|
||||
|
||||
ColumnarWriteState *writeState = ColumnarBeginWrite(NewHeap->rd_node,
|
||||
ColumnarWriteState *writeState = ColumnarBeginWrite(RelationPhysicalIdentifier_compat(
|
||||
NewHeap),
|
||||
columnarOptions,
|
||||
targetDesc);
|
||||
|
||||
|
@ -1028,7 +1035,8 @@ NeededColumnsList(TupleDesc tupdesc, Bitmapset *attr_needed)
|
|||
static uint64
|
||||
ColumnarTableTupleCount(Relation relation)
|
||||
{
|
||||
List *stripeList = StripesForRelfilenode(relation->rd_node);
|
||||
List *stripeList = StripesForRelfilelocator(RelationPhysicalIdentifier_compat(
|
||||
relation));
|
||||
uint64 tupleCount = 0;
|
||||
|
||||
ListCell *lc = NULL;
|
||||
|
@ -1091,12 +1099,38 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
|
|||
List *indexList = RelationGetIndexList(rel);
|
||||
int nindexes = list_length(indexList);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
struct VacuumCutoffs cutoffs;
|
||||
vacuum_get_cutoffs(rel, params, &cutoffs);
|
||||
|
||||
Assert(MultiXactIdPrecedesOrEquals(cutoffs.MultiXactCutoff, cutoffs.OldestMxact));
|
||||
Assert(TransactionIdPrecedesOrEquals(cutoffs.FreezeLimit, cutoffs.OldestXmin));
|
||||
|
||||
/*
|
||||
* Columnar storage doesn't hold any transaction IDs, so we can always
|
||||
* just advance to the most aggressive value.
|
||||
*/
|
||||
TransactionId newRelFrozenXid = cutoffs.OldestXmin;
|
||||
MultiXactId newRelminMxid = cutoffs.OldestMxact;
|
||||
double new_live_tuples = ColumnarTableTupleCount(rel);
|
||||
|
||||
/* all visible pages are always 0 */
|
||||
BlockNumber new_rel_allvisible = 0;
|
||||
|
||||
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
|
||||
TransactionId oldestXmin;
|
||||
TransactionId freezeLimit;
|
||||
MultiXactId multiXactCutoff;
|
||||
|
||||
/* initialize xids */
|
||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||
#if (PG_VERSION_NUM >= PG_VERSION_15) && (PG_VERSION_NUM < PG_VERSION_16)
|
||||
MultiXactId oldestMxact;
|
||||
vacuum_set_xid_limits(rel,
|
||||
params->freeze_min_age,
|
||||
|
@ -1126,7 +1160,7 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
|
|||
* just advance to the most aggressive value.
|
||||
*/
|
||||
TransactionId newRelFrozenXid = oldestXmin;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||
#if (PG_VERSION_NUM >= PG_VERSION_15) && (PG_VERSION_NUM < PG_VERSION_16)
|
||||
MultiXactId newRelminMxid = oldestMxact;
|
||||
#else
|
||||
MultiXactId newRelminMxid = multiXactCutoff;
|
||||
|
@ -1137,7 +1171,7 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
|
|||
/* all visible pages are always 0 */
|
||||
BlockNumber new_rel_allvisible = 0;
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||
#if (PG_VERSION_NUM >= PG_VERSION_15) && (PG_VERSION_NUM < PG_VERSION_16)
|
||||
bool frozenxid_updated;
|
||||
bool minmulti_updated;
|
||||
|
||||
|
@ -1149,6 +1183,7 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
|
|||
vac_update_relstats(rel, new_rel_pages, new_live_tuples,
|
||||
new_rel_allvisible, nindexes > 0,
|
||||
newRelFrozenXid, newRelminMxid, false);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
pgstat_report_vacuum(RelationGetRelid(rel),
|
||||
|
@ -1166,7 +1201,7 @@ static void
|
|||
LogRelationStats(Relation rel, int elevel)
|
||||
{
|
||||
ListCell *stripeMetadataCell = NULL;
|
||||
RelFileNode relfilenode = rel->rd_node;
|
||||
RelFileLocator relfilelocator = RelationPhysicalIdentifier_compat(rel);
|
||||
StringInfo infoBuf = makeStringInfo();
|
||||
|
||||
int compressionStats[COMPRESSION_COUNT] = { 0 };
|
||||
|
@ -1177,13 +1212,13 @@ LogRelationStats(Relation rel, int elevel)
|
|||
uint64 droppedChunksWithData = 0;
|
||||
uint64 totalDecompressedLength = 0;
|
||||
|
||||
List *stripeList = StripesForRelfilenode(relfilenode);
|
||||
List *stripeList = StripesForRelfilelocator(relfilelocator);
|
||||
int stripeCount = list_length(stripeList);
|
||||
|
||||
foreach(stripeMetadataCell, stripeList)
|
||||
{
|
||||
StripeMetadata *stripe = lfirst(stripeMetadataCell);
|
||||
StripeSkipList *skiplist = ReadStripeSkipList(relfilenode, stripe->id,
|
||||
StripeSkipList *skiplist = ReadStripeSkipList(relfilelocator, stripe->id,
|
||||
RelationGetDescr(rel),
|
||||
stripe->chunkCount,
|
||||
GetTransactionSnapshot());
|
||||
|
@ -1319,7 +1354,8 @@ TruncateColumnar(Relation rel, int elevel)
|
|||
* new stripes be added beyond highestPhysicalAddress while
|
||||
* we're truncating.
|
||||
*/
|
||||
uint64 newDataReservation = Max(GetHighestUsedAddress(rel->rd_node) + 1,
|
||||
uint64 newDataReservation = Max(GetHighestUsedAddress(
|
||||
RelationPhysicalIdentifier_compat(rel)) + 1,
|
||||
ColumnarFirstLogicalOffset);
|
||||
|
||||
BlockNumber old_rel_pages = smgrnblocks(RelationGetSmgr(rel), MAIN_FORKNUM);
|
||||
|
@ -1826,8 +1862,8 @@ TupleSortSkipSmallerItemPointers(Tuplesortstate *tupleSort, ItemPointer targetIt
|
|||
Datum *abbrev = NULL;
|
||||
Datum tsDatum;
|
||||
bool tsDatumIsNull;
|
||||
if (!tuplesort_getdatum(tupleSort, forwardDirection, &tsDatum,
|
||||
&tsDatumIsNull, abbrev))
|
||||
if (!tuplesort_getdatum_compat(tupleSort, forwardDirection, false,
|
||||
&tsDatum, &tsDatumIsNull, abbrev))
|
||||
{
|
||||
ItemPointerSetInvalid(&tsItemPointerData);
|
||||
break;
|
||||
|
@ -2068,12 +2104,13 @@ ColumnarTableDropHook(Oid relid)
|
|||
* tableam tables storage is managed by postgres.
|
||||
*/
|
||||
Relation rel = table_open(relid, AccessExclusiveLock);
|
||||
RelFileNode relfilenode = rel->rd_node;
|
||||
RelFileLocator relfilelocator = RelationPhysicalIdentifier_compat(rel);
|
||||
|
||||
DeleteMetadataRows(relfilenode);
|
||||
DeleteMetadataRows(relfilelocator);
|
||||
DeleteColumnarTableOptions(rel->rd_id, true);
|
||||
|
||||
MarkRelfilenodeDropped(relfilenode.relNode, GetCurrentSubTransactionId());
|
||||
MarkRelfilenumberDropped(RelationPhysicalIdentifierNumber_compat(relfilelocator),
|
||||
GetCurrentSubTransactionId());
|
||||
|
||||
/* keep the lock since we did physical changes to the relation */
|
||||
table_close(rel, NoLock);
|
||||
|
@ -2490,7 +2527,11 @@ static const TableAmRoutine columnar_am_methods = {
|
|||
.tuple_lock = columnar_tuple_lock,
|
||||
.finish_bulk_insert = columnar_finish_bulk_insert,
|
||||
|
||||
.relation_set_new_filenode = columnar_relation_set_new_filenode,
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
.relation_set_new_filelocator = columnar_relation_set_new_filelocator,
|
||||
#else
|
||||
.relation_set_new_filenode = columnar_relation_set_new_filelocator,
|
||||
#endif
|
||||
.relation_nontransactional_truncate = columnar_relation_nontransactional_truncate,
|
||||
.relation_copy_data = columnar_relation_copy_data,
|
||||
.relation_copy_for_cluster = columnar_relation_copy_for_cluster,
|
||||
|
|
|
@ -22,12 +22,18 @@
|
|||
#include "access/nbtree.h"
|
||||
#include "catalog/pg_am.h"
|
||||
#include "miscadmin.h"
|
||||
#include "pg_version_compat.h"
|
||||
#include "storage/fd.h"
|
||||
#include "storage/smgr.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "storage/relfilelocator.h"
|
||||
#include "utils/relfilenumbermap.h"
|
||||
#else
|
||||
#include "utils/relfilenodemap.h"
|
||||
#endif
|
||||
|
||||
#include "columnar/columnar.h"
|
||||
#include "columnar/columnar_storage.h"
|
||||
|
@ -37,7 +43,7 @@ struct ColumnarWriteState
|
|||
{
|
||||
TupleDesc tupleDescriptor;
|
||||
FmgrInfo **comparisonFunctionArray;
|
||||
RelFileNode relfilenode;
|
||||
RelFileLocator relfilelocator;
|
||||
|
||||
MemoryContext stripeWriteContext;
|
||||
MemoryContext perTupleContext;
|
||||
|
@ -84,7 +90,7 @@ static StringInfo CopyStringInfo(StringInfo sourceString);
|
|||
* data load operation.
|
||||
*/
|
||||
ColumnarWriteState *
|
||||
ColumnarBeginWrite(RelFileNode relfilenode,
|
||||
ColumnarBeginWrite(RelFileLocator relfilelocator,
|
||||
ColumnarOptions options,
|
||||
TupleDesc tupleDescriptor)
|
||||
{
|
||||
|
@ -124,7 +130,7 @@ ColumnarBeginWrite(RelFileNode relfilenode,
|
|||
options.chunkRowCount);
|
||||
|
||||
ColumnarWriteState *writeState = palloc0(sizeof(ColumnarWriteState));
|
||||
writeState->relfilenode = relfilenode;
|
||||
writeState->relfilelocator = relfilelocator;
|
||||
writeState->options = options;
|
||||
writeState->tupleDescriptor = CreateTupleDescCopy(tupleDescriptor);
|
||||
writeState->comparisonFunctionArray = comparisonFunctionArray;
|
||||
|
@ -174,8 +180,10 @@ ColumnarWriteRow(ColumnarWriteState *writeState, Datum *columnValues, bool *colu
|
|||
writeState->stripeSkipList = stripeSkipList;
|
||||
writeState->compressionBuffer = makeStringInfo();
|
||||
|
||||
Oid relationId = RelidByRelfilenode(writeState->relfilenode.spcNode,
|
||||
writeState->relfilenode.relNode);
|
||||
Oid relationId = RelidByRelfilenumber(RelationTablespace_compat(
|
||||
writeState->relfilelocator),
|
||||
RelationPhysicalIdentifierNumber_compat(
|
||||
writeState->relfilelocator));
|
||||
Relation relation = relation_open(relationId, NoLock);
|
||||
writeState->emptyStripeReservation =
|
||||
ReserveEmptyStripe(relation, columnCount, chunkRowCount,
|
||||
|
@ -393,8 +401,10 @@ FlushStripe(ColumnarWriteState *writeState)
|
|||
|
||||
elog(DEBUG1, "Flushing Stripe of size %d", stripeBuffers->rowCount);
|
||||
|
||||
Oid relationId = RelidByRelfilenode(writeState->relfilenode.spcNode,
|
||||
writeState->relfilenode.relNode);
|
||||
Oid relationId = RelidByRelfilenumber(RelationTablespace_compat(
|
||||
writeState->relfilelocator),
|
||||
RelationPhysicalIdentifierNumber_compat(
|
||||
writeState->relfilelocator));
|
||||
Relation relation = relation_open(relationId, NoLock);
|
||||
|
||||
/*
|
||||
|
@ -486,10 +496,10 @@ FlushStripe(ColumnarWriteState *writeState)
|
|||
}
|
||||
}
|
||||
|
||||
SaveChunkGroups(writeState->relfilenode,
|
||||
SaveChunkGroups(writeState->relfilelocator,
|
||||
stripeMetadata->id,
|
||||
writeState->chunkGroupRowCounts);
|
||||
SaveStripeSkipList(writeState->relfilenode,
|
||||
SaveStripeSkipList(writeState->relfilelocator,
|
||||
stripeMetadata->id,
|
||||
stripeSkipList, tupleDescriptor);
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
-- citus_columnar--11.3-1--12.2-1
|
|
@ -0,0 +1 @@
|
|||
-- citus_columnar--12.2-1--11.3-1
|
|
@ -29,6 +29,7 @@
|
|||
#include "executor/executor.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "optimizer/plancat.h"
|
||||
#include "pg_version_compat.h"
|
||||
#include "pgstat.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/bufpage.h"
|
||||
|
@ -77,7 +78,7 @@ typedef struct SubXidWriteState
|
|||
typedef struct WriteStateMapEntry
|
||||
{
|
||||
/* key of the entry */
|
||||
Oid relfilenode;
|
||||
RelFileNumber relfilenumber;
|
||||
|
||||
/*
|
||||
* If a table is dropped, we set dropped to true and set dropSubXid to the
|
||||
|
@ -132,7 +133,7 @@ columnar_init_write_state(Relation relation, TupleDesc tupdesc,
|
|||
HASHCTL info;
|
||||
uint32 hashFlags = (HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.keysize = sizeof(Oid);
|
||||
info.keysize = sizeof(RelFileNumber);
|
||||
info.hash = oid_hash;
|
||||
info.entrysize = sizeof(WriteStateMapEntry);
|
||||
info.hcxt = WriteStateContext;
|
||||
|
@ -146,7 +147,10 @@ columnar_init_write_state(Relation relation, TupleDesc tupdesc,
|
|||
MemoryContextRegisterResetCallback(WriteStateContext, &cleanupCallback);
|
||||
}
|
||||
|
||||
WriteStateMapEntry *hashEntry = hash_search(WriteStateMap, &relation->rd_node.relNode,
|
||||
WriteStateMapEntry *hashEntry = hash_search(WriteStateMap,
|
||||
&RelationPhysicalIdentifierNumber_compat(
|
||||
RelationPhysicalIdentifier_compat(
|
||||
relation)),
|
||||
HASH_ENTER, &found);
|
||||
if (!found)
|
||||
{
|
||||
|
@ -189,7 +193,8 @@ columnar_init_write_state(Relation relation, TupleDesc tupdesc,
|
|||
ReadColumnarOptions(tupSlotRelationId, &columnarOptions);
|
||||
|
||||
SubXidWriteState *stackEntry = palloc0(sizeof(SubXidWriteState));
|
||||
stackEntry->writeState = ColumnarBeginWrite(relation->rd_node,
|
||||
stackEntry->writeState = ColumnarBeginWrite(RelationPhysicalIdentifier_compat(
|
||||
relation),
|
||||
columnarOptions,
|
||||
tupdesc);
|
||||
stackEntry->subXid = currentSubXid;
|
||||
|
@ -206,14 +211,16 @@ columnar_init_write_state(Relation relation, TupleDesc tupdesc,
|
|||
* Flushes pending writes for given relfilenode in the given subtransaction.
|
||||
*/
|
||||
void
|
||||
FlushWriteStateForRelfilenode(Oid relfilenode, SubTransactionId currentSubXid)
|
||||
FlushWriteStateForRelfilenumber(RelFileNumber relfilenumber,
|
||||
SubTransactionId currentSubXid)
|
||||
{
|
||||
if (WriteStateMap == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteStateMapEntry *entry = hash_search(WriteStateMap, &relfilenode, HASH_FIND, NULL);
|
||||
WriteStateMapEntry *entry = hash_search(WriteStateMap, &relfilenumber, HASH_FIND,
|
||||
NULL);
|
||||
|
||||
Assert(!entry || !entry->dropped);
|
||||
|
||||
|
@ -320,14 +327,14 @@ DiscardWriteStateForAllRels(SubTransactionId currentSubXid, SubTransactionId par
|
|||
* Called when the given relfilenode is dropped.
|
||||
*/
|
||||
void
|
||||
MarkRelfilenodeDropped(Oid relfilenode, SubTransactionId currentSubXid)
|
||||
MarkRelfilenumberDropped(RelFileNumber relfilenumber, SubTransactionId currentSubXid)
|
||||
{
|
||||
if (WriteStateMap == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteStateMapEntry *entry = hash_search(WriteStateMap, &relfilenode, HASH_FIND,
|
||||
WriteStateMapEntry *entry = hash_search(WriteStateMap, &relfilenumber, HASH_FIND,
|
||||
NULL);
|
||||
if (!entry || entry->dropped)
|
||||
{
|
||||
|
@ -343,11 +350,11 @@ MarkRelfilenodeDropped(Oid relfilenode, SubTransactionId currentSubXid)
|
|||
* Called when the given relfilenode is dropped in non-transactional TRUNCATE.
|
||||
*/
|
||||
void
|
||||
NonTransactionDropWriteState(Oid relfilenode)
|
||||
NonTransactionDropWriteState(RelFileNumber relfilenumber)
|
||||
{
|
||||
if (WriteStateMap)
|
||||
{
|
||||
hash_search(WriteStateMap, &relfilenode, HASH_REMOVE, false);
|
||||
hash_search(WriteStateMap, &relfilenumber, HASH_REMOVE, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -356,14 +363,16 @@ NonTransactionDropWriteState(Oid relfilenode)
|
|||
* Returns true if there are any pending writes in upper transactions.
|
||||
*/
|
||||
bool
|
||||
PendingWritesInUpperTransactions(Oid relfilenode, SubTransactionId currentSubXid)
|
||||
PendingWritesInUpperTransactions(RelFileNumber relfilenumber,
|
||||
SubTransactionId currentSubXid)
|
||||
{
|
||||
if (WriteStateMap == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteStateMapEntry *entry = hash_search(WriteStateMap, &relfilenode, HASH_FIND, NULL);
|
||||
WriteStateMapEntry *entry = hash_search(WriteStateMap, &relfilenumber, HASH_FIND,
|
||||
NULL);
|
||||
|
||||
if (entry && entry->writeStateStack != NULL)
|
||||
{
|
||||
|
|
|
@ -72,7 +72,7 @@ DistShardRelationId(void)
|
|||
|
||||
|
||||
/*
|
||||
* DistShardRelationId returns the relation id of the pg_dist_shard
|
||||
* DistShardShardidIndexId returns the relation id of the pg_dist_shard_shardid_index
|
||||
*/
|
||||
static Oid
|
||||
DistShardShardidIndexId(void)
|
||||
|
@ -87,7 +87,7 @@ DistShardShardidIndexId(void)
|
|||
|
||||
|
||||
/*
|
||||
* DistShardRelationId returns the relation id of the pg_dist_shard
|
||||
* DistPartitionRelationId returns the relation id of the pg_dist_partition
|
||||
*/
|
||||
static Oid
|
||||
DistPartitionRelationId(void)
|
||||
|
@ -184,9 +184,9 @@ CdcExtractShardIdFromTableName(const char *tableName, bool missingOk)
|
|||
|
||||
|
||||
/*
|
||||
* CdcGetLocalGroupId returns the group identifier of the local node. The function assumes
|
||||
* that pg_dist_local_node_group has exactly one row and has at least one column.
|
||||
* Otherwise, the function errors out.
|
||||
* CdcGetLocalGroupId returns the group identifier of the local node. The
|
||||
* function assumes that pg_dist_local_group has exactly one row and has at
|
||||
* least one column. Otherwise, the function errors out.
|
||||
*/
|
||||
static int32
|
||||
CdcGetLocalGroupId(void)
|
||||
|
@ -376,7 +376,8 @@ CdcIsReferenceTableViaCatalog(Oid relationId)
|
|||
* A table is a reference table when its partition method is 'none'
|
||||
* and replication model is 'two phase commit'
|
||||
*/
|
||||
return partitionMethodChar == 'n' && replicationModelChar == 't';
|
||||
return partitionMethodChar == DISTRIBUTE_BY_NONE &&
|
||||
replicationModelChar == REPLICATION_MODEL_2PC;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Citus extension
|
||||
comment = 'Citus distributed database'
|
||||
default_version = '12.0-1'
|
||||
default_version = '12.2-1'
|
||||
module_pathname = '$libdir/citus'
|
||||
relocatable = false
|
||||
schema = pg_catalog
|
||||
|
|
|
@ -53,11 +53,13 @@
|
|||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/multi_logical_planner.h"
|
||||
#include "distributed/multi_partitioning_utils.h"
|
||||
#include "distributed/namespace_utils.h"
|
||||
#include "distributed/reference_table_utils.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
#include "distributed/replication_origin_session_utils.h"
|
||||
#include "distributed/shared_library_init.h"
|
||||
#include "distributed/shard_utils.h"
|
||||
#include "distributed/tenant_schema_metadata.h"
|
||||
#include "distributed/worker_protocol.h"
|
||||
#include "distributed/worker_transaction.h"
|
||||
#include "executor/spi.h"
|
||||
|
@ -1764,10 +1766,7 @@ CreateMaterializedViewDDLCommand(Oid matViewOid)
|
|||
* 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);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
/*
|
||||
* Push the transaction snapshot to be able to get vief definition with pg_get_viewdef
|
||||
|
@ -1779,7 +1778,7 @@ CreateMaterializedViewDDLCommand(Oid matViewOid)
|
|||
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
|
||||
|
||||
PopActiveSnapshot();
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
appendStringInfo(query, "AS %s", viewDefinition);
|
||||
|
||||
|
|
|
@ -892,7 +892,7 @@ GetConstraintNameList(Oid relationId)
|
|||
Relation pgConstraint = table_open(ConstraintRelationId, AccessShareLock);
|
||||
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, relationId);
|
||||
BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId));
|
||||
|
||||
bool useIndex = true;
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgConstraint,
|
||||
|
@ -1478,11 +1478,20 @@ InsertMetadataForCitusLocalTable(Oid citusLocalTableId, uint64 shardId,
|
|||
static void
|
||||
FinalizeCitusLocalTableCreation(Oid relationId)
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/*
|
||||
* PG16+ supports truncate triggers on foreign tables
|
||||
*/
|
||||
if (RegularTable(relationId) || IsForeignTable(relationId))
|
||||
#else
|
||||
|
||||
/*
|
||||
* If it is a foreign table, then skip creating citus truncate trigger
|
||||
* as foreign tables do not support truncate triggers.
|
||||
*/
|
||||
if (RegularTable(relationId))
|
||||
#endif
|
||||
{
|
||||
CreateTruncateTrigger(relationId);
|
||||
}
|
||||
|
|
|
@ -188,7 +188,16 @@ CreateCollationDDLInternal(Oid collationId, Oid *collowner, char **quotedCollati
|
|||
pfree(collcollate);
|
||||
pfree(collctype);
|
||||
#endif
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
char *collicurules = NULL;
|
||||
datum = SysCacheGetAttr(COLLOID, heapTuple, Anum_pg_collation_collicurules, &isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
collicurules = TextDatumGetCString(datum);
|
||||
appendStringInfo(&collationNameDef, ", rules = %s",
|
||||
quote_literal_cstr(collicurules));
|
||||
}
|
||||
#endif
|
||||
if (!collisdeterministic)
|
||||
{
|
||||
appendStringInfoString(&collationNameDef, ", deterministic = false");
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
#include "distributed/reference_table_utils.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
#include "distributed/remote_commands.h"
|
||||
#include "distributed/replicate_none_dist_table_shard.h"
|
||||
#include "distributed/resource_lock.h"
|
||||
#include "distributed/shard_cleaner.h"
|
||||
#include "distributed/shard_rebalancer.h"
|
||||
|
@ -139,6 +140,10 @@ static CitusTableParams DecideCitusTableParams(CitusTableType tableType,
|
|||
distributedTableParams);
|
||||
static void CreateCitusTable(Oid relationId, CitusTableType tableType,
|
||||
DistributedTableParams *distributedTableParams);
|
||||
static void ConvertCitusLocalTableToTableType(Oid relationId,
|
||||
CitusTableType tableType,
|
||||
DistributedTableParams *
|
||||
distributedTableParams);
|
||||
static void CreateHashDistributedTableShards(Oid relationId, int shardCount,
|
||||
Oid colocatedTableId, bool localTableEmpty);
|
||||
static void CreateSingleShardTableShard(Oid relationId, Oid colocatedTableId,
|
||||
|
@ -159,7 +164,7 @@ static void EnsureCitusTableCanBeCreated(Oid relationOid);
|
|||
static void PropagatePrerequisiteObjectsForDistributedTable(Oid relationId);
|
||||
static void EnsureDistributedSequencesHaveOneType(Oid relationId,
|
||||
List *seqInfoList);
|
||||
static void CopyLocalDataIntoShards(Oid relationId);
|
||||
static void CopyLocalDataIntoShards(Oid distributedTableId);
|
||||
static List * TupleDescColumnNameList(TupleDesc tupleDescriptor);
|
||||
|
||||
#if (PG_VERSION_NUM >= PG_VERSION_15)
|
||||
|
@ -172,10 +177,10 @@ static bool is_valid_numeric_typmod(int32 typmod);
|
|||
static bool DistributionColumnUsesGeneratedStoredColumn(TupleDesc relationDesc,
|
||||
Var *distributionColumn);
|
||||
static bool CanUseExclusiveConnections(Oid relationId, bool localTableEmpty);
|
||||
static void DoCopyFromLocalTableIntoShards(Relation distributedRelation,
|
||||
DestReceiver *copyDest,
|
||||
TupleTableSlot *slot,
|
||||
EState *estate);
|
||||
static uint64 DoCopyFromLocalTableIntoShards(Relation distributedRelation,
|
||||
DestReceiver *copyDest,
|
||||
TupleTableSlot *slot,
|
||||
EState *estate);
|
||||
static void ErrorIfTemporaryTable(Oid relationId);
|
||||
static void ErrorIfForeignTable(Oid relationOid);
|
||||
static void SendAddLocalTableToMetadataCommandOutsideTransaction(Oid relationId);
|
||||
|
@ -1019,19 +1024,29 @@ CreateDistributedTable(Oid relationId, char *distributionColumnName,
|
|||
|
||||
|
||||
/*
|
||||
* CreateReferenceTable is a wrapper around CreateCitusTable that creates a
|
||||
* reference table.
|
||||
* CreateReferenceTable creates a reference table.
|
||||
*/
|
||||
void
|
||||
CreateReferenceTable(Oid relationId)
|
||||
{
|
||||
CreateCitusTable(relationId, REFERENCE_TABLE, NULL);
|
||||
if (IsCitusTableType(relationId, CITUS_LOCAL_TABLE))
|
||||
{
|
||||
/*
|
||||
* Create the shard of given Citus local table on workers to convert
|
||||
* it into a reference table.
|
||||
*/
|
||||
ConvertCitusLocalTableToTableType(relationId, REFERENCE_TABLE, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateCitusTable(relationId, REFERENCE_TABLE, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateSingleShardTable is a wrapper around CreateCitusTable that creates a
|
||||
* single shard distributed table that doesn't have a shard key.
|
||||
* CreateSingleShardTable creates a single shard distributed table that
|
||||
* doesn't have a shard key.
|
||||
*/
|
||||
void
|
||||
CreateSingleShardTable(Oid relationId, ColocationParam colocationParam)
|
||||
|
@ -1042,7 +1057,21 @@ CreateSingleShardTable(Oid relationId, ColocationParam colocationParam)
|
|||
.shardCountIsStrict = true,
|
||||
.distributionColumnName = NULL
|
||||
};
|
||||
CreateCitusTable(relationId, SINGLE_SHARD_DISTRIBUTED, &distributedTableParams);
|
||||
|
||||
if (IsCitusTableType(relationId, CITUS_LOCAL_TABLE))
|
||||
{
|
||||
/*
|
||||
* Create the shard of given Citus local table on appropriate node
|
||||
* and drop the local one to convert it into a single-shard distributed
|
||||
* table.
|
||||
*/
|
||||
ConvertCitusLocalTableToTableType(relationId, SINGLE_SHARD_DISTRIBUTED,
|
||||
&distributedTableParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateCitusTable(relationId, SINGLE_SHARD_DISTRIBUTED, &distributedTableParams);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1097,7 +1126,7 @@ CreateCitusTable(Oid relationId, CitusTableType tableType,
|
|||
/*
|
||||
* EnsureTableNotDistributed errors out when relation is a citus table but
|
||||
* we don't want to ask user to first undistribute their citus local tables
|
||||
* when creating reference or distributed tables from them.
|
||||
* when creating distributed tables from them.
|
||||
* For this reason, here we undistribute citus local tables beforehand.
|
||||
* But since UndistributeTable does not support undistributing relations
|
||||
* involved in foreign key relationships, we first drop foreign keys that
|
||||
|
@ -1107,6 +1136,13 @@ CreateCitusTable(Oid relationId, CitusTableType tableType,
|
|||
List *originalForeignKeyRecreationCommands = NIL;
|
||||
if (IsCitusTableType(relationId, CITUS_LOCAL_TABLE))
|
||||
{
|
||||
/*
|
||||
* We use ConvertCitusLocalTableToTableType instead of CreateCitusTable
|
||||
* to create a reference table or a single-shard table from a Citus
|
||||
* local table.
|
||||
*/
|
||||
Assert(tableType != REFERENCE_TABLE && tableType != SINGLE_SHARD_DISTRIBUTED);
|
||||
|
||||
/* store foreign key creation commands that relation is involved */
|
||||
originalForeignKeyRecreationCommands =
|
||||
GetFKeyCreationCommandsRelationInvolvedWithTableType(relationId,
|
||||
|
@ -1220,25 +1256,36 @@ CreateCitusTable(Oid relationId, CitusTableType tableType,
|
|||
colocationId, citusTableParams.replicationModel,
|
||||
autoConverted);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/*
|
||||
* PG16+ supports truncate triggers on foreign tables
|
||||
*/
|
||||
if (RegularTable(relationId) || IsForeignTable(relationId))
|
||||
#else
|
||||
|
||||
/* foreign tables do not support TRUNCATE trigger */
|
||||
if (RegularTable(relationId))
|
||||
#endif
|
||||
{
|
||||
CreateTruncateTrigger(relationId);
|
||||
}
|
||||
|
||||
/* create shards for hash distributed and reference tables */
|
||||
if (tableType == HASH_DISTRIBUTED)
|
||||
{
|
||||
/* create shards for hash distributed table */
|
||||
CreateHashDistributedTableShards(relationId, distributedTableParams->shardCount,
|
||||
colocatedTableId,
|
||||
localTableEmpty);
|
||||
}
|
||||
else if (tableType == REFERENCE_TABLE)
|
||||
{
|
||||
/* create shards for reference table */
|
||||
CreateReferenceTableShard(relationId);
|
||||
}
|
||||
else if (tableType == SINGLE_SHARD_DISTRIBUTED)
|
||||
{
|
||||
/* create the shard of given single-shard distributed table */
|
||||
CreateSingleShardTableShard(relationId, colocatedTableId,
|
||||
colocationId);
|
||||
}
|
||||
|
@ -1319,6 +1366,206 @@ CreateCitusTable(Oid relationId, CitusTableType tableType,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* ConvertCitusLocalTableToTableType converts given Citus local table to
|
||||
* given table type.
|
||||
*
|
||||
* This only supports converting Citus local tables to reference tables
|
||||
* (by replicating the shard to workers) and single-shard distributed
|
||||
* tables (by replicating the shard to the appropriate worker and dropping
|
||||
* the local one).
|
||||
*/
|
||||
static void
|
||||
ConvertCitusLocalTableToTableType(Oid relationId, CitusTableType tableType,
|
||||
DistributedTableParams *distributedTableParams)
|
||||
{
|
||||
if (!IsCitusTableType(relationId, CITUS_LOCAL_TABLE))
|
||||
{
|
||||
ereport(ERROR, (errmsg("table is not a local table added to metadata")));
|
||||
}
|
||||
|
||||
if (tableType != REFERENCE_TABLE && tableType != SINGLE_SHARD_DISTRIBUTED)
|
||||
{
|
||||
ereport(ERROR, (errmsg("table type is not supported for conversion")));
|
||||
}
|
||||
|
||||
if ((tableType == SINGLE_SHARD_DISTRIBUTED) != (distributedTableParams != NULL))
|
||||
{
|
||||
ereport(ERROR, (errmsg("distributed table params must be provided "
|
||||
"when creating a distributed table and must "
|
||||
"not be otherwise")));
|
||||
}
|
||||
|
||||
EnsureCitusTableCanBeCreated(relationId);
|
||||
|
||||
Relation relation = try_relation_open(relationId, ExclusiveLock);
|
||||
if (relation == NULL)
|
||||
{
|
||||
ereport(ERROR, (errmsg("could not create Citus table: "
|
||||
"relation does not exist")));
|
||||
}
|
||||
|
||||
relation_close(relation, NoLock);
|
||||
|
||||
if (tableType == SINGLE_SHARD_DISTRIBUTED && ShardReplicationFactor > 1)
|
||||
{
|
||||
ereport(ERROR, (errmsg("could not create single shard table: "
|
||||
"citus.shard_replication_factor is greater than 1"),
|
||||
errhint("Consider setting citus.shard_replication_factor to 1 "
|
||||
"and try again")));
|
||||
}
|
||||
|
||||
LockRelationOid(relationId, ExclusiveLock);
|
||||
|
||||
Var *distributionColumn = NULL;
|
||||
CitusTableParams citusTableParams = DecideCitusTableParams(tableType,
|
||||
distributedTableParams);
|
||||
|
||||
uint32 colocationId = INVALID_COLOCATION_ID;
|
||||
if (distributedTableParams &&
|
||||
distributedTableParams->colocationParam.colocationParamType ==
|
||||
COLOCATE_WITH_COLOCATION_ID)
|
||||
{
|
||||
colocationId = distributedTableParams->colocationParam.colocationId;
|
||||
}
|
||||
else
|
||||
{
|
||||
colocationId = ColocationIdForNewTable(relationId, tableType,
|
||||
distributedTableParams,
|
||||
distributionColumn);
|
||||
}
|
||||
|
||||
/* check constraints etc. on table based on new distribution params */
|
||||
EnsureRelationCanBeDistributed(relationId, distributionColumn,
|
||||
citusTableParams.distributionMethod,
|
||||
colocationId, citusTableParams.replicationModel);
|
||||
|
||||
/*
|
||||
* Regarding the foreign key relationships that given relation is involved,
|
||||
* EnsureRelationCanBeDistributed() only checks the ones where the relation
|
||||
* is the referencing table. And given that the table at hand is a Citus
|
||||
* local table, right now it may only be referenced by a reference table
|
||||
* or a Citus local table. However, given that neither of those two cases
|
||||
* are not applicable for a distributed table, here we throw an error if
|
||||
* that's the case.
|
||||
*
|
||||
* Note that we don't need to check the same if we're creating a reference
|
||||
* table from a Citus local table because all the foreign keys referencing
|
||||
* Citus local tables are supported by reference tables.
|
||||
*/
|
||||
if (tableType == SINGLE_SHARD_DISTRIBUTED)
|
||||
{
|
||||
EnsureNoFKeyFromTableType(relationId, INCLUDE_CITUS_LOCAL_TABLES |
|
||||
INCLUDE_REFERENCE_TABLES);
|
||||
}
|
||||
|
||||
EnsureReferenceTablesExistOnAllNodes();
|
||||
|
||||
LockColocationId(colocationId, ShareLock);
|
||||
|
||||
/*
|
||||
* When converting to a single shard table, we want to drop the placement
|
||||
* on the coordinator, but only if transferring to a different node. In that
|
||||
* case, shouldDropLocalPlacement is true. When converting to a reference
|
||||
* table, we always keep the placement on the coordinator, so for reference
|
||||
* tables shouldDropLocalPlacement is always false.
|
||||
*/
|
||||
bool shouldDropLocalPlacement = false;
|
||||
|
||||
List *targetNodeList = NIL;
|
||||
if (tableType == SINGLE_SHARD_DISTRIBUTED)
|
||||
{
|
||||
uint32 targetNodeId = SingleShardTableColocationNodeId(colocationId);
|
||||
if (targetNodeId != CoordinatorNodeIfAddedAsWorkerOrError()->nodeId)
|
||||
{
|
||||
bool missingOk = false;
|
||||
WorkerNode *targetNode = FindNodeWithNodeId(targetNodeId, missingOk);
|
||||
targetNodeList = list_make1(targetNode);
|
||||
|
||||
shouldDropLocalPlacement = true;
|
||||
}
|
||||
}
|
||||
else if (tableType == REFERENCE_TABLE)
|
||||
{
|
||||
targetNodeList = ActivePrimaryNonCoordinatorNodeList(ShareLock);
|
||||
targetNodeList = SortList(targetNodeList, CompareWorkerNodes);
|
||||
}
|
||||
|
||||
bool autoConverted = false;
|
||||
UpdateNoneDistTableMetadataGlobally(
|
||||
relationId, citusTableParams.replicationModel,
|
||||
colocationId, autoConverted);
|
||||
|
||||
/* create the shard placement on workers and insert into pg_dist_placement globally */
|
||||
if (list_length(targetNodeList) > 0)
|
||||
{
|
||||
NoneDistTableReplicateCoordinatorPlacement(relationId, targetNodeList);
|
||||
}
|
||||
|
||||
if (shouldDropLocalPlacement)
|
||||
{
|
||||
/*
|
||||
* We don't yet drop the local placement before handling partitions.
|
||||
* Otherewise, local shard placements of the partitions will be gone
|
||||
* before we create them on workers.
|
||||
*
|
||||
* However, we need to delete the related entry from pg_dist_placement
|
||||
* before distributing partitions (if any) because we need a sane metadata
|
||||
* state before doing so.
|
||||
*/
|
||||
NoneDistTableDeleteCoordinatorPlacement(relationId);
|
||||
}
|
||||
|
||||
/* if this table is partitioned table, distribute its partitions too */
|
||||
if (PartitionedTable(relationId))
|
||||
{
|
||||
/* right now we don't allow partitioned reference tables */
|
||||
Assert(tableType == SINGLE_SHARD_DISTRIBUTED);
|
||||
|
||||
List *partitionList = PartitionList(relationId);
|
||||
|
||||
char *parentRelationName = generate_qualified_relation_name(relationId);
|
||||
|
||||
/*
|
||||
* When there are many partitions, each call to
|
||||
* ConvertCitusLocalTableToTableType accumulates used memory.
|
||||
* Create and free citus_per_partition_context for each call.
|
||||
*/
|
||||
MemoryContext citusPartitionContext =
|
||||
AllocSetContextCreate(CurrentMemoryContext,
|
||||
"citus_per_partition_context",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
MemoryContext oldContext = MemoryContextSwitchTo(citusPartitionContext);
|
||||
|
||||
Oid partitionRelationId = InvalidOid;
|
||||
foreach_oid(partitionRelationId, partitionList)
|
||||
{
|
||||
MemoryContextReset(citusPartitionContext);
|
||||
|
||||
DistributedTableParams childDistributedTableParams = {
|
||||
.colocationParam = {
|
||||
.colocationParamType = COLOCATE_WITH_TABLE_LIKE_OPT,
|
||||
.colocateWithTableName = parentRelationName,
|
||||
},
|
||||
.shardCount = distributedTableParams->shardCount,
|
||||
.shardCountIsStrict = false,
|
||||
.distributionColumnName = distributedTableParams->distributionColumnName,
|
||||
};
|
||||
ConvertCitusLocalTableToTableType(partitionRelationId, tableType,
|
||||
&childDistributedTableParams);
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
MemoryContextDelete(citusPartitionContext);
|
||||
}
|
||||
|
||||
if (shouldDropLocalPlacement)
|
||||
{
|
||||
NoneDistTableDropCoordinatorPlacementTable(relationId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DecideCitusTableParams decides CitusTableParams based on given CitusTableType
|
||||
* and DistributedTableParams if it's a distributed table.
|
||||
|
@ -1421,6 +1668,7 @@ PropagatePrerequisiteObjectsForDistributedTable(Oid relationId)
|
|||
ObjectAddress *tableAddress = palloc0(sizeof(ObjectAddress));
|
||||
ObjectAddressSet(*tableAddress, RelationRelationId, relationId);
|
||||
EnsureAllObjectDependenciesExistOnAllNodes(list_make1(tableAddress));
|
||||
TrackPropagatedTableAndSequences(relationId);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1664,7 +1912,7 @@ CreateHashDistributedTableShards(Oid relationId, int shardCount,
|
|||
|
||||
|
||||
/*
|
||||
* CreateHashDistributedTableShards creates the shard of given single-shard
|
||||
* CreateSingleShardTableShard creates the shard of given single-shard
|
||||
* distributed table.
|
||||
*/
|
||||
static void
|
||||
|
@ -2381,9 +2629,37 @@ RegularTable(Oid relationId)
|
|||
|
||||
|
||||
/*
|
||||
* CopyLocalDataIntoShards copies data from the local table, which is hidden
|
||||
* after converting it to a distributed table, into the shards of the distributed
|
||||
* table. For partitioned tables, this functions returns without copying the data
|
||||
* CopyLocalDataIntoShards is a wrapper around CopyFromLocalTableIntoDistTable
|
||||
* to copy data from the local table, which is hidden after converting it to a
|
||||
* distributed table, into the shards of the distributed table.
|
||||
*
|
||||
* After copying local data into the distributed table, the local data remains
|
||||
* in place and should be truncated at a later time.
|
||||
*/
|
||||
static void
|
||||
CopyLocalDataIntoShards(Oid distributedTableId)
|
||||
{
|
||||
uint64 rowsCopied = CopyFromLocalTableIntoDistTable(distributedTableId,
|
||||
distributedTableId);
|
||||
if (rowsCopied > 0)
|
||||
{
|
||||
char *qualifiedRelationName =
|
||||
generate_qualified_relation_name(distributedTableId);
|
||||
ereport(NOTICE, (errmsg("copying the data has completed"),
|
||||
errdetail("The local data in the table is no longer visible, "
|
||||
"but is still on disk."),
|
||||
errhint("To remove the local data, run: SELECT "
|
||||
"truncate_local_data_after_distributing_table($$%s$$)",
|
||||
qualifiedRelationName)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CopyFromLocalTableIntoDistTable copies data from given local table into
|
||||
* the shards of given distributed table.
|
||||
*
|
||||
* For partitioned tables, this functions returns without copying the data
|
||||
* because we call this function for both partitioned tables and its partitions.
|
||||
* Returning early saves us from copying data to workers twice.
|
||||
*
|
||||
|
@ -2393,35 +2669,30 @@ RegularTable(Oid relationId)
|
|||
* opens a connection and starts a COPY for each shard placement that will have
|
||||
* data.
|
||||
*
|
||||
* We could call the planner and executor here and send the output to the
|
||||
* DestReceiver, but we are in a tricky spot here since Citus is already
|
||||
* intercepting queries on this table in the planner and executor hooks and we
|
||||
* want to read from the local table. To keep it simple, we perform a heap scan
|
||||
* directly on the table.
|
||||
* We assume that the local table might indeed be a distributed table and the
|
||||
* caller would want to read the local data from the shell table in that case.
|
||||
* For this reason, to keep it simple, we perform a heap scan directly on the
|
||||
* table instead of using SELECT.
|
||||
*
|
||||
* Any writes on the table that are started during this operation will be handled
|
||||
* as distributed queries once the current transaction commits. SELECTs will
|
||||
* continue to read from the local table until the current transaction commits,
|
||||
* after which new SELECTs will be handled as distributed queries.
|
||||
*
|
||||
* After copying local data into the distributed table, the local data remains
|
||||
* in place and should be truncated at a later time.
|
||||
* We read from the table and pass each tuple to the CitusCopyDestReceiver which
|
||||
* opens a connection and starts a COPY for each shard placement that will have
|
||||
* data.
|
||||
*/
|
||||
static void
|
||||
CopyLocalDataIntoShards(Oid distributedRelationId)
|
||||
uint64
|
||||
CopyFromLocalTableIntoDistTable(Oid localTableId, Oid distributedTableId)
|
||||
{
|
||||
/* take an ExclusiveLock to block all operations except SELECT */
|
||||
Relation distributedRelation = table_open(distributedRelationId, ExclusiveLock);
|
||||
Relation localRelation = table_open(localTableId, ExclusiveLock);
|
||||
|
||||
/*
|
||||
* Skip copying from partitioned tables, we will copy the data from
|
||||
* partition to partition's shards.
|
||||
*/
|
||||
if (PartitionedTable(distributedRelationId))
|
||||
if (PartitionedTable(distributedTableId))
|
||||
{
|
||||
table_close(distributedRelation, NoLock);
|
||||
table_close(localRelation, NoLock);
|
||||
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2435,35 +2706,43 @@ CopyLocalDataIntoShards(Oid distributedRelationId)
|
|||
*/
|
||||
PushActiveSnapshot(GetLatestSnapshot());
|
||||
|
||||
/* get the table columns */
|
||||
TupleDesc tupleDescriptor = RelationGetDescr(distributedRelation);
|
||||
TupleTableSlot *slot = table_slot_create(distributedRelation, NULL);
|
||||
List *columnNameList = TupleDescColumnNameList(tupleDescriptor);
|
||||
Relation distributedRelation = RelationIdGetRelation(distributedTableId);
|
||||
|
||||
/* get the table columns for distributed table */
|
||||
TupleDesc destTupleDescriptor = RelationGetDescr(distributedRelation);
|
||||
List *columnNameList = TupleDescColumnNameList(destTupleDescriptor);
|
||||
|
||||
RelationClose(distributedRelation);
|
||||
|
||||
int partitionColumnIndex = INVALID_PARTITION_COLUMN_INDEX;
|
||||
|
||||
/* determine the partition column in the tuple descriptor */
|
||||
Var *partitionColumn = PartitionColumn(distributedRelationId, 0);
|
||||
Var *partitionColumn = PartitionColumn(distributedTableId, 0);
|
||||
if (partitionColumn != NULL)
|
||||
{
|
||||
partitionColumnIndex = partitionColumn->varattno - 1;
|
||||
}
|
||||
|
||||
/* create tuple slot for local relation */
|
||||
TupleDesc sourceTupleDescriptor = RelationGetDescr(localRelation);
|
||||
TupleTableSlot *slot = table_slot_create(localRelation, NULL);
|
||||
|
||||
/* initialise per-tuple memory context */
|
||||
EState *estate = CreateExecutorState();
|
||||
ExprContext *econtext = GetPerTupleExprContext(estate);
|
||||
econtext->ecxt_scantuple = slot;
|
||||
const bool nonPublishableData = false;
|
||||
DestReceiver *copyDest =
|
||||
(DestReceiver *) CreateCitusCopyDestReceiver(distributedRelationId,
|
||||
(DestReceiver *) CreateCitusCopyDestReceiver(distributedTableId,
|
||||
columnNameList,
|
||||
partitionColumnIndex,
|
||||
estate, NULL, nonPublishableData);
|
||||
|
||||
/* initialise state for writing to shards, we'll open connections on demand */
|
||||
copyDest->rStartup(copyDest, 0, tupleDescriptor);
|
||||
copyDest->rStartup(copyDest, 0, sourceTupleDescriptor);
|
||||
|
||||
DoCopyFromLocalTableIntoShards(distributedRelation, copyDest, slot, estate);
|
||||
uint64 rowsCopied = DoCopyFromLocalTableIntoShards(localRelation, copyDest, slot,
|
||||
estate);
|
||||
|
||||
/* finish writing into the shards */
|
||||
copyDest->rShutdown(copyDest);
|
||||
|
@ -2472,24 +2751,28 @@ CopyLocalDataIntoShards(Oid distributedRelationId)
|
|||
/* free memory and close the relation */
|
||||
ExecDropSingleTupleTableSlot(slot);
|
||||
FreeExecutorState(estate);
|
||||
table_close(distributedRelation, NoLock);
|
||||
table_close(localRelation, NoLock);
|
||||
|
||||
PopActiveSnapshot();
|
||||
|
||||
return rowsCopied;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DoCopyFromLocalTableIntoShards performs a copy operation
|
||||
* from local tables into shards.
|
||||
*
|
||||
* Returns the number of rows copied.
|
||||
*/
|
||||
static void
|
||||
DoCopyFromLocalTableIntoShards(Relation distributedRelation,
|
||||
static uint64
|
||||
DoCopyFromLocalTableIntoShards(Relation localRelation,
|
||||
DestReceiver *copyDest,
|
||||
TupleTableSlot *slot,
|
||||
EState *estate)
|
||||
{
|
||||
/* begin reading from local table */
|
||||
TableScanDesc scan = table_beginscan(distributedRelation, GetActiveSnapshot(), 0,
|
||||
TableScanDesc scan = table_beginscan(localRelation, GetActiveSnapshot(), 0,
|
||||
NULL);
|
||||
|
||||
MemoryContext oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
|
||||
|
@ -2524,22 +2807,12 @@ DoCopyFromLocalTableIntoShards(Relation distributedRelation,
|
|||
ereport(DEBUG1, (errmsg("Copied " UINT64_FORMAT " rows", rowsCopied)));
|
||||
}
|
||||
|
||||
if (rowsCopied > 0)
|
||||
{
|
||||
char *qualifiedRelationName =
|
||||
generate_qualified_relation_name(RelationGetRelid(distributedRelation));
|
||||
ereport(NOTICE, (errmsg("copying the data has completed"),
|
||||
errdetail("The local data in the table is no longer visible, "
|
||||
"but is still on disk."),
|
||||
errhint("To remove the local data, run: SELECT "
|
||||
"truncate_local_data_after_distributing_table($$%s$$)",
|
||||
qualifiedRelationName)));
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
/* finish reading from the local table */
|
||||
table_endscan(scan);
|
||||
|
||||
return rowsCopied;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
static AlterOwnerStmt * RecreateAlterDatabaseOwnerStmt(Oid databaseOid);
|
||||
static Oid get_database_owner(Oid db_oid);
|
||||
List * PreprocessGrantOnDatabaseStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext);
|
||||
|
||||
/* controlled via GUC */
|
||||
bool EnableAlterDatabaseOwner = true;
|
||||
|
@ -107,3 +109,136 @@ get_database_owner(Oid db_oid)
|
|||
|
||||
return dba;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessGrantOnDatabaseStmt 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 databases.
|
||||
*/
|
||||
List *
|
||||
PreprocessGrantOnDatabaseStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_DATABASE);
|
||||
|
||||
List *databaseList = stmt->objects;
|
||||
|
||||
if (list_length(databaseList) == 0)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterDatabaseStmt 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 databases.
|
||||
*/
|
||||
List *
|
||||
PreprocessAlterDatabaseStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
AlterDatabaseStmt *stmt = castNode(AlterDatabaseStmt, node);
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||
|
||||
/*
|
||||
* PreprocessAlterDatabaseSetStmt 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 databases.
|
||||
*/
|
||||
List *
|
||||
PreprocessAlterDatabaseRefreshCollStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
AlterDatabaseRefreshCollStmt *stmt = castNode(AlterDatabaseRefreshCollStmt, node);
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterDatabaseSetStmt 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 databases.
|
||||
*/
|
||||
List *
|
||||
PreprocessAlterDatabaseSetStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
AlterDatabaseSetStmt *stmt = castNode(AlterDatabaseSetStmt, node);
|
||||
|
||||
EnsureCoordinator();
|
||||
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
|
|
@ -112,15 +112,35 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
|
|||
dependency->objectSubId, ExclusiveLock);
|
||||
}
|
||||
|
||||
WorkerNode *workerNode = NULL;
|
||||
foreach_ptr(workerNode, workerNodeList)
|
||||
{
|
||||
const char *nodeName = workerNode->workerName;
|
||||
uint32 nodePort = workerNode->workerPort;
|
||||
|
||||
SendCommandListToWorkerOutsideTransaction(nodeName, nodePort,
|
||||
CitusExtensionOwnerName(),
|
||||
ddlCommands);
|
||||
/*
|
||||
* We need to propagate dependencies via the current user's metadata connection if
|
||||
* any dependency for the target is created in the current transaction. Our assumption
|
||||
* is that if we rely on a dependency created in the current transaction, then the
|
||||
* current user, most probably, has permissions to create the target object as well.
|
||||
* Note that, user still may not be able to create the target due to no permissions
|
||||
* for any of its dependencies. But this is ok since it should be rare.
|
||||
*
|
||||
* If we opted to use a separate superuser connection for the target, then we would
|
||||
* have visibility issues since propagated dependencies would be invisible to
|
||||
* the separate connection until we locally commit.
|
||||
*/
|
||||
if (HasAnyDependencyInPropagatedObjects(target))
|
||||
{
|
||||
SendCommandListToWorkersWithMetadata(ddlCommands);
|
||||
}
|
||||
else
|
||||
{
|
||||
WorkerNode *workerNode = NULL;
|
||||
foreach_ptr(workerNode, workerNodeList)
|
||||
{
|
||||
const char *nodeName = workerNode->workerName;
|
||||
uint32 nodePort = workerNode->workerPort;
|
||||
|
||||
SendCommandListToWorkerOutsideTransaction(nodeName, nodePort,
|
||||
CitusExtensionOwnerName(),
|
||||
ddlCommands);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -432,6 +432,54 @@ static DistributeObjectOps Database_AlterOwner = {
|
|||
.address = AlterDatabaseOwnerObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
|
||||
static DistributeObjectOps Database_Grant = {
|
||||
.deparse = DeparseGrantOnDatabaseStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessGrantOnDatabaseStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_DATABASE,
|
||||
.operationType = DIST_OPS_ALTER,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
|
||||
static DistributeObjectOps Database_Alter = {
|
||||
.deparse = DeparseAlterDatabaseStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessAlterDatabaseStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_DATABASE,
|
||||
.operationType = DIST_OPS_ALTER,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||
static DistributeObjectOps Database_RefreshColl = {
|
||||
.deparse = DeparseAlterDatabaseRefreshCollStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessAlterDatabaseRefreshCollStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_DATABASE,
|
||||
.operationType = DIST_OPS_ALTER,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
#endif
|
||||
|
||||
static DistributeObjectOps Database_Set = {
|
||||
.deparse = DeparseAlterDatabaseSetStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = PreprocessAlterDatabaseSetStmt,
|
||||
.postprocess = NULL,
|
||||
.objectType = OBJECT_DATABASE,
|
||||
.operationType = DIST_OPS_ALTER,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
|
||||
|
||||
static DistributeObjectOps Domain_Alter = {
|
||||
.deparse = DeparseAlterDomainStmt,
|
||||
.qualify = QualifyAlterDomainStmt,
|
||||
|
@ -1260,7 +1308,6 @@ static DistributeObjectOps Trigger_Rename = {
|
|||
.markDistributed = false,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* GetDistributeObjectOps looks up the DistributeObjectOps which handles the node.
|
||||
*
|
||||
|
@ -1271,6 +1318,25 @@ GetDistributeObjectOps(Node *node)
|
|||
{
|
||||
switch (nodeTag(node))
|
||||
{
|
||||
case T_AlterDatabaseStmt:
|
||||
{
|
||||
return &Database_Alter;
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||
case T_AlterDatabaseRefreshCollStmt:
|
||||
{
|
||||
return &Database_RefreshColl;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
case T_AlterDatabaseSetStmt:
|
||||
{
|
||||
return &Database_Set;
|
||||
}
|
||||
|
||||
|
||||
case T_AlterDomainStmt:
|
||||
{
|
||||
return &Domain_Alter;
|
||||
|
@ -1911,6 +1977,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Routine_Grant;
|
||||
}
|
||||
|
||||
case OBJECT_DATABASE:
|
||||
{
|
||||
return &Database_Grant;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return &Any_Grant;
|
||||
|
|
|
@ -64,7 +64,8 @@ CreateDomainStmt *
|
|||
RecreateDomainStmt(Oid domainOid)
|
||||
{
|
||||
CreateDomainStmt *stmt = makeNode(CreateDomainStmt);
|
||||
stmt->domainname = stringToQualifiedNameList(format_type_be_qualified(domainOid));
|
||||
stmt->domainname = stringToQualifiedNameList_compat(format_type_be_qualified(
|
||||
domainOid));
|
||||
|
||||
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainOid));
|
||||
if (!HeapTupleIsValid(tup))
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "distributed/coordinator_protocol.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/multi_partitioning_utils.h"
|
||||
#include "distributed/tenant_schema_metadata.h"
|
||||
#include "distributed/worker_transaction.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
|
|
@ -50,7 +50,7 @@ static List * GetAllViews(void);
|
|||
static bool ShouldPropagateExtensionCommand(Node *parseTree);
|
||||
static bool IsAlterExtensionSetSchemaCitus(Node *parseTree);
|
||||
static Node * RecreateExtensionStmt(Oid extensionOid);
|
||||
static List * GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId);
|
||||
static List * GenerateGrantCommandsOnExtensionDependentFDWs(Oid extensionId);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -985,7 +985,7 @@ CreateExtensionDDLCommand(const ObjectAddress *extensionAddress)
|
|||
|
||||
/* any privilege granted on FDWs that belong to the extension should be included */
|
||||
List *FDWGrants =
|
||||
GenerateGrantCommandsOnExtesionDependentFDWs(extensionAddress->objectId);
|
||||
GenerateGrantCommandsOnExtensionDependentFDWs(extensionAddress->objectId);
|
||||
|
||||
ddlCommands = list_concat(ddlCommands, FDWGrants);
|
||||
|
||||
|
@ -1048,11 +1048,11 @@ RecreateExtensionStmt(Oid extensionOid)
|
|||
|
||||
|
||||
/*
|
||||
* GenerateGrantCommandsOnExtesionDependentFDWs returns a list of commands that GRANTs
|
||||
* GenerateGrantCommandsOnExtensionDependentFDWs returns a list of commands that GRANTs
|
||||
* the privileges on FDWs that are depending on the given extension.
|
||||
*/
|
||||
static List *
|
||||
GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId)
|
||||
GenerateGrantCommandsOnExtensionDependentFDWs(Oid extensionId)
|
||||
{
|
||||
List *commands = NIL;
|
||||
List *FDWOids = GetDependentFDWsToExtension(extensionId);
|
||||
|
|
|
@ -132,7 +132,7 @@ EnsureNoFKeyFromTableType(Oid relationId, int tableTypeFlag)
|
|||
|
||||
|
||||
/*
|
||||
* EnsureNoFKeyToTableType ensures that given relation is not referencing by any table specified
|
||||
* EnsureNoFKeyToTableType ensures that given relation is not referencing any table specified
|
||||
* by table type flag.
|
||||
*/
|
||||
void
|
||||
|
@ -839,6 +839,22 @@ GetForeignConstraintToReferenceTablesCommands(Oid relationId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetForeignConstraintToReferenceTablesCommands takes in a relationId, and
|
||||
* returns the list of foreign constraint commands needed to reconstruct
|
||||
* foreign key constraints that the table is involved in as the "referenced"
|
||||
* one and the "referencing" table is a reference table.
|
||||
*/
|
||||
List *
|
||||
GetForeignConstraintFromOtherReferenceTablesCommands(Oid relationId)
|
||||
{
|
||||
int flags = INCLUDE_REFERENCED_CONSTRAINTS |
|
||||
EXCLUDE_SELF_REFERENCES |
|
||||
INCLUDE_REFERENCE_TABLES;
|
||||
return GetForeignConstraintCommandsInternal(relationId, flags);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetForeignConstraintToDistributedTablesCommands takes in a relationId, and
|
||||
* returns the list of foreign constraint commands needed to reconstruct
|
||||
|
@ -879,7 +895,7 @@ GetForeignConstraintCommandsInternal(Oid relationId, int flags)
|
|||
|
||||
List *foreignKeyCommands = NIL;
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
Oid foreignKeyOid = InvalidOid;
|
||||
foreach_oid(foreignKeyOid, foreignKeyOids)
|
||||
|
@ -890,7 +906,7 @@ GetForeignConstraintCommandsInternal(Oid relationId, int flags)
|
|||
}
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
return foreignKeyCommands;
|
||||
}
|
||||
|
@ -1227,7 +1243,7 @@ GetForeignKeyOids(Oid relationId, int flags)
|
|||
|
||||
Relation pgConstraint = table_open(ConstraintRelationId, AccessShareLock);
|
||||
ScanKeyInit(&scanKey[0], pgConstraintTargetAttrNumber,
|
||||
BTEqualStrategyNumber, F_OIDEQ, relationId);
|
||||
BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId));
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgConstraint, indexOid, useIndex,
|
||||
NULL, scanKeyCount, scanKey);
|
||||
|
||||
|
|
|
@ -780,7 +780,7 @@ UpdateFunctionDistributionInfo(const ObjectAddress *distAddress,
|
|||
ScanKeyInit(&scanKey[1], Anum_pg_dist_object_objid, BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(distAddress->objectId));
|
||||
ScanKeyInit(&scanKey[2], Anum_pg_dist_object_objsubid, BTEqualStrategyNumber,
|
||||
F_INT4EQ, ObjectIdGetDatum(distAddress->objectSubId));
|
||||
F_INT4EQ, Int32GetDatum(distAddress->objectSubId));
|
||||
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgDistObjectRel,
|
||||
DistObjectPrimaryKeyIndexId(),
|
||||
|
@ -909,15 +909,14 @@ GetFunctionDDLCommand(const RegProcedure funcOid, bool useCreateOrReplace)
|
|||
else
|
||||
{
|
||||
Datum sqlTextDatum = (Datum) 0;
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
sqlTextDatum = DirectFunctionCall1(pg_get_functiondef,
|
||||
ObjectIdGetDatum(funcOid));
|
||||
createFunctionSQL = TextDatumGetCString(sqlTextDatum);
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
}
|
||||
|
||||
return createFunctionSQL;
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
#include "catalog/index.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/pg_class.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "catalog/pg_namespace.h"
|
||||
#endif
|
||||
#include "commands/defrem.h"
|
||||
#include "commands/tablecmds.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
|
@ -1055,8 +1058,8 @@ RangeVarCallbackForDropIndex(const RangeVar *rel, Oid relOid, Oid oldRelOid, voi
|
|||
errmsg("\"%s\" is not an index", rel->relname)));
|
||||
|
||||
/* Allow DROP to either table owner or schema owner */
|
||||
if (!pg_class_ownercheck(relOid, GetUserId()) &&
|
||||
!pg_namespace_ownercheck(classform->relnamespace, GetUserId()))
|
||||
if (!object_ownercheck(RelationRelationId, relOid, GetUserId()) &&
|
||||
!object_ownercheck(NamespaceRelationId, classform->relnamespace, GetUserId()))
|
||||
{
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX, rel->relname);
|
||||
}
|
||||
|
@ -1140,7 +1143,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation, Oid relId, Oid oldRelI
|
|||
errmsg("\"%s\" is not an index", relation->relname)));
|
||||
|
||||
/* Check permissions */
|
||||
if (!pg_class_ownercheck(relId, GetUserId()))
|
||||
if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX, relation->relname);
|
||||
|
||||
/* Lock heap before index to avoid deadlock. */
|
||||
|
|
|
@ -83,6 +83,9 @@
|
|||
#include "distributed/locally_reserved_shared_connections.h"
|
||||
#include "distributed/placement_connection.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "distributed/relation_utils.h"
|
||||
#endif
|
||||
#include "distributed/remote_commands.h"
|
||||
#include "distributed/remote_transaction.h"
|
||||
#include "distributed/replication_origin_session_utils.h"
|
||||
|
@ -422,7 +425,8 @@ EnsureCopyCanRunOnRelation(Oid relationId)
|
|||
*/
|
||||
if (RecoveryInProgress() && WritableStandbyCoordinator)
|
||||
{
|
||||
ereport(ERROR, (errmsg("COPY command to Citus tables is not allowed in "
|
||||
ereport(ERROR, (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
|
||||
errmsg("COPY command to Citus tables is not allowed in "
|
||||
"read-only mode"),
|
||||
errhint("All COPY commands to citus tables happen via 2PC, "
|
||||
"and 2PC requires the database to be in a writable state."),
|
||||
|
@ -1544,7 +1548,7 @@ CoerceColumnValue(Datum inputValue, CopyCoercionData *coercionPath)
|
|||
{
|
||||
switch (coercionPath->coercionType)
|
||||
{
|
||||
case 0:
|
||||
case COERCION_PATH_NONE:
|
||||
{
|
||||
return inputValue; /* this was a dropped column */
|
||||
}
|
||||
|
@ -3149,10 +3153,17 @@ CheckCopyPermissions(CopyStmt *copyStatement)
|
|||
rel = table_openrv(copyStatement->relation,
|
||||
is_from ? RowExclusiveLock : AccessShareLock);
|
||||
|
||||
range_table = CreateRangeTable(rel, required_access);
|
||||
range_table = CreateRangeTable(rel);
|
||||
RangeTblEntry *rte = (RangeTblEntry*) linitial(range_table);
|
||||
tupDesc = RelationGetDescr(rel);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
/* create permission info for rte */
|
||||
RTEPermissionInfo *perminfo = GetFilledPermissionInfo(rel->rd_id, rte->inh, required_access);
|
||||
#else
|
||||
rte->requiredPerms = required_access;
|
||||
#endif
|
||||
|
||||
attnums = CopyGetAttnums(tupDesc, rel, copyStatement->attlist);
|
||||
foreach(cur, attnums)
|
||||
{
|
||||
|
@ -3160,15 +3171,29 @@ CheckCopyPermissions(CopyStmt *copyStatement)
|
|||
|
||||
if (is_from)
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
perminfo->insertedCols = bms_add_member(perminfo->insertedCols, attno);
|
||||
#else
|
||||
rte->insertedCols = bms_add_member(rte->insertedCols, attno);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
perminfo->selectedCols = bms_add_member(perminfo->selectedCols, attno);
|
||||
#else
|
||||
rte->selectedCols = bms_add_member(rte->selectedCols, attno);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
/* link rte to its permission info then check permissions */
|
||||
rte->perminfoindex = 1;
|
||||
ExecCheckPermissions(list_make1(rte), list_make1(perminfo), true);
|
||||
#else
|
||||
ExecCheckRTPerms(range_table, true);
|
||||
#endif
|
||||
|
||||
/* TODO: Perform RLS checks once supported */
|
||||
|
||||
|
@ -3181,13 +3206,12 @@ CheckCopyPermissions(CopyStmt *copyStatement)
|
|||
* CreateRangeTable creates a range table with the given relation.
|
||||
*/
|
||||
List *
|
||||
CreateRangeTable(Relation rel, AclMode requiredAccess)
|
||||
CreateRangeTable(Relation rel)
|
||||
{
|
||||
RangeTblEntry *rte = makeNode(RangeTblEntry);
|
||||
rte->rtekind = RTE_RELATION;
|
||||
rte->relid = rel->rd_id;
|
||||
rte->relkind = rel->rd_rel->relkind;
|
||||
rte->requiredPerms = requiredAccess;
|
||||
return list_make1(rte);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ static const char * WrapQueryInAlterRoleIfExistsCall(const char *query, RoleSpec
|
|||
static VariableSetStmt * MakeVariableSetStmt(const char *config);
|
||||
static int ConfigGenericNameCompare(const void *lhs, const void *rhs);
|
||||
static List * RoleSpecToObjectAddress(RoleSpec *role, bool missing_ok);
|
||||
static bool IsGrantRoleWithInheritOrSetOption(GrantRoleStmt *stmt);
|
||||
|
||||
/* controlled via GUC */
|
||||
bool EnableCreateRolePropagation = true;
|
||||
|
@ -703,12 +704,13 @@ MakeSetStatementArguments(char *configurationName, char *configurationValue)
|
|||
* is no other way to determine allowed units, and value types other than
|
||||
* using this function
|
||||
*/
|
||||
struct config_generic **gucVariables = get_guc_variables();
|
||||
int numOpts = GetNumConfigOptions();
|
||||
int gucCount = 0;
|
||||
struct config_generic **gucVariables = get_guc_variables_compat(&gucCount);
|
||||
|
||||
struct config_generic **matchingConfig =
|
||||
(struct config_generic **) SafeBsearch((void *) &key,
|
||||
(void *) gucVariables,
|
||||
numOpts,
|
||||
gucCount,
|
||||
sizeof(struct config_generic *),
|
||||
ConfigGenericNameCompare);
|
||||
|
||||
|
@ -820,7 +822,12 @@ GenerateGrantRoleStmtsFromOptions(RoleSpec *roleSpec, List *options)
|
|||
|
||||
if (strcmp(option->defname, "adminmembers") == 0)
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
DefElem *opt = makeDefElem("admin", (Node *) makeBoolean(true), -1);
|
||||
grantRoleStmt->opt = list_make1(opt);
|
||||
#else
|
||||
grantRoleStmt->admin_opt = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
stmts = lappend(stmts, grantRoleStmt);
|
||||
|
@ -868,7 +875,15 @@ GenerateGrantRoleStmtsOfRole(Oid roleid)
|
|||
|
||||
grantRoleStmt->grantor = NULL;
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
if (membership->admin_option)
|
||||
{
|
||||
DefElem *opt = makeDefElem("admin", (Node *) makeBoolean(true), -1);
|
||||
grantRoleStmt->opt = list_make1(opt);
|
||||
}
|
||||
#else
|
||||
grantRoleStmt->admin_opt = membership->admin_option;
|
||||
#endif
|
||||
|
||||
stmts = lappend(stmts, grantRoleStmt);
|
||||
}
|
||||
|
@ -1127,6 +1142,19 @@ PreprocessGrantRoleStmt(Node *node, const char *queryString,
|
|||
return NIL;
|
||||
}
|
||||
|
||||
if (IsGrantRoleWithInheritOrSetOption(stmt))
|
||||
{
|
||||
if (EnableUnsupportedFeatureMessages)
|
||||
{
|
||||
ereport(NOTICE, (errmsg("not propagating GRANT/REVOKE commands with specified"
|
||||
" INHERIT/SET options to worker nodes"),
|
||||
errhint(
|
||||
"Connect to worker nodes directly to manually run the same"
|
||||
" GRANT/REVOKE command after disabling DDL propagation.")));
|
||||
}
|
||||
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
|
||||
|
@ -1176,6 +1204,27 @@ PostprocessGrantRoleStmt(Node *node, const char *queryString)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* IsGrantRoleWithInheritOrSetOption returns true if the given
|
||||
* GrantRoleStmt has inherit or set option specified in its options
|
||||
*/
|
||||
static bool
|
||||
IsGrantRoleWithInheritOrSetOption(GrantRoleStmt *stmt)
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
DefElem *opt = NULL;
|
||||
foreach_ptr(opt, stmt->opt)
|
||||
{
|
||||
if (strcmp(opt->defname, "inherit") == 0 || strcmp(opt->defname, "set") == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ConfigGenericNameCompare compares two config_generic structs based on their
|
||||
* name fields. If the name fields contain the same strings two structs are
|
||||
|
|
|
@ -369,7 +369,7 @@ SchemaHasDistributedTableWithFKey(char *schemaName)
|
|||
Relation pgClass = table_open(RelationRelationId, AccessShareLock);
|
||||
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_class_relnamespace, BTEqualStrategyNumber,
|
||||
F_OIDEQ, namespaceOid);
|
||||
F_OIDEQ, ObjectIdGetDatum(namespaceOid));
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgClass, scanIndexId, useIndex, NULL,
|
||||
scanKeyCount, scanKey);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/multi_partitioning_utils.h"
|
||||
#include "distributed/shard_transfer.h"
|
||||
#include "distributed/tenant_schema_metadata.h"
|
||||
#include "distributed/worker_shard_visibility.h"
|
||||
#include "utils/builtins.h"
|
||||
|
@ -29,6 +30,16 @@
|
|||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
/* return value of CreateCitusMoveSchemaParams() */
|
||||
typedef struct
|
||||
{
|
||||
uint64 anchorShardId;
|
||||
uint32 sourceNodeId;
|
||||
char *sourceNodeName;
|
||||
uint32 sourceNodePort;
|
||||
} CitusMoveSchemaParams;
|
||||
|
||||
|
||||
static void UnregisterTenantSchemaGlobally(Oid schemaId, char *schemaName);
|
||||
static List * SchemaGetNonShardTableIdList(Oid schemaId);
|
||||
static void EnsureSchemaCanBeDistributed(Oid schemaId, List *schemaTableIdList);
|
||||
|
@ -36,10 +47,14 @@ static void EnsureTenantSchemaNameAllowed(Oid schemaId);
|
|||
static void EnsureTableKindSupportedForTenantSchema(Oid relationId);
|
||||
static void EnsureFKeysForTenantTable(Oid relationId);
|
||||
static void EnsureSchemaExist(Oid schemaId);
|
||||
static CitusMoveSchemaParams * CreateCitusMoveSchemaParams(Oid schemaId);
|
||||
static uint64 TenantSchemaPickAnchorShardId(Oid schemaId);
|
||||
|
||||
|
||||
/* controlled via citus.enable_schema_based_sharding GUC */
|
||||
bool EnableSchemaBasedSharding = false;
|
||||
|
||||
|
||||
const char *TenantOperationNames[TOTAL_TENANT_OPERATION] = {
|
||||
"undistribute_table",
|
||||
"alter_distributed_table",
|
||||
|
@ -52,6 +67,8 @@ const char *TenantOperationNames[TOTAL_TENANT_OPERATION] = {
|
|||
PG_FUNCTION_INFO_V1(citus_internal_unregister_tenant_schema_globally);
|
||||
PG_FUNCTION_INFO_V1(citus_schema_distribute);
|
||||
PG_FUNCTION_INFO_V1(citus_schema_undistribute);
|
||||
PG_FUNCTION_INFO_V1(citus_schema_move);
|
||||
PG_FUNCTION_INFO_V1(citus_schema_move_with_nodeid);
|
||||
|
||||
/*
|
||||
* ShouldUseSchemaBasedSharding returns true if schema given name should be
|
||||
|
@ -757,6 +774,139 @@ citus_schema_undistribute(PG_FUNCTION_ARGS)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* citus_schema_move moves the shards that belong to given distributed tenant
|
||||
* schema from one node to the other node by using citus_move_shard_placement().
|
||||
*/
|
||||
Datum
|
||||
citus_schema_move(PG_FUNCTION_ARGS)
|
||||
{
|
||||
CheckCitusVersion(ERROR);
|
||||
EnsureCoordinator();
|
||||
|
||||
Oid schemaId = PG_GETARG_OID(0);
|
||||
CitusMoveSchemaParams *params = CreateCitusMoveSchemaParams(schemaId);
|
||||
|
||||
DirectFunctionCall6(citus_move_shard_placement,
|
||||
UInt64GetDatum(params->anchorShardId),
|
||||
CStringGetTextDatum(params->sourceNodeName),
|
||||
UInt32GetDatum(params->sourceNodePort),
|
||||
PG_GETARG_DATUM(1),
|
||||
PG_GETARG_DATUM(2),
|
||||
PG_GETARG_DATUM(3));
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* citus_schema_move_with_nodeid does the same as citus_schema_move(), but
|
||||
* accepts node id as parameter instead of hostname and port, hence uses
|
||||
* citus_move_shard_placement_with_nodeid().
|
||||
*/
|
||||
Datum
|
||||
citus_schema_move_with_nodeid(PG_FUNCTION_ARGS)
|
||||
{
|
||||
CheckCitusVersion(ERROR);
|
||||
EnsureCoordinator();
|
||||
|
||||
Oid schemaId = PG_GETARG_OID(0);
|
||||
CitusMoveSchemaParams *params = CreateCitusMoveSchemaParams(schemaId);
|
||||
|
||||
DirectFunctionCall4(citus_move_shard_placement_with_nodeid,
|
||||
UInt64GetDatum(params->anchorShardId),
|
||||
UInt32GetDatum(params->sourceNodeId),
|
||||
PG_GETARG_DATUM(1),
|
||||
PG_GETARG_DATUM(2));
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateCitusMoveSchemaParams is a helper function for
|
||||
* citus_schema_move() and citus_schema_move_with_nodeid()
|
||||
* that validates input schema and returns the parameters to be used in underlying
|
||||
* shard transfer functions.
|
||||
*/
|
||||
static CitusMoveSchemaParams *
|
||||
CreateCitusMoveSchemaParams(Oid schemaId)
|
||||
{
|
||||
EnsureSchemaExist(schemaId);
|
||||
EnsureSchemaOwner(schemaId);
|
||||
|
||||
if (!IsTenantSchema(schemaId))
|
||||
{
|
||||
ereport(ERROR, (errmsg("schema %s is not a distributed schema",
|
||||
get_namespace_name(schemaId))));
|
||||
}
|
||||
|
||||
uint64 anchorShardId = TenantSchemaPickAnchorShardId(schemaId);
|
||||
if (anchorShardId == INVALID_SHARD_ID)
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot move distributed schema %s because it is empty",
|
||||
get_namespace_name(schemaId))));
|
||||
}
|
||||
|
||||
uint32 colocationId = SchemaIdGetTenantColocationId(schemaId);
|
||||
uint32 sourceNodeId = SingleShardTableColocationNodeId(colocationId);
|
||||
|
||||
bool missingOk = false;
|
||||
WorkerNode *sourceNode = FindNodeWithNodeId(sourceNodeId, missingOk);
|
||||
|
||||
CitusMoveSchemaParams *params = palloc0(sizeof(CitusMoveSchemaParams));
|
||||
params->anchorShardId = anchorShardId;
|
||||
params->sourceNodeId = sourceNodeId;
|
||||
params->sourceNodeName = sourceNode->workerName;
|
||||
params->sourceNodePort = sourceNode->workerPort;
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TenantSchemaPickAnchorShardId returns the id of one of the shards
|
||||
* created in given tenant schema.
|
||||
*
|
||||
* Returns INVALID_SHARD_ID if the schema was initially empty or if it's not
|
||||
* a tenant schema.
|
||||
*
|
||||
* Throws an error if all the tables in the schema are concurrently dropped.
|
||||
*/
|
||||
static uint64
|
||||
TenantSchemaPickAnchorShardId(Oid schemaId)
|
||||
{
|
||||
uint32 colocationId = SchemaIdGetTenantColocationId(schemaId);
|
||||
List *tablesInSchema = ColocationGroupTableList(colocationId, 0);
|
||||
if (list_length(tablesInSchema) == 0)
|
||||
{
|
||||
return INVALID_SHARD_ID;
|
||||
}
|
||||
|
||||
Oid relationId = InvalidOid;
|
||||
foreach_oid(relationId, tablesInSchema)
|
||||
{
|
||||
/*
|
||||
* Make sure the relation isn't dropped for the remainder of
|
||||
* the transaction.
|
||||
*/
|
||||
LockRelationOid(relationId, AccessShareLock);
|
||||
|
||||
/*
|
||||
* The relation might have been dropped just before we locked it.
|
||||
* Let's look it up.
|
||||
*/
|
||||
Relation relation = RelationIdGetRelation(relationId);
|
||||
if (RelationIsValid(relation))
|
||||
{
|
||||
/* relation still exists, we can use it */
|
||||
RelationClose(relation);
|
||||
return GetFirstShardId(relationId);
|
||||
}
|
||||
}
|
||||
|
||||
ereport(ERROR, (errmsg("tables in schema %s are concurrently dropped",
|
||||
get_namespace_name(schemaId))));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ErrorIfTenantTable errors out with the given operation name,
|
||||
* if the given relation is a tenant table.
|
||||
|
|
|
@ -77,6 +77,14 @@ PreprocessCreateStatisticsStmt(Node *node, const char *queryString,
|
|||
|
||||
EnsureCoordinator();
|
||||
|
||||
if (!(stmt->defnames))
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot create statistics without a name on a "
|
||||
"Citus table"),
|
||||
errhint("Consider specifying a name for the statistics")));
|
||||
}
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
|
||||
Oid statsOid = get_statistics_object_oid(stmt->defnames, true);
|
||||
|
@ -522,7 +530,7 @@ GetExplicitStatisticsCommandList(Oid relationId)
|
|||
RelationClose(relation);
|
||||
|
||||
/* generate fully-qualified names */
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
Oid statisticsId = InvalidOid;
|
||||
foreach_oid(statisticsId, statisticsIdList)
|
||||
|
@ -571,7 +579,7 @@ GetExplicitStatisticsCommandList(Oid relationId)
|
|||
}
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
return explicitStatisticsCommandList;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,8 @@ static void DistributePartitionUsingParent(Oid parentRelationId,
|
|||
static void ErrorIfMultiLevelPartitioning(Oid parentRelationId, Oid partitionRelationId);
|
||||
static void ErrorIfAttachCitusTableToPgLocalTable(Oid parentRelationId,
|
||||
Oid partitionRelationId);
|
||||
static bool DeparserSupportsAlterTableAddColumn(AlterTableStmt *alterTableStatement,
|
||||
AlterTableCmd *addColumnSubCommand);
|
||||
static bool ATDefinesFKeyBetweenPostgresAndCitusLocalOrRef(
|
||||
AlterTableStmt *alterTableStatement);
|
||||
static bool ShouldMarkConnectedRelationsNotAutoConverted(Oid leftRelationId,
|
||||
|
@ -101,8 +103,6 @@ static List * GetRelationIdListFromRangeVarList(List *rangeVarList, LOCKMODE loc
|
|||
static bool AlterTableCommandTypeIsTrigger(AlterTableType alterTableType);
|
||||
static bool AlterTableDropsForeignKey(AlterTableStmt *alterTableStatement);
|
||||
static void ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement);
|
||||
static List * InterShardDDLTaskList(Oid leftRelationId, Oid rightRelationId,
|
||||
const char *commandString);
|
||||
static bool AlterInvolvesPartitionColumn(AlterTableStmt *alterTableStatement,
|
||||
AlterTableCmd *command);
|
||||
static bool AlterColumnInvolvesIdentityColumn(AlterTableStmt *alterTableStatement,
|
||||
|
@ -120,7 +120,8 @@ static void SetInterShardDDLTaskRelationShardList(Task *task,
|
|||
static Oid get_attrdef_oid(Oid relationId, AttrNumber attnum);
|
||||
|
||||
static char * GetAddColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId,
|
||||
char *colname, TypeName *typeName);
|
||||
char *colname, TypeName *typeName,
|
||||
bool ifNotExists);
|
||||
static void ErrorIfAlterTableDropTableNameFromPostgresFdw(List *optionList, Oid
|
||||
relationId);
|
||||
|
||||
|
@ -1028,30 +1029,7 @@ PreprocessAlterTableAddConstraint(AlterTableStmt *alterTableStatement, Oid
|
|||
relationId,
|
||||
Constraint *constraint)
|
||||
{
|
||||
/*
|
||||
* We should only preprocess an ADD CONSTRAINT command if we have empty conname
|
||||
* This only happens when we have to create a constraint name in citus since the client does
|
||||
* not specify a name.
|
||||
* indexname should also be NULL to make sure this is not an
|
||||
* ADD {PRIMARY KEY, UNIQUE} USING INDEX command
|
||||
* which doesn't need a conname since the indexname will be used
|
||||
*/
|
||||
Assert(constraint->conname == NULL && constraint->indexname == NULL);
|
||||
|
||||
Relation rel = RelationIdGetRelation(relationId);
|
||||
|
||||
/*
|
||||
* Change the alterTableCommand so that the standard utility
|
||||
* hook runs it with the name we created.
|
||||
*/
|
||||
|
||||
constraint->conname = GenerateConstraintName(RelationGetRelationName(rel),
|
||||
RelationGetNamespace(rel),
|
||||
constraint);
|
||||
|
||||
RelationClose(rel);
|
||||
|
||||
SwitchToSequentialAndLocalExecutionIfConstraintNameTooLong(relationId, constraint);
|
||||
PrepareAlterTableStmtForConstraint(alterTableStatement, relationId, constraint);
|
||||
|
||||
char *ddlCommand = DeparseTreeNode((Node *) alterTableStatement);
|
||||
|
||||
|
@ -1067,11 +1045,6 @@ PreprocessAlterTableAddConstraint(AlterTableStmt *alterTableStatement, Oid
|
|||
Oid rightRelationId = RangeVarGetRelid(constraint->pktable, NoLock,
|
||||
false);
|
||||
|
||||
if (IsCitusTableType(rightRelationId, REFERENCE_TABLE))
|
||||
{
|
||||
EnsureSequentialModeForAlterTableOperation();
|
||||
}
|
||||
|
||||
/*
|
||||
* If one of the relations involved in the FOREIGN KEY constraint is not a distributed table, citus errors out eventually.
|
||||
* PreprocessAlterTableStmt function returns an empty tasklist in those cases.
|
||||
|
@ -1099,6 +1072,47 @@ PreprocessAlterTableAddConstraint(AlterTableStmt *alterTableStatement, Oid
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* PrepareAlterTableStmtForConstraint assigns a name to the constraint if it
|
||||
* does not have one and switches to sequential and local execution if the
|
||||
* constraint name is too long.
|
||||
*/
|
||||
void
|
||||
PrepareAlterTableStmtForConstraint(AlterTableStmt *alterTableStatement,
|
||||
Oid relationId,
|
||||
Constraint *constraint)
|
||||
{
|
||||
if (constraint->conname == NULL && constraint->indexname == NULL)
|
||||
{
|
||||
Relation rel = RelationIdGetRelation(relationId);
|
||||
|
||||
/*
|
||||
* Change the alterTableCommand so that the standard utility
|
||||
* hook runs it with the name we created.
|
||||
*/
|
||||
|
||||
constraint->conname = GenerateConstraintName(RelationGetRelationName(rel),
|
||||
RelationGetNamespace(rel),
|
||||
constraint);
|
||||
|
||||
RelationClose(rel);
|
||||
}
|
||||
|
||||
SwitchToSequentialAndLocalExecutionIfConstraintNameTooLong(relationId, constraint);
|
||||
|
||||
if (constraint->contype == CONSTR_FOREIGN)
|
||||
{
|
||||
Oid rightRelationId = RangeVarGetRelid(constraint->pktable, NoLock,
|
||||
false);
|
||||
|
||||
if (IsCitusTableType(rightRelationId, REFERENCE_TABLE))
|
||||
{
|
||||
EnsureSequentialModeForAlterTableOperation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterTableStmt determines whether a given ALTER TABLE statement
|
||||
* involves a distributed table. If so (and if the statement does not use
|
||||
|
@ -1267,6 +1281,7 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
|
|||
* we also set skip_validation to true to prevent PostgreSQL to verify validity
|
||||
* of the foreign constraint in master. Validity will be checked in workers
|
||||
* anyway.
|
||||
* - an ADD COLUMN .. that is the only subcommand in the list OR
|
||||
* - an ADD COLUMN .. DEFAULT nextval('..') OR
|
||||
* an ADD COLUMN .. SERIAL pseudo-type OR
|
||||
* an ALTER COLUMN .. SET DEFAULT nextval('..'). If there is we set
|
||||
|
@ -1396,13 +1411,6 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
|
|||
}
|
||||
else if (alterTableType == AT_AddColumn)
|
||||
{
|
||||
/*
|
||||
* TODO: This code path is nothing beneficial since we do not
|
||||
* support ALTER TABLE %s ADD COLUMN %s [constraint] for foreign keys.
|
||||
* However, the code is kept in case we fix the constraint
|
||||
* creation without a name and allow foreign key creation with the mentioned
|
||||
* command.
|
||||
*/
|
||||
ColumnDef *columnDefinition = (ColumnDef *) command->def;
|
||||
List *columnConstraints = columnDefinition->constraints;
|
||||
|
||||
|
@ -1426,12 +1434,36 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
|
|||
}
|
||||
}
|
||||
|
||||
if (DeparserSupportsAlterTableAddColumn(alterTableStatement, command))
|
||||
{
|
||||
deparseAT = true;
|
||||
|
||||
constraint = NULL;
|
||||
foreach_ptr(constraint, columnConstraints)
|
||||
{
|
||||
if (ConstrTypeCitusCanDefaultName(constraint->contype))
|
||||
{
|
||||
PrepareAlterTableStmtForConstraint(alterTableStatement,
|
||||
leftRelationId,
|
||||
constraint);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the constraints to the new subcommand because now we
|
||||
* might have assigned names to some of them.
|
||||
*/
|
||||
ColumnDef *newColumnDef = (ColumnDef *) newCmd->def;
|
||||
newColumnDef->constraints = copyObject(columnConstraints);
|
||||
}
|
||||
|
||||
/*
|
||||
* We check for ADD COLUMN .. DEFAULT expr
|
||||
* if expr contains nextval('user_defined_seq')
|
||||
* we should deparse the statement
|
||||
*/
|
||||
constraint = NULL;
|
||||
int constraintIdx = 0;
|
||||
foreach_ptr(constraint, columnConstraints)
|
||||
{
|
||||
if (constraint->contype == CONSTR_DEFAULT)
|
||||
|
@ -1447,14 +1479,19 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
|
|||
deparseAT = true;
|
||||
useInitialDDLCommandString = false;
|
||||
|
||||
/* the new column definition will have no constraint */
|
||||
ColumnDef *newColDef = copyObject(columnDefinition);
|
||||
newColDef->constraints = NULL;
|
||||
|
||||
newCmd->def = (Node *) newColDef;
|
||||
/* drop the default expression from new subcomand */
|
||||
ColumnDef *newColumnDef = (ColumnDef *) newCmd->def;
|
||||
newColumnDef->constraints =
|
||||
list_delete_nth_cell(newColumnDef->constraints,
|
||||
constraintIdx);
|
||||
}
|
||||
}
|
||||
|
||||
/* there can only be one DEFAULT constraint that can be used per column */
|
||||
break;
|
||||
}
|
||||
|
||||
constraintIdx++;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1638,6 +1675,49 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparserSupportsAlterTableAddColumn returns true if it's safe to deparse
|
||||
* the given ALTER TABLE statement that is known to contain given ADD COLUMN
|
||||
* subcommand.
|
||||
*/
|
||||
static bool
|
||||
DeparserSupportsAlterTableAddColumn(AlterTableStmt *alterTableStatement,
|
||||
AlterTableCmd *addColumnSubCommand)
|
||||
{
|
||||
/*
|
||||
* We support deparsing for ADD COLUMN only of it's the only
|
||||
* subcommand.
|
||||
*/
|
||||
if (list_length(alterTableStatement->cmds) == 1 &&
|
||||
alterTableStatement->objtype == OBJECT_TABLE)
|
||||
{
|
||||
ColumnDef *columnDefinition = (ColumnDef *) addColumnSubCommand->def;
|
||||
Constraint *constraint = NULL;
|
||||
foreach_ptr(constraint, columnDefinition->constraints)
|
||||
{
|
||||
if (constraint->contype == CONSTR_CHECK)
|
||||
{
|
||||
/*
|
||||
* Given that we're in the preprocess, any reference to the
|
||||
* column that we're adding would break the deparser. This
|
||||
* can only be the case with CHECK constraints. For this
|
||||
* reason, we skip deparsing the command and fall back to
|
||||
* legacy behavior that we follow for ADD COLUMN subcommands.
|
||||
*
|
||||
* For other constraint types, we prepare the constraint to
|
||||
* make sure that we can deparse it.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ATDefinesFKeyBetweenPostgresAndCitusLocalOrRef returns true if given
|
||||
* alter table command defines foreign key between a postgres table and a
|
||||
|
@ -2637,7 +2717,9 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement)
|
|||
columnDefinition
|
||||
->colname,
|
||||
columnDefinition
|
||||
->typeName);
|
||||
->typeName,
|
||||
command->
|
||||
missing_ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2902,7 +2984,7 @@ GetAlterColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, char *colna
|
|||
*/
|
||||
static char *
|
||||
GetAddColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, char *colname,
|
||||
TypeName *typeName)
|
||||
TypeName *typeName, bool ifNotExists)
|
||||
{
|
||||
char *qualifiedSequenceName = generate_qualified_relation_name(sequenceOid);
|
||||
char *qualifiedRelationName = generate_qualified_relation_name(relationId);
|
||||
|
@ -2927,8 +3009,9 @@ GetAddColumnWithNextvalDefaultCmd(Oid sequenceOid, Oid relationId, char *colname
|
|||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
appendStringInfo(&str,
|
||||
"ALTER TABLE %s ADD COLUMN %s %s "
|
||||
"DEFAULT %s(%s::regclass)", qualifiedRelationName, colname,
|
||||
"ALTER TABLE %s ADD COLUMN %s %s %s "
|
||||
"DEFAULT %s(%s::regclass)", qualifiedRelationName,
|
||||
ifNotExists ? "IF NOT EXISTS" : "", colname,
|
||||
format_type_extended(typeOid, typmod, formatFlags),
|
||||
quote_qualified_identifier("pg_catalog", nextvalFunctionName),
|
||||
quote_literal_cstr(qualifiedSequenceName));
|
||||
|
@ -3676,13 +3759,6 @@ SetupExecutionModeForAlterTable(Oid relationId, AlterTableCmd *command)
|
|||
}
|
||||
else if (alterTableType == AT_AddColumn)
|
||||
{
|
||||
/*
|
||||
* TODO: This code path will never be executed since we do not
|
||||
* support foreign constraint creation via
|
||||
* ALTER TABLE %s ADD COLUMN %s [constraint]. However, the code
|
||||
* is kept in case we fix the constraint creation without a name
|
||||
* and allow foreign key creation with the mentioned command.
|
||||
*/
|
||||
ColumnDef *columnDefinition = (ColumnDef *) command->def;
|
||||
List *columnConstraints = columnDefinition->constraints;
|
||||
|
||||
|
@ -3780,7 +3856,7 @@ SetupExecutionModeForAlterTable(Oid relationId, AlterTableCmd *command)
|
|||
* applied. rightRelationId is the relation id of either index or distributed table which
|
||||
* given command refers to.
|
||||
*/
|
||||
static List *
|
||||
List *
|
||||
InterShardDDLTaskList(Oid leftRelationId, Oid rightRelationId,
|
||||
const char *commandString)
|
||||
{
|
||||
|
@ -3878,25 +3954,29 @@ static void
|
|||
SetInterShardDDLTaskPlacementList(Task *task, ShardInterval *leftShardInterval,
|
||||
ShardInterval *rightShardInterval)
|
||||
{
|
||||
Oid leftRelationId = leftShardInterval->relationId;
|
||||
Oid rightRelationId = rightShardInterval->relationId;
|
||||
if (IsCitusTableType(leftRelationId, REFERENCE_TABLE) &&
|
||||
IsCitusTableType(rightRelationId, CITUS_LOCAL_TABLE))
|
||||
uint64 leftShardId = leftShardInterval->shardId;
|
||||
List *leftShardPlacementList = ActiveShardPlacementList(leftShardId);
|
||||
|
||||
uint64 rightShardId = rightShardInterval->shardId;
|
||||
List *rightShardPlacementList = ActiveShardPlacementList(rightShardId);
|
||||
|
||||
List *intersectedPlacementList = NIL;
|
||||
|
||||
ShardPlacement *leftShardPlacement = NULL;
|
||||
foreach_ptr(leftShardPlacement, leftShardPlacementList)
|
||||
{
|
||||
/*
|
||||
* If we are defining/dropping a foreign key from a reference table
|
||||
* to a citus local table, then we will execute ADD/DROP constraint
|
||||
* command only for coordinator placement of reference table.
|
||||
*/
|
||||
uint64 leftShardId = leftShardInterval->shardId;
|
||||
task->taskPlacementList = ActiveShardPlacementListOnGroup(leftShardId,
|
||||
COORDINATOR_GROUP_ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64 leftShardId = leftShardInterval->shardId;
|
||||
task->taskPlacementList = ActiveShardPlacementList(leftShardId);
|
||||
ShardPlacement *rightShardPlacement = NULL;
|
||||
foreach_ptr(rightShardPlacement, rightShardPlacementList)
|
||||
{
|
||||
if (leftShardPlacement->nodeId == rightShardPlacement->nodeId)
|
||||
{
|
||||
intersectedPlacementList = lappend(intersectedPlacementList,
|
||||
leftShardPlacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task->taskPlacementList = intersectedPlacementList;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ GetExplicitTriggerCommandList(Oid relationId)
|
|||
{
|
||||
List *createTriggerCommandList = NIL;
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
List *triggerIdList = GetExplicitTriggerIdList(relationId);
|
||||
|
||||
|
@ -116,7 +116,7 @@ GetExplicitTriggerCommandList(Oid relationId)
|
|||
}
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
return createTriggerCommandList;
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ GetExplicitTriggerIdList(Oid relationId)
|
|||
ScanKeyData scanKey[1];
|
||||
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_trigger_tgrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, relationId);
|
||||
BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId));
|
||||
|
||||
bool useIndex = true;
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgTrigger, TriggerRelidNameIndexId,
|
||||
|
|
|
@ -182,7 +182,7 @@ truncate_local_data_after_distributing_table(PG_FUNCTION_ARGS)
|
|||
TruncateStmt *truncateStmt = makeNode(TruncateStmt);
|
||||
|
||||
char *relationName = generate_qualified_relation_name(relationId);
|
||||
List *names = stringToQualifiedNameList(relationName);
|
||||
List *names = stringToQualifiedNameList_compat(relationName);
|
||||
truncateStmt->relations = list_make1(makeRangeVarFromNameList(names));
|
||||
truncateStmt->restart_seqs = false;
|
||||
truncateStmt->behavior = DROP_CASCADE;
|
||||
|
|
|
@ -187,7 +187,7 @@ RecreateCompositeTypeStmt(Oid typeOid)
|
|||
Assert(get_typtype(typeOid) == TYPTYPE_COMPOSITE);
|
||||
|
||||
CompositeTypeStmt *stmt = makeNode(CompositeTypeStmt);
|
||||
List *names = stringToQualifiedNameList(format_type_be_qualified(typeOid));
|
||||
List *names = stringToQualifiedNameList_compat(format_type_be_qualified(typeOid));
|
||||
stmt->typevar = makeRangeVarFromNameList(names);
|
||||
stmt->coldeflist = CompositeTypeColumnDefList(typeOid);
|
||||
|
||||
|
@ -252,7 +252,7 @@ RecreateEnumStmt(Oid typeOid)
|
|||
Assert(get_typtype(typeOid) == TYPTYPE_ENUM);
|
||||
|
||||
CreateEnumStmt *stmt = makeNode(CreateEnumStmt);
|
||||
stmt->typeName = stringToQualifiedNameList(format_type_be_qualified(typeOid));
|
||||
stmt->typeName = stringToQualifiedNameList_compat(format_type_be_qualified(typeOid));
|
||||
stmt->vals = EnumValsList(typeOid);
|
||||
|
||||
return stmt;
|
||||
|
@ -565,7 +565,8 @@ CreateTypeDDLCommandsIdempotent(const ObjectAddress *typeAddress)
|
|||
char *
|
||||
GenerateBackupNameForTypeCollision(const ObjectAddress *address)
|
||||
{
|
||||
List *names = stringToQualifiedNameList(format_type_be_qualified(address->objectId));
|
||||
List *names = stringToQualifiedNameList_compat(format_type_be_qualified(
|
||||
address->objectId));
|
||||
RangeVar *rel = makeRangeVarFromNameList(names);
|
||||
|
||||
char *newName = palloc0(NAMEDATALEN);
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
#include "tcop/utility.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/inval.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
@ -193,6 +194,7 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
|||
|
||||
bool isCreateAlterExtensionUpdateCitusStmt = IsCreateAlterExtensionUpdateCitusStmt(
|
||||
parsetree);
|
||||
|
||||
if (EnableVersionChecks && isCreateAlterExtensionUpdateCitusStmt)
|
||||
{
|
||||
ErrorIfUnstableCreateOrAlterExtensionStmt(parsetree);
|
||||
|
@ -207,6 +209,18 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
|||
PreprocessCreateExtensionStmtForCitusColumnar(parsetree);
|
||||
}
|
||||
|
||||
if (isCreateAlterExtensionUpdateCitusStmt || IsDropCitusExtensionStmt(parsetree))
|
||||
{
|
||||
/*
|
||||
* Citus maintains a higher level cache. We use the cache invalidation mechanism
|
||||
* of Postgres to achieve cache coherency between backends. Any change to citus
|
||||
* extension should be made known to other backends. We do this by invalidating the
|
||||
* relcache and therefore invoking the citus registered callback that invalidates
|
||||
* the citus cache in other backends.
|
||||
*/
|
||||
CacheInvalidateRelcacheAll();
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure that on DROP DATABASE we terminate the background daemon
|
||||
* associated with it.
|
||||
|
@ -923,18 +937,10 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
|||
foreach_ptr(address, addresses)
|
||||
{
|
||||
MarkObjectDistributed(address);
|
||||
TrackPropagatedObject(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsDropCitusExtensionStmt(parsetree) && !IsA(parsetree, DropdbStmt))
|
||||
{
|
||||
/*
|
||||
* Ensure value is valid, we can't do some checks during CREATE
|
||||
* EXTENSION. This is important to register some invalidation callbacks.
|
||||
*/
|
||||
CitusHasBeenLoaded(); /* lgtm[cpp/return-value-ignored] */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ typedef struct CitusVacuumParams
|
|||
VacOptValue truncate;
|
||||
VacOptValue index_cleanup;
|
||||
int nworkers;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
int ring_size;
|
||||
#endif
|
||||
} CitusVacuumParams;
|
||||
|
||||
/* Local functions forward declarations for processing distributed table commands */
|
||||
|
@ -318,13 +321,26 @@ DeparseVacuumStmtPrefix(CitusVacuumParams vacuumParams)
|
|||
}
|
||||
|
||||
/* if no flags remain, exit early */
|
||||
if (vacuumFlags == 0 &&
|
||||
vacuumParams.truncate == VACOPTVALUE_UNSPECIFIED &&
|
||||
vacuumParams.index_cleanup == VACOPTVALUE_UNSPECIFIED &&
|
||||
vacuumParams.nworkers == VACUUM_PARALLEL_NOTSET
|
||||
)
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
if (vacuumFlags & VACOPT_PROCESS_TOAST &&
|
||||
vacuumFlags & VACOPT_PROCESS_MAIN)
|
||||
{
|
||||
return vacuumPrefix->data;
|
||||
/* process toast and process main are true by default */
|
||||
if (((vacuumFlags & ~VACOPT_PROCESS_TOAST) & ~VACOPT_PROCESS_MAIN) == 0 &&
|
||||
vacuumParams.ring_size == -1 &&
|
||||
#else
|
||||
if (vacuumFlags & VACOPT_PROCESS_TOAST)
|
||||
{
|
||||
/* process toast is true by default */
|
||||
if ((vacuumFlags & ~VACOPT_PROCESS_TOAST) == 0 &&
|
||||
#endif
|
||||
vacuumParams.truncate == VACOPTVALUE_UNSPECIFIED &&
|
||||
vacuumParams.index_cleanup == VACOPTVALUE_UNSPECIFIED &&
|
||||
vacuumParams.nworkers == VACUUM_PARALLEL_NOTSET
|
||||
)
|
||||
{
|
||||
return vacuumPrefix->data;
|
||||
}
|
||||
}
|
||||
|
||||
/* otherwise, handle options */
|
||||
|
@ -360,11 +376,33 @@ DeparseVacuumStmtPrefix(CitusVacuumParams vacuumParams)
|
|||
appendStringInfoString(vacuumPrefix, "SKIP_LOCKED,");
|
||||
}
|
||||
|
||||
if (vacuumFlags & VACOPT_PROCESS_TOAST)
|
||||
if (!(vacuumFlags & VACOPT_PROCESS_TOAST))
|
||||
{
|
||||
appendStringInfoString(vacuumPrefix, "PROCESS_TOAST,");
|
||||
appendStringInfoString(vacuumPrefix, "PROCESS_TOAST FALSE,");
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
if (!(vacuumFlags & VACOPT_PROCESS_MAIN))
|
||||
{
|
||||
appendStringInfoString(vacuumPrefix, "PROCESS_MAIN FALSE,");
|
||||
}
|
||||
|
||||
if (vacuumFlags & VACOPT_SKIP_DATABASE_STATS)
|
||||
{
|
||||
appendStringInfoString(vacuumPrefix, "SKIP_DATABASE_STATS,");
|
||||
}
|
||||
|
||||
if (vacuumFlags & VACOPT_ONLY_DATABASE_STATS)
|
||||
{
|
||||
appendStringInfoString(vacuumPrefix, "ONLY_DATABASE_STATS,");
|
||||
}
|
||||
|
||||
if (vacuumParams.ring_size != -1)
|
||||
{
|
||||
appendStringInfo(vacuumPrefix, "BUFFER_USAGE_LIMIT %d,", vacuumParams.ring_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (vacuumParams.truncate != VACOPTVALUE_UNSPECIFIED)
|
||||
{
|
||||
appendStringInfoString(vacuumPrefix,
|
||||
|
@ -499,7 +537,14 @@ VacuumStmtParams(VacuumStmt *vacstmt)
|
|||
bool freeze = false;
|
||||
bool full = false;
|
||||
bool disable_page_skipping = false;
|
||||
bool process_toast = false;
|
||||
bool process_toast = true;
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
bool process_main = true;
|
||||
bool skip_database_stats = false;
|
||||
bool only_database_stats = false;
|
||||
params.ring_size = -1;
|
||||
#endif
|
||||
|
||||
/* Set default value */
|
||||
params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
|
||||
|
@ -519,6 +564,13 @@ VacuumStmtParams(VacuumStmt *vacstmt)
|
|||
{
|
||||
skip_locked = defGetBoolean(opt);
|
||||
}
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
|
||||
{
|
||||
char *vac_buffer_size = defGetString(opt);
|
||||
parse_int(vac_buffer_size, ¶ms.ring_size, GUC_UNIT_KB, NULL);
|
||||
}
|
||||
#endif
|
||||
else if (!vacstmt->is_vacuumcmd)
|
||||
{
|
||||
ereport(ERROR,
|
||||
|
@ -543,6 +595,20 @@ VacuumStmtParams(VacuumStmt *vacstmt)
|
|||
{
|
||||
disable_page_skipping = defGetBoolean(opt);
|
||||
}
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
else if (strcmp(opt->defname, "process_main") == 0)
|
||||
{
|
||||
process_main = defGetBoolean(opt);
|
||||
}
|
||||
else if (strcmp(opt->defname, "skip_database_stats") == 0)
|
||||
{
|
||||
skip_database_stats = defGetBoolean(opt);
|
||||
}
|
||||
else if (strcmp(opt->defname, "only_database_stats") == 0)
|
||||
{
|
||||
only_database_stats = defGetBoolean(opt);
|
||||
}
|
||||
#endif
|
||||
else if (strcmp(opt->defname, "process_toast") == 0)
|
||||
{
|
||||
process_toast = defGetBoolean(opt);
|
||||
|
@ -613,6 +679,11 @@ VacuumStmtParams(VacuumStmt *vacstmt)
|
|||
(analyze ? VACOPT_ANALYZE : 0) |
|
||||
(freeze ? VACOPT_FREEZE : 0) |
|
||||
(full ? VACOPT_FULL : 0) |
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
(process_main ? VACOPT_PROCESS_MAIN : 0) |
|
||||
(skip_database_stats ? VACOPT_SKIP_DATABASE_STATS : 0) |
|
||||
(only_database_stats ? VACOPT_ONLY_DATABASE_STATS : 0) |
|
||||
#endif
|
||||
(process_toast ? VACOPT_PROCESS_TOAST : 0) |
|
||||
(disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0);
|
||||
return params;
|
||||
|
|
|
@ -479,10 +479,7 @@ 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);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
/*
|
||||
* Push the transaction snapshot to be able to get vief definition with pg_get_viewdef
|
||||
|
@ -494,7 +491,7 @@ AppendViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid)
|
|||
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
|
||||
|
||||
PopActiveSnapshot();
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
appendStringInfo(buf, "AS %s ", viewDefinition);
|
||||
}
|
||||
|
|
|
@ -371,7 +371,7 @@ StartNodeUserDatabaseConnection(uint32 flags, const char *hostname, int32 port,
|
|||
*/
|
||||
MultiConnection *connection = MemoryContextAllocZero(ConnectionContext,
|
||||
sizeof(MultiConnection));
|
||||
connection->initilizationState = POOL_STATE_NOT_INITIALIZED;
|
||||
connection->initializationState = POOL_STATE_NOT_INITIALIZED;
|
||||
dlist_push_tail(entry->connections, &connection->connectionNode);
|
||||
|
||||
/* these two flags are by nature cannot happen at the same time */
|
||||
|
@ -417,7 +417,7 @@ StartNodeUserDatabaseConnection(uint32 flags, const char *hostname, int32 port,
|
|||
* We've already incremented the counter above, so we should decrement
|
||||
* when we're done with the connection.
|
||||
*/
|
||||
connection->initilizationState = POOL_STATE_COUNTER_INCREMENTED;
|
||||
connection->initializationState = POOL_STATE_COUNTER_INCREMENTED;
|
||||
|
||||
StartConnectionEstablishment(connection, &key);
|
||||
|
||||
|
@ -430,7 +430,7 @@ StartNodeUserDatabaseConnection(uint32 flags, const char *hostname, int32 port,
|
|||
}
|
||||
|
||||
/* fully initialized the connection, record it */
|
||||
connection->initilizationState = POOL_STATE_INITIALIZED;
|
||||
connection->initializationState = POOL_STATE_INITIALIZED;
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ FindAvailableConnection(dlist_head *connections, uint32 flags)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (connection->initilizationState != POOL_STATE_INITIALIZED)
|
||||
if (connection->initializationState != POOL_STATE_INITIALIZED)
|
||||
{
|
||||
/*
|
||||
* If the connection has not been initialized, it should not be
|
||||
|
@ -780,7 +780,7 @@ ShutdownConnection(MultiConnection *connection)
|
|||
|
||||
|
||||
/*
|
||||
* MultiConnectionStatePoll executes a PQconnectPoll on the connection to progres the
|
||||
* MultiConnectionStatePoll executes a PQconnectPoll on the connection to progress the
|
||||
* connection establishment. The return value of this function indicates if the
|
||||
* MultiConnectionPollState has been changed, which could require a change to the WaitEventSet
|
||||
*/
|
||||
|
@ -1182,10 +1182,10 @@ CitusPQFinish(MultiConnection *connection)
|
|||
}
|
||||
|
||||
/* behave idempotently, there is no gurantee that CitusPQFinish() is called once */
|
||||
if (connection->initilizationState >= POOL_STATE_COUNTER_INCREMENTED)
|
||||
if (connection->initializationState >= POOL_STATE_COUNTER_INCREMENTED)
|
||||
{
|
||||
DecrementSharedConnectionCounter(connection->hostname, connection->port);
|
||||
connection->initilizationState = POOL_STATE_NOT_INITIALIZED;
|
||||
connection->initializationState = POOL_STATE_NOT_INITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1482,7 +1482,7 @@ ShouldShutdownConnection(MultiConnection *connection, const int cachedConnection
|
|||
* from their application name.
|
||||
*/
|
||||
return (IsCitusInternalBackend() || IsRebalancerInternalBackend()) ||
|
||||
connection->initilizationState != POOL_STATE_INITIALIZED ||
|
||||
connection->initializationState != POOL_STATE_INITIALIZED ||
|
||||
cachedConnectionCount >= MaxCachedConnectionsPerWorker ||
|
||||
connection->forceCloseAtTransactionEnd ||
|
||||
PQstatus(connection->pgConn) != CONNECTION_OK ||
|
||||
|
@ -1541,7 +1541,7 @@ RestartConnection(MultiConnection *connection)
|
|||
* Not that we have to do this because ShutdownConnection() sets the
|
||||
* state to not initialized.
|
||||
*/
|
||||
connection->initilizationState = POOL_STATE_INITIALIZED;
|
||||
connection->initializationState = POOL_STATE_INITIALIZED;
|
||||
connection->connectionState = MULTI_CONNECTION_CONNECTING;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* (b) Reserving connections, the logic that this
|
||||
* file implements.
|
||||
*
|
||||
* Finally, as the name already implies, once a node has reserved a shared
|
||||
* Finally, as the name already implies, once a node has reserved a shared
|
||||
* connection, it is guaranteed to have the right to establish a connection
|
||||
* to the given remote node when needed.
|
||||
*
|
||||
|
@ -505,7 +505,7 @@ IsReservationPossible(void)
|
|||
|
||||
|
||||
/*
|
||||
* AllocateReservedConectionEntry allocates the required entry in the hash
|
||||
* AllocateOrGetReservedConnectionEntry allocates the required entry in the hash
|
||||
* map by HASH_ENTER. The function throws an error if it cannot allocate
|
||||
* the entry.
|
||||
*/
|
||||
|
|
|
@ -716,14 +716,14 @@ PutRemoteCopyData(MultiConnection *connection, const char *buffer, int nbytes)
|
|||
Assert(PQisnonblocking(pgConn));
|
||||
|
||||
int copyState = PQputCopyData(pgConn, buffer, nbytes);
|
||||
if (copyState == -1)
|
||||
if (copyState <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* PQputCopyData may have queued up part of the data even if it managed
|
||||
* to send some of it succesfully. We provide back pressure by waiting
|
||||
* to send some of it successfully. We provide back pressure by waiting
|
||||
* until the socket is writable to prevent the internal libpq buffers
|
||||
* from growing excessively.
|
||||
*
|
||||
|
|
|
@ -339,7 +339,7 @@ TryToIncrementSharedConnectionCounter(const char *hostname, int port)
|
|||
LockConnectionSharedMemory(LW_EXCLUSIVE);
|
||||
|
||||
/*
|
||||
* As the hash map is allocated in shared memory, it doesn't rely on palloc for
|
||||
* As the hash map is allocated in shared memory, it doesn't rely on palloc for
|
||||
* memory allocation, so we could get NULL via HASH_ENTER_NULL when there is no
|
||||
* space in the shared memory. That's why we prefer continuing the execution
|
||||
* instead of throwing an error.
|
||||
|
@ -440,7 +440,7 @@ IncrementSharedConnectionCounter(const char *hostname, int port)
|
|||
LockConnectionSharedMemory(LW_EXCLUSIVE);
|
||||
|
||||
/*
|
||||
* As the hash map is allocated in shared memory, it doesn't rely on palloc for
|
||||
* As the hash map is allocated in shared memory, it doesn't rely on palloc for
|
||||
* memory allocation, so we could get NULL via HASH_ENTER_NULL. That's why we prefer
|
||||
* continuing the execution instead of throwing an error.
|
||||
*/
|
||||
|
@ -694,7 +694,7 @@ SharedConnectionStatsShmemInit(void)
|
|||
ConditionVariableInit(&ConnectionStatsSharedState->waitersConditionVariable);
|
||||
}
|
||||
|
||||
/* allocate hash table */
|
||||
/* allocate hash table */
|
||||
SharedConnStatsHash =
|
||||
ShmemInitHash("Shared Conn. Stats Hash", MaxWorkerNodesTracked,
|
||||
MaxWorkerNodesTracked, &info, hashFlags);
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
#include "postgres.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
|
||||
/*
|
||||
* Append the 'WITH GRANT OPTION' clause to the given buffer if the given
|
||||
* statement is a 'GRANT' statement and the grant option is specified.
|
||||
*/
|
||||
void
|
||||
AppendWithGrantOption(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
if (stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfo(buf, " WITH GRANT OPTION");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Append the 'GRANT OPTION FOR' clause to the given buffer if the given
|
||||
* statement is a 'REVOKE' statement and the grant option is specified.
|
||||
*/
|
||||
void
|
||||
AppendGrantOptionFor(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
if (!stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfo(buf, "GRANT OPTION FOR ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Append the 'RESTRICT' or 'CASCADE' clause to the given buffer if the given
|
||||
* statement is a 'REVOKE' statement and the behavior is specified.
|
||||
*/
|
||||
void
|
||||
AppendGrantRestrictAndCascadeForRoleSpec(StringInfo buf, DropBehavior behavior, bool
|
||||
isGrant)
|
||||
{
|
||||
if (!isGrant)
|
||||
{
|
||||
if (behavior == DROP_RESTRICT)
|
||||
{
|
||||
appendStringInfo(buf, " RESTRICT");
|
||||
}
|
||||
else if (behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfo(buf, " CASCADE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Append the 'RESTRICT' or 'CASCADE' clause to the given buffer using 'GrantStmt',
|
||||
* if the given statement is a 'REVOKE' statement and the behavior is specified.
|
||||
*/
|
||||
void
|
||||
AppendGrantRestrictAndCascade(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
AppendGrantRestrictAndCascadeForRoleSpec(buf, stmt->behavior, stmt->is_grant);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Append the 'GRANTED BY' clause to the given buffer if the given statement is a
|
||||
* 'GRANT' statement and the grantor is specified.
|
||||
*/
|
||||
void
|
||||
AppendGrantedByInGrantForRoleSpec(StringInfo buf, RoleSpec *grantor, bool isGrant)
|
||||
{
|
||||
if (isGrant && grantor)
|
||||
{
|
||||
appendStringInfo(buf, " GRANTED BY %s", RoleSpecString(grantor, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Append the 'GRANTED BY' clause to the given buffer using 'GrantStmt',
|
||||
* if the given statement is a 'GRANT' statement and the grantor is specified.
|
||||
*/
|
||||
void
|
||||
AppendGrantedByInGrant(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
AppendGrantedByInGrantForRoleSpec(buf, stmt->grantor, stmt->is_grant);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AppendGrantSharedPrefix(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
|
||||
AppendGrantOptionFor(buf, stmt);
|
||||
AppendGrantPrivileges(buf, stmt);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AppendGrantSharedSuffix(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
AppendGrantGrantees(buf, stmt);
|
||||
AppendWithGrantOption(buf, stmt);
|
||||
AppendGrantRestrictAndCascade(buf, stmt);
|
||||
AppendGrantedByInGrant(buf, stmt);
|
||||
appendStringInfo(buf, ";");
|
||||
}
|
|
@ -818,7 +818,7 @@ deparse_shard_index_statement(IndexStmt *origStmt, Oid distrelid, int64 shardid,
|
|||
* Switch to empty search_path to deparse_index_columns to produce fully-
|
||||
* qualified names in expressions.
|
||||
*/
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
/* index column or expression list begins here */
|
||||
appendStringInfoChar(buffer, '(');
|
||||
|
@ -855,7 +855,7 @@ deparse_shard_index_statement(IndexStmt *origStmt, Oid distrelid, int64 shardid,
|
|||
}
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
#include "postgres.h"
|
||||
|
||||
#include "pg_version_compat.h"
|
||||
|
||||
#include "catalog/namespace.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "distributed/log_utils.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "nodes/print.h"
|
||||
|
||||
|
||||
void AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt);
|
||||
|
||||
/*
|
||||
* AppendVarSetValueDb deparses a VariableSetStmt with VAR_SET_VALUE kind.
|
||||
* It takes from flatten_set_variable_args in postgres's utils/misc/guc.c,
|
||||
* however flatten_set_variable_args does not apply correct quoting.
|
||||
*/
|
||||
void
|
||||
AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
|
||||
{
|
||||
ListCell *varArgCell = NULL;
|
||||
ListCell *firstCell = list_head(setStmt->args);
|
||||
|
||||
Assert(setStmt->kind == VAR_SET_VALUE);
|
||||
|
||||
foreach(varArgCell, setStmt->args)
|
||||
{
|
||||
Node *varArgNode = lfirst(varArgCell);
|
||||
A_Const *varArgConst = NULL;
|
||||
TypeName *typeName = NULL;
|
||||
|
||||
if (IsA(varArgNode, A_Const))
|
||||
{
|
||||
varArgConst = (A_Const *) varArgNode;
|
||||
}
|
||||
else if (IsA(varArgNode, TypeCast))
|
||||
{
|
||||
TypeCast *varArgTypeCast = (TypeCast *) varArgNode;
|
||||
|
||||
varArgConst = castNode(A_Const, varArgTypeCast->arg);
|
||||
typeName = varArgTypeCast->typeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR, "unrecognized node type: %d", varArgNode->type);
|
||||
}
|
||||
|
||||
/* don't know how to start SET until we inspect first arg */
|
||||
if (varArgCell != firstCell)
|
||||
{
|
||||
appendStringInfoChar(buf, ',');
|
||||
}
|
||||
else if (typeName != NULL)
|
||||
{
|
||||
appendStringInfoString(buf, " SET TIME ZONE");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " SET %s =", quote_identifier(setStmt->name));
|
||||
}
|
||||
|
||||
Node *value = (Node *) &varArgConst->val;
|
||||
switch (value->type)
|
||||
{
|
||||
case T_Integer:
|
||||
{
|
||||
appendStringInfo(buf, " %d", intVal(value));
|
||||
break;
|
||||
}
|
||||
|
||||
case T_Float:
|
||||
{
|
||||
appendStringInfo(buf, " %s", nodeToString(value));
|
||||
break;
|
||||
}
|
||||
|
||||
case T_String:
|
||||
{
|
||||
if (typeName != NULL)
|
||||
{
|
||||
/*
|
||||
* Must be a ConstInterval argument for TIME ZONE. Coerce
|
||||
* to interval and back to normalize the value and account
|
||||
* for any typmod.
|
||||
*/
|
||||
Oid typoid = InvalidOid;
|
||||
int32 typmod = -1;
|
||||
|
||||
typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod);
|
||||
Assert(typoid == INTERVALOID);
|
||||
|
||||
Datum interval =
|
||||
DirectFunctionCall3(interval_in,
|
||||
CStringGetDatum(strVal(value)),
|
||||
ObjectIdGetDatum(InvalidOid),
|
||||
Int32GetDatum(typmod));
|
||||
|
||||
char *intervalout =
|
||||
DatumGetCString(DirectFunctionCall1(interval_out,
|
||||
interval));
|
||||
appendStringInfo(buf, " INTERVAL '%s'", intervalout);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " %s", quote_literal_cstr(strVal(value)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
elog(ERROR, "Unexpected Value type in VAR_SET_VALUE arguments.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendVariableSetDb appends a string representing the VariableSetStmt to a buffer
|
||||
*/
|
||||
void
|
||||
AppendVariableSet(StringInfo buf, VariableSetStmt *setStmt)
|
||||
{
|
||||
switch (setStmt->kind)
|
||||
{
|
||||
case VAR_SET_VALUE:
|
||||
{
|
||||
AppendVarSetValue(buf, setStmt);
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_SET_CURRENT:
|
||||
{
|
||||
appendStringInfo(buf, " SET %s FROM CURRENT", quote_identifier(
|
||||
setStmt->name));
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_SET_DEFAULT:
|
||||
{
|
||||
appendStringInfo(buf, " SET %s TO DEFAULT", quote_identifier(setStmt->name));
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_RESET:
|
||||
{
|
||||
appendStringInfo(buf, " RESET %s", quote_identifier(setStmt->name));
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_RESET_ALL:
|
||||
{
|
||||
appendStringInfoString(buf, " RESET ALL");
|
||||
break;
|
||||
}
|
||||
|
||||
/* VAR_SET_MULTI is a special case for SET TRANSACTION that should not occur here */
|
||||
case VAR_SET_MULTI:
|
||||
default:
|
||||
{
|
||||
ereport(ERROR, (errmsg("Unable to deparse SET statement")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,11 +18,17 @@
|
|||
#include "nodes/parsenodes.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/log_utils.h"
|
||||
#include "parser/parse_type.h"
|
||||
|
||||
|
||||
static void AppendAlterDatabaseOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
||||
|
||||
static void AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt);
|
||||
static void AppendDefElemConnLimit(StringInfo buf, DefElem *def);
|
||||
|
||||
char *
|
||||
DeparseAlterDatabaseOwnerStmt(Node *node)
|
||||
|
@ -49,3 +55,153 @@ AppendAlterDatabaseOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt)
|
|||
quote_identifier(strVal((String *) stmt->object)),
|
||||
RoleSpecString(stmt->newowner, true));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantDatabases(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
appendStringInfo(buf, " ON DATABASE ");
|
||||
|
||||
foreach(cell, stmt->objects)
|
||||
{
|
||||
char *database = strVal(lfirst(cell));
|
||||
appendStringInfoString(buf, quote_identifier(database));
|
||||
if (cell != list_tail(stmt->objects))
|
||||
{
|
||||
appendStringInfo(buf, ", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantOnDatabaseStmt(StringInfo buf, GrantStmt *stmt)
|
||||
{
|
||||
Assert(stmt->objtype == OBJECT_DATABASE);
|
||||
|
||||
AppendGrantSharedPrefix(buf, stmt);
|
||||
|
||||
AppendGrantDatabases(buf, stmt);
|
||||
|
||||
AppendGrantSharedSuffix(buf, stmt);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendDefElemConnLimit(StringInfo buf, DefElem *def)
|
||||
{
|
||||
appendStringInfo(buf, " CONNECTION LIMIT %ld", (long int) defGetNumeric(def));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt)
|
||||
{
|
||||
appendStringInfo(buf, "ALTER DATABASE %s ", quote_identifier(stmt->dbname));
|
||||
|
||||
if (stmt->options)
|
||||
{
|
||||
ListCell *cell = NULL;
|
||||
appendStringInfo(buf, "WITH ");
|
||||
foreach(cell, stmt->options)
|
||||
{
|
||||
DefElem *def = castNode(DefElem, lfirst(cell));
|
||||
if (strcmp(def->defname, "is_template") == 0)
|
||||
{
|
||||
appendStringInfo(buf, "IS_TEMPLATE %s",
|
||||
quote_literal_cstr(strVal(def->arg)));
|
||||
}
|
||||
else if (strcmp(def->defname, "connection_limit") == 0)
|
||||
{
|
||||
AppendDefElemConnLimit(buf, def);
|
||||
}
|
||||
else if (strcmp(def->defname, "allow_connections") == 0)
|
||||
{
|
||||
ereport(ERROR,
|
||||
errmsg("ALLOW_CONNECTIONS is not supported"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(ERROR,
|
||||
errmsg("unrecognized ALTER DATABASE option: %s",
|
||||
def->defname));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appendStringInfo(buf, ";");
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
DeparseGrantOnDatabaseStmt(Node *node)
|
||||
{
|
||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_DATABASE);
|
||||
|
||||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
|
||||
AppendGrantOnDatabaseStmt(&str, stmt);
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
DeparseAlterDatabaseStmt(Node *node)
|
||||
{
|
||||
AlterDatabaseStmt *stmt = castNode(AlterDatabaseStmt, node);
|
||||
|
||||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
|
||||
AppendAlterDatabaseStmt(&str, stmt);
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||
char *
|
||||
DeparseAlterDatabaseRefreshCollStmt(Node *node)
|
||||
{
|
||||
AlterDatabaseRefreshCollStmt *stmt = (AlterDatabaseRefreshCollStmt *) node;
|
||||
|
||||
StringInfoData str;
|
||||
initStringInfo(&str);
|
||||
|
||||
appendStringInfo(&str, "ALTER DATABASE %s REFRESH COLLATION VERSION;",
|
||||
quote_identifier(
|
||||
stmt->dbname));
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
AppendAlterDatabaseSetStmt(StringInfo buf, AlterDatabaseSetStmt *stmt)
|
||||
{
|
||||
appendStringInfo(buf, "ALTER DATABASE %s", quote_identifier(stmt->dbname));
|
||||
|
||||
VariableSetStmt *varSetStmt = castNode(VariableSetStmt, stmt->setstmt);
|
||||
|
||||
AppendVariableSet(buf, varSetStmt);
|
||||
}
|
||||
|
||||
|
||||
char *
|
||||
DeparseAlterDatabaseSetStmt(Node *node)
|
||||
{
|
||||
AlterDatabaseSetStmt *stmt = castNode(AlterDatabaseSetStmt, node);
|
||||
|
||||
StringInfoData str = { 0 };
|
||||
initStringInfo(&str);
|
||||
|
||||
AppendAlterDatabaseSetStmt(&str, stmt);
|
||||
|
||||
return str.data;
|
||||
}
|
||||
|
|
|
@ -345,9 +345,9 @@ AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt)
|
|||
expr = TransformDefaultExpr(expr, stmt->typeName, baseTypeName);
|
||||
|
||||
/* deparse while the searchpath is cleared to force qualification of identifiers */
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
char *exprSql = deparse_expression(expr, NIL, true, true);
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
appendStringInfo(buf, "SET DEFAULT %s", exprSql);
|
||||
}
|
||||
|
@ -443,9 +443,9 @@ AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
|
|||
elog(ERROR, "missing expression for domain constraint");
|
||||
}
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
char *exprSql = deparse_expression(expr, NIL, true, true);
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
appendStringInfo(buf, " CHECK (%s)", exprSql);
|
||||
return;
|
||||
|
@ -469,9 +469,9 @@ AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
|
|||
elog(ERROR, "missing expression for domain default");
|
||||
}
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
char *exprSql = deparse_expression(expr, NIL, true, true);
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
appendStringInfo(buf, " DEFAULT %s", exprSql);
|
||||
return;
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
static void AppendGrantOnFDWStmt(StringInfo buf, GrantStmt *stmt);
|
||||
static void AppendGrantOnFDWNames(StringInfo buf, GrantStmt *stmt);
|
||||
|
||||
|
||||
char *
|
||||
DeparseGrantOnFDWStmt(Node *node)
|
||||
{
|
||||
|
@ -41,36 +40,9 @@ 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);
|
||||
|
||||
AppendGrantSharedPrefix(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, ";");
|
||||
AppendGrantSharedSuffix(buf, stmt);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -298,36 +298,9 @@ 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);
|
||||
|
||||
AppendGrantSharedPrefix(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, ";");
|
||||
AppendGrantSharedSuffix(buf, stmt);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ static void AppendDefElemRows(StringInfo buf, DefElem *def);
|
|||
static void AppendDefElemSet(StringInfo buf, DefElem *def);
|
||||
static void AppendDefElemSupport(StringInfo buf, DefElem *def);
|
||||
|
||||
static void AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt);
|
||||
static void AppendRenameFunctionStmt(StringInfo buf, RenameStmt *stmt);
|
||||
static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt);
|
||||
static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
||||
|
@ -300,164 +299,6 @@ AppendDefElemSupport(StringInfo buf, DefElem *def)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendVariableSet appends a string representing the VariableSetStmt to a buffer
|
||||
*/
|
||||
void
|
||||
AppendVariableSet(StringInfo buf, VariableSetStmt *setStmt)
|
||||
{
|
||||
switch (setStmt->kind)
|
||||
{
|
||||
case VAR_SET_VALUE:
|
||||
{
|
||||
AppendVarSetValue(buf, setStmt);
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_SET_CURRENT:
|
||||
{
|
||||
appendStringInfo(buf, " SET %s FROM CURRENT", quote_identifier(
|
||||
setStmt->name));
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_SET_DEFAULT:
|
||||
{
|
||||
appendStringInfo(buf, " SET %s TO DEFAULT", quote_identifier(setStmt->name));
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_RESET:
|
||||
{
|
||||
appendStringInfo(buf, " RESET %s", quote_identifier(setStmt->name));
|
||||
break;
|
||||
}
|
||||
|
||||
case VAR_RESET_ALL:
|
||||
{
|
||||
appendStringInfoString(buf, " RESET ALL");
|
||||
break;
|
||||
}
|
||||
|
||||
/* VAR_SET_MULTI is a special case for SET TRANSACTION that should not occur here */
|
||||
case VAR_SET_MULTI:
|
||||
default:
|
||||
{
|
||||
ereport(ERROR, (errmsg("Unable to deparse SET statement")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendVarSetValue deparses a VariableSetStmt with VAR_SET_VALUE kind.
|
||||
* It takes from flatten_set_variable_args in postgres's utils/misc/guc.c,
|
||||
* however flatten_set_variable_args does not apply correct quoting.
|
||||
*/
|
||||
static void
|
||||
AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
|
||||
{
|
||||
ListCell *varArgCell = NULL;
|
||||
ListCell *firstCell = list_head(setStmt->args);
|
||||
|
||||
Assert(setStmt->kind == VAR_SET_VALUE);
|
||||
|
||||
foreach(varArgCell, setStmt->args)
|
||||
{
|
||||
Node *varArgNode = lfirst(varArgCell);
|
||||
A_Const *varArgConst = NULL;
|
||||
TypeName *typeName = NULL;
|
||||
|
||||
if (IsA(varArgNode, A_Const))
|
||||
{
|
||||
varArgConst = (A_Const *) varArgNode;
|
||||
}
|
||||
else if (IsA(varArgNode, TypeCast))
|
||||
{
|
||||
TypeCast *varArgTypeCast = (TypeCast *) varArgNode;
|
||||
|
||||
varArgConst = castNode(A_Const, varArgTypeCast->arg);
|
||||
typeName = varArgTypeCast->typeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR, "unrecognized node type: %d", varArgNode->type);
|
||||
}
|
||||
|
||||
/* don't know how to start SET until we inspect first arg */
|
||||
if (varArgCell != firstCell)
|
||||
{
|
||||
appendStringInfoChar(buf, ',');
|
||||
}
|
||||
else if (typeName != NULL)
|
||||
{
|
||||
appendStringInfoString(buf, " SET TIME ZONE");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " SET %s =", quote_identifier(setStmt->name));
|
||||
}
|
||||
|
||||
Node *value = (Node *) &varArgConst->val;
|
||||
switch (value->type)
|
||||
{
|
||||
case T_Integer:
|
||||
{
|
||||
appendStringInfo(buf, " %d", intVal(value));
|
||||
break;
|
||||
}
|
||||
|
||||
case T_Float:
|
||||
{
|
||||
appendStringInfo(buf, " %s", strVal(value));
|
||||
break;
|
||||
}
|
||||
|
||||
case T_String:
|
||||
{
|
||||
if (typeName != NULL)
|
||||
{
|
||||
/*
|
||||
* Must be a ConstInterval argument for TIME ZONE. Coerce
|
||||
* to interval and back to normalize the value and account
|
||||
* for any typmod.
|
||||
*/
|
||||
Oid typoid = InvalidOid;
|
||||
int32 typmod = -1;
|
||||
|
||||
typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod);
|
||||
Assert(typoid == INTERVALOID);
|
||||
|
||||
Datum interval =
|
||||
DirectFunctionCall3(interval_in,
|
||||
CStringGetDatum(strVal(value)),
|
||||
ObjectIdGetDatum(InvalidOid),
|
||||
Int32GetDatum(typmod));
|
||||
|
||||
char *intervalout =
|
||||
DatumGetCString(DirectFunctionCall1(interval_out,
|
||||
interval));
|
||||
appendStringInfo(buf, " INTERVAL '%s'", intervalout);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfo(buf, " %s", quote_literal_cstr(strVal(
|
||||
value)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
elog(ERROR, "Unexpected Value type in VAR_SET_VALUE arguments.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseRenameFunctionStmt builds and returns a string representing the RenameStmt
|
||||
*/
|
||||
|
@ -749,35 +590,11 @@ AppendGrantOnFunctionStmt(StringInfo buf, GrantStmt *stmt)
|
|||
"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);
|
||||
AppendGrantSharedPrefix(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, ";");
|
||||
AppendGrantSharedSuffix(buf, stmt);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -307,11 +307,11 @@ AppendWhereClauseExpression(StringInfo buf, RangeVar *tableName,
|
|||
|
||||
List *relationContext = deparse_context_for(tableName->relname, relation->rd_id);
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
char *whereClauseString = deparse_expression(whereClause,
|
||||
relationContext,
|
||||
true, true);
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
appendStringInfoString(buf, whereClauseString);
|
||||
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
|
||||
#include "pg_version_compat.h"
|
||||
|
||||
#include "commands/defrem.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "lib/stringinfo.h"
|
||||
#include "nodes/parsenodes.h"
|
||||
#include "utils/builtins.h"
|
||||
|
@ -28,6 +30,8 @@ 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);
|
||||
static void AppendRevokeAdminOptionFor(StringInfo buf, GrantRoleStmt *stmt);
|
||||
static void AppendGrantWithAdminOption(StringInfo buf, GrantRoleStmt *stmt);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -340,6 +344,66 @@ DeparseGrantRoleStmt(Node *node)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Append the 'RESTRICT' or 'CASCADE' clause to the given buffer if the given
|
||||
* statement is a 'REVOKE' statement and the behavior is specified.
|
||||
* After PostgreSQL 16, the behavior is specified in the 'opt' field of
|
||||
* GrantRoleStmt and may have multiple values.
|
||||
* Here, compile time version is checked to support both versions.
|
||||
*/
|
||||
static void
|
||||
AppendRevokeAdminOptionFor(StringInfo buf, GrantRoleStmt *stmt)
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
if (!stmt->is_grant)
|
||||
{
|
||||
DefElem *opt = NULL;
|
||||
foreach_ptr(opt, stmt->opt)
|
||||
{
|
||||
if (strcmp(opt->defname, "admin") == 0)
|
||||
{
|
||||
appendStringInfo(buf, "ADMIN OPTION FOR ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!stmt->is_grant && stmt->admin_opt)
|
||||
{
|
||||
appendStringInfo(buf, "ADMIN OPTION FOR ");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AppendGrantWithAdminOption(StringInfo buf, GrantRoleStmt *stmt)
|
||||
{
|
||||
if (stmt->is_grant)
|
||||
{
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
DefElem *opt = NULL;
|
||||
foreach_ptr(opt, stmt->opt)
|
||||
{
|
||||
bool admin_option = false;
|
||||
char *optval = defGetString(opt);
|
||||
if (strcmp(opt->defname, "admin") == 0 &&
|
||||
parse_bool(optval, &admin_option) && admin_option)
|
||||
{
|
||||
appendStringInfo(buf, " WITH ADMIN OPTION");
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (stmt->admin_opt)
|
||||
{
|
||||
appendStringInfo(buf, " WITH ADMIN OPTION");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendGrantRoleStmt generates the string representation of the
|
||||
* GrantRoleStmt and appends it to the buffer.
|
||||
|
@ -348,41 +412,15 @@ 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 ");
|
||||
}
|
||||
|
||||
AppendRevokeAdminOptionFor(buf, stmt);
|
||||
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");
|
||||
}
|
||||
}
|
||||
AppendGrantWithAdminOption(buf, stmt);
|
||||
AppendGrantedByInGrantForRoleSpec(buf, stmt->grantor, stmt->is_grant);
|
||||
AppendGrantRestrictAndCascadeForRoleSpec(buf, stmt->behavior, stmt->is_grant);
|
||||
AppendGrantedByInGrantForRoleSpec(buf, stmt->grantor, stmt->is_grant);
|
||||
appendStringInfo(buf, ";");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -178,35 +178,11 @@ AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt)
|
|||
{
|
||||
Assert(stmt->objtype == OBJECT_SCHEMA);
|
||||
|
||||
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
|
||||
|
||||
if (!stmt->is_grant && stmt->grant_option)
|
||||
{
|
||||
appendStringInfo(buf, "GRANT OPTION FOR ");
|
||||
}
|
||||
|
||||
AppendGrantPrivileges(buf, stmt);
|
||||
AppendGrantSharedPrefix(buf, stmt);
|
||||
|
||||
AppendGrantOnSchemaSchemas(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, ";");
|
||||
AppendGrantSharedSuffix(buf, stmt);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -389,35 +389,11 @@ AppendGrantOnSequenceStmt(StringInfo buf, GrantStmt *stmt)
|
|||
"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);
|
||||
AppendGrantSharedPrefix(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, ";");
|
||||
AppendGrantSharedSuffix(buf, stmt);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "catalog/heap.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/version_compat.h"
|
||||
|
@ -30,7 +32,8 @@ static void AppendAlterTableSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *st
|
|||
static void AppendAlterTableStmt(StringInfo buf, AlterTableStmt *stmt);
|
||||
static void AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd,
|
||||
AlterTableStmt *stmt);
|
||||
static void AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd);
|
||||
static void AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd,
|
||||
AlterTableStmt *stmt);
|
||||
static void AppendAlterTableCmdDropConstraint(StringInfo buf,
|
||||
AlterTableCmd *alterTableCmd);
|
||||
|
||||
|
@ -142,13 +145,19 @@ AppendColumnNameList(StringInfo buf, List *columns)
|
|||
|
||||
|
||||
/*
|
||||
* AppendAlterTableCmdAddConstraint builds the add constraint command for index constraints
|
||||
* in the ADD CONSTRAINT <conname> {PRIMARY KEY, UNIQUE, EXCLUSION} form and appends it to the buf.
|
||||
* AppendAlterTableCmdConstraint builds a string required to create given
|
||||
* constraint as part of an ADD CONSTRAINT or an ADD COLUMN subcommand,
|
||||
* and appends it to the buf.
|
||||
*/
|
||||
static void
|
||||
AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint,
|
||||
AlterTableStmt *stmt)
|
||||
AppendAlterTableCmdConstraint(StringInfo buf, Constraint *constraint,
|
||||
AlterTableStmt *stmt, AlterTableType subtype)
|
||||
{
|
||||
if (subtype != AT_AddConstraint && subtype != AT_AddColumn)
|
||||
{
|
||||
ereport(ERROR, (errmsg("Unsupported alter table subtype: %d", (int) subtype)));
|
||||
}
|
||||
|
||||
/* Need to deparse the alter table constraint command only if we are adding a constraint name.*/
|
||||
if (constraint->conname == NULL)
|
||||
{
|
||||
|
@ -156,7 +165,15 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint,
|
|||
"Constraint name can not be NULL when deparsing the constraint.")));
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, " ADD CONSTRAINT ");
|
||||
if (subtype == AT_AddConstraint)
|
||||
{
|
||||
appendStringInfoString(buf, " ADD CONSTRAINT ");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfoString(buf, " CONSTRAINT ");
|
||||
}
|
||||
|
||||
appendStringInfo(buf, "%s ", quote_identifier(constraint->conname));
|
||||
|
||||
/* postgres version >= PG15
|
||||
|
@ -184,7 +201,10 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint,
|
|||
#endif
|
||||
}
|
||||
|
||||
AppendColumnNameList(buf, constraint->keys);
|
||||
if (subtype == AT_AddConstraint)
|
||||
{
|
||||
AppendColumnNameList(buf, constraint->keys);
|
||||
}
|
||||
|
||||
if (constraint->including != NULL)
|
||||
{
|
||||
|
@ -192,6 +212,24 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint,
|
|||
|
||||
AppendColumnNameList(buf, constraint->including);
|
||||
}
|
||||
|
||||
if (constraint->options != NIL)
|
||||
{
|
||||
appendStringInfoString(buf, " WITH(");
|
||||
|
||||
ListCell *defListCell;
|
||||
foreach(defListCell, constraint->options)
|
||||
{
|
||||
DefElem *def = (DefElem *) lfirst(defListCell);
|
||||
|
||||
bool first = (defListCell == list_head(constraint->options));
|
||||
appendStringInfo(buf, "%s%s=%s", first ? "" : ",",
|
||||
quote_identifier(def->defname),
|
||||
quote_literal_cstr(defGetString(def)));
|
||||
}
|
||||
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
}
|
||||
else if (constraint->contype == CONSTR_EXCLUSION)
|
||||
{
|
||||
|
@ -240,6 +278,18 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint,
|
|||
}
|
||||
else if (constraint->contype == CONSTR_CHECK)
|
||||
{
|
||||
if (subtype == AT_AddColumn)
|
||||
{
|
||||
/*
|
||||
* Preprocess should've rejected deparsing such an ALTER TABLE
|
||||
* command but be on the safe side.
|
||||
*/
|
||||
ereport(ERROR, (errmsg("cannot add check constraint to column by "
|
||||
"using ADD COLUMN command"),
|
||||
errhint("Consider using ALTER TABLE ... ADD CONSTRAINT "
|
||||
"... CHECK command after adding the column")));
|
||||
}
|
||||
|
||||
LOCKMODE lockmode = AlterTableGetLockLevel(stmt->cmds);
|
||||
Oid leftRelationId = AlterTableLookupRelation(stmt, lockmode);
|
||||
|
||||
|
@ -275,9 +325,12 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint,
|
|||
}
|
||||
else if (constraint->contype == CONSTR_FOREIGN)
|
||||
{
|
||||
appendStringInfoString(buf, " FOREIGN KEY");
|
||||
if (subtype == AT_AddConstraint)
|
||||
{
|
||||
appendStringInfoString(buf, " FOREIGN KEY");
|
||||
|
||||
AppendColumnNameList(buf, constraint->fk_attrs);
|
||||
AppendColumnNameList(buf, constraint->fk_attrs);
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, " REFERENCES");
|
||||
|
||||
|
@ -379,12 +432,32 @@ AppendAlterTableCmdAddConstraint(StringInfo buf, Constraint *constraint,
|
|||
}
|
||||
}
|
||||
|
||||
/* FOREIGN KEY and CHECK constraints migth have NOT VALID option */
|
||||
if (constraint->skip_validation)
|
||||
/*
|
||||
* For ADD CONSTRAINT subcommand, FOREIGN KEY and CHECK constraints migth
|
||||
* have NOT VALID option.
|
||||
*
|
||||
* Note that skip_validation might be true for an ADD COLUMN too but this
|
||||
* is not because Postgres supports this but because Citus sets this flag
|
||||
* to true for foreign key constraints added via ADD COLUMN. So we don't
|
||||
* check for skip_validation for ADD COLUMN subcommand.
|
||||
*/
|
||||
if (subtype == AT_AddConstraint && constraint->skip_validation)
|
||||
{
|
||||
appendStringInfoString(buf, " NOT VALID ");
|
||||
}
|
||||
|
||||
if (subtype == AT_AddColumn &&
|
||||
(constraint->deferrable || constraint->initdeferred))
|
||||
{
|
||||
/*
|
||||
* For ADD COLUMN subcommand, the fact that whether given constraint
|
||||
* is deferrable or initially deferred is indicated by another Constraint
|
||||
* object, not via deferrable / initdeferred fields.
|
||||
*/
|
||||
ereport(ERROR, (errmsg("unexpected value set for deferrable/initdeferred "
|
||||
"field for an ADD COLUMN subcommand")));
|
||||
}
|
||||
|
||||
if (constraint->deferrable)
|
||||
{
|
||||
appendStringInfoString(buf, " DEFERRABLE");
|
||||
|
@ -409,7 +482,7 @@ AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt
|
|||
{
|
||||
case AT_AddColumn:
|
||||
{
|
||||
AppendAlterTableCmdAddColumn(buf, alterTableCmd);
|
||||
AppendAlterTableCmdAddColumn(buf, alterTableCmd, stmt);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -429,7 +502,7 @@ AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt
|
|||
*/
|
||||
if (ConstrTypeCitusCanDefaultName(constraint->contype))
|
||||
{
|
||||
AppendAlterTableCmdAddConstraint(buf, constraint, stmt);
|
||||
AppendAlterTableCmdConstraint(buf, constraint, stmt, AT_AddConstraint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -445,28 +518,81 @@ AppendAlterTableCmd(StringInfo buf, AlterTableCmd *alterTableCmd, AlterTableStmt
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GeneratedWhenStr returns the char representation of given generated_when
|
||||
* value.
|
||||
*/
|
||||
static const char *
|
||||
GeneratedWhenStr(char generatedWhen)
|
||||
{
|
||||
switch (generatedWhen)
|
||||
{
|
||||
case 'a':
|
||||
{
|
||||
return "ALWAYS";
|
||||
}
|
||||
|
||||
case 'd':
|
||||
{
|
||||
return "BY DEFAULT";
|
||||
}
|
||||
|
||||
default:
|
||||
ereport(ERROR, (errmsg("unrecognized generated_when: %d",
|
||||
generatedWhen)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseRawExprForColumnDefault returns string representation of given
|
||||
* rawExpr based on given column type information.
|
||||
*/
|
||||
static char *
|
||||
DeparseRawExprForColumnDefault(Oid relationId, Oid columnTypeId, int32 columnTypeMod,
|
||||
char *columnName, char attgenerated, Node *rawExpr)
|
||||
{
|
||||
ParseState *pstate = make_parsestate(NULL);
|
||||
Relation relation = RelationIdGetRelation(relationId);
|
||||
AddRangeTableEntryToQueryCompat(pstate, relation);
|
||||
|
||||
Node *defaultExpr = cookDefault(pstate, rawExpr,
|
||||
columnTypeId, columnTypeMod,
|
||||
columnName, attgenerated);
|
||||
|
||||
List *deparseContext = deparse_context_for(get_rel_name(relationId), relationId);
|
||||
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
char *defaultExprStr = deparse_expression(defaultExpr, deparseContext, false, false);
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
RelationClose(relation);
|
||||
|
||||
return defaultExprStr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendAlterTableCmd builds and appends to the given buffer an AT_AddColumn command
|
||||
* from given AlterTableCmd object in the form ADD COLUMN ...
|
||||
*/
|
||||
static void
|
||||
AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd)
|
||||
AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd,
|
||||
AlterTableStmt *stmt)
|
||||
{
|
||||
Assert(alterTableCmd->subtype == AT_AddColumn);
|
||||
|
||||
Oid relationId = AlterTableLookupRelation(stmt, NoLock);
|
||||
|
||||
appendStringInfoString(buf, " ADD COLUMN ");
|
||||
|
||||
ColumnDef *columnDefinition = (ColumnDef *) alterTableCmd->def;
|
||||
|
||||
/*
|
||||
* the way we use the deparser now, constraints are always NULL
|
||||
* adding this check for ColumnDef consistency
|
||||
*/
|
||||
if (columnDefinition->constraints != NULL)
|
||||
if (alterTableCmd->missing_ok)
|
||||
{
|
||||
ereport(ERROR, (errmsg("Constraints are not supported for AT_AddColumn")));
|
||||
appendStringInfoString(buf, "IF NOT EXISTS ");
|
||||
}
|
||||
|
||||
ColumnDef *columnDefinition = (ColumnDef *) alterTableCmd->def;
|
||||
|
||||
if (columnDefinition->colname)
|
||||
{
|
||||
appendStringInfo(buf, "%s ", quote_identifier(columnDefinition->colname));
|
||||
|
@ -478,23 +604,91 @@ AppendAlterTableCmdAddColumn(StringInfo buf, AlterTableCmd *alterTableCmd)
|
|||
typenameTypeIdAndMod(NULL, columnDefinition->typeName, &typeOid, &typmod);
|
||||
appendStringInfo(buf, "%s", format_type_extended(typeOid, typmod,
|
||||
formatFlags));
|
||||
if (columnDefinition->is_not_null)
|
||||
|
||||
if (columnDefinition->compression)
|
||||
{
|
||||
appendStringInfoString(buf, " NOT NULL");
|
||||
appendStringInfo(buf, " COMPRESSION %s",
|
||||
quote_identifier(columnDefinition->compression));
|
||||
}
|
||||
|
||||
/*
|
||||
* the way we use the deparser now, collation is never used
|
||||
* since the data type of columns that use sequences for default
|
||||
* are only int,smallint and bigint (never text, varchar, char)
|
||||
* Adding this part only for ColumnDef consistency
|
||||
*/
|
||||
Oid collationOid = GetColumnDefCollation(NULL, columnDefinition, typeOid);
|
||||
if (OidIsValid(collationOid))
|
||||
{
|
||||
const char *identifier = FormatCollateBEQualified(collationOid);
|
||||
appendStringInfo(buf, " COLLATE %s", identifier);
|
||||
}
|
||||
|
||||
ListCell *constraintCell = NULL;
|
||||
foreach(constraintCell, columnDefinition->constraints)
|
||||
{
|
||||
Constraint *constraint = (Constraint *) lfirst(constraintCell);
|
||||
|
||||
if (constraint->contype == CONSTR_NOTNULL)
|
||||
{
|
||||
appendStringInfoString(buf, " NOT NULL");
|
||||
}
|
||||
else if (constraint->contype == CONSTR_NULL)
|
||||
{
|
||||
appendStringInfoString(buf, " NULL");
|
||||
}
|
||||
else if (constraint->contype == CONSTR_DEFAULT)
|
||||
{
|
||||
char attgenerated = '\0';
|
||||
appendStringInfo(buf, " DEFAULT %s",
|
||||
DeparseRawExprForColumnDefault(relationId, typeOid, typmod,
|
||||
columnDefinition->colname,
|
||||
attgenerated,
|
||||
constraint->raw_expr));
|
||||
}
|
||||
else if (constraint->contype == CONSTR_IDENTITY)
|
||||
{
|
||||
/*
|
||||
* Citus doesn't support adding identity columns via ALTER TABLE,
|
||||
* so we don't bother teaching the deparser about them.
|
||||
*/
|
||||
ereport(ERROR, (errmsg("unexpectedly found identity column "
|
||||
"definition in ALTER TABLE command")));
|
||||
}
|
||||
else if (constraint->contype == CONSTR_GENERATED)
|
||||
{
|
||||
char attgenerated = 's';
|
||||
appendStringInfo(buf, " GENERATED %s AS (%s) STORED",
|
||||
GeneratedWhenStr(constraint->generated_when),
|
||||
DeparseRawExprForColumnDefault(relationId, typeOid, typmod,
|
||||
columnDefinition->colname,
|
||||
attgenerated,
|
||||
constraint->raw_expr));
|
||||
}
|
||||
else if (constraint->contype == CONSTR_CHECK ||
|
||||
constraint->contype == CONSTR_PRIMARY ||
|
||||
constraint->contype == CONSTR_UNIQUE ||
|
||||
constraint->contype == CONSTR_EXCLUSION ||
|
||||
constraint->contype == CONSTR_FOREIGN)
|
||||
{
|
||||
AppendAlterTableCmdConstraint(buf, constraint, stmt, AT_AddColumn);
|
||||
}
|
||||
else if (constraint->contype == CONSTR_ATTR_DEFERRABLE)
|
||||
{
|
||||
appendStringInfoString(buf, " DEFERRABLE");
|
||||
}
|
||||
else if (constraint->contype == CONSTR_ATTR_NOT_DEFERRABLE)
|
||||
{
|
||||
appendStringInfoString(buf, " NOT DEFERRABLE");
|
||||
}
|
||||
else if (constraint->contype == CONSTR_ATTR_DEFERRED)
|
||||
{
|
||||
appendStringInfoString(buf, " INITIALLY DEFERRED");
|
||||
}
|
||||
else if (constraint->contype == CONSTR_ATTR_IMMEDIATE)
|
||||
{
|
||||
appendStringInfoString(buf, " INITIALLY IMMEDIATE");
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(ERROR, (errmsg("unsupported constraint type"),
|
||||
errdetail("constraint type: %d", constraint->contype)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
#include "common/keywords.h"
|
||||
#include "distributed/citus_nodefuncs.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/namespace_utils.h"
|
||||
#include "executor/spi.h"
|
||||
#include "foreign/foreign.h"
|
||||
#include "funcapi.h"
|
||||
|
@ -610,18 +611,14 @@ pg_get_rule_expr(Node *expression)
|
|||
{
|
||||
bool showImplicitCasts = true;
|
||||
deparse_context context;
|
||||
OverrideSearchPath *overridePath = NULL;
|
||||
StringInfo buffer = makeStringInfo();
|
||||
|
||||
/*
|
||||
* Set search_path to NIL so that all objects outside of pg_catalog will be
|
||||
* schema-prefixed. pg_catalog will be added automatically when we call
|
||||
* PushOverrideSearchPath(), since we set addCatalog to true;
|
||||
* PushEmptySearchPath().
|
||||
*/
|
||||
overridePath = GetOverrideSearchPath(CurrentMemoryContext);
|
||||
overridePath->schemas = NIL;
|
||||
overridePath->addCatalog = true;
|
||||
PushOverrideSearchPath(overridePath);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
context.buf = buffer;
|
||||
context.namespaces = NIL;
|
||||
|
@ -638,7 +635,7 @@ pg_get_rule_expr(Node *expression)
|
|||
get_rule_expr(expression, &context, showImplicitCasts);
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
return buffer->data;
|
||||
}
|
||||
|
@ -1955,8 +1952,6 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
|||
deparse_context context;
|
||||
deparse_namespace dpns;
|
||||
|
||||
OverrideSearchPath *overridePath = NULL;
|
||||
|
||||
/* Guard against excessively long or deeply-nested queries */
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
check_stack_depth();
|
||||
|
@ -1975,12 +1970,9 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
|||
/*
|
||||
* Set search_path to NIL so that all objects outside of pg_catalog will be
|
||||
* schema-prefixed. pg_catalog will be added automatically when we call
|
||||
* PushOverrideSearchPath(), since we set addCatalog to true;
|
||||
* PushEmptySearchPath().
|
||||
*/
|
||||
overridePath = GetOverrideSearchPath(CurrentMemoryContext);
|
||||
overridePath->schemas = NIL;
|
||||
overridePath->addCatalog = true;
|
||||
PushOverrideSearchPath(overridePath);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
context.buf = buf;
|
||||
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
||||
|
@ -2031,7 +2023,7 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
|||
}
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
}
|
||||
|
||||
/* ----------
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
#include "distributed/citus_nodefuncs.h"
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/multi_router_planner.h"
|
||||
#include "distributed/namespace_utils.h"
|
||||
#include "executor/spi.h"
|
||||
#include "foreign/foreign.h"
|
||||
#include "funcapi.h"
|
||||
|
@ -624,18 +625,14 @@ pg_get_rule_expr(Node *expression)
|
|||
{
|
||||
bool showImplicitCasts = true;
|
||||
deparse_context context;
|
||||
OverrideSearchPath *overridePath = NULL;
|
||||
StringInfo buffer = makeStringInfo();
|
||||
|
||||
/*
|
||||
* Set search_path to NIL so that all objects outside of pg_catalog will be
|
||||
* schema-prefixed. pg_catalog will be added automatically when we call
|
||||
* PushOverrideSearchPath(), since we set addCatalog to true;
|
||||
* PushEmptySearchPath(), since we set addCatalog to true;
|
||||
*/
|
||||
overridePath = GetOverrideSearchPath(CurrentMemoryContext);
|
||||
overridePath->schemas = NIL;
|
||||
overridePath->addCatalog = true;
|
||||
PushOverrideSearchPath(overridePath);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
context.buf = buffer;
|
||||
context.namespaces = NIL;
|
||||
|
@ -652,7 +649,7 @@ pg_get_rule_expr(Node *expression)
|
|||
get_rule_expr(expression, &context, showImplicitCasts);
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
return buffer->data;
|
||||
}
|
||||
|
@ -2038,8 +2035,6 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
|||
deparse_context context;
|
||||
deparse_namespace dpns;
|
||||
|
||||
OverrideSearchPath *overridePath = NULL;
|
||||
|
||||
/* Guard against excessively long or deeply-nested queries */
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
check_stack_depth();
|
||||
|
@ -2058,12 +2053,9 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
|||
/*
|
||||
* Set search_path to NIL so that all objects outside of pg_catalog will be
|
||||
* schema-prefixed. pg_catalog will be added automatically when we call
|
||||
* PushOverrideSearchPath(), since we set addCatalog to true;
|
||||
* PushEmptySearchPath().
|
||||
*/
|
||||
overridePath = GetOverrideSearchPath(CurrentMemoryContext);
|
||||
overridePath->schemas = NIL;
|
||||
overridePath->addCatalog = true;
|
||||
PushOverrideSearchPath(overridePath);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
context.buf = buf;
|
||||
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
||||
|
@ -2118,7 +2110,7 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
|||
}
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
}
|
||||
|
||||
/* ----------
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -519,7 +519,9 @@ typedef enum TaskExecutionState
|
|||
/*
|
||||
* PlacementExecutionOrder indicates whether a command should be executed
|
||||
* on any replica, on all replicas sequentially (in order), or on all
|
||||
* replicas in parallel.
|
||||
* replicas in parallel. In other words, EXECUTION_ORDER_ANY is used for
|
||||
* SELECTs, EXECUTION_ORDER_SEQUENTIAL/EXECUTION_ORDER_PARALLEL is used for
|
||||
* DML/DDL.
|
||||
*/
|
||||
typedef enum PlacementExecutionOrder
|
||||
{
|
||||
|
@ -2043,7 +2045,7 @@ ProcessSessionsWithFailedWaitEventSetOperations(DistributedExecution *execution)
|
|||
|
||||
/*
|
||||
* HasIncompleteConnectionEstablishment returns true if any of the connections
|
||||
* that has been initiated by the executor is in initilization stage.
|
||||
* that has been initiated by the executor is in initialization stage.
|
||||
*/
|
||||
static bool
|
||||
HasIncompleteConnectionEstablishment(DistributedExecution *execution)
|
||||
|
@ -2656,7 +2658,7 @@ OpenNewConnections(WorkerPool *workerPool, int newConnectionCount,
|
|||
{
|
||||
/*
|
||||
* The worker pool has just started to establish connections. We need to
|
||||
* defer this initilization after StartNodeUserDatabaseConnection()
|
||||
* defer this initialization after StartNodeUserDatabaseConnection()
|
||||
* because for non-optional connections, we have some logic to wait
|
||||
* until a connection is allowed to be established.
|
||||
*/
|
||||
|
@ -4687,6 +4689,10 @@ TaskExecutionStateMachine(ShardCommandExecution *shardCommandExecution)
|
|||
{
|
||||
currentTaskExecutionState = TASK_EXECUTION_FAILED;
|
||||
}
|
||||
else if (executionOrder != EXECUTION_ORDER_ANY && failedPlacementCount > 0)
|
||||
{
|
||||
currentTaskExecutionState = TASK_EXECUTION_FAILED;
|
||||
}
|
||||
else if (executionOrder == EXECUTION_ORDER_ANY && donePlacementCount > 0)
|
||||
{
|
||||
currentTaskExecutionState = TASK_EXECUTION_FINISHED;
|
||||
|
|
|
@ -279,7 +279,7 @@ RemoteFileDestReceiverStartup(DestReceiver *dest, int operation,
|
|||
|
||||
/*
|
||||
* PrepareIntermediateResultBroadcast gets a RemoteFileDestReceiver and does
|
||||
* the necessary initilizations including initiating the remote connections
|
||||
* the necessary initializations including initiating the remote connections
|
||||
* and creating the local file, which is necessary (it might be both).
|
||||
*/
|
||||
static void
|
||||
|
|
|
@ -100,7 +100,7 @@ ExecuteSourceAtWorkerAndRepartition(CitusScanState *scanState)
|
|||
Query *mergeQuery =
|
||||
copyObject(distributedPlan->modifyQueryViaCoordinatorOrRepartition);
|
||||
RangeTblEntry *targetRte = ExtractResultRelationRTE(mergeQuery);
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery);
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery, false);
|
||||
Oid targetRelationId = targetRte->relid;
|
||||
bool hasReturning = distributedPlan->expectResults;
|
||||
Query *sourceQuery = sourceRte->subquery;
|
||||
|
@ -211,7 +211,7 @@ ExecuteSourceAtCoordAndRedistribution(CitusScanState *scanState)
|
|||
Query *mergeQuery =
|
||||
copyObject(distributedPlan->modifyQueryViaCoordinatorOrRepartition);
|
||||
RangeTblEntry *targetRte = ExtractResultRelationRTE(mergeQuery);
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery);
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery, false);
|
||||
Query *sourceQuery = sourceRte->subquery;
|
||||
Oid targetRelationId = targetRte->relid;
|
||||
PlannedStmt *sourcePlan =
|
||||
|
|
|
@ -1022,13 +1022,12 @@ GetUndistributableDependency(const ObjectAddress *objectAddress)
|
|||
if (!SupportedDependencyByCitus(dependency))
|
||||
{
|
||||
/*
|
||||
* Skip roles and text search templates.
|
||||
*
|
||||
* Roles should be handled manually with Citus community whereas text search
|
||||
* templates should be handled manually in both community and enterprise
|
||||
* Since we do not yet support distributed TS TEMPLATE and AM objects, we skip
|
||||
* dependency checks for text search templates. The user is expected to
|
||||
* manually create the TS TEMPLATE and AM objects.
|
||||
*/
|
||||
if (getObjectClass(dependency) != OCLASS_ROLE &&
|
||||
getObjectClass(dependency) != OCLASS_TSTEMPLATE)
|
||||
if (getObjectClass(dependency) != OCLASS_TSTEMPLATE &&
|
||||
getObjectClass(dependency) != OCLASS_AM)
|
||||
{
|
||||
return dependency;
|
||||
}
|
||||
|
@ -1200,7 +1199,7 @@ FirstExtensionWithSchema(Oid schemaId)
|
|||
|
||||
ScanKeyData entry[1];
|
||||
ScanKeyInit(&entry[0], Anum_pg_extension_extnamespace, BTEqualStrategyNumber,
|
||||
F_INT4EQ, schemaId);
|
||||
F_OIDEQ, ObjectIdGetDatum(schemaId));
|
||||
|
||||
SysScanDesc scan = systable_beginscan(relation, InvalidOid, false, NULL, 1, entry);
|
||||
HeapTuple extensionTuple = systable_getnext(scan);
|
||||
|
|
|
@ -510,7 +510,7 @@ UpdateDistributedObjectColocationId(uint32 oldColocationId,
|
|||
/* scan pg_dist_object for colocationId equal to old colocationId */
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_dist_object_colocationid,
|
||||
BTEqualStrategyNumber,
|
||||
F_INT4EQ, UInt32GetDatum(oldColocationId));
|
||||
F_INT4EQ, Int32GetDatum(oldColocationId));
|
||||
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgDistObjectRel,
|
||||
InvalidOid,
|
||||
|
|
|
@ -83,7 +83,9 @@
|
|||
#include "utils/memutils.h"
|
||||
#include "utils/palloc.h"
|
||||
#include "utils/rel.h"
|
||||
#if PG_VERSION_NUM < PG_VERSION_16
|
||||
#include "utils/relfilenodemap.h"
|
||||
#endif
|
||||
#include "utils/relmapper.h"
|
||||
#include "utils/resowner.h"
|
||||
#include "utils/syscache.h"
|
||||
|
@ -131,6 +133,19 @@ typedef struct ShardIdCacheEntry
|
|||
int shardIndex;
|
||||
} ShardIdCacheEntry;
|
||||
|
||||
/*
|
||||
* ExtensionCreatedState is used to track if citus extension has been created
|
||||
* using CREATE EXTENSION command.
|
||||
* UNKNOWN : MetadataCache is invalid. State is UNKNOWN.
|
||||
* CREATED : Citus is created.
|
||||
* NOTCREATED : Citus is not created.
|
||||
*/
|
||||
typedef enum ExtensionCreatedState
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
CREATED = 1,
|
||||
NOTCREATED = 2,
|
||||
} ExtensionCreatedState;
|
||||
|
||||
/*
|
||||
* State which should be cleared upon DROP EXTENSION. When the configuration
|
||||
|
@ -138,7 +153,7 @@ typedef struct ShardIdCacheEntry
|
|||
*/
|
||||
typedef struct MetadataCacheData
|
||||
{
|
||||
bool extensionLoaded;
|
||||
ExtensionCreatedState extensionCreatedState;
|
||||
Oid distShardRelationId;
|
||||
Oid distPlacementRelationId;
|
||||
Oid distBackgroundJobRelationId;
|
||||
|
@ -286,7 +301,6 @@ static void CreateDistTableCache(void);
|
|||
static void CreateShardIdCache(void);
|
||||
static void CreateDistObjectCache(void);
|
||||
static void InvalidateForeignRelationGraphCacheCallback(Datum argument, Oid relationId);
|
||||
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);
|
||||
|
@ -352,7 +366,8 @@ EnsureModificationsCanRun(void)
|
|||
{
|
||||
if (RecoveryInProgress() && !WritableStandbyCoordinator)
|
||||
{
|
||||
ereport(ERROR, (errmsg("writing to worker nodes is not currently allowed"),
|
||||
ereport(ERROR, (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
|
||||
errmsg("writing to worker nodes is not currently allowed"),
|
||||
errdetail("the database is read-only")));
|
||||
}
|
||||
|
||||
|
@ -413,7 +428,8 @@ EnsureModificationsCanRunOnRelation(Oid relationId)
|
|||
|
||||
if (modifiedTableReplicated)
|
||||
{
|
||||
ereport(ERROR, (errmsg("writing to worker nodes is not currently "
|
||||
ereport(ERROR, (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
|
||||
errmsg("writing to worker nodes is not currently "
|
||||
"allowed for replicated tables such as reference "
|
||||
"tables or hash distributed tables with replication "
|
||||
"factor greater than 1."),
|
||||
|
@ -445,6 +461,52 @@ IsCitusTableType(Oid relationId, CitusTableType tableType)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetCitusTableType is a helper function that returns the CitusTableType
|
||||
* for the given relationId.
|
||||
* Note that a single table can be qualified as multiple CitusTableType, such
|
||||
* as hash distributed tables are both HASH_DISTRIBUTED and DISTRIBUTED_TABLE.
|
||||
* This function returns the base type for a given table.
|
||||
*
|
||||
* If the table is not a Citus table, ANY_CITUS_TABLE_TYPE is returned.
|
||||
*/
|
||||
CitusTableType
|
||||
GetCitusTableType(CitusTableCacheEntry *tableEntry)
|
||||
{
|
||||
/* we do not expect local tables here */
|
||||
Assert(tableEntry != NULL);
|
||||
|
||||
if (IsCitusTableTypeCacheEntry(tableEntry, HASH_DISTRIBUTED))
|
||||
{
|
||||
return HASH_DISTRIBUTED;
|
||||
}
|
||||
else if (IsCitusTableTypeCacheEntry(tableEntry, SINGLE_SHARD_DISTRIBUTED))
|
||||
{
|
||||
return SINGLE_SHARD_DISTRIBUTED;
|
||||
}
|
||||
else if (IsCitusTableTypeCacheEntry(tableEntry, REFERENCE_TABLE))
|
||||
{
|
||||
return REFERENCE_TABLE;
|
||||
}
|
||||
else if (IsCitusTableTypeCacheEntry(tableEntry, CITUS_LOCAL_TABLE))
|
||||
{
|
||||
return CITUS_LOCAL_TABLE;
|
||||
}
|
||||
else if (IsCitusTableTypeCacheEntry(tableEntry, APPEND_DISTRIBUTED))
|
||||
{
|
||||
return APPEND_DISTRIBUTED;
|
||||
}
|
||||
else if (IsCitusTableTypeCacheEntry(tableEntry, RANGE_DISTRIBUTED))
|
||||
{
|
||||
return RANGE_DISTRIBUTED;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ANY_CITUS_TABLE_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IsCitusTableTypeCacheEntry returns true if the given table cache entry
|
||||
* belongs to a citus table that matches the given table type.
|
||||
|
@ -2137,16 +2199,30 @@ HasOverlappingShardInterval(ShardInterval **shardIntervalArray,
|
|||
bool
|
||||
CitusHasBeenLoaded(void)
|
||||
{
|
||||
if (!MetadataCache.extensionLoaded || creating_extension)
|
||||
/*
|
||||
* We do not use Citus hooks during CREATE/ALTER EXTENSION citus
|
||||
* since the objects used by the C code might be not be there yet.
|
||||
*/
|
||||
if (creating_extension)
|
||||
{
|
||||
/*
|
||||
* Refresh if we have not determined whether the extension has been
|
||||
* loaded yet, or in case of ALTER EXTENSION since we want to treat
|
||||
* Citus as "not loaded" during ALTER EXTENSION citus.
|
||||
*/
|
||||
bool extensionLoaded = CitusHasBeenLoadedInternal();
|
||||
Oid citusExtensionOid = get_extension_oid("citus", true);
|
||||
|
||||
if (extensionLoaded && !MetadataCache.extensionLoaded)
|
||||
if (CurrentExtensionObject == citusExtensionOid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If extensionCreatedState is UNKNOWN, query pg_extension for Citus
|
||||
* and cache the result. Otherwise return the value extensionCreatedState
|
||||
* indicates.
|
||||
*/
|
||||
if (MetadataCache.extensionCreatedState == UNKNOWN)
|
||||
{
|
||||
bool extensionCreated = CitusHasBeenLoadedInternal();
|
||||
|
||||
if (extensionCreated)
|
||||
{
|
||||
/*
|
||||
* Loaded Citus for the first time in this session, or first time after
|
||||
|
@ -2158,31 +2234,22 @@ CitusHasBeenLoaded(void)
|
|||
*/
|
||||
StartupCitusBackend();
|
||||
|
||||
/*
|
||||
* InvalidateDistRelationCacheCallback resets state such as extensionLoaded
|
||||
* when it notices changes to pg_dist_partition (which usually indicate
|
||||
* `DROP EXTENSION citus;` has been run)
|
||||
*
|
||||
* Ensure InvalidateDistRelationCacheCallback will notice those changes
|
||||
* by caching pg_dist_partition's oid.
|
||||
*
|
||||
* We skip these checks during upgrade since pg_dist_partition is not
|
||||
* present during early stages of upgrade operation.
|
||||
*/
|
||||
DistPartitionRelationId();
|
||||
|
||||
/*
|
||||
* This needs to be initialized so we can receive foreign relation graph
|
||||
* invalidation messages in InvalidateForeignRelationGraphCacheCallback().
|
||||
* See the comments of InvalidateForeignKeyGraph for more context.
|
||||
*/
|
||||
DistColocationRelationId();
|
||||
}
|
||||
|
||||
MetadataCache.extensionLoaded = extensionLoaded;
|
||||
MetadataCache.extensionCreatedState = CREATED;
|
||||
}
|
||||
else
|
||||
{
|
||||
MetadataCache.extensionCreatedState = NOTCREATED;
|
||||
}
|
||||
}
|
||||
|
||||
return MetadataCache.extensionLoaded;
|
||||
return (MetadataCache.extensionCreatedState == CREATED) ? true : false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2207,15 +2274,6 @@ CitusHasBeenLoadedInternal(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (creating_extension && CurrentExtensionObject == citusExtensionOid)
|
||||
{
|
||||
/*
|
||||
* We do not use Citus hooks during CREATE/ALTER EXTENSION citus
|
||||
* since the objects used by the C code might be not be there yet.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
/* citus extension exists and has been created */
|
||||
return true;
|
||||
}
|
||||
|
@ -4151,10 +4209,6 @@ InitializeDistCache(void)
|
|||
CreateShardIdCache();
|
||||
|
||||
InitializeDistObjectCache();
|
||||
|
||||
/* Watch for invalidation events. */
|
||||
CacheRegisterRelcacheCallback(InvalidateDistRelationCacheCallback,
|
||||
(Datum) 0);
|
||||
}
|
||||
|
||||
|
||||
|
@ -4367,9 +4421,9 @@ RegisterCitusTableCacheEntryReleaseCallbacks(void)
|
|||
|
||||
|
||||
/*
|
||||
* GetLocalGroupId returns the group identifier of the local node. The function assumes
|
||||
* that pg_dist_local_node_group has exactly one row and has at least one column.
|
||||
* Otherwise, the function errors out.
|
||||
* GetLocalGroupId returns the group identifier of the local node. The function
|
||||
* assumes that pg_dist_local_group has exactly one row and has at least one
|
||||
* column. Otherwise, the function errors out.
|
||||
*/
|
||||
int32
|
||||
GetLocalGroupId(void)
|
||||
|
@ -4704,7 +4758,7 @@ InvalidateForeignKeyGraph(void)
|
|||
* InvalidateDistRelationCacheCallback flushes cache entries when a relation
|
||||
* is updated (or flushes the entire cache).
|
||||
*/
|
||||
static void
|
||||
void
|
||||
InvalidateDistRelationCacheCallback(Datum argument, Oid relationId)
|
||||
{
|
||||
/* invalidate either entire cache or a specific entry */
|
||||
|
@ -4712,12 +4766,18 @@ InvalidateDistRelationCacheCallback(Datum argument, Oid relationId)
|
|||
{
|
||||
InvalidateDistTableCache();
|
||||
InvalidateDistObjectCache();
|
||||
InvalidateMetadataSystemCache();
|
||||
}
|
||||
else
|
||||
{
|
||||
void *hashKey = (void *) &relationId;
|
||||
bool foundInCache = false;
|
||||
|
||||
if (DistTableCacheHash == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CitusTableCacheEntrySlot *cacheSlot =
|
||||
hash_search(DistTableCacheHash, hashKey, HASH_FIND, &foundInCache);
|
||||
if (foundInCache)
|
||||
|
@ -4726,21 +4786,19 @@ InvalidateDistRelationCacheCallback(Datum argument, Oid relationId)
|
|||
}
|
||||
|
||||
/*
|
||||
* If pg_dist_partition is being invalidated drop all state
|
||||
* This happens pretty rarely, but most importantly happens during
|
||||
* DROP EXTENSION citus; This isn't the only time when this happens
|
||||
* though, it can happen for multiple other reasons, such as an
|
||||
* autovacuum running ANALYZE on pg_dist_partition. Such an ANALYZE
|
||||
* wouldn't really need a full Metadata cache invalidation, but we
|
||||
* don't know how to differentiate between DROP EXTENSION and ANALYZE.
|
||||
* So for now we simply drop it in both cases and take the slight
|
||||
* temporary performance hit.
|
||||
* if pg_dist_partition relcache is invalidated for some reason,
|
||||
* invalidate the MetadataCache. It is likely an overkill to invalidate
|
||||
* the entire cache here. But until a better fix, we keep it this way
|
||||
* for postgres regression tests that includes
|
||||
* REINDEX SCHEMA CONCURRENTLY pg_catalog
|
||||
* command.
|
||||
*/
|
||||
if (relationId == MetadataCache.distPartitionRelationId)
|
||||
{
|
||||
InvalidateMetadataSystemCache();
|
||||
}
|
||||
|
||||
|
||||
if (relationId == MetadataCache.distObjectRelationId)
|
||||
{
|
||||
InvalidateDistObjectCache();
|
||||
|
@ -4780,6 +4838,11 @@ InvalidateDistTableCache(void)
|
|||
CitusTableCacheEntrySlot *cacheSlot = NULL;
|
||||
HASH_SEQ_STATUS status;
|
||||
|
||||
if (DistTableCacheHash == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hash_seq_init(&status, DistTableCacheHash);
|
||||
|
||||
while ((cacheSlot = (CitusTableCacheEntrySlot *) hash_seq_search(&status)) != NULL)
|
||||
|
@ -4798,6 +4861,11 @@ InvalidateDistObjectCache(void)
|
|||
DistObjectCacheEntry *cacheEntry = NULL;
|
||||
HASH_SEQ_STATUS status;
|
||||
|
||||
if (DistObjectCacheHash == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hash_seq_init(&status, DistObjectCacheHash);
|
||||
|
||||
while ((cacheEntry = (DistObjectCacheEntry *) hash_seq_search(&status)) != NULL)
|
||||
|
@ -4880,8 +4948,8 @@ CreateDistObjectCache(void)
|
|||
|
||||
|
||||
/*
|
||||
* InvalidateMetadataSystemCache resets all the cached OIDs and the extensionLoaded flag,
|
||||
* and invalidates the worker node, ConnParams, and local group ID caches.
|
||||
* InvalidateMetadataSystemCache resets all the cached OIDs and the extensionCreatedState
|
||||
* flag and invalidates the worker node, ConnParams, and local group ID caches.
|
||||
*/
|
||||
void
|
||||
InvalidateMetadataSystemCache(void)
|
||||
|
@ -4939,8 +5007,8 @@ CitusTableTypeIdList(CitusTableType citusTableType)
|
|||
Datum replicationModelDatum = datumArray[Anum_pg_dist_partition_repmodel - 1];
|
||||
Datum colocationIdDatum = datumArray[Anum_pg_dist_partition_colocationid - 1];
|
||||
|
||||
Oid partitionMethod = DatumGetChar(partMethodDatum);
|
||||
Oid replicationModel = DatumGetChar(replicationModelDatum);
|
||||
char partitionMethod = DatumGetChar(partMethodDatum);
|
||||
char replicationModel = DatumGetChar(replicationModelDatum);
|
||||
uint32 colocationId = DatumGetUInt32(colocationIdDatum);
|
||||
|
||||
if (IsCitusTableTypeInternal(partitionMethod, replicationModel, colocationId,
|
||||
|
@ -5600,7 +5668,7 @@ role_exists(PG_FUNCTION_ARGS)
|
|||
* Otherwise, this function returns NULL.
|
||||
*/
|
||||
char *
|
||||
GetPoolinfoViaCatalog(int64 nodeId)
|
||||
GetPoolinfoViaCatalog(int32 nodeId)
|
||||
{
|
||||
ScanKeyData scanKey[1];
|
||||
const int scanKeyCount = 1;
|
||||
|
|
|
@ -150,6 +150,7 @@ static char * RemoteSchemaIdExpressionById(Oid schemaId);
|
|||
static char * RemoteSchemaIdExpressionByName(char *schemaName);
|
||||
static char * RemoteTypeIdExpression(Oid typeId);
|
||||
static char * RemoteCollationIdExpression(Oid colocationId);
|
||||
static char * RemoteTableIdExpression(Oid relationId);
|
||||
|
||||
|
||||
PG_FUNCTION_INFO_V1(start_metadata_sync_to_all_nodes);
|
||||
|
@ -167,6 +168,7 @@ PG_FUNCTION_INFO_V1(citus_internal_add_partition_metadata);
|
|||
PG_FUNCTION_INFO_V1(citus_internal_delete_partition_metadata);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_add_shard_metadata);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_add_placement_metadata);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_delete_placement_metadata);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_add_placement_metadata_legacy);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_update_placement_metadata);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_delete_shard_metadata);
|
||||
|
@ -176,6 +178,7 @@ PG_FUNCTION_INFO_V1(citus_internal_add_colocation_metadata);
|
|||
PG_FUNCTION_INFO_V1(citus_internal_delete_colocation_metadata);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_add_tenant_schema);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_delete_tenant_schema);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_update_none_dist_table_metadata);
|
||||
|
||||
|
||||
static bool got_SIGTERM = false;
|
||||
|
@ -1757,8 +1760,8 @@ GetFunctionDependenciesForObjects(ObjectAddress *objectAddress)
|
|||
ObjectIdGetDatum(objectAddress->objectId));
|
||||
ScanKeyInit(&key[2],
|
||||
Anum_pg_depend_objsubid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(objectAddress->objectSubId));
|
||||
BTEqualStrategyNumber, F_INT4EQ,
|
||||
Int32GetDatum(objectAddress->objectSubId));
|
||||
|
||||
SysScanDesc scan = systable_beginscan(depRel, DependDependerIndexId, true,
|
||||
NULL, 3, key);
|
||||
|
@ -3449,6 +3452,28 @@ citus_internal_add_placement_metadata(PG_FUNCTION_ARGS)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* citus_internal_add_placement_metadata is an internal UDF to
|
||||
* delete a row from pg_dist_placement.
|
||||
*/
|
||||
Datum
|
||||
citus_internal_delete_placement_metadata(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_ENSURE_ARGNOTNULL(0, "placement_id");
|
||||
int64 placementId = PG_GETARG_INT64(0);
|
||||
|
||||
if (!ShouldSkipMetadataChecks())
|
||||
{
|
||||
/* this UDF is not allowed allowed for executing as a separate command */
|
||||
EnsureCoordinatorInitiatedOperation();
|
||||
}
|
||||
|
||||
DeleteShardPlacementRow(placementId);
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* citus_internal_add_placement_metadata_legacy is the old function that will be dropped.
|
||||
*/
|
||||
|
@ -3836,6 +3861,40 @@ citus_internal_delete_tenant_schema(PG_FUNCTION_ARGS)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* citus_internal_update_none_dist_table_metadata is an internal UDF to
|
||||
* update a row in pg_dist_partition that belongs to given none-distributed
|
||||
* table.
|
||||
*/
|
||||
Datum
|
||||
citus_internal_update_none_dist_table_metadata(PG_FUNCTION_ARGS)
|
||||
{
|
||||
CheckCitusVersion(ERROR);
|
||||
|
||||
PG_ENSURE_ARGNOTNULL(0, "relation_id");
|
||||
Oid relationId = PG_GETARG_OID(0);
|
||||
|
||||
PG_ENSURE_ARGNOTNULL(1, "replication_model");
|
||||
char replicationModel = PG_GETARG_CHAR(1);
|
||||
|
||||
PG_ENSURE_ARGNOTNULL(2, "colocation_id");
|
||||
uint32 colocationId = PG_GETARG_INT32(2);
|
||||
|
||||
PG_ENSURE_ARGNOTNULL(3, "auto_converted");
|
||||
bool autoConverted = PG_GETARG_BOOL(3);
|
||||
|
||||
if (!ShouldSkipMetadataChecks())
|
||||
{
|
||||
EnsureCoordinatorInitiatedOperation();
|
||||
}
|
||||
|
||||
UpdateNoneDistTableMetadata(relationId, replicationModel,
|
||||
colocationId, autoConverted);
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SyncNewColocationGroup synchronizes a new pg_dist_colocation entry to a worker.
|
||||
*/
|
||||
|
@ -4017,6 +4076,55 @@ TenantSchemaDeleteCommand(char *schemaName)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* UpdateNoneDistTableMetadataCommand returns a command to call
|
||||
* citus_internal_update_none_dist_table_metadata().
|
||||
*/
|
||||
char *
|
||||
UpdateNoneDistTableMetadataCommand(Oid relationId, char replicationModel,
|
||||
uint32 colocationId, bool autoConverted)
|
||||
{
|
||||
StringInfo command = makeStringInfo();
|
||||
appendStringInfo(command,
|
||||
"SELECT pg_catalog.citus_internal_update_none_dist_table_metadata(%s, '%c', %u, %s)",
|
||||
RemoteTableIdExpression(relationId), replicationModel, colocationId,
|
||||
autoConverted ? "true" : "false");
|
||||
|
||||
return command->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AddPlacementMetadataCommand returns a command to call
|
||||
* citus_internal_add_placement_metadata().
|
||||
*/
|
||||
char *
|
||||
AddPlacementMetadataCommand(uint64 shardId, uint64 placementId,
|
||||
uint64 shardLength, int32 groupId)
|
||||
{
|
||||
StringInfo command = makeStringInfo();
|
||||
appendStringInfo(command,
|
||||
"SELECT citus_internal_add_placement_metadata(%ld, %ld, %d, %ld)",
|
||||
shardId, shardLength, groupId, placementId);
|
||||
return command->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeletePlacementMetadataCommand returns a command to call
|
||||
* citus_internal_delete_placement_metadata().
|
||||
*/
|
||||
char *
|
||||
DeletePlacementMetadataCommand(uint64 placementId)
|
||||
{
|
||||
StringInfo command = makeStringInfo();
|
||||
appendStringInfo(command,
|
||||
"SELECT pg_catalog.citus_internal_delete_placement_metadata(%ld)",
|
||||
placementId);
|
||||
return command->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RemoteSchemaIdExpressionById returns an expression in text form that
|
||||
* can be used to obtain the OID of the schema with given schema id on a
|
||||
|
@ -4051,6 +4159,22 @@ RemoteSchemaIdExpressionByName(char *schemaName)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* RemoteTableIdExpression returns an expression in text form that
|
||||
* can be used to obtain the OID of given table on a different node
|
||||
* when included in a query string.
|
||||
*/
|
||||
static char *
|
||||
RemoteTableIdExpression(Oid relationId)
|
||||
{
|
||||
StringInfo regclassExpr = makeStringInfo();
|
||||
appendStringInfo(regclassExpr, "%s::regclass",
|
||||
quote_literal_cstr(generate_qualified_relation_name(relationId)));
|
||||
|
||||
return regclassExpr->data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SetMetadataSyncNodesFromNodeList sets list of nodes that needs to be metadata
|
||||
* synced among given node list into metadataSyncContext.
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
#include "catalog/pg_constraint.h"
|
||||
#include "catalog/pg_extension.h"
|
||||
#include "catalog/pg_namespace.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "catalog/pg_proc_d.h"
|
||||
#endif
|
||||
#include "catalog/pg_type.h"
|
||||
#include "commands/extension.h"
|
||||
#include "commands/sequence.h"
|
||||
|
@ -1395,6 +1398,17 @@ IsActiveShardPlacement(ShardPlacement *shardPlacement)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* IsRemoteShardPlacement returns true if the shard placement is on a remote
|
||||
* node.
|
||||
*/
|
||||
bool
|
||||
IsRemoteShardPlacement(ShardPlacement *shardPlacement)
|
||||
{
|
||||
return shardPlacement->groupId != GetLocalGroupId();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IsPlacementOnWorkerNode checks if the shard placement is for to the given
|
||||
* workenode.
|
||||
|
@ -1780,6 +1794,24 @@ InsertShardRow(Oid relationId, uint64 shardId, char storageType,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* InsertShardPlacementRowGlobally inserts shard placement that has given
|
||||
* parameters into pg_dist_placement globally.
|
||||
*/
|
||||
ShardPlacement *
|
||||
InsertShardPlacementRowGlobally(uint64 shardId, uint64 placementId,
|
||||
uint64 shardLength, int32 groupId)
|
||||
{
|
||||
InsertShardPlacementRow(shardId, placementId, shardLength, groupId);
|
||||
|
||||
char *insertPlacementCommand =
|
||||
AddPlacementMetadataCommand(shardId, placementId, shardLength, groupId);
|
||||
SendCommandToWorkersWithMetadata(insertPlacementCommand);
|
||||
|
||||
return LoadShardPlacement(shardId, placementId);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* InsertShardPlacementRow opens the shard placement system catalog, and inserts
|
||||
* a new row with the given values into that system catalog. If placementId is
|
||||
|
@ -1996,6 +2028,21 @@ DeleteShardRow(uint64 shardId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeleteShardPlacementRowGlobally deletes shard placement that has given
|
||||
* parameters from pg_dist_placement globally.
|
||||
*/
|
||||
void
|
||||
DeleteShardPlacementRowGlobally(uint64 placementId)
|
||||
{
|
||||
DeleteShardPlacementRow(placementId);
|
||||
|
||||
char *deletePlacementCommand =
|
||||
DeletePlacementMetadataCommand(placementId);
|
||||
SendCommandToWorkersWithMetadata(deletePlacementCommand);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeleteShardPlacementRow opens the shard placement system catalog, finds the placement
|
||||
* with the given placementId, and deletes it.
|
||||
|
@ -2240,6 +2287,93 @@ UpdateDistributionColumn(Oid relationId, char distributionMethod, Var *distribut
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* UpdateNoneDistTableMetadataGlobally globally updates pg_dist_partition for
|
||||
* given none-distributed table.
|
||||
*/
|
||||
void
|
||||
UpdateNoneDistTableMetadataGlobally(Oid relationId, char replicationModel,
|
||||
uint32 colocationId, bool autoConverted)
|
||||
{
|
||||
UpdateNoneDistTableMetadata(relationId, replicationModel,
|
||||
colocationId, autoConverted);
|
||||
|
||||
if (ShouldSyncTableMetadata(relationId))
|
||||
{
|
||||
char *metadataCommand =
|
||||
UpdateNoneDistTableMetadataCommand(relationId,
|
||||
replicationModel,
|
||||
colocationId,
|
||||
autoConverted);
|
||||
SendCommandToWorkersWithMetadata(metadataCommand);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* UpdateNoneDistTableMetadata locally updates pg_dist_partition for given
|
||||
* none-distributed table.
|
||||
*/
|
||||
void
|
||||
UpdateNoneDistTableMetadata(Oid relationId, char replicationModel, uint32 colocationId,
|
||||
bool autoConverted)
|
||||
{
|
||||
if (HasDistributionKey(relationId))
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot update metadata for a distributed "
|
||||
"table that has a distribution column")));
|
||||
}
|
||||
|
||||
ScanKeyData scanKey[1];
|
||||
int scanKeyCount = 1;
|
||||
bool indexOK = true;
|
||||
Datum values[Natts_pg_dist_partition];
|
||||
bool isnull[Natts_pg_dist_partition];
|
||||
bool replace[Natts_pg_dist_partition];
|
||||
|
||||
Relation pgDistPartition = table_open(DistPartitionRelationId(), RowExclusiveLock);
|
||||
TupleDesc tupleDescriptor = RelationGetDescr(pgDistPartition);
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_dist_partition_logicalrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId));
|
||||
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgDistPartition,
|
||||
DistPartitionLogicalRelidIndexId(),
|
||||
indexOK,
|
||||
NULL, scanKeyCount, scanKey);
|
||||
|
||||
HeapTuple heapTuple = systable_getnext(scanDescriptor);
|
||||
if (!HeapTupleIsValid(heapTuple))
|
||||
{
|
||||
ereport(ERROR, (errmsg("could not find valid entry for Citus table with oid: %u",
|
||||
relationId)));
|
||||
}
|
||||
|
||||
memset(replace, 0, sizeof(replace));
|
||||
|
||||
values[Anum_pg_dist_partition_colocationid - 1] = UInt32GetDatum(colocationId);
|
||||
isnull[Anum_pg_dist_partition_colocationid - 1] = false;
|
||||
replace[Anum_pg_dist_partition_colocationid - 1] = true;
|
||||
|
||||
values[Anum_pg_dist_partition_repmodel - 1] = CharGetDatum(replicationModel);
|
||||
isnull[Anum_pg_dist_partition_repmodel - 1] = false;
|
||||
replace[Anum_pg_dist_partition_repmodel - 1] = true;
|
||||
|
||||
values[Anum_pg_dist_partition_autoconverted - 1] = BoolGetDatum(autoConverted);
|
||||
isnull[Anum_pg_dist_partition_autoconverted - 1] = false;
|
||||
replace[Anum_pg_dist_partition_autoconverted - 1] = true;
|
||||
|
||||
heapTuple = heap_modify_tuple(heapTuple, tupleDescriptor, values, isnull, replace);
|
||||
|
||||
CatalogTupleUpdate(pgDistPartition, &heapTuple->t_self, heapTuple);
|
||||
|
||||
CitusInvalidateRelcacheByRelid(relationId);
|
||||
CommandCounterIncrement();
|
||||
|
||||
systable_endscan(scanDescriptor);
|
||||
table_close(pgDistPartition, NoLock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check that the current user has `mode` permissions on relationId, error out
|
||||
* if not. Superusers always have such permissions.
|
||||
|
@ -2263,7 +2397,7 @@ EnsureTablePermissions(Oid relationId, AclMode mode)
|
|||
void
|
||||
EnsureTableOwner(Oid relationId)
|
||||
{
|
||||
if (!pg_class_ownercheck(relationId, GetUserId()))
|
||||
if (!object_ownercheck(RelationRelationId, relationId, GetUserId()))
|
||||
{
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TABLE,
|
||||
get_rel_name(relationId));
|
||||
|
@ -2278,7 +2412,7 @@ EnsureTableOwner(Oid relationId)
|
|||
void
|
||||
EnsureSchemaOwner(Oid schemaId)
|
||||
{
|
||||
if (!pg_namespace_ownercheck(schemaId, GetUserId()))
|
||||
if (!object_ownercheck(NamespaceRelationId, schemaId, GetUserId()))
|
||||
{
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
|
||||
get_namespace_name(schemaId));
|
||||
|
@ -2294,7 +2428,7 @@ EnsureSchemaOwner(Oid schemaId)
|
|||
void
|
||||
EnsureFunctionOwner(Oid functionId)
|
||||
{
|
||||
if (!pg_proc_ownercheck(functionId, GetUserId()))
|
||||
if (!object_ownercheck(ProcedureRelationId, functionId, GetUserId()))
|
||||
{
|
||||
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
|
||||
get_func_name(functionId));
|
||||
|
@ -3286,11 +3420,11 @@ BackgroundTaskHasUmnetDependencies(int64 jobId, int64 taskId)
|
|||
|
||||
/* pg_catalog.pg_dist_background_task_depend.job_id = jobId */
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_dist_background_task_depend_job_id,
|
||||
BTEqualStrategyNumber, F_INT8EQ, jobId);
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(jobId));
|
||||
|
||||
/* pg_catalog.pg_dist_background_task_depend.task_id = $taskId */
|
||||
ScanKeyInit(&scanKey[1], Anum_pg_dist_background_task_depend_task_id,
|
||||
BTEqualStrategyNumber, F_INT8EQ, taskId);
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(taskId));
|
||||
|
||||
SysScanDesc scanDescriptor =
|
||||
systable_beginscan(pgDistBackgroundTasksDepend,
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "funcapi.h"
|
||||
#include "utils/plancache.h"
|
||||
|
||||
|
||||
#include "access/genam.h"
|
||||
#include "access/heapam.h"
|
||||
#include "access/htup.h"
|
||||
|
@ -102,8 +101,8 @@ static HeapTuple GetNodeByNodeId(int32 nodeId);
|
|||
static int32 GetNextGroupId(void);
|
||||
static int GetNextNodeId(void);
|
||||
static void InsertPlaceholderCoordinatorRecord(void);
|
||||
static void InsertNodeRow(int nodeid, char *nodename, int32 nodeport, NodeMetadata
|
||||
*nodeMetadata);
|
||||
static void InsertNodeRow(int nodeid, char *nodename, int32 nodeport,
|
||||
NodeMetadata *nodeMetadata);
|
||||
static void DeleteNodeRow(char *nodename, int32 nodeport);
|
||||
static void BlockDistributedQueriesOnMetadataNodes(void);
|
||||
static WorkerNode * TupleToWorkerNode(TupleDesc tupleDescriptor, HeapTuple heapTuple);
|
||||
|
@ -134,6 +133,13 @@ static void MarkNodesNotSyncedInLoopBackConnection(MetadataSyncContext *context,
|
|||
static void EnsureParentSessionHasExclusiveLockOnPgDistNode(pid_t parentSessionPid);
|
||||
static void SetNodeMetadata(MetadataSyncContext *context, bool localOnly);
|
||||
static void EnsureTransactionalMetadataSyncMode(void);
|
||||
static void LockShardsInWorkerPlacementList(WorkerNode *workerNode, LOCKMODE
|
||||
lockMode);
|
||||
static BackgroundWorkerHandle * CheckBackgroundWorkerToObtainLocks(int32 lock_cooldown);
|
||||
static BackgroundWorkerHandle * LockPlacementsWithBackgroundWorkersInPrimaryNode(
|
||||
WorkerNode *workerNode, bool force, int32 lock_cooldown);
|
||||
|
||||
/* Function definitions go here */
|
||||
|
||||
/* declarations for dynamic loading */
|
||||
PG_FUNCTION_INFO_V1(citus_set_coordinator_host);
|
||||
|
@ -152,6 +158,7 @@ PG_FUNCTION_INFO_V1(master_disable_node);
|
|||
PG_FUNCTION_INFO_V1(citus_activate_node);
|
||||
PG_FUNCTION_INFO_V1(master_activate_node);
|
||||
PG_FUNCTION_INFO_V1(citus_update_node);
|
||||
PG_FUNCTION_INFO_V1(citus_pause_node_within_txn);
|
||||
PG_FUNCTION_INFO_V1(master_update_node);
|
||||
PG_FUNCTION_INFO_V1(get_shard_id_for_distribution_column);
|
||||
PG_FUNCTION_INFO_V1(citus_nodename_for_nodeid);
|
||||
|
@ -160,7 +167,6 @@ PG_FUNCTION_INFO_V1(citus_coordinator_nodeid);
|
|||
PG_FUNCTION_INFO_V1(citus_is_coordinator);
|
||||
PG_FUNCTION_INFO_V1(citus_internal_mark_node_not_synced);
|
||||
|
||||
|
||||
/*
|
||||
* DefaultNodeMetadata creates a NodeMetadata struct with the fields set to
|
||||
* sane defaults, e.g. nodeRack = WORKER_DEFAULT_RACK.
|
||||
|
@ -544,7 +550,8 @@ citus_disable_node(PG_FUNCTION_ARGS)
|
|||
"metadata is not allowed"),
|
||||
errhint("You can force disabling node, SELECT "
|
||||
"citus_disable_node('%s', %d, "
|
||||
"synchronous:=true);", workerNode->workerName,
|
||||
"synchronous:=true);",
|
||||
workerNode->workerName,
|
||||
nodePort),
|
||||
errdetail("Citus uses the first worker node in the "
|
||||
"metadata for certain internal operations when "
|
||||
|
@ -693,8 +700,7 @@ citus_set_node_property(PG_FUNCTION_ARGS)
|
|||
else
|
||||
{
|
||||
ereport(ERROR, (errmsg(
|
||||
"only the 'shouldhaveshards' property can be set using this function"
|
||||
)));
|
||||
"only the 'shouldhaveshards' property can be set using this function")));
|
||||
}
|
||||
|
||||
TransactionModifiedNodeMetadata = true;
|
||||
|
@ -1160,6 +1166,100 @@ ActivateNodeList(MetadataSyncContext *context)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Acquires shard metadata locks on all shards residing in the given worker node
|
||||
*
|
||||
* TODO: This function is not compatible with query from any node feature.
|
||||
* To ensure proper behavior, it is essential to acquire locks on placements across all nodes
|
||||
* rather than limiting it to just the coordinator (or the specific node from which this function is called)
|
||||
*/
|
||||
void
|
||||
LockShardsInWorkerPlacementList(WorkerNode *workerNode, LOCKMODE lockMode)
|
||||
{
|
||||
List *placementList = AllShardPlacementsOnNodeGroup(workerNode->groupId);
|
||||
LockShardsInPlacementListMetadata(placementList, lockMode);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function is used to start a background worker to kill backends holding conflicting
|
||||
* locks with this backend. It returns NULL if the background worker could not be started.
|
||||
*/
|
||||
BackgroundWorkerHandle *
|
||||
CheckBackgroundWorkerToObtainLocks(int32 lock_cooldown)
|
||||
{
|
||||
BackgroundWorkerHandle *handle = StartLockAcquireHelperBackgroundWorker(MyProcPid,
|
||||
lock_cooldown);
|
||||
if (!handle)
|
||||
{
|
||||
/*
|
||||
* We failed to start a background worker, which probably means that we exceeded
|
||||
* max_worker_processes, and this is unlikely to be resolved by retrying. We do not want
|
||||
* to repeatedly throw an error because if citus_update_node is called to complete a
|
||||
* failover then finishing is the only way to bring the cluster back up. Therefore we
|
||||
* give up on killing other backends and simply wait for the lock. We do set
|
||||
* lock_timeout to lock_cooldown, because we don't want to wait forever to get a lock.
|
||||
*/
|
||||
SetLockTimeoutLocally(lock_cooldown);
|
||||
ereport(WARNING, (errmsg(
|
||||
"could not start background worker to kill backends with conflicting"
|
||||
" locks to force the update. Degrading to acquiring locks "
|
||||
"with a lock time out."),
|
||||
errhint(
|
||||
"Increasing max_worker_processes might help.")));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function is used to lock shards in a primary node.
|
||||
* If force is true, we start a background worker to kill backends holding
|
||||
* conflicting locks with this backend.
|
||||
*
|
||||
* If the node is a primary node we block reads and writes.
|
||||
*
|
||||
* This lock has two purposes:
|
||||
*
|
||||
* - Ensure buggy code in Citus doesn't cause failures when the
|
||||
* nodename/nodeport of a node changes mid-query
|
||||
*
|
||||
* - Provide fencing during failover, after this function returns all
|
||||
* connections will use the new node location.
|
||||
*
|
||||
* Drawback:
|
||||
*
|
||||
* - This function blocks until all previous queries have finished. This
|
||||
* means that long-running queries will prevent failover.
|
||||
*
|
||||
* In case of node failure said long-running queries will fail in the end
|
||||
* anyway as they will be unable to commit successfully on the failed
|
||||
* machine. To cause quick failure of these queries use force => true
|
||||
* during the invocation of citus_update_node to terminate conflicting
|
||||
* backends proactively.
|
||||
*
|
||||
* It might be worth blocking reads to a secondary for the same reasons,
|
||||
* though we currently only query secondaries on follower clusters
|
||||
* where these locks will have no effect.
|
||||
*/
|
||||
BackgroundWorkerHandle *
|
||||
LockPlacementsWithBackgroundWorkersInPrimaryNode(WorkerNode *workerNode, bool force, int32
|
||||
lock_cooldown)
|
||||
{
|
||||
BackgroundWorkerHandle *handle = NULL;
|
||||
|
||||
if (NodeIsPrimary(workerNode))
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
handle = CheckBackgroundWorkerToObtainLocks(lock_cooldown);
|
||||
}
|
||||
LockShardsInWorkerPlacementList(workerNode, AccessExclusiveLock);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* citus_update_node moves the requested node to a different nodename and nodeport. It
|
||||
* locks to ensure no queries are running concurrently; and is intended for customers who
|
||||
|
@ -1188,8 +1288,6 @@ citus_update_node(PG_FUNCTION_ARGS)
|
|||
int32 lock_cooldown = PG_GETARG_INT32(4);
|
||||
|
||||
char *newNodeNameString = text_to_cstring(newNodeName);
|
||||
List *placementList = NIL;
|
||||
BackgroundWorkerHandle *handle = NULL;
|
||||
|
||||
WorkerNode *workerNodeWithSameAddress = FindWorkerNodeAnyCluster(newNodeNameString,
|
||||
newNodePort);
|
||||
|
@ -1226,64 +1324,9 @@ citus_update_node(PG_FUNCTION_ARGS)
|
|||
EnsureTransactionalMetadataSyncMode();
|
||||
}
|
||||
|
||||
/*
|
||||
* If the node is a primary node we block reads and writes.
|
||||
*
|
||||
* This lock has two purposes:
|
||||
*
|
||||
* - Ensure buggy code in Citus doesn't cause failures when the
|
||||
* nodename/nodeport of a node changes mid-query
|
||||
*
|
||||
* - Provide fencing during failover, after this function returns all
|
||||
* connections will use the new node location.
|
||||
*
|
||||
* Drawback:
|
||||
*
|
||||
* - This function blocks until all previous queries have finished. This
|
||||
* means that long-running queries will prevent failover.
|
||||
*
|
||||
* In case of node failure said long-running queries will fail in the end
|
||||
* anyway as they will be unable to commit successfully on the failed
|
||||
* machine. To cause quick failure of these queries use force => true
|
||||
* during the invocation of citus_update_node to terminate conflicting
|
||||
* backends proactively.
|
||||
*
|
||||
* It might be worth blocking reads to a secondary for the same reasons,
|
||||
* though we currently only query secondaries on follower clusters
|
||||
* where these locks will have no effect.
|
||||
*/
|
||||
if (NodeIsPrimary(workerNode))
|
||||
{
|
||||
/*
|
||||
* before acquiring the locks check if we want a background worker to help us to
|
||||
* aggressively obtain the locks.
|
||||
*/
|
||||
if (force)
|
||||
{
|
||||
handle = StartLockAcquireHelperBackgroundWorker(MyProcPid, lock_cooldown);
|
||||
if (!handle)
|
||||
{
|
||||
/*
|
||||
* We failed to start a background worker, which probably means that we exceeded
|
||||
* max_worker_processes, and this is unlikely to be resolved by retrying. We do not want
|
||||
* to repeatedly throw an error because if citus_update_node is called to complete a
|
||||
* failover then finishing is the only way to bring the cluster back up. Therefore we
|
||||
* give up on killing other backends and simply wait for the lock. We do set
|
||||
* lock_timeout to lock_cooldown, because we don't want to wait forever to get a lock.
|
||||
*/
|
||||
SetLockTimeoutLocally(lock_cooldown);
|
||||
ereport(WARNING, (errmsg(
|
||||
"could not start background worker to kill backends with conflicting"
|
||||
" locks to force the update. Degrading to acquiring locks "
|
||||
"with a lock time out."),
|
||||
errhint(
|
||||
"Increasing max_worker_processes might help.")));
|
||||
}
|
||||
}
|
||||
|
||||
placementList = AllShardPlacementsOnNodeGroup(workerNode->groupId);
|
||||
LockShardsInPlacementListMetadata(placementList, AccessExclusiveLock);
|
||||
}
|
||||
BackgroundWorkerHandle *handle = LockPlacementsWithBackgroundWorkersInPrimaryNode(
|
||||
workerNode, force,
|
||||
lock_cooldown);
|
||||
|
||||
/*
|
||||
* if we have planned statements such as prepared statements, we should clear the cache so that
|
||||
|
@ -1330,6 +1373,34 @@ citus_update_node(PG_FUNCTION_ARGS)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function is designed to obtain locks for all the shards in a worker placement list.
|
||||
* Once the transaction is committed, the acquired locks will be automatically released.
|
||||
* Therefore, it is essential to invoke this function within a transaction.
|
||||
* This function proves beneficial when there is a need to temporarily disable writes to a specific node within a transaction.
|
||||
*/
|
||||
Datum
|
||||
citus_pause_node_within_txn(PG_FUNCTION_ARGS)
|
||||
{
|
||||
CheckCitusVersion(ERROR);
|
||||
|
||||
int32 nodeId = PG_GETARG_INT32(0);
|
||||
bool force = PG_GETARG_BOOL(1);
|
||||
int32 lock_cooldown = PG_GETARG_INT32(2);
|
||||
|
||||
WorkerNode *workerNode = FindNodeAnyClusterByNodeId(nodeId);
|
||||
if (workerNode == NULL)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_NO_DATA_FOUND),
|
||||
errmsg("node %u not found", nodeId)));
|
||||
}
|
||||
|
||||
LockPlacementsWithBackgroundWorkersInPrimaryNode(workerNode, force, lock_cooldown);
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* master_update_node is a wrapper function for old UDF name.
|
||||
*/
|
||||
|
@ -1947,7 +2018,8 @@ ErrorIfNodeContainsNonRemovablePlacements(WorkerNode *workerNode)
|
|||
ereport(ERROR, (errmsg("cannot remove or disable the node "
|
||||
"%s:%d because because it contains "
|
||||
"the only shard placement for "
|
||||
"shard " UINT64_FORMAT, workerNode->workerName,
|
||||
"shard " UINT64_FORMAT,
|
||||
workerNode->workerName,
|
||||
workerNode->workerPort, placement->shardId),
|
||||
errdetail("One of the table(s) that prevents the operation "
|
||||
"complete successfully is %s",
|
||||
|
@ -2499,7 +2571,8 @@ ErrorIfCoordinatorMetadataSetFalse(WorkerNode *workerNode, Datum value, char *fi
|
|||
if (!valueBool && workerNode->groupId == COORDINATOR_GROUP_ID)
|
||||
{
|
||||
ereport(ERROR, (errmsg("cannot change \"%s\" field of the "
|
||||
"coordinator node", field)));
|
||||
"coordinator node",
|
||||
field)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ PgGetObjectAddress(char *ttype, ArrayType *namearr, ArrayType *argsarr)
|
|||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("name or argument lists may not contain nulls")));
|
||||
}
|
||||
typename = typeStringToTypeName(TextDatumGetCString(elems[0]));
|
||||
typename = typeStringToTypeName_compat(TextDatumGetCString(elems[0]), NULL);
|
||||
}
|
||||
else if (type == OBJECT_LARGEOBJECT)
|
||||
{
|
||||
|
@ -160,7 +160,8 @@ PgGetObjectAddress(char *ttype, ArrayType *namearr, ArrayType *argsarr)
|
|||
errmsg("name or argument lists may not contain nulls")));
|
||||
}
|
||||
args = lappend(args,
|
||||
typeStringToTypeName(TextDatumGetCString(elems[i])));
|
||||
typeStringToTypeName_compat(TextDatumGetCString(elems[i]),
|
||||
NULL));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -82,8 +82,8 @@ CreateShardsWithRoundRobinPolicy(Oid distributedTableId, int32 shardCount,
|
|||
int32 replicationFactor, bool useExclusiveConnections)
|
||||
{
|
||||
CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(distributedTableId);
|
||||
bool colocatedShard = false;
|
||||
List *insertedShardPlacements = NIL;
|
||||
List *insertedShardIds = NIL;
|
||||
|
||||
/* make sure table is hash partitioned */
|
||||
CheckHashPartitionedTable(distributedTableId);
|
||||
|
@ -175,7 +175,9 @@ CreateShardsWithRoundRobinPolicy(Oid distributedTableId, int32 shardCount,
|
|||
/* initialize the hash token space for this shard */
|
||||
int32 shardMinHashToken = PG_INT32_MIN + (shardIndex * hashTokenIncrement);
|
||||
int32 shardMaxHashToken = shardMinHashToken + (hashTokenIncrement - 1);
|
||||
uint64 shardId = GetNextShardId();
|
||||
uint64 *shardIdPtr = (uint64 *) palloc0(sizeof(uint64));
|
||||
*shardIdPtr = GetNextShardId();
|
||||
insertedShardIds = lappend(insertedShardIds, shardIdPtr);
|
||||
|
||||
/* if we are at the last shard, make sure the max token value is INT_MAX */
|
||||
if (shardIndex == (shardCount - 1))
|
||||
|
@ -187,21 +189,31 @@ CreateShardsWithRoundRobinPolicy(Oid distributedTableId, int32 shardCount,
|
|||
text *minHashTokenText = IntegerToText(shardMinHashToken);
|
||||
text *maxHashTokenText = IntegerToText(shardMaxHashToken);
|
||||
|
||||
InsertShardRow(distributedTableId, shardId, shardStorageType,
|
||||
InsertShardRow(distributedTableId, *shardIdPtr, shardStorageType,
|
||||
minHashTokenText, maxHashTokenText);
|
||||
|
||||
List *currentInsertedShardPlacements = InsertShardPlacementRows(
|
||||
distributedTableId,
|
||||
shardId,
|
||||
workerNodeList,
|
||||
roundRobinNodeIndex,
|
||||
replicationFactor);
|
||||
InsertShardPlacementRows(distributedTableId,
|
||||
*shardIdPtr,
|
||||
workerNodeList,
|
||||
roundRobinNodeIndex,
|
||||
replicationFactor);
|
||||
}
|
||||
|
||||
/*
|
||||
* load shard placements for the shard at once after all placement insertions
|
||||
* finished. This prevents MetadataCache from rebuilding unnecessarily after
|
||||
* each placement insertion.
|
||||
*/
|
||||
uint64 *shardIdPtr;
|
||||
foreach_ptr(shardIdPtr, insertedShardIds)
|
||||
{
|
||||
List *placementsForShard = ShardPlacementList(*shardIdPtr);
|
||||
insertedShardPlacements = list_concat(insertedShardPlacements,
|
||||
currentInsertedShardPlacements);
|
||||
placementsForShard);
|
||||
}
|
||||
|
||||
CreateShardsOnWorkers(distributedTableId, insertedShardPlacements,
|
||||
useExclusiveConnections, colocatedShard);
|
||||
useExclusiveConnections);
|
||||
}
|
||||
|
||||
|
||||
|
@ -213,7 +225,6 @@ void
|
|||
CreateColocatedShards(Oid targetRelationId, Oid sourceRelationId, bool
|
||||
useExclusiveConnections)
|
||||
{
|
||||
bool colocatedShard = true;
|
||||
List *insertedShardPlacements = NIL;
|
||||
List *insertedShardIds = NIL;
|
||||
|
||||
|
@ -294,7 +305,7 @@ CreateColocatedShards(Oid targetRelationId, Oid sourceRelationId, bool
|
|||
|
||||
/*
|
||||
* load shard placements for the shard at once after all placement insertions
|
||||
* finished. That prevents MetadataCache from rebuilding unnecessarily after
|
||||
* finished. This prevents MetadataCache from rebuilding unnecessarily after
|
||||
* each placement insertion.
|
||||
*/
|
||||
uint64 *shardIdPtr;
|
||||
|
@ -306,7 +317,7 @@ CreateColocatedShards(Oid targetRelationId, Oid sourceRelationId, bool
|
|||
}
|
||||
|
||||
CreateShardsOnWorkers(targetRelationId, insertedShardPlacements,
|
||||
useExclusiveConnections, colocatedShard);
|
||||
useExclusiveConnections);
|
||||
}
|
||||
|
||||
|
||||
|
@ -322,7 +333,6 @@ CreateReferenceTableShard(Oid distributedTableId)
|
|||
text *shardMinValue = NULL;
|
||||
text *shardMaxValue = NULL;
|
||||
bool useExclusiveConnection = false;
|
||||
bool colocatedShard = false;
|
||||
|
||||
/*
|
||||
* In contrast to append/range partitioned tables it makes more sense to
|
||||
|
@ -363,12 +373,21 @@ CreateReferenceTableShard(Oid distributedTableId)
|
|||
InsertShardRow(distributedTableId, shardId, shardStorageType, shardMinValue,
|
||||
shardMaxValue);
|
||||
|
||||
List *insertedShardPlacements = InsertShardPlacementRows(distributedTableId, shardId,
|
||||
nodeList, workerStartIndex,
|
||||
replicationFactor);
|
||||
InsertShardPlacementRows(distributedTableId,
|
||||
shardId,
|
||||
nodeList,
|
||||
workerStartIndex,
|
||||
replicationFactor);
|
||||
|
||||
/*
|
||||
* load shard placements for the shard at once after all placement insertions
|
||||
* finished. This prevents MetadataCache from rebuilding unnecessarily after
|
||||
* each placement insertion.
|
||||
*/
|
||||
List *insertedShardPlacements = ShardPlacementList(shardId);
|
||||
|
||||
CreateShardsOnWorkers(distributedTableId, insertedShardPlacements,
|
||||
useExclusiveConnection, colocatedShard);
|
||||
useExclusiveConnection);
|
||||
}
|
||||
|
||||
|
||||
|
@ -400,13 +419,8 @@ CreateSingleShardTableShardWithRoundRobinPolicy(Oid relationId, uint32 colocatio
|
|||
List *workerNodeList = DistributedTablePlacementNodeList(RowShareLock);
|
||||
workerNodeList = SortList(workerNodeList, CompareWorkerNodes);
|
||||
|
||||
int32 workerNodeCount = list_length(workerNodeList);
|
||||
if (workerNodeCount == 0)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("couldn't find any worker nodes"),
|
||||
errhint("Add more worker nodes")));
|
||||
}
|
||||
int roundRobinNodeIdx =
|
||||
EmptySingleShardTableColocationDecideNodeId(colocationId);
|
||||
|
||||
char shardStorageType = ShardStorageType(relationId);
|
||||
text *minHashTokenText = NULL;
|
||||
|
@ -415,26 +429,51 @@ CreateSingleShardTableShardWithRoundRobinPolicy(Oid relationId, uint32 colocatio
|
|||
InsertShardRow(relationId, shardId, shardStorageType,
|
||||
minHashTokenText, maxHashTokenText);
|
||||
|
||||
/* determine the node index based on colocation id */
|
||||
int roundRobinNodeIdx = colocationId % workerNodeCount;
|
||||
|
||||
int replicationFactor = 1;
|
||||
List *insertedShardPlacements = InsertShardPlacementRows(
|
||||
relationId,
|
||||
shardId,
|
||||
workerNodeList,
|
||||
roundRobinNodeIdx,
|
||||
replicationFactor);
|
||||
InsertShardPlacementRows(relationId,
|
||||
shardId,
|
||||
workerNodeList,
|
||||
roundRobinNodeIdx,
|
||||
replicationFactor);
|
||||
|
||||
/*
|
||||
* load shard placements for the shard at once after all placement insertions
|
||||
* finished. This prevents MetadataCache from rebuilding unnecessarily after
|
||||
* each placement insertion.
|
||||
*/
|
||||
List *insertedShardPlacements = ShardPlacementList(shardId);
|
||||
|
||||
/*
|
||||
* We don't need to force using exclusive connections because we're anyway
|
||||
* creating a single shard.
|
||||
*/
|
||||
bool useExclusiveConnection = false;
|
||||
|
||||
bool colocatedShard = false;
|
||||
CreateShardsOnWorkers(relationId, insertedShardPlacements,
|
||||
useExclusiveConnection, colocatedShard);
|
||||
useExclusiveConnection);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* EmptySingleShardTableColocationDecideNodeId returns index of the node
|
||||
* that first shard to be created in given "single-shard table colocation
|
||||
* group" should be placed on.
|
||||
*
|
||||
* This is determined by modulo of the colocation id by the length of the
|
||||
* list returned by DistributedTablePlacementNodeList().
|
||||
*/
|
||||
int
|
||||
EmptySingleShardTableColocationDecideNodeId(uint32 colocationId)
|
||||
{
|
||||
List *workerNodeList = DistributedTablePlacementNodeList(RowShareLock);
|
||||
int32 workerNodeCount = list_length(workerNodeList);
|
||||
if (workerNodeCount == 0)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("couldn't find any worker nodes"),
|
||||
errhint("Add more worker nodes")));
|
||||
}
|
||||
|
||||
return colocationId % workerNodeCount;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -277,7 +277,7 @@ master_get_new_placementid(PG_FUNCTION_ARGS)
|
|||
/*
|
||||
* GetNextPlacementId allocates and returns a unique placementId for
|
||||
* the placement to be created. This allocation occurs both in shared memory
|
||||
* and in write ahead logs; writing to logs avoids the risk of having shardId
|
||||
* and in write ahead logs; writing to logs avoids the risk of having placementId
|
||||
* collisions.
|
||||
*
|
||||
* NB: This can be called by any user; for now we have decided that that's
|
||||
|
@ -612,7 +612,7 @@ GetPreLoadTableCreationCommands(Oid relationId,
|
|||
{
|
||||
List *tableDDLEventList = NIL;
|
||||
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
/* fetch table schema and column option definitions */
|
||||
char *tableSchemaDef = pg_get_tableschemadef_string(relationId,
|
||||
|
@ -665,7 +665,7 @@ GetPreLoadTableCreationCommands(Oid relationId,
|
|||
tableDDLEventList = list_concat(tableDDLEventList, policyCommands);
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
|
||||
return tableDDLEventList;
|
||||
}
|
||||
|
@ -754,7 +754,7 @@ GatherIndexAndConstraintDefinitionList(Form_pg_index indexForm, List **indexDDLE
|
|||
int indexFlags)
|
||||
{
|
||||
/* generate fully-qualified names */
|
||||
PushOverrideEmptySearchPath(CurrentMemoryContext);
|
||||
int saveNestLevel = PushEmptySearchPath();
|
||||
|
||||
Oid indexId = indexForm->indexrelid;
|
||||
bool indexImpliedByConstraint = IndexImpliedByAConstraint(indexForm);
|
||||
|
@ -805,7 +805,7 @@ GatherIndexAndConstraintDefinitionList(Form_pg_index indexForm, List **indexDDLE
|
|||
}
|
||||
|
||||
/* revert back to original search_path */
|
||||
PopOverrideSearchPath();
|
||||
PopEmptySearchPath(saveNestLevel);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* replicate_none_dist_table_shard.c
|
||||
* Routines to replicate shard of none-distributed table to
|
||||
* a remote node.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/pg_list.h"
|
||||
|
||||
#include "distributed/adaptive_executor.h"
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/coordinator_protocol.h"
|
||||
#include "distributed/deparse_shard_query.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/replicate_none_dist_table_shard.h"
|
||||
#include "distributed/shard_utils.h"
|
||||
#include "distributed/worker_manager.h"
|
||||
#include "distributed/worker_protocol.h"
|
||||
|
||||
|
||||
static void CreateForeignKeysFromReferenceTablesOnShards(Oid noneDistTableId);
|
||||
static Oid ForeignConstraintGetReferencingTableId(const char *queryString);
|
||||
static void EnsureNoneDistTableWithCoordinatorPlacement(Oid noneDistTableId);
|
||||
static void SetLocalEnableManualChangesToShard(bool state);
|
||||
|
||||
|
||||
/*
|
||||
* NoneDistTableReplicateCoordinatorPlacement replicates local (presumably
|
||||
* coordinator) shard placement of given none-distributed table to given
|
||||
* target nodes and inserts records for new placements into pg_dist_placement.
|
||||
*/
|
||||
void
|
||||
NoneDistTableReplicateCoordinatorPlacement(Oid noneDistTableId,
|
||||
List *targetNodeList)
|
||||
{
|
||||
EnsureCoordinator();
|
||||
EnsureNoneDistTableWithCoordinatorPlacement(noneDistTableId);
|
||||
|
||||
/*
|
||||
* We don't expect callers try to replicate the shard to remote nodes
|
||||
* if some of the remote nodes have a placement for the shard already.
|
||||
*/
|
||||
int64 shardId = GetFirstShardId(noneDistTableId);
|
||||
List *remoteShardPlacementList =
|
||||
FilterShardPlacementList(ActiveShardPlacementList(shardId),
|
||||
IsRemoteShardPlacement);
|
||||
if (list_length(remoteShardPlacementList) > 0)
|
||||
{
|
||||
ereport(ERROR, (errmsg("table already has a remote shard placement")));
|
||||
}
|
||||
|
||||
uint64 shardLength = ShardLength(shardId);
|
||||
|
||||
/* insert new placements to pg_dist_placement */
|
||||
List *insertedPlacementList = NIL;
|
||||
WorkerNode *targetNode = NULL;
|
||||
foreach_ptr(targetNode, targetNodeList)
|
||||
{
|
||||
ShardPlacement *shardPlacement =
|
||||
InsertShardPlacementRowGlobally(shardId, GetNextPlacementId(),
|
||||
shardLength, targetNode->groupId);
|
||||
|
||||
/* and save the placement for shard creation on workers */
|
||||
insertedPlacementList = lappend(insertedPlacementList, shardPlacement);
|
||||
}
|
||||
|
||||
/* create new placements */
|
||||
bool useExclusiveConnection = false;
|
||||
CreateShardsOnWorkers(noneDistTableId, insertedPlacementList,
|
||||
useExclusiveConnection);
|
||||
|
||||
/* fetch coordinator placement before deleting it */
|
||||
Oid localPlacementTableId = GetTableLocalShardOid(noneDistTableId, shardId);
|
||||
ShardPlacement *coordinatorPlacement =
|
||||
linitial(ActiveShardPlacementListOnGroup(shardId, COORDINATOR_GROUP_ID));
|
||||
|
||||
/*
|
||||
* CreateForeignKeysFromReferenceTablesOnShards and CopyFromLocalTableIntoDistTable
|
||||
* need to ignore the local placement, hence we temporarily delete it before
|
||||
* calling them.
|
||||
*/
|
||||
DeleteShardPlacementRowGlobally(coordinatorPlacement->placementId);
|
||||
|
||||
/* and copy data from local placement to new placements */
|
||||
CopyFromLocalTableIntoDistTable(
|
||||
localPlacementTableId, noneDistTableId
|
||||
);
|
||||
|
||||
/*
|
||||
* CreateShardsOnWorkers only creates the foreign keys where given relation
|
||||
* is the referencing one, so we need to create the foreign keys where given
|
||||
* relation is the referenced one as well. We're only interested in the cases
|
||||
* where the referencing relation is a reference table because the other
|
||||
* possible table types --i.e., Citus local tables atm-- cannot have placements
|
||||
* on remote nodes.
|
||||
*
|
||||
* Note that we need to create the foreign keys where given relation is the
|
||||
* referenced one after copying the data so that constraint checks can pass.
|
||||
*/
|
||||
CreateForeignKeysFromReferenceTablesOnShards(noneDistTableId);
|
||||
|
||||
/* using the same placement id, re-insert the deleted placement */
|
||||
InsertShardPlacementRowGlobally(shardId, coordinatorPlacement->placementId,
|
||||
shardLength, COORDINATOR_GROUP_ID);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* NoneDistTableDeleteCoordinatorPlacement deletes pg_dist_placement record for
|
||||
* local (presumably coordinator) shard placement of given none-distributed table.
|
||||
*/
|
||||
void
|
||||
NoneDistTableDeleteCoordinatorPlacement(Oid noneDistTableId)
|
||||
{
|
||||
EnsureCoordinator();
|
||||
EnsureNoneDistTableWithCoordinatorPlacement(noneDistTableId);
|
||||
|
||||
int64 shardId = GetFirstShardId(noneDistTableId);
|
||||
|
||||
/* we've already verified that table has a coordinator placement */
|
||||
ShardPlacement *coordinatorPlacement =
|
||||
linitial(ActiveShardPlacementListOnGroup(shardId, COORDINATOR_GROUP_ID));
|
||||
|
||||
/* remove the old placement from metadata of local node, i.e., coordinator */
|
||||
DeleteShardPlacementRowGlobally(coordinatorPlacement->placementId);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* NoneDistTableDropCoordinatorPlacementTable drops local (presumably coordinator)
|
||||
* shard placement table of given none-distributed table.
|
||||
*/
|
||||
void
|
||||
NoneDistTableDropCoordinatorPlacementTable(Oid noneDistTableId)
|
||||
{
|
||||
EnsureCoordinator();
|
||||
|
||||
if (HasDistributionKey(noneDistTableId))
|
||||
{
|
||||
ereport(ERROR, (errmsg("table is not a none-distributed table")));
|
||||
}
|
||||
|
||||
/*
|
||||
* We undistribute Citus local tables that are not chained with any reference
|
||||
* tables via foreign keys at the end of the utility hook.
|
||||
* Here we temporarily set the related GUC to off to disable the logic for
|
||||
* internally executed DDL's that might invoke this mechanism unnecessarily.
|
||||
*
|
||||
* We also temporarily disable citus.enable_manual_changes_to_shards GUC to
|
||||
* allow given command to modify shard. Note that we disable it only for
|
||||
* local session because changes made to shards are allowed for Citus internal
|
||||
* backends anyway.
|
||||
*/
|
||||
int saveNestLevel = NewGUCNestLevel();
|
||||
|
||||
SetLocalEnableLocalReferenceForeignKeys(false);
|
||||
SetLocalEnableManualChangesToShard(true);
|
||||
|
||||
StringInfo dropShardCommand = makeStringInfo();
|
||||
int64 shardId = GetFirstShardId(noneDistTableId);
|
||||
ShardInterval *shardInterval = LoadShardInterval(shardId);
|
||||
appendStringInfo(dropShardCommand, DROP_REGULAR_TABLE_COMMAND,
|
||||
ConstructQualifiedShardName(shardInterval));
|
||||
|
||||
Task *task = CitusMakeNode(Task);
|
||||
task->jobId = INVALID_JOB_ID;
|
||||
task->taskId = INVALID_TASK_ID;
|
||||
task->taskType = DDL_TASK;
|
||||
task->replicationModel = REPLICATION_MODEL_INVALID;
|
||||
SetTaskQueryString(task, dropShardCommand->data);
|
||||
|
||||
ShardPlacement *targetPlacement = CitusMakeNode(ShardPlacement);
|
||||
SetPlacementNodeMetadata(targetPlacement, CoordinatorNodeIfAddedAsWorkerOrError());
|
||||
|
||||
task->taskPlacementList = list_make1(targetPlacement);
|
||||
|
||||
bool localExecutionSupported = true;
|
||||
ExecuteUtilityTaskList(list_make1(task), localExecutionSupported);
|
||||
|
||||
AtEOXact_GUC(true, saveNestLevel);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateForeignKeysFromReferenceTablesOnShards creates foreign keys on shards
|
||||
* where given none-distributed table is the referenced table and the referencing
|
||||
* one is a reference table.
|
||||
*/
|
||||
static void
|
||||
CreateForeignKeysFromReferenceTablesOnShards(Oid noneDistTableId)
|
||||
{
|
||||
EnsureCoordinator();
|
||||
|
||||
if (HasDistributionKey(noneDistTableId))
|
||||
{
|
||||
ereport(ERROR, (errmsg("table is not a none-distributed table")));
|
||||
}
|
||||
|
||||
List *ddlCommandList =
|
||||
GetForeignConstraintFromOtherReferenceTablesCommands(noneDistTableId);
|
||||
if (list_length(ddlCommandList) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List *taskList = NIL;
|
||||
|
||||
char *command = NULL;
|
||||
foreach_ptr(command, ddlCommandList)
|
||||
{
|
||||
List *commandTaskList = InterShardDDLTaskList(
|
||||
ForeignConstraintGetReferencingTableId(command),
|
||||
noneDistTableId, command
|
||||
);
|
||||
taskList = list_concat(taskList, commandTaskList);
|
||||
}
|
||||
|
||||
if (list_length(taskList) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool localExecutionSupported = true;
|
||||
ExecuteUtilityTaskList(taskList, localExecutionSupported);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ForeignConstraintGetReferencedTableId parses given foreign constraint command and
|
||||
* extracts refenrencing table id from it.
|
||||
*/
|
||||
static Oid
|
||||
ForeignConstraintGetReferencingTableId(const char *queryString)
|
||||
{
|
||||
Node *queryNode = ParseTreeNode(queryString);
|
||||
if (!IsA(queryNode, AlterTableStmt))
|
||||
{
|
||||
ereport(ERROR, (errmsg("command is not an ALTER TABLE statement")));
|
||||
}
|
||||
|
||||
AlterTableStmt *foreignConstraintStmt = (AlterTableStmt *) queryNode;
|
||||
if (list_length(foreignConstraintStmt->cmds) != 1)
|
||||
{
|
||||
ereport(ERROR, (errmsg("command does not contain a single command")));
|
||||
}
|
||||
|
||||
AlterTableCmd *command = (AlterTableCmd *) linitial(foreignConstraintStmt->cmds);
|
||||
if (command->subtype == AT_AddConstraint)
|
||||
{
|
||||
Constraint *constraint = (Constraint *) command->def;
|
||||
if (constraint && constraint->contype == CONSTR_FOREIGN)
|
||||
{
|
||||
bool missingOk = false;
|
||||
return RangeVarGetRelid(foreignConstraintStmt->relation, NoLock,
|
||||
missingOk);
|
||||
}
|
||||
}
|
||||
|
||||
ereport(ERROR, (errmsg("command does not contain a foreign constraint")));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* EnsureNoneDistTableWithCoordinatorPlacement throws an error if given
|
||||
* table is not a none-distributed that has a coordinator placement.
|
||||
*/
|
||||
static void
|
||||
EnsureNoneDistTableWithCoordinatorPlacement(Oid noneDistTableId)
|
||||
{
|
||||
if (HasDistributionKey(noneDistTableId))
|
||||
{
|
||||
ereport(ERROR, (errmsg("table is not a none-distributed table")));
|
||||
}
|
||||
|
||||
int64 shardId = GetFirstShardId(noneDistTableId);
|
||||
if (!ActiveShardPlacementListOnGroup(shardId, COORDINATOR_GROUP_ID))
|
||||
{
|
||||
ereport(ERROR, (errmsg("table does not have a coordinator placement")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SetLocalEnableManualChangesToShard locally enables
|
||||
* citus.enable_manual_changes_to_shards GUC.
|
||||
*/
|
||||
static void
|
||||
SetLocalEnableManualChangesToShard(bool state)
|
||||
{
|
||||
set_config_option("citus.enable_manual_changes_to_shards",
|
||||
state ? "on" : "off",
|
||||
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||
GUC_ACTION_LOCAL, true, 0, false);
|
||||
}
|
|
@ -450,7 +450,7 @@ CompareCleanupRecordsByObjectType(const void *leftElement, const void *rightElem
|
|||
|
||||
|
||||
/*
|
||||
* InsertCleanupRecordInCurrentTransaction inserts a new pg_dist_cleanup_record entry
|
||||
* InsertCleanupRecordInCurrentTransaction inserts a new pg_dist_cleanup entry
|
||||
* as part of the current transaction. This is primarily useful for deferred drop scenarios,
|
||||
* since these records would roll back in case of operation failure.
|
||||
*/
|
||||
|
@ -497,8 +497,8 @@ InsertCleanupRecordInCurrentTransaction(CleanupObject objectType,
|
|||
|
||||
|
||||
/*
|
||||
* InsertCleanupRecordInSubtransaction inserts a new pg_dist_cleanup_record entry
|
||||
* in a separate transaction to ensure the record persists after rollback. We should
|
||||
* InsertCleanupRecordInSubtransaction inserts a new pg_dist_cleanup entry in a
|
||||
* separate transaction to ensure the record persists after rollback. We should
|
||||
* delete these records if the operation completes successfully.
|
||||
*
|
||||
* For failure scenarios, use a subtransaction (direct insert via localhost).
|
||||
|
@ -541,7 +541,7 @@ InsertCleanupRecordInSubtransaction(CleanupObject objectType,
|
|||
|
||||
|
||||
/*
|
||||
* DeleteCleanupRecordByRecordId deletes a cleanup record by record id.
|
||||
* DeleteCleanupRecordByRecordIdOutsideTransaction deletes a cleanup record by record id.
|
||||
*/
|
||||
static void
|
||||
DeleteCleanupRecordByRecordIdOutsideTransaction(uint64 recordId)
|
||||
|
@ -1005,7 +1005,7 @@ ListCleanupRecordsForCurrentOperation(void)
|
|||
|
||||
ScanKeyData scanKey[1];
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_dist_cleanup_operation_id, BTEqualStrategyNumber,
|
||||
F_INT8EQ, UInt64GetDatum(CurrentOperationId));
|
||||
F_INT8EQ, Int64GetDatum(CurrentOperationId));
|
||||
|
||||
int scanKeyCount = 1;
|
||||
Oid scanIndexId = InvalidOid;
|
||||
|
@ -1106,7 +1106,7 @@ TupleToCleanupRecord(HeapTuple heapTuple, TupleDesc tupleDescriptor)
|
|||
|
||||
/*
|
||||
* CleanupRecordExists returns whether a cleanup record with the given
|
||||
* record ID exists in pg_dist_cleanup_record.
|
||||
* record ID exists in pg_dist_cleanup.
|
||||
*/
|
||||
static bool
|
||||
CleanupRecordExists(uint64 recordId)
|
||||
|
@ -1119,7 +1119,7 @@ CleanupRecordExists(uint64 recordId)
|
|||
bool indexOK = true;
|
||||
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_dist_cleanup_record_id,
|
||||
BTEqualStrategyNumber, F_INT8EQ, UInt64GetDatum(recordId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(recordId));
|
||||
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgDistCleanup,
|
||||
DistCleanupPrimaryKeyIndexId(),
|
||||
|
@ -1139,7 +1139,7 @@ CleanupRecordExists(uint64 recordId)
|
|||
|
||||
|
||||
/*
|
||||
* DeleteCleanupRecordByRecordId deletes a single pg_dist_cleanup_record entry.
|
||||
* DeleteCleanupRecordByRecordId deletes a single pg_dist_cleanup entry.
|
||||
*/
|
||||
static void
|
||||
DeleteCleanupRecordByRecordId(uint64 recordId)
|
||||
|
@ -1152,7 +1152,7 @@ DeleteCleanupRecordByRecordId(uint64 recordId)
|
|||
bool indexOK = true;
|
||||
|
||||
ScanKeyInit(&scanKey[0], Anum_pg_dist_cleanup_record_id,
|
||||
BTEqualStrategyNumber, F_INT8EQ, UInt64GetDatum(recordId));
|
||||
BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(recordId));
|
||||
|
||||
SysScanDesc scanDescriptor = systable_beginscan(pgDistCleanup,
|
||||
DistCleanupPrimaryKeyIndexId(),
|
||||
|
|
|
@ -526,6 +526,13 @@ GetRebalanceSteps(RebalanceOptions *options)
|
|||
}
|
||||
}
|
||||
|
||||
if (shardAllowedNodeCount < ShardReplicationFactor)
|
||||
{
|
||||
ereport(ERROR, (errmsg("Shard replication factor (%d) cannot be greater than "
|
||||
"number of nodes with should_have_shards=true (%d).",
|
||||
ShardReplicationFactor, shardAllowedNodeCount)));
|
||||
}
|
||||
|
||||
List *activeShardPlacementListList = NIL;
|
||||
List *unbalancedShards = NIL;
|
||||
|
||||
|
@ -875,7 +882,7 @@ ExecutePlacementUpdates(List *placementUpdateList, Oid shardReplicationModeOid,
|
|||
* ones) and the relation id of the target table. The dynamic shared memory
|
||||
* portion consists of a RebalanceMonitorHeader and multiple
|
||||
* PlacementUpdateEventProgress, one for each planned shard placement move. The
|
||||
* dsm_handle of the created segment is savedin the progress of the current backend so
|
||||
* dsm_handle of the created segment is saved in the progress of the current backend so
|
||||
* that it can be read by external agents such as get_rebalance_progress function by
|
||||
* calling pg_stat_get_progress_info UDF. Since currently only VACUUM commands are
|
||||
* officially allowed as the command type, we describe ourselves as a VACUUM command and
|
||||
|
@ -2373,8 +2380,8 @@ GetSetCommandListForNewConnections(void)
|
|||
{
|
||||
List *commandList = NIL;
|
||||
|
||||
struct config_generic **guc_vars = get_guc_variables();
|
||||
int gucCount = GetNumConfigOptions();
|
||||
int gucCount = 0;
|
||||
struct config_generic **guc_vars = get_guc_variables_compat(&gucCount);
|
||||
|
||||
for (int gucIndex = 0; gucIndex < gucCount; gucIndex++)
|
||||
{
|
||||
|
@ -2789,7 +2796,15 @@ FindAllowedTargetFillState(RebalanceState *state, uint64 shardId)
|
|||
targetFillState->node,
|
||||
state->functions->context))
|
||||
{
|
||||
return targetFillState;
|
||||
bool targetHasShard = PlacementsHashFind(state->placementsHash,
|
||||
shardId,
|
||||
targetFillState->node);
|
||||
|
||||
/* skip if the shard is already placed on the target node */
|
||||
if (!targetHasShard)
|
||||
{
|
||||
return targetFillState;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
|
|
@ -216,7 +216,7 @@ ErrorIfCannotSplitShard(SplitOperation splitOperation, ShardInterval *sourceShar
|
|||
|
||||
|
||||
/*
|
||||
* Exteded checks before we decide to split the shard.
|
||||
* Extended checks before we decide to split the shard.
|
||||
* When all consumers (Example : ISOLATE_TENANT_TO_NEW_SHARD) directly call 'SplitShard' API,
|
||||
* this method will be merged with 'ErrorIfCannotSplitShard' above.
|
||||
*/
|
||||
|
@ -425,7 +425,7 @@ GetWorkerNodesFromWorkerIds(List *nodeIdsForPlacementList)
|
|||
* 'shardInterval' : Source shard interval to be split.
|
||||
* 'shardSplitPointsList' : Split Points list for the source 'shardInterval'.
|
||||
* 'nodeIdsForPlacementList' : Placement list corresponding to split children.
|
||||
* 'distributionColumnList' : Maps relation IDs to distribution columns.
|
||||
* 'distributionColumnOverrides': Maps relation IDs to distribution columns.
|
||||
* If not specified, the distribution column is read
|
||||
* from the metadata.
|
||||
* 'colocatedShardIntervalList' : Shard interval list for colocation group. (only used for
|
||||
|
|
|
@ -1841,7 +1841,11 @@ CopyShardForeignConstraintCommandListGrouped(ShardInterval *shardInterval,
|
|||
char *referencedSchemaName = get_namespace_name(referencedSchemaId);
|
||||
char *escapedReferencedSchemaName = quote_literal_cstr(referencedSchemaName);
|
||||
|
||||
if (IsCitusTableType(referencedRelationId, REFERENCE_TABLE))
|
||||
if (relationId == referencedRelationId)
|
||||
{
|
||||
referencedShardId = shardInterval->shardId;
|
||||
}
|
||||
else if (IsCitusTableType(referencedRelationId, REFERENCE_TABLE))
|
||||
{
|
||||
referencedShardId = GetFirstShardId(referencedRelationId);
|
||||
}
|
||||
|
|
|
@ -312,8 +312,6 @@ CreateAppendDistributedShardPlacements(Oid relationId, int64 shardId,
|
|||
int attemptCount = replicationFactor;
|
||||
int workerNodeCount = list_length(workerNodeList);
|
||||
int placementsCreated = 0;
|
||||
List *foreignConstraintCommandList =
|
||||
GetReferencingForeignConstaintCommands(relationId);
|
||||
IncludeSequenceDefaults includeSequenceDefaults = NO_SEQUENCE_DEFAULTS;
|
||||
IncludeIdentities includeIdentityDefaults = NO_IDENTITY;
|
||||
|
||||
|
@ -346,7 +344,6 @@ CreateAppendDistributedShardPlacements(Oid relationId, int64 shardId,
|
|||
uint32 nodeGroupId = workerNode->groupId;
|
||||
char *nodeName = workerNode->workerName;
|
||||
uint32 nodePort = workerNode->workerPort;
|
||||
int shardIndex = -1; /* not used in this code path */
|
||||
const uint64 shardSize = 0;
|
||||
MultiConnection *connection =
|
||||
GetNodeUserDatabaseConnection(connectionFlag, nodeName, nodePort,
|
||||
|
@ -360,9 +357,8 @@ CreateAppendDistributedShardPlacements(Oid relationId, int64 shardId,
|
|||
continue;
|
||||
}
|
||||
|
||||
List *commandList = WorkerCreateShardCommandList(relationId, shardIndex, shardId,
|
||||
ddlCommandList,
|
||||
foreignConstraintCommandList);
|
||||
List *commandList = WorkerCreateShardCommandList(relationId, shardId,
|
||||
ddlCommandList);
|
||||
|
||||
ExecuteCriticalRemoteCommandList(connection, commandList);
|
||||
|
||||
|
@ -387,47 +383,37 @@ CreateAppendDistributedShardPlacements(Oid relationId, int64 shardId,
|
|||
|
||||
/*
|
||||
* InsertShardPlacementRows inserts shard placements to the metadata table on
|
||||
* the coordinator node. Then, returns the list of added shard placements.
|
||||
* the coordinator node.
|
||||
*/
|
||||
List *
|
||||
void
|
||||
InsertShardPlacementRows(Oid relationId, int64 shardId, List *workerNodeList,
|
||||
int workerStartIndex, int replicationFactor)
|
||||
{
|
||||
int workerNodeCount = list_length(workerNodeList);
|
||||
int placementsInserted = 0;
|
||||
List *insertedShardPlacements = NIL;
|
||||
|
||||
for (int attemptNumber = 0; attemptNumber < replicationFactor; attemptNumber++)
|
||||
for (int placementIndex = 0; placementIndex < replicationFactor; placementIndex++)
|
||||
{
|
||||
int workerNodeIndex = (workerStartIndex + attemptNumber) % workerNodeCount;
|
||||
int workerNodeIndex = (workerStartIndex + placementIndex) % workerNodeCount;
|
||||
WorkerNode *workerNode = (WorkerNode *) list_nth(workerNodeList, workerNodeIndex);
|
||||
uint32 nodeGroupId = workerNode->groupId;
|
||||
const uint64 shardSize = 0;
|
||||
|
||||
uint64 shardPlacementId = InsertShardPlacementRow(shardId, INVALID_PLACEMENT_ID,
|
||||
shardSize, nodeGroupId);
|
||||
ShardPlacement *shardPlacement = LoadShardPlacement(shardId, shardPlacementId);
|
||||
insertedShardPlacements = lappend(insertedShardPlacements, shardPlacement);
|
||||
|
||||
placementsInserted++;
|
||||
if (placementsInserted >= replicationFactor)
|
||||
{
|
||||
break;
|
||||
}
|
||||
InsertShardPlacementRow(shardId,
|
||||
INVALID_PLACEMENT_ID,
|
||||
shardSize,
|
||||
nodeGroupId);
|
||||
}
|
||||
|
||||
return insertedShardPlacements;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateShardsOnWorkers creates shards on worker nodes given the shard placements
|
||||
* as a parameter The function creates the shards via the executor. This means
|
||||
* as a parameter. The function creates the shards via the executor. This means
|
||||
* that it can adopt the number of connections required to create the shards.
|
||||
*/
|
||||
void
|
||||
CreateShardsOnWorkers(Oid distributedRelationId, List *shardPlacements,
|
||||
bool useExclusiveConnection, bool colocatedShard)
|
||||
bool useExclusiveConnection)
|
||||
{
|
||||
IncludeSequenceDefaults includeSequenceDefaults = NO_SEQUENCE_DEFAULTS;
|
||||
IncludeIdentities includeIdentityDefaults = NO_IDENTITY;
|
||||
|
@ -437,8 +423,6 @@ CreateShardsOnWorkers(Oid distributedRelationId, List *shardPlacements,
|
|||
includeSequenceDefaults,
|
||||
includeIdentityDefaults,
|
||||
creatingShellTableOnRemoteNode);
|
||||
List *foreignConstraintCommandList =
|
||||
GetReferencingForeignConstaintCommands(distributedRelationId);
|
||||
|
||||
int taskId = 1;
|
||||
List *taskList = NIL;
|
||||
|
@ -449,18 +433,10 @@ CreateShardsOnWorkers(Oid distributedRelationId, List *shardPlacements,
|
|||
{
|
||||
uint64 shardId = shardPlacement->shardId;
|
||||
ShardInterval *shardInterval = LoadShardInterval(shardId);
|
||||
int shardIndex = -1;
|
||||
List *relationShardList = RelationShardListForShardCreate(shardInterval);
|
||||
|
||||
if (colocatedShard)
|
||||
{
|
||||
shardIndex = ShardIndex(shardInterval);
|
||||
}
|
||||
|
||||
List *commandList = WorkerCreateShardCommandList(distributedRelationId,
|
||||
shardIndex,
|
||||
shardId, ddlCommandList,
|
||||
foreignConstraintCommandList);
|
||||
shardId, ddlCommandList);
|
||||
|
||||
Task *task = CitusMakeNode(Task);
|
||||
task->jobId = INVALID_JOB_ID;
|
||||
|
@ -604,14 +580,12 @@ RelationShardListForShardCreate(ShardInterval *shardInterval)
|
|||
* shardId to create the shard on the worker node.
|
||||
*/
|
||||
List *
|
||||
WorkerCreateShardCommandList(Oid relationId, int shardIndex, uint64 shardId,
|
||||
List *ddlCommandList,
|
||||
List *foreignConstraintCommandList)
|
||||
WorkerCreateShardCommandList(Oid relationId, uint64 shardId,
|
||||
List *ddlCommandList)
|
||||
{
|
||||
List *commandList = NIL;
|
||||
Oid schemaId = get_rel_namespace(relationId);
|
||||
char *schemaName = get_namespace_name(schemaId);
|
||||
char *escapedSchemaName = quote_literal_cstr(schemaName);
|
||||
|
||||
TableDDLCommand *ddlCommand = NULL;
|
||||
foreach_ptr(ddlCommand, ddlCommandList)
|
||||
|
@ -622,57 +596,12 @@ WorkerCreateShardCommandList(Oid relationId, int shardIndex, uint64 shardId,
|
|||
commandList = lappend(commandList, applyDDLCommand);
|
||||
}
|
||||
|
||||
const char *command = NULL;
|
||||
foreach_ptr(command, foreignConstraintCommandList)
|
||||
{
|
||||
char *escapedCommand = quote_literal_cstr(command);
|
||||
ShardInterval *shardInterval = LoadShardInterval(shardId);
|
||||
|
||||
uint64 referencedShardId = INVALID_SHARD_ID;
|
||||
|
||||
StringInfo applyForeignConstraintCommand = makeStringInfo();
|
||||
|
||||
/* we need to parse the foreign constraint command to get referencing table id */
|
||||
Oid referencedRelationId = ForeignConstraintGetReferencedTableId(command);
|
||||
if (referencedRelationId == InvalidOid)
|
||||
{
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("cannot create foreign key constraint"),
|
||||
errdetail("Referenced relation cannot be found.")));
|
||||
}
|
||||
|
||||
Oid referencedSchemaId = get_rel_namespace(referencedRelationId);
|
||||
char *referencedSchemaName = get_namespace_name(referencedSchemaId);
|
||||
char *escapedReferencedSchemaName = quote_literal_cstr(referencedSchemaName);
|
||||
|
||||
/*
|
||||
* In case of self referencing shards, relation itself might not be distributed
|
||||
* already. Therefore we cannot use ColocatedShardIdInRelation which assumes
|
||||
* given relation is distributed. Besides, since we know foreign key references
|
||||
* itself, referencedShardId is actual shardId anyway. Also, if the referenced
|
||||
* relation is a reference table, we cannot use ColocatedShardIdInRelation since
|
||||
* reference tables only have one shard. Instead, we fetch the one and only shard
|
||||
* from shardlist and use it.
|
||||
*/
|
||||
if (relationId == referencedRelationId)
|
||||
{
|
||||
referencedShardId = shardId;
|
||||
}
|
||||
else if (IsCitusTableType(referencedRelationId, REFERENCE_TABLE))
|
||||
{
|
||||
referencedShardId = GetFirstShardId(referencedRelationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
referencedShardId = ColocatedShardIdInRelation(referencedRelationId,
|
||||
shardIndex);
|
||||
}
|
||||
|
||||
appendStringInfo(applyForeignConstraintCommand,
|
||||
WORKER_APPLY_INTER_SHARD_DDL_COMMAND, shardId, escapedSchemaName,
|
||||
referencedShardId, escapedReferencedSchemaName, escapedCommand);
|
||||
|
||||
commandList = lappend(commandList, applyForeignConstraintCommand->data);
|
||||
}
|
||||
commandList = list_concat(
|
||||
commandList,
|
||||
CopyShardForeignConstraintCommandList(shardInterval)
|
||||
);
|
||||
|
||||
/*
|
||||
* If the shard is created for a partition, send the command to create the
|
||||
|
@ -680,7 +609,6 @@ WorkerCreateShardCommandList(Oid relationId, int shardIndex, uint64 shardId,
|
|||
*/
|
||||
if (PartitionTable(relationId))
|
||||
{
|
||||
ShardInterval *shardInterval = LoadShardInterval(shardId);
|
||||
char *attachPartitionCommand = GenerateAttachShardPartitionCommand(shardInterval);
|
||||
|
||||
commandList = lappend(commandList, attachPartitionCommand);
|
||||
|
|
|
@ -52,7 +52,7 @@ static char * TraceWorkerSplitCopyUdf(char *sourceShardToCopySchemaName,
|
|||
* worker_split_copy(source_shard_id bigint, splitCopyInfo pg_catalog.split_copy_info[])
|
||||
* UDF to split copy shard to list of destination shards.
|
||||
* 'source_shard_id' : Source ShardId to split copy.
|
||||
* 'splitCopyInfos' : Array of Split Copy Info (destination_shard's id, min/max ranges and node_id)
|
||||
* 'splitCopyInfos' : Array of Split Copy Info (destination_shard's id, min/max ranges and node_id)
|
||||
*/
|
||||
Datum
|
||||
worker_split_copy(PG_FUNCTION_ARGS)
|
||||
|
@ -139,7 +139,7 @@ TraceWorkerSplitCopyUdf(char *sourceShardToCopySchemaName,
|
|||
appendStringInfo(splitCopyTrace, "performing copy from shard %s to [",
|
||||
sourceShardToCopyQualifiedName);
|
||||
|
||||
/* split copy always has atleast two destinations */
|
||||
/* split copy always has at least two destinations */
|
||||
int index = 1;
|
||||
int splitWayCount = list_length(splitCopyInfoList);
|
||||
SplitCopyInfo *splitCopyInfo = NULL;
|
||||
|
|
|
@ -243,7 +243,7 @@ CreateShardSplitInfo(uint64 sourceShardIdToSplit,
|
|||
|
||||
|
||||
/*
|
||||
* AddShardSplitInfoEntryForNodeInMap function add's ShardSplitInfo entry
|
||||
* AddShardSplitInfoEntryForNodeInMap function adds ShardSplitInfo entry
|
||||
* to the hash map. The key is nodeId on which the new shard is to be placed.
|
||||
*/
|
||||
static void
|
||||
|
|
|
@ -358,6 +358,11 @@ ConvertRteToSubqueryWithEmptyResult(RangeTblEntry *rte)
|
|||
subquery->jointree = joinTree;
|
||||
|
||||
rte->rtekind = RTE_SUBQUERY;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/* no permission checking for this RTE */
|
||||
rte->perminfoindex = 0;
|
||||
#endif
|
||||
rte->subquery = subquery;
|
||||
rte->alias = copyObject(rte->eref);
|
||||
}
|
||||
|
|
|
@ -56,6 +56,9 @@
|
|||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "nodes/pg_list.h"
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
#include "parser/parse_relation.h"
|
||||
#endif
|
||||
#include "parser/parsetree.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
|
@ -108,6 +111,7 @@ static int AssignRTEIdentities(List *rangeTableList, int rteIdCounter);
|
|||
static void AssignRTEIdentity(RangeTblEntry *rangeTableEntry, int rteIdentifier);
|
||||
static void AdjustPartitioningForDistributedPlanning(List *rangeTableList,
|
||||
bool setPartitionedTablesInherited);
|
||||
static bool RTEWentThroughAdjustPartitioning(RangeTblEntry *rangeTableEntry);
|
||||
static PlannedStmt * FinalizeNonRouterPlan(PlannedStmt *localPlan,
|
||||
DistributedPlan *distributedPlan,
|
||||
CustomScan *customScan);
|
||||
|
@ -144,6 +148,8 @@ static void WarnIfListHasForeignDistributedTable(List *rangeTableList);
|
|||
static RouterPlanType GetRouterPlanType(Query *query,
|
||||
Query *originalQuery,
|
||||
bool hasUnresolvedParams);
|
||||
static void ConcatenateRTablesAndPerminfos(PlannedStmt *mainPlan,
|
||||
PlannedStmt *concatPlan);
|
||||
|
||||
|
||||
/* Distributed planner hook */
|
||||
|
@ -494,6 +500,20 @@ AdjustPartitioningForDistributedPlanning(List *rangeTableList,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* RTEWentThroughAdjustPartitioning returns true if the given rangetableentry
|
||||
* has been modified through AdjustPartitioningForDistributedPlanning
|
||||
* function, false otherwise.
|
||||
*/
|
||||
static bool
|
||||
RTEWentThroughAdjustPartitioning(RangeTblEntry *rangeTableEntry)
|
||||
{
|
||||
return (rangeTableEntry->rtekind == RTE_RELATION &&
|
||||
PartitionedTable(rangeTableEntry->relid) &&
|
||||
rangeTableEntry->inh == false);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AssignRTEIdentity assigns the given rteIdentifier to the given range table
|
||||
* entry.
|
||||
|
@ -1066,6 +1086,11 @@ CreateDistributedPlan(uint64 planId, bool allowRecursivePlanning, Query *origina
|
|||
/*
|
||||
* Plan subqueries and CTEs that cannot be pushed down by recursively
|
||||
* calling the planner and return the resulting plans to subPlanList.
|
||||
* Note that GenerateSubplansForSubqueriesAndCTEs will reset perminfoindexes
|
||||
* for some RTEs in originalQuery->rtable list, while not changing
|
||||
* originalQuery->rteperminfos. That's fine because we will go through
|
||||
* standard_planner again, which will adjust things accordingly in
|
||||
* set_plan_references>add_rtes_to_flat_rtable>add_rte_to_flat_rtable.
|
||||
*/
|
||||
List *subPlanList = GenerateSubplansForSubqueriesAndCTEs(planId, originalQuery,
|
||||
plannerRestrictionContext);
|
||||
|
@ -1465,12 +1490,42 @@ FinalizeNonRouterPlan(PlannedStmt *localPlan, DistributedPlan *distributedPlan,
|
|||
finalPlan->utilityStmt = localPlan->utilityStmt;
|
||||
|
||||
/* add original range table list for access permission checks */
|
||||
finalPlan->rtable = list_concat(finalPlan->rtable, localPlan->rtable);
|
||||
ConcatenateRTablesAndPerminfos(finalPlan, localPlan);
|
||||
|
||||
return finalPlan;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ConcatenateRTablesAndPerminfos(PlannedStmt *mainPlan, PlannedStmt *concatPlan)
|
||||
{
|
||||
mainPlan->rtable = list_concat(mainPlan->rtable, concatPlan->rtable);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/*
|
||||
* concatPlan's range table list is concatenated to mainPlan's range table list
|
||||
* therefore all the perminfoindexes should be updated to their value
|
||||
* PLUS the highest perminfoindex in mainPlan's perminfos, which is exactly
|
||||
* the list length.
|
||||
*/
|
||||
int mainPlan_highest_perminfoindex = list_length(mainPlan->permInfos);
|
||||
|
||||
ListCell *lc;
|
||||
foreach(lc, concatPlan->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
||||
if (rte->perminfoindex != 0)
|
||||
{
|
||||
rte->perminfoindex = rte->perminfoindex + mainPlan_highest_perminfoindex;
|
||||
}
|
||||
}
|
||||
|
||||
/* finally, concatenate perminfos as well */
|
||||
mainPlan->permInfos = list_concat(mainPlan->permInfos, concatPlan->permInfos);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* FinalizeRouterPlan gets a CustomScan node which already wrapped distributed
|
||||
* part of a router plan and sets it as the direct child of the router plan
|
||||
|
@ -1502,7 +1557,7 @@ FinalizeRouterPlan(PlannedStmt *localPlan, CustomScan *customScan)
|
|||
routerPlan->rtable = list_make1(remoteScanRangeTableEntry);
|
||||
|
||||
/* add original range table list for access permission checks */
|
||||
routerPlan->rtable = list_concat(routerPlan->rtable, localPlan->rtable);
|
||||
ConcatenateRTablesAndPerminfos(routerPlan, localPlan);
|
||||
|
||||
routerPlan->canSetTag = true;
|
||||
routerPlan->relationOids = NIL;
|
||||
|
@ -1976,6 +2031,62 @@ multi_relation_restriction_hook(PlannerInfo *root, RelOptInfo *relOptInfo,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* multi_get_relation_info_hook modifies the relation's indexlist
|
||||
* if necessary, to avoid a crash in PG16 caused by our
|
||||
* Citus function AdjustPartitioningForDistributedPlanning().
|
||||
*
|
||||
* AdjustPartitioningForDistributedPlanning() is a hack that we use
|
||||
* to prevent Postgres' standard_planner() to expand all the partitions
|
||||
* for the distributed planning when a distributed partitioned table
|
||||
* is queried. It is required for both correctness and performance
|
||||
* reasons. Although we can eliminate the use of the function for
|
||||
* the correctness (e.g., make sure that rest of the planner can handle
|
||||
* partitions), it's performance implication is hard to avoid. Certain
|
||||
* planning logic of Citus (such as router or query pushdown) relies
|
||||
* heavily on the relationRestrictionList. If
|
||||
* AdjustPartitioningForDistributedPlanning() is removed, all the
|
||||
* partitions show up in the relationRestrictionList, causing high
|
||||
* planning times for such queries.
|
||||
*/
|
||||
void
|
||||
multi_get_relation_info_hook(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
||||
RelOptInfo *rel)
|
||||
{
|
||||
if (!CitusHasBeenLoaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Index varno = rel->relid;
|
||||
RangeTblEntry *rangeTableEntry = planner_rt_fetch(varno, root);
|
||||
|
||||
if (RTEWentThroughAdjustPartitioning(rangeTableEntry))
|
||||
{
|
||||
ListCell *lc = NULL;
|
||||
foreach(lc, rel->indexlist)
|
||||
{
|
||||
IndexOptInfo *indexOptInfo = (IndexOptInfo *) lfirst(lc);
|
||||
if (get_rel_relkind(indexOptInfo->indexoid) == RELKIND_PARTITIONED_INDEX)
|
||||
{
|
||||
/*
|
||||
* Normally, we should not need this. However, the combination of
|
||||
* Postgres commit 3c569049b7b502bb4952483d19ce622ff0af5fd6 and
|
||||
* Citus function AdjustPartitioningForDistributedPlanning()
|
||||
* forces us to do this. The commit expects partitioned indexes
|
||||
* to belong to relations with "inh" flag set properly. Whereas, the
|
||||
* function overrides "inh" flag. To avoid a crash,
|
||||
* we go over the list of indexinfos and remove all partitioned indexes.
|
||||
* Partitioned indexes were ignored pre PG16 anyway, we are essentially
|
||||
* not breaking any logic.
|
||||
*/
|
||||
rel->indexlist = foreach_delete_current(rel->indexlist, lc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TranslatedVars deep copies the translated vars for the given relation index
|
||||
* if there is any append rel list.
|
||||
|
|
|
@ -103,15 +103,24 @@ PlannedStmt *
|
|||
GeneratePlaceHolderPlannedStmt(Query *parse)
|
||||
{
|
||||
PlannedStmt *result = makeNode(PlannedStmt);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
SeqScan *scanNode = makeNode(SeqScan);
|
||||
Plan *plan = &(scanNode->scan.plan);
|
||||
#else
|
||||
Scan *scanNode = makeNode(Scan);
|
||||
Plan *plan = &scanNode->plan;
|
||||
#endif
|
||||
|
||||
Node *distKey PG_USED_FOR_ASSERTS_ONLY = NULL;
|
||||
|
||||
Assert(FastPathRouterQuery(parse, &distKey));
|
||||
|
||||
/* there is only a single relation rte */
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
scanNode->scan.scanrelid = 1;
|
||||
#else
|
||||
scanNode->scanrelid = 1;
|
||||
#endif
|
||||
|
||||
plan->targetlist =
|
||||
copyObject(FetchStatementTargetList((Node *) parse));
|
||||
|
@ -127,6 +136,9 @@ GeneratePlaceHolderPlannedStmt(Query *parse)
|
|||
result->stmt_len = parse->stmt_len;
|
||||
|
||||
result->rtable = copyObject(parse->rtable);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
result->permInfos = copyObject(parse->rteperminfos);
|
||||
#endif
|
||||
result->planTree = (Plan *) plan;
|
||||
result->hasReturning = (parse->returningList != NIL);
|
||||
|
||||
|
|
|
@ -61,6 +61,9 @@ static DistributedPlan * CreateInsertSelectPlanInternal(uint64 planId,
|
|||
static DistributedPlan * CreateDistributedInsertSelectPlan(Query *originalQuery,
|
||||
PlannerRestrictionContext *
|
||||
plannerRestrictionContext);
|
||||
static bool InsertSelectHasRouterSelect(Query *originalQuery,
|
||||
PlannerRestrictionContext *
|
||||
plannerRestrictionContext);
|
||||
static Task * RouterModifyTaskForShardInterval(Query *originalQuery,
|
||||
CitusTableCacheEntry *targetTableCacheEntry,
|
||||
ShardInterval *shardInterval,
|
||||
|
@ -75,6 +78,7 @@ static DeferredErrorMessage * DistributedInsertSelectSupported(Query *queryTree,
|
|||
RangeTblEntry *insertRte,
|
||||
RangeTblEntry *subqueryRte,
|
||||
bool allReferenceTables,
|
||||
bool routerSelect,
|
||||
PlannerRestrictionContext *
|
||||
plannerRestrictionContext);
|
||||
static DeferredErrorMessage * InsertPartitionColumnMatchesSelect(Query *query,
|
||||
|
@ -282,6 +286,9 @@ CreateDistributedInsertSelectPlan(Query *originalQuery,
|
|||
RelationRestrictionContext *relationRestrictionContext =
|
||||
plannerRestrictionContext->relationRestrictionContext;
|
||||
bool allReferenceTables = relationRestrictionContext->allReferenceTables;
|
||||
bool routerSelect =
|
||||
InsertSelectHasRouterSelect(copyObject(originalQuery),
|
||||
plannerRestrictionContext);
|
||||
|
||||
distributedPlan->modLevel = RowModifyLevelForQuery(originalQuery);
|
||||
|
||||
|
@ -293,13 +300,27 @@ CreateDistributedInsertSelectPlan(Query *originalQuery,
|
|||
insertRte,
|
||||
subqueryRte,
|
||||
allReferenceTables,
|
||||
routerSelect,
|
||||
plannerRestrictionContext);
|
||||
if (distributedPlan->planningError)
|
||||
{
|
||||
return distributedPlan;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* if the query goes to a single node ("router" in Citus' parlance),
|
||||
* we don't need to go through AllDistributionKeysInQueryAreEqual checks.
|
||||
*
|
||||
* For PG16+, this is required as some of the outer JOINs are converted to
|
||||
* "ON(true)" and filters are pushed down to the table scans. As
|
||||
* AllDistributionKeysInQueryAreEqual rely on JOIN filters, it will fail to
|
||||
* detect the router case. However, we can still detect it by checking if
|
||||
* the query is a router query as the router query checks the filters on
|
||||
* the tables.
|
||||
*/
|
||||
bool allDistributionKeysInQueryAreEqual =
|
||||
routerSelect ||
|
||||
AllDistributionKeysInQueryAreEqual(originalQuery, plannerRestrictionContext);
|
||||
|
||||
/*
|
||||
|
@ -361,6 +382,23 @@ CreateDistributedInsertSelectPlan(Query *originalQuery,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* InsertSelectHasRouterSelect is a helper function that returns true of the SELECT
|
||||
* part of the INSERT .. SELECT query is a router query.
|
||||
*/
|
||||
static bool
|
||||
InsertSelectHasRouterSelect(Query *originalQuery,
|
||||
PlannerRestrictionContext *plannerRestrictionContext)
|
||||
{
|
||||
RangeTblEntry *subqueryRte = ExtractSelectRangeTableEntry(originalQuery);
|
||||
DistributedPlan *distributedPlan = CreateRouterPlan(subqueryRte->subquery,
|
||||
subqueryRte->subquery,
|
||||
plannerRestrictionContext);
|
||||
|
||||
return distributedPlan->planningError == NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateInsertSelectIntoLocalTablePlan creates the plan for INSERT .. SELECT queries
|
||||
* where the selected table is distributed and the inserted table is not.
|
||||
|
@ -566,6 +604,22 @@ CreateCombineQueryForRouterPlan(DistributedPlan *distPlan)
|
|||
combineQuery->querySource = QSRC_ORIGINAL;
|
||||
combineQuery->canSetTag = true;
|
||||
combineQuery->rtable = list_make1(rangeTableEntry);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/*
|
||||
* This part of the code is more of a sanity check for readability,
|
||||
* it doesn't really do anything.
|
||||
* We know that Only relation RTEs and subquery RTEs that were once relation
|
||||
* RTEs (views) have their perminfoindex set. (see ExecCheckPermissions function)
|
||||
* DerivedRangeTableEntry sets the rtekind to RTE_FUNCTION
|
||||
* Hence we should have no perminfos here.
|
||||
*/
|
||||
Assert(rangeTableEntry->rtekind == RTE_FUNCTION &&
|
||||
rangeTableEntry->perminfoindex == 0);
|
||||
combineQuery->rteperminfos = NIL;
|
||||
#endif
|
||||
|
||||
combineQuery->targetList = targetList;
|
||||
combineQuery->jointree = joinTree;
|
||||
return combineQuery;
|
||||
|
@ -615,6 +669,7 @@ CreateTargetListForCombineQuery(List *targetList)
|
|||
static DeferredErrorMessage *
|
||||
DistributedInsertSelectSupported(Query *queryTree, RangeTblEntry *insertRte,
|
||||
RangeTblEntry *subqueryRte, bool allReferenceTables,
|
||||
bool routerSelect,
|
||||
PlannerRestrictionContext *plannerRestrictionContext)
|
||||
{
|
||||
Oid selectPartitionColumnTableId = InvalidOid;
|
||||
|
@ -689,19 +744,28 @@ DistributedInsertSelectSupported(Query *queryTree, RangeTblEntry *insertRte,
|
|||
NULL, NULL);
|
||||
}
|
||||
|
||||
/* first apply toplevel pushdown checks to SELECT query */
|
||||
DeferredErrorMessage *error = DeferErrorIfUnsupportedSubqueryPushdown(subquery,
|
||||
plannerRestrictionContext);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
DeferredErrorMessage *error = NULL;
|
||||
|
||||
/* then apply subquery pushdown checks to SELECT query */
|
||||
error = DeferErrorIfCannotPushdownSubquery(subquery, false);
|
||||
if (error)
|
||||
/*
|
||||
* We can skip SQL support related checks for router queries as
|
||||
* they are safe to route with any SQL.
|
||||
*/
|
||||
if (!routerSelect)
|
||||
{
|
||||
return error;
|
||||
/* first apply toplevel pushdown checks to SELECT query */
|
||||
error =
|
||||
DeferErrorIfUnsupportedSubqueryPushdown(subquery, plannerRestrictionContext);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
/* then apply subquery pushdown checks to SELECT query */
|
||||
error = DeferErrorIfCannotPushdownSubquery(subquery, false);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsCitusTableType(targetRelationId, CITUS_LOCAL_TABLE))
|
||||
|
@ -853,15 +917,7 @@ RouterModifyTaskForShardInterval(Query *originalQuery,
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* passing NULL for plannerInfo will be problematic if we have placeholder
|
||||
* vars. However, it won't be the case here because we are building
|
||||
* the expression from shard intervals which don't have placeholder vars.
|
||||
* Note that this is only the case with PG14 as the parameter doesn't exist
|
||||
* prior to that.
|
||||
*/
|
||||
shardRestrictionList = make_simple_restrictinfo(NULL,
|
||||
shardRestrictionList = make_simple_restrictinfo(restriction->plannerInfo,
|
||||
(Expr *) shardOpExpressions);
|
||||
extendedBaseRestrictInfo = lappend(extendedBaseRestrictInfo,
|
||||
shardRestrictionList);
|
||||
|
@ -1493,6 +1549,20 @@ WrapSubquery(Query *subquery)
|
|||
selectAlias, false, true));
|
||||
outerQuery->rtable = list_make1(newRangeTableEntry);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/*
|
||||
* This part of the code is more of a sanity check for readability,
|
||||
* it doesn't really do anything.
|
||||
* addRangeTableEntryForSubquery doesn't add permission info
|
||||
* because the range table is set to be RTE_SUBQUERY.
|
||||
* Hence we should also have no perminfos here.
|
||||
*/
|
||||
Assert(newRangeTableEntry->rtekind == RTE_SUBQUERY &&
|
||||
newRangeTableEntry->perminfoindex == 0);
|
||||
outerQuery->rteperminfos = NIL;
|
||||
#endif
|
||||
|
||||
/* set the FROM expression to the subquery */
|
||||
RangeTblRef *newRangeTableRef = makeNode(RangeTblRef);
|
||||
newRangeTableRef->rtindex = 1;
|
||||
|
|
|
@ -107,6 +107,7 @@
|
|||
#include "optimizer/optimizer.h"
|
||||
#include "optimizer/planner.h"
|
||||
#include "optimizer/prep.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
|
@ -136,6 +137,9 @@ typedef struct RangeTableEntryDetails
|
|||
RangeTblEntry *rangeTableEntry;
|
||||
List *requiredAttributeNumbers;
|
||||
bool hasConstantFilterOnUniqueColumn;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
RTEPermissionInfo *perminfo;
|
||||
#endif
|
||||
} RangeTableEntryDetails;
|
||||
|
||||
/*
|
||||
|
@ -176,7 +180,8 @@ static bool HasConstantFilterOnUniqueColumn(RangeTblEntry *rangeTableEntry,
|
|||
static ConversionCandidates * CreateConversionCandidates(PlannerRestrictionContext *
|
||||
plannerRestrictionContext,
|
||||
List *rangeTableList,
|
||||
int resultRTEIdentity);
|
||||
int resultRTEIdentity,
|
||||
List *rteperminfos);
|
||||
static void AppendUniqueIndexColumnsToList(Form_pg_index indexForm, List **uniqueIndexes,
|
||||
int flags);
|
||||
static ConversionChoice GetConversionChoice(ConversionCandidates *
|
||||
|
@ -205,10 +210,17 @@ RecursivelyPlanLocalTableJoins(Query *query,
|
|||
GetPlannerRestrictionContext(context);
|
||||
|
||||
List *rangeTableList = query->rtable;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
List *rteperminfos = query->rteperminfos;
|
||||
#endif
|
||||
int resultRTEIdentity = ResultRTEIdentity(query);
|
||||
ConversionCandidates *conversionCandidates =
|
||||
CreateConversionCandidates(plannerRestrictionContext,
|
||||
rangeTableList, resultRTEIdentity);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
rangeTableList, resultRTEIdentity, rteperminfos);
|
||||
#else
|
||||
rangeTableList, resultRTEIdentity, NIL);
|
||||
#endif
|
||||
|
||||
ConversionChoice conversionChoise =
|
||||
GetConversionChoice(conversionCandidates, plannerRestrictionContext);
|
||||
|
@ -323,7 +335,12 @@ ConvertRTEsToSubquery(List *rangeTableEntryDetailsList, RecursivePlanningContext
|
|||
RangeTblEntry *rangeTableEntry = rangeTableEntryDetails->rangeTableEntry;
|
||||
List *requiredAttributeNumbers = rangeTableEntryDetails->requiredAttributeNumbers;
|
||||
ReplaceRTERelationWithRteSubquery(rangeTableEntry,
|
||||
requiredAttributeNumbers, context);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
requiredAttributeNumbers, context,
|
||||
rangeTableEntryDetails->perminfo);
|
||||
#else
|
||||
requiredAttributeNumbers, context, NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -530,7 +547,9 @@ RequiredAttrNumbersForRelationInternal(Query *queryToProcess, int rteIndex)
|
|||
*/
|
||||
static ConversionCandidates *
|
||||
CreateConversionCandidates(PlannerRestrictionContext *plannerRestrictionContext,
|
||||
List *rangeTableList, int resultRTEIdentity)
|
||||
List *rangeTableList,
|
||||
int resultRTEIdentity,
|
||||
List *rteperminfos)
|
||||
{
|
||||
ConversionCandidates *conversionCandidates =
|
||||
palloc0(sizeof(ConversionCandidates));
|
||||
|
@ -564,6 +583,14 @@ CreateConversionCandidates(PlannerRestrictionContext *plannerRestrictionContext,
|
|||
RequiredAttrNumbersForRelation(rangeTableEntry, plannerRestrictionContext);
|
||||
rangeTableEntryDetails->hasConstantFilterOnUniqueColumn =
|
||||
HasConstantFilterOnUniqueColumn(rangeTableEntry, relationRestriction);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
rangeTableEntryDetails->perminfo = NULL;
|
||||
if (rangeTableEntry->perminfoindex)
|
||||
{
|
||||
rangeTableEntryDetails->perminfo = getRTEPermissionInfo(rteperminfos,
|
||||
rangeTableEntry);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool referenceOrDistributedTable =
|
||||
IsCitusTableType(rangeTableEntry->relid, REFERENCE_TABLE) ||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "utils/lsyscache.h"
|
||||
|
@ -57,6 +58,9 @@ static DeferredErrorMessage * DeferErrorIfRoutableMergeNotSupported(Query *query
|
|||
*
|
||||
plannerRestrictionContext,
|
||||
Oid targetRelationId);
|
||||
static bool MergeSourceHasRouterSelect(Query *query,
|
||||
PlannerRestrictionContext *
|
||||
plannerRestrictionContext);
|
||||
static DeferredErrorMessage * MergeQualAndTargetListFunctionsSupported(Oid
|
||||
resultRelationId,
|
||||
Query *query,
|
||||
|
@ -234,7 +238,7 @@ CreateNonPushableMergePlan(Oid targetRelationId, uint64 planId, Query *originalQ
|
|||
ParamListInfo boundParams)
|
||||
{
|
||||
Query *mergeQuery = copyObject(originalQuery);
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery);
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery, false);
|
||||
DistributedPlan *distributedPlan = CitusMakeNode(DistributedPlan);
|
||||
|
||||
ereport(DEBUG1, (errmsg("Creating MERGE repartition plan")));
|
||||
|
@ -774,6 +778,11 @@ ConvertCteRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte)
|
|||
Query *cteQuery = (Query *) copyObject(sourceCte->ctequery);
|
||||
|
||||
sourceRte->rtekind = RTE_SUBQUERY;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
|
||||
/* sanity check - sourceRte was RTE_CTE previously so it should have no perminfo */
|
||||
Assert(sourceRte->perminfoindex == 0);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* As we are delinking the CTE from main query, we have to walk through the
|
||||
|
@ -824,6 +833,20 @@ ConvertRelationRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte,
|
|||
RangeTblEntry *newRangeTableEntry = copyObject(sourceRte);
|
||||
sourceResultsQuery->rtable = list_make1(newRangeTableEntry);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
sourceResultsQuery->rteperminfos = NIL;
|
||||
if (sourceRte->perminfoindex)
|
||||
{
|
||||
/* create permission info for newRangeTableEntry */
|
||||
RTEPermissionInfo *perminfo = getRTEPermissionInfo(mergeQuery->rteperminfos,
|
||||
sourceRte);
|
||||
|
||||
/* update the sourceResultsQuery's rteperminfos accordingly */
|
||||
newRangeTableEntry->perminfoindex = 1;
|
||||
sourceResultsQuery->rteperminfos = list_make1(perminfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* set the FROM expression to the subquery */
|
||||
newRangeTableRef->rtindex = SINGLE_RTE_INDEX;
|
||||
sourceResultsQuery->jointree = makeFromExpr(list_make1(newRangeTableRef), NULL);
|
||||
|
@ -849,6 +872,9 @@ ConvertRelationRTEIntoSubquery(Query *mergeQuery, RangeTblEntry *sourceRte,
|
|||
|
||||
/* replace the function with the constructed subquery */
|
||||
sourceRte->rtekind = RTE_SUBQUERY;
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
sourceRte->perminfoindex = 0;
|
||||
#endif
|
||||
sourceRte->subquery = sourceResultsQuery;
|
||||
sourceRte->inh = false;
|
||||
}
|
||||
|
@ -959,7 +985,8 @@ DeferErrorIfTargetHasFalseClause(Oid targetRelationId,
|
|||
|
||||
List *baseRestrictionList = relationRestriction->relOptInfo->baserestrictinfo;
|
||||
List *restrictClauseList = get_all_actual_clauses(baseRestrictionList);
|
||||
if (ContainsFalseClause(restrictClauseList))
|
||||
if (ContainsFalseClause(restrictClauseList) ||
|
||||
JoinConditionIsOnFalse(relationRestriction->relOptInfo->joininfo))
|
||||
{
|
||||
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
||||
"Routing query is not possible with "
|
||||
|
@ -1047,22 +1074,41 @@ DeferErrorIfRoutableMergeNotSupported(Query *query, List *rangeTableList,
|
|||
"must be colocated", NULL, NULL);
|
||||
}
|
||||
|
||||
DeferredErrorMessage *deferredError =
|
||||
DeferErrorIfUnsupportedSubqueryPushdown(query,
|
||||
plannerRestrictionContext);
|
||||
if (deferredError)
|
||||
{
|
||||
ereport(DEBUG1, (errmsg("Sub-query is not pushable, try repartitioning")));
|
||||
return deferredError;
|
||||
}
|
||||
DeferredErrorMessage *deferredError = NULL;
|
||||
|
||||
if (HasDangerousJoinUsing(query->rtable, (Node *) query->jointree))
|
||||
|
||||
/*
|
||||
* if the query goes to a single node ("router" in Citus' parlance),
|
||||
* we don't need to go through certain SQL support and colocation checks.
|
||||
*
|
||||
* For PG16+, this is required as some of the outer JOINs are converted to
|
||||
* "ON(true)" and filters are pushed down to the table scans. As
|
||||
* DeferErrorIfUnsupportedSubqueryPushdown rely on JOIN filters, it will fail to
|
||||
* detect the router case. However, we can still detect it by checking if
|
||||
* the query is a router query as the router query checks the filters on
|
||||
* the tables.
|
||||
*/
|
||||
|
||||
|
||||
if (!MergeSourceHasRouterSelect(query, plannerRestrictionContext))
|
||||
{
|
||||
ereport(DEBUG1, (errmsg(
|
||||
"Query has ambigious joins, merge is not pushable, try repartitioning")));
|
||||
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
||||
"a join with USING causes an internal naming "
|
||||
"conflict, use ON instead", NULL, NULL);
|
||||
deferredError =
|
||||
DeferErrorIfUnsupportedSubqueryPushdown(query,
|
||||
plannerRestrictionContext);
|
||||
if (deferredError)
|
||||
{
|
||||
ereport(DEBUG1, (errmsg("Sub-query is not pushable, try repartitioning")));
|
||||
return deferredError;
|
||||
}
|
||||
|
||||
if (HasDangerousJoinUsing(query->rtable, (Node *) query->jointree))
|
||||
{
|
||||
ereport(DEBUG1, (errmsg(
|
||||
"Query has ambigious joins, merge is not pushable, try repartitioning")));
|
||||
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
||||
"a join with USING causes an internal naming "
|
||||
"conflict, use ON instead", NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
deferredError = DeferErrorIfTargetHasFalseClause(targetRelationId,
|
||||
|
@ -1080,6 +1126,36 @@ DeferErrorIfRoutableMergeNotSupported(Query *query, List *rangeTableList,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* MergeSourceHasRouterSelect is a helper function that returns true of the source
|
||||
* part of the merge query is a router query.
|
||||
*/
|
||||
static bool
|
||||
MergeSourceHasRouterSelect(Query *query,
|
||||
PlannerRestrictionContext *plannerRestrictionContext)
|
||||
{
|
||||
Query *copiedQuery = copyObject(query);
|
||||
RangeTblEntry *mergeSourceRte = ExtractMergeSourceRangeTableEntry(copiedQuery, true);
|
||||
|
||||
if (mergeSourceRte == NULL)
|
||||
{
|
||||
/*
|
||||
* We might potentially support this case in the future, but for now,
|
||||
* we don't support MERGE with JOIN in the source.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
ConvertSourceRTEIntoSubquery(copiedQuery, mergeSourceRte, plannerRestrictionContext);
|
||||
Query *sourceQuery = mergeSourceRte->subquery;
|
||||
|
||||
DistributedPlan *distributedPlan = CreateRouterPlan(sourceQuery, sourceQuery,
|
||||
plannerRestrictionContext);
|
||||
|
||||
return distributedPlan->planningError == NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ErrorIfMergeQueryQualAndTargetListNotSupported does check for a MERGE command in the query, if it finds
|
||||
* one, it will verify the below criteria
|
||||
|
@ -1184,26 +1260,37 @@ SourceResultPartitionColumnIndex(Query *mergeQuery, List *sourceTargetList,
|
|||
{
|
||||
if (IsCitusTableType(targetRelation->relationId, SINGLE_SHARD_DISTRIBUTED))
|
||||
{
|
||||
ereport(ERROR, (errmsg("MERGE operation on non-colocated "
|
||||
"distributed table(s) without a shard "
|
||||
"key is not yet supported")));
|
||||
ereport(ERROR, (errmsg("MERGE operation across distributed schemas "
|
||||
"or with a row-based distributed table is "
|
||||
"not yet supported")));
|
||||
}
|
||||
|
||||
/* Get all the Join conditions from the ON clause */
|
||||
List *mergeJoinConditionList = WhereClauseList(mergeQuery->jointree);
|
||||
Var *targetColumn = targetRelation->partitionColumn;
|
||||
Var *sourceRepartitionVar = NULL;
|
||||
bool foundTypeMismatch = false;
|
||||
|
||||
OpExpr *validJoinClause =
|
||||
SinglePartitionJoinClause(list_make1(targetColumn), mergeJoinConditionList);
|
||||
SinglePartitionJoinClause(list_make1(targetColumn), mergeJoinConditionList,
|
||||
&foundTypeMismatch);
|
||||
if (!validJoinClause)
|
||||
{
|
||||
if (foundTypeMismatch)
|
||||
{
|
||||
ereport(ERROR, (errmsg("In the MERGE ON clause, there is a datatype mismatch "
|
||||
"between target's distribution "
|
||||
"column and the expression originating from the source."),
|
||||
errdetail(
|
||||
"If the types are different, Citus uses different hash "
|
||||
"functions for the two column types, which might "
|
||||
"lead to incorrect repartitioning of the result data")));
|
||||
}
|
||||
|
||||
ereport(ERROR, (errmsg("The required join operation is missing between "
|
||||
"the target's distribution column and any "
|
||||
"expression originating from the source. The "
|
||||
"issue may arise from either a non-equi-join or "
|
||||
"a mismatch in the datatypes of the columns being "
|
||||
"joined."),
|
||||
"issue may arise from a non-equi-join."),
|
||||
errdetail("Without a equi-join condition on the target's "
|
||||
"distribution column, the source rows "
|
||||
"cannot be efficiently redistributed, and "
|
||||
|
@ -1277,7 +1364,7 @@ SourceResultPartitionColumnIndex(Query *mergeQuery, List *sourceTargetList,
|
|||
* table or source query in USING clause.
|
||||
*/
|
||||
RangeTblEntry *
|
||||
ExtractMergeSourceRangeTableEntry(Query *query)
|
||||
ExtractMergeSourceRangeTableEntry(Query *query, bool joinSourceOk)
|
||||
{
|
||||
/* function is void for pre-15 versions of Postgres */
|
||||
#if PG_VERSION_NUM < PG_VERSION_15
|
||||
|
@ -1290,7 +1377,10 @@ ExtractMergeSourceRangeTableEntry(Query *query)
|
|||
|
||||
List *fromList = query->jointree->fromlist;
|
||||
|
||||
/* We should have only one RTE(MergeStmt->sourceRelation) in the from-list */
|
||||
/*
|
||||
* We should have only one RTE(MergeStmt->sourceRelation) in the from-list
|
||||
* unless Postgres community changes the representation of merge.
|
||||
*/
|
||||
if (list_length(fromList) != 1)
|
||||
{
|
||||
ereport(ERROR, (errmsg("Unexpected source list in MERGE sql USING clause")));
|
||||
|
@ -1305,12 +1395,18 @@ ExtractMergeSourceRangeTableEntry(Query *query)
|
|||
*/
|
||||
if (reference->rtindex == 0)
|
||||
{
|
||||
ereport(ERROR, (errmsg("Source is not an explicit query"),
|
||||
errhint("Source query is a Join expression, "
|
||||
"try converting into a query as SELECT * "
|
||||
"FROM (..Join..)")));
|
||||
if (!joinSourceOk)
|
||||
{
|
||||
ereport(ERROR, (errmsg("Source is not an explicit query"),
|
||||
errhint("Source query is a Join expression, "
|
||||
"try converting into a query as SELECT * "
|
||||
"FROM (..Join..)")));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
Assert(reference->rtindex >= 1);
|
||||
RangeTblEntry *subqueryRte = rt_fetch(reference->rtindex, query->rtable);
|
||||
|
||||
|
|
|
@ -295,7 +295,7 @@ NonPushableMergeCommandExplainScan(CustomScanState *node, List *ancestors,
|
|||
CitusScanState *scanState = (CitusScanState *) node;
|
||||
DistributedPlan *distributedPlan = scanState->distributedPlan;
|
||||
Query *mergeQuery = distributedPlan->modifyQueryViaCoordinatorOrRepartition;
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery);
|
||||
RangeTblEntry *sourceRte = ExtractMergeSourceRangeTableEntry(mergeQuery, false);
|
||||
|
||||
/*
|
||||
* Create a copy because ExplainOneQuery can modify the query, and later
|
||||
|
@ -992,12 +992,18 @@ BuildRemoteExplainQuery(char *queryString, ExplainState *es)
|
|||
appendStringInfo(explainQuery,
|
||||
"EXPLAIN (ANALYZE %s, VERBOSE %s, "
|
||||
"COSTS %s, BUFFERS %s, WAL %s, "
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
"GENERIC_PLAN %s, "
|
||||
#endif
|
||||
"TIMING %s, SUMMARY %s, FORMAT %s) %s",
|
||||
es->analyze ? "TRUE" : "FALSE",
|
||||
es->verbose ? "TRUE" : "FALSE",
|
||||
es->costs ? "TRUE" : "FALSE",
|
||||
es->buffers ? "TRUE" : "FALSE",
|
||||
es->wal ? "TRUE" : "FALSE",
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
es->generic ? "TRUE" : "FALSE",
|
||||
#endif
|
||||
es->timing ? "TRUE" : "FALSE",
|
||||
es->summary ? "TRUE" : "FALSE",
|
||||
formatStr,
|
||||
|
|
|
@ -999,7 +999,8 @@ SinglePartitionJoin(JoinOrderNode *currentJoinNode, TableEntry *candidateTable,
|
|||
}
|
||||
|
||||
OpExpr *joinClause =
|
||||
SinglePartitionJoinClause(currentPartitionColumnList, applicableJoinClauses);
|
||||
SinglePartitionJoinClause(currentPartitionColumnList, applicableJoinClauses,
|
||||
NULL);
|
||||
if (joinClause != NULL)
|
||||
{
|
||||
if (currentPartitionMethod == DISTRIBUTE_BY_HASH)
|
||||
|
@ -1037,7 +1038,8 @@ SinglePartitionJoin(JoinOrderNode *currentJoinNode, TableEntry *candidateTable,
|
|||
*/
|
||||
List *candidatePartitionColumnList = list_make1(candidatePartitionColumn);
|
||||
joinClause = SinglePartitionJoinClause(candidatePartitionColumnList,
|
||||
applicableJoinClauses);
|
||||
applicableJoinClauses,
|
||||
NULL);
|
||||
if (joinClause != NULL)
|
||||
{
|
||||
if (candidatePartitionMethod == DISTRIBUTE_BY_HASH)
|
||||
|
@ -1078,8 +1080,14 @@ SinglePartitionJoin(JoinOrderNode *currentJoinNode, TableEntry *candidateTable,
|
|||
* clause exists, the function returns NULL.
|
||||
*/
|
||||
OpExpr *
|
||||
SinglePartitionJoinClause(List *partitionColumnList, List *applicableJoinClauses)
|
||||
SinglePartitionJoinClause(List *partitionColumnList, List *applicableJoinClauses, bool
|
||||
*foundTypeMismatch)
|
||||
{
|
||||
if (foundTypeMismatch)
|
||||
{
|
||||
*foundTypeMismatch = false;
|
||||
}
|
||||
|
||||
if (list_length(partitionColumnList) == 0)
|
||||
{
|
||||
return NULL;
|
||||
|
@ -1121,6 +1129,10 @@ SinglePartitionJoinClause(List *partitionColumnList, List *applicableJoinClauses
|
|||
{
|
||||
ereport(DEBUG1, (errmsg("single partition column types do not "
|
||||
"match")));
|
||||
if (foundTypeMismatch)
|
||||
{
|
||||
*foundTypeMismatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2140,7 +2140,8 @@ ApplySinglePartitionJoin(MultiNode *leftNode, MultiNode *rightNode,
|
|||
* we introduce a (re-)partition operator for the other column.
|
||||
*/
|
||||
OpExpr *joinClause = SinglePartitionJoinClause(partitionColumnList,
|
||||
applicableJoinClauses);
|
||||
applicableJoinClauses,
|
||||
NULL);
|
||||
Assert(joinClause != NULL);
|
||||
|
||||
/* both are verified in SinglePartitionJoinClause to not be NULL, assert is to guard */
|
||||
|
|
|
@ -155,6 +155,7 @@ static DeferredErrorMessage * ErrorIfQueryHasUnroutableModifyingCTE(Query *query
|
|||
static DeferredErrorMessage * ErrorIfQueryHasCTEWithSearchClause(Query *queryTree);
|
||||
static bool ContainsSearchClauseWalker(Node *node, void *context);
|
||||
static bool SelectsFromDistributedTable(List *rangeTableList, Query *query);
|
||||
static bool AllShardsColocated(List *relationShardList);
|
||||
static ShardPlacement * CreateDummyPlacement(bool hasLocalRelation);
|
||||
static ShardPlacement * CreateLocalDummyPlacement();
|
||||
static int CompareInsertValuesByShardId(const void *leftElement,
|
||||
|
@ -395,7 +396,7 @@ ExtractSourceResultRangeTableEntry(Query *query)
|
|||
{
|
||||
if (IsMergeQuery(query))
|
||||
{
|
||||
return ExtractMergeSourceRangeTableEntry(query);
|
||||
return ExtractMergeSourceRangeTableEntry(query, false);
|
||||
}
|
||||
else if (CheckInsertSelectQuery(query))
|
||||
{
|
||||
|
@ -2392,6 +2393,15 @@ PlanRouterQuery(Query *originalQuery,
|
|||
RelationShardListForShardIntervalList(*prunedShardIntervalListList,
|
||||
&shardsPresent);
|
||||
|
||||
if (!EnableNonColocatedRouterQueryPushdown &&
|
||||
!AllShardsColocated(*relationShardList))
|
||||
{
|
||||
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
||||
"router planner does not support queries that "
|
||||
"reference non-colocated distributed tables",
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
if (!shardsPresent && !replacePrunedQueryWithDummy)
|
||||
{
|
||||
/*
|
||||
|
@ -2460,6 +2470,92 @@ PlanRouterQuery(Query *originalQuery,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* AllShardsColocated returns true if all the shards in the given relationShardList
|
||||
* have colocated tables and are on the same shard index.
|
||||
*/
|
||||
static bool
|
||||
AllShardsColocated(List *relationShardList)
|
||||
{
|
||||
RelationShard *relationShard = NULL;
|
||||
int shardIndex = -1;
|
||||
int colocationId = -1;
|
||||
CitusTableType tableType = ANY_CITUS_TABLE_TYPE;
|
||||
|
||||
foreach_ptr(relationShard, relationShardList)
|
||||
{
|
||||
Oid relationId = relationShard->relationId;
|
||||
uint64 shardId = relationShard->shardId;
|
||||
if (shardId == INVALID_SHARD_ID)
|
||||
{
|
||||
/* intermediate results are always colocated, so ignore */
|
||||
continue;
|
||||
}
|
||||
|
||||
CitusTableCacheEntry *tableEntry = LookupCitusTableCacheEntry(relationId);
|
||||
if (tableEntry == NULL)
|
||||
{
|
||||
/* local tables never colocated */
|
||||
return false;
|
||||
}
|
||||
|
||||
CitusTableType currentTableType = GetCitusTableType(tableEntry);
|
||||
if (currentTableType == REFERENCE_TABLE)
|
||||
{
|
||||
/*
|
||||
* Reference tables are always colocated so it is
|
||||
* safe to skip them.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
else if (IsCitusTableTypeCacheEntry(tableEntry, DISTRIBUTED_TABLE))
|
||||
{
|
||||
if (tableType == ANY_CITUS_TABLE_TYPE)
|
||||
{
|
||||
tableType = currentTableType;
|
||||
}
|
||||
else if (tableType != currentTableType)
|
||||
{
|
||||
/*
|
||||
* We cannot qualify different types of distributed tables
|
||||
* as colocated.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentTableType == RANGE_DISTRIBUTED ||
|
||||
currentTableType == APPEND_DISTRIBUTED)
|
||||
{
|
||||
/* we do not have further strict colocation chceks */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int currentColocationId = TableColocationId(relationId);
|
||||
if (colocationId == -1)
|
||||
{
|
||||
colocationId = currentColocationId;
|
||||
}
|
||||
else if (colocationId != currentColocationId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentIndex = ShardIndex(LoadShardInterval(shardId));
|
||||
if (shardIndex == -1)
|
||||
{
|
||||
shardIndex = currentIndex;
|
||||
}
|
||||
else if (shardIndex != currentIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ContainsOnlyLocalTables returns true if there is only
|
||||
* local tables and not any distributed or reference table.
|
||||
|
@ -3745,15 +3841,6 @@ DeferErrorIfUnsupportedRouterPlannableSelectQuery(Query *query)
|
|||
NULL, NULL);
|
||||
}
|
||||
|
||||
if (!EnableNonColocatedRouterQueryPushdown &&
|
||||
!AllDistributedRelationsInListColocated(distributedRelationList))
|
||||
{
|
||||
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
||||
"router planner does not support queries that "
|
||||
"reference non-colocated distributed tables",
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
DeferredErrorMessage *CTEWithSearchClauseError =
|
||||
ErrorIfQueryHasCTEWithSearchClause(query);
|
||||
if (CTEWithSearchClauseError != NULL)
|
||||
|
|
|
@ -83,7 +83,16 @@ CreateColocatedJoinChecker(Query *subquery, PlannerRestrictionContext *restricti
|
|||
* functions (i.e., FilterPlannerRestrictionForQuery()) rely on queries
|
||||
* not relations.
|
||||
*/
|
||||
anchorSubquery = WrapRteRelationIntoSubquery(anchorRangeTblEntry, NIL);
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
RTEPermissionInfo *perminfo = NULL;
|
||||
if (anchorRangeTblEntry->perminfoindex)
|
||||
{
|
||||
perminfo = getRTEPermissionInfo(subquery->rteperminfos, anchorRangeTblEntry);
|
||||
}
|
||||
anchorSubquery = WrapRteRelationIntoSubquery(anchorRangeTblEntry, NIL, perminfo);
|
||||
#else
|
||||
anchorSubquery = WrapRteRelationIntoSubquery(anchorRangeTblEntry, NIL, NULL);
|
||||
#endif
|
||||
}
|
||||
else if (anchorRangeTblEntry->rtekind == RTE_SUBQUERY)
|
||||
{
|
||||
|
@ -126,7 +135,7 @@ static RangeTblEntry *
|
|||
AnchorRte(Query *subquery)
|
||||
{
|
||||
FromExpr *joinTree = subquery->jointree;
|
||||
Relids joinRelIds = get_relids_in_jointree((Node *) joinTree, false);
|
||||
Relids joinRelIds = get_relids_in_jointree_compat((Node *) joinTree, false, false);
|
||||
int currentRTEIndex = -1;
|
||||
RangeTblEntry *anchorRangeTblEntry = NULL;
|
||||
|
||||
|
@ -266,7 +275,9 @@ SubqueryColocated(Query *subquery, ColocatedJoinChecker *checker)
|
|||
* designed for generating a stub query.
|
||||
*/
|
||||
Query *
|
||||
WrapRteRelationIntoSubquery(RangeTblEntry *rteRelation, List *requiredAttributes)
|
||||
WrapRteRelationIntoSubquery(RangeTblEntry *rteRelation,
|
||||
List *requiredAttributes,
|
||||
RTEPermissionInfo *perminfo)
|
||||
{
|
||||
Query *subquery = makeNode(Query);
|
||||
RangeTblRef *newRangeTableRef = makeNode(RangeTblRef);
|
||||
|
@ -277,6 +288,14 @@ WrapRteRelationIntoSubquery(RangeTblEntry *rteRelation, List *requiredAttributes
|
|||
RangeTblEntry *newRangeTableEntry = copyObject(rteRelation);
|
||||
subquery->rtable = list_make1(newRangeTableEntry);
|
||||
|
||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||
if (perminfo)
|
||||
{
|
||||
newRangeTableEntry->perminfoindex = 1;
|
||||
subquery->rteperminfos = list_make1(perminfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* set the FROM expression to the subquery */
|
||||
newRangeTableRef = makeNode(RangeTblRef);
|
||||
newRangeTableRef->rtindex = SINGLE_RTE_INDEX;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue