Compare commits

...

73 Commits

Author SHA1 Message Date
Hanefi Onaldi 5ca792aef9
Bump Citus version to 11.0.4 2022-07-13 18:06:04 +03:00
Hanefi Onaldi 096047bfbc
Add changelog entry for 11.0.4 2022-07-13 18:05:19 +03:00
Onder Kalaci c51095c462 Add more generic read-replica tests
(cherry picked from commit 6cd7319f12)
2022-07-13 15:16:04 +02:00
Onder Kalaci 857a770b86 Add regression tests for LOCK command citus.use_secondary_nodes=always mode
(cherry picked from commit 3c343d4563)
2022-07-13 15:15:52 +02:00
Onder Kalaci 06e55df141 Make sure citus_is_coordinator works on read replicas
(cherry picked from commit b2e9a5baf1)
2022-07-13 15:15:46 +02:00
Onder Kalaci 06d6ffbb6e LOCK COMMAND does not require primaries at the start
(cherry picked from commit 8ab696f7e2)
2022-07-13 15:15:40 +02:00
Hanefi Onaldi 2bb106508a
Bump Citus version to 11.0.3 2022-07-05 13:19:10 +03:00
Hanefi Onaldi 5f46f2e9f7
Add changelog entry for 11.0.3
(cherry picked from commit c33915c3e6)
2022-07-05 13:19:10 +03:00
Ahmet Gedemenli ac7511de7d Fix matviews for citus_add_local_table_to_metadata (#6023)
(cherry picked from commit c8e1e243b8)
2022-07-04 17:01:40 +03:00
Hanefi Onaldi 0eee7fd9b8
Fix downgrade scripts from 11.0-2 to 11.0-1
(cherry picked from commit f60809a6c1)

Conflicts:
	src/test/regress/expected/multi_extension.out
	src/test/regress/sql/multi_extension.sql
2022-06-29 22:52:07 +03:00
Önder Kalacı 03a4305e06
Fixes a bug that prevents upgrades when there are no worker nodes (#6037)
(cherry picked from commit bab4c0a8c3)
2022-06-29 14:36:24 +03:00
Onder Kalaci d397dd0dfe Fixes a bug that prevents upgrades when there COMPRESSION and DEFAULT columns 2022-06-29 10:45:33 +02:00
Hanefi Onaldi 9d05c30c13
Bump Citus version to 11.0.2 2022-06-16 16:54:47 +03:00
Hanefi Onaldi bd02bd2dda
Add changelog entries for 11.0.2 (#6007)
(cherry picked from commit 26172636c9)
2022-06-16 16:53:40 +03:00
Ahmet Gedemenli b559ae5813 Fix creating stats bug when CREATE TABLE LIKE (#6006)
(cherry picked from commit 1ee3e8b7f4)
2022-06-16 12:45:23 +03:00
Jelte Fennema a01e45f3df Make enterprise features open source
This PR makes all of the features open source that were previously only
available in Citus Enterprise.

Features that this adds:
1. Non blocking shard moves/shard rebalancer
   (`citus.logical_replication_timeout`)
2. Propagation of CREATE/DROP/ALTER ROLE statements
3. Propagation of GRANT statements
4. Propagation of CLUSTER statements
5. Propagation of ALTER DATABASE ... OWNER TO ...
6. Optimization for COPY when loading JSON to avoid double parsing of
   the JSON object (`citus.skip_jsonb_validation_in_copy`)
7. Support for row level security
8. Support for `pg_dist_authinfo`, which allows storing different
   authentication options for different users, e.g. you can store
   passwords or certificates here.
9. Support for `pg_dist_poolinfo`, which allows using connection poolers
   in between coordinator and workers
10. Tracking distributed query execution times using
   citus_stat_statements (`citus.stat_statements_max`,
   `citus.stat_statements_purge_interval`,
   `citus.stat_statements_track`). This is disabled by default.
11. Blocking tenant_isolation
12. Support for `sslkey` and `sslcert` in `citus.node_conninfo`
2022-06-16 08:09:45 +02:00
Marco Slot 0861c80c8b Fix bug in unqualified, non-existing DROP DOMAIN IF EXISTS
(cherry picked from commit ee34e1ed9d)
2022-06-15 16:53:25 +02:00
Burak Velioglu de6373b842 Fix dropping temporary view without specifying the explicit schema name
(cherry picked from commit 4d533c3c56)
2022-06-15 16:36:52 +02:00
Ahmet Gedemenli 4345627480 Fix materialized view intermediate result filename (#5982)
(cherry picked from commit 268d3fa3a6)
2022-06-14 15:43:18 +03:00
Onder Kalaci 978d31f330 Use citus_finish_citus_upgrade() in the tests
We already have tests relying on citus_finalize_upgrade_to_citus11().
Now, adjust those to rely on citus_finish_citus_upgrade() and
always call citus_finish_citus_upgrade().
2022-06-13 13:28:41 +02:00
Marco Slot 4bcffce036 Introduce a citus_finish_citus_upgrade() function 2022-06-13 13:28:31 +02:00
Halil Ozan Akgul 7166901492 Fixes the bug where undistribute can drop Citus extension
(cherry picked from commit b255706189)
2022-06-01 18:56:56 +03:00
Hanefi Onaldi 8ef705012a
Add normalization rules for flaky isolation tests
We remove `<waiting ...>` and `<... completed>` outputs for some CREATE
INDEX CONCURRENTLY commands since they can cause flakiness in some scenarios.

Postgres calls WaitForOlderSnapshots() and this can cause CREATE INDEX
CONCURRENTLY commands for shards to get blocked by each other for brief
periods of time. The extra waits can pop-up, or they can get completed
at different lines in the output files. To remedy that, we rename those
indexes to be captured by the new normalization rule.

(cherry picked from commit 52541c5802)
2022-06-01 16:12:01 +03:00
Hanefi Onaldi 530aafd8ee
Grep logs for deterministic global_cancel test results (#5948)
(cherry picked from commit 313104ab9b)
2022-06-01 16:12:01 +03:00
Gledis Zeneli c440cbb643 Fix memory error with citus_add_node reported by valgrind test (#5967)
The error comes due to the datum jsonb in pg_dist_metadata_node.metadata being 0 in some scenarios. This is likely due to not copying the data when receiving a datum from a tuple and pg deciding to deallocate that memory when the table that the tuple was from is closed.
Also fix another place in the code that might have been susceptible to this issue.
I tested on both multi-vg and multi-1-vg and the test were successful.

(cherry picked from commit beef392f5a)
2022-06-01 13:06:54 +03:00
gledis69 a64e135a36 Revert "Copy data from heap tuples instead of using references"
This reverts commit 50e8638ede.
2022-06-01 13:06:38 +03:00
gledis69 50e8638ede Copy data from heap tuples instead of using references
The general rule is:
If the data is used within the bounds of table_open ... table_close > no need to copy
If the data is required for use even after the table is closed > copy

(cherry picked from commit dc9da7630f)
2022-06-01 12:27:11 +03:00
jeff-davis b34b1ce06b Columnar: fix wraparound bug. (#5962)
columnar_vacuum_rel() now advances relfrozenxid.

Fixes #5958.

(cherry picked from commit 74ce210f8b)
2022-05-31 07:46:12 -07:00
Onder Kalaci 0d0dd0af1c Show that no metadata is sent when disabled
(cherry picked from commit 89c1ccb7a5)
2022-05-30 17:01:49 +02:00
Onder Kalaci 3227d6551e Do not send metadata changes during add node if citus.enable_metadata_sync is set to false
(cherry picked from commit 7157152f6c)
2022-05-30 17:01:44 +02:00
Onder Kalaci d147d5d0c5 Avoid assertion failure on citus_add_node
(cherry picked from commit 010a2a408e)
2022-05-30 17:01:38 +02:00
Ahmet Gedemenli 4b5f749c23 Propagate dependent views upon distribution (#5950)
(cherry picked from commit 26d927178c)
2022-05-26 18:58:04 +03:00
Burak Velioglu 29c67c660d Create view and materialized views with right schema and owner while
altering the distributed table.

To be able to alter view's owner without enforcing sequential mode.
Alter view process functions have been udpated to use metadata
connection.
2022-05-25 10:42:54 +03:00
Gledis Zeneli 6da2d41e00 Do not obtain AccessShareLock before actual lock (#5965)
Do not obtain AccessShareLock before acquiring the distributed locks.

Acquiring an AccessShareLock ensures that the relations which we are trying to get a distributed lock on will not be dropped in the time between when the LOCK command is issued and the LOCK commands are send to the worker. However, this also leads to distributed deadlocks in such scenarios:

```sql
-- for dist lock acquiring order coor, w1, w2

-- on w2
LOCK t1 IN ACCESS EXLUSIVE MODE;
-- acquire AccessShareLock locally on t1 to ensure it is not dropped while we get ready to distribute the lock

      -- concurrently on w1
      LOCK t1 IN ACCESS EXLUSIVE MODE;
      -- acquire AccessShareLock locally on t1 to ensure it is not dropped while we get ready to distribute the lock
      -- acquire dist lock on coor, w1, gets blocked on local AccessShareLock on w2

-- on w2 continuation of the execution above
-- starts to acquire dist locks and gets blocked on the coor by the lock acquired by w1

-- distributed deadlock

```

We opt for avoiding such deadlocks with the cost of the possibility of running into errors when the relations on which we are trying to acquire locks on get dropped.

(cherry picked from commit 27ddb4fc8e)
2022-05-23 17:28:37 +03:00
Onder Kalaci 2d5560537b Due to new commits in master branch, outputs diverged 2022-05-23 09:36:38 +02:00
Onder Kalaci 8b0499c91a Parallelize metadata syncing on node activate
It is often useful to be able to sync the metadata in parallel
across nodes.

Also citus_finalize_upgrade_to_citus11() uses
start_metadata_sync_to_primary_nodes() after this commit.

Note that this commit does not parallelize all pieces of node
activation or metadata syncing. Instead, it tries to parallelize
potenially large parts of metadata, which is the objects and
distributed tables (in general Citus tables).

In the future, it would be nice to sync the reference tables
in parallel across nodes.

Create ~720 distributed tables / ~23450 shards
```SQL
-- declaratively partitioned table
CREATE TABLE github_events_looooooooooooooong_name (
  event_id bigint,
  event_type text,
  event_public boolean,
  repo_id bigint,
  payload jsonb,
  repo jsonb,
  actor jsonb,
  org jsonb,
  created_at timestamp
) PARTITION BY RANGE (created_at);

SELECT create_time_partitions(
  table_name         := 'github_events_looooooooooooooong_name',
  partition_interval := '1 day',
  end_at             := now() + '24 months'
);

CREATE INDEX ON github_events_looooooooooooooong_name USING btree (event_id, event_type, event_public, repo_id);
SELECT create_distributed_table('github_events_looooooooooooooong_name', 'repo_id');

SET client_min_messages TO ERROR;

```

across 1 node: almost same as expected
```SQL

SELECT start_metadata_sync_to_primary_nodes();
Time: 15664.418 ms (00:15.664)

select start_metadata_sync_to_node(nodename,nodeport) from pg_dist_node;
Time: 14284.069 ms (00:14.284)
```

across 7 nodes: ~3.5x improvement
```SQL

SELECT start_metadata_sync_to_primary_nodes();
┌──────────────────────────────────────┐
│ start_metadata_sync_to_primary_nodes │
├──────────────────────────────────────┤
│ t                                    │
└──────────────────────────────────────┘
(1 row)

Time: 25711.192 ms (00:25.711)

-- across 7 nodes
select start_metadata_sync_to_node(nodename,nodeport) from pg_dist_node;
Time: 82126.075 ms (01:22.126)
```

(cherry picked from commit dd02e1755f)
2022-05-23 09:25:31 +02:00
Onder Kalaci 513e073206 Fixes a bug that prevents dropping/altering indexes
There are two problems in this area. First, when there are expressions
on the index name, we should call `transformIndexExpression()` before
generating the index name. That is what Postgres does.

Second, because of 40c24bfef9
PG 13 and PG 14 generates different names for indexes with function calls even for local PG tables.
Assume we have:
```SQL
create table t(id int);
select create_distributed_table('t', 'id');
create index ON t (my_very_boring_function(id));
```

On PG 13, the name of the index is `t_expr_idx`
```SQL
\d t
Table "public.t"
┌────────┬─────────┬───────────┬──────────┬─────────┐
│ Column │  Type   │ Collation │ Nullable │ Default │
├────────┼─────────┼───────────┼──────────┼─────────┤
│ id     │ integer │           │          │         │
└────────┴─────────┴───────────┴──────────┴─────────┘
Indexes:
    "t_expr_idx" btree (my_very_boring_function(id::bigint))
```

On PG 14, the name of the index is `t_my_very_boring_function_idx`
```SQL
\d t
 Table "public.t"
┌────────┬─────────┬───────────┬──────────┬─────────┐
│ Column │  Type   │ Collation │ Nullable │ Default │
├────────┼─────────┼───────────┼──────────┼─────────┤
│ id     │ integer │           │          │         │
└────────┴─────────┴───────────┴──────────┴─────────┘
Indexes:
    "t_my_very_boring_function_idx" btree (my_very_boring_function(id::bigint))

```

The second issue is not very critical. The important part is that
we adjust regression tests to drop all the indexes, which ensures
the index names are sane on any version.

(cherry picked from commit 2cc4053fc1)
2022-05-23 09:22:25 +02:00
Onder Kalaci 4b5cb7e2b9 Mark existing views as distributed when upgrade to 11.0+
We have a mechanism which ensures that newly distributed
objects are recorded during `alter extension citus update`.

However, the logic was lacking "view"s. With this commit, we make
sure that existing views are also marked as distributed during
upgrade.

(cherry picked from commit ee45e7bfbf)
2022-05-23 09:22:17 +02:00
Gledis Zeneli 97b453e679 Add TRUNCATE arbitrary config tests (#5848)
Adds TRUNCATE arbitrary config tests.
Also adds the ability to skip tests from particular configs.
2022-05-20 19:53:18 +02:00
Marco Slot 8c5035c0a5 Improve nested execution checks and add GUC to disable 2022-05-20 19:35:59 +02:00
Marco Slot 7c6784b1f4 Add caching for functions that check the backend type 2022-05-20 19:35:52 +02:00
Marco Slot 556f43f24a Fix prepared statement bug when switching from local to remote execution 2022-05-20 19:35:45 +02:00
gledis69 909b72b027 Add distributing lock command support
(cherry picked from commit 4731630741)
2022-05-20 18:02:34 +03:00
Gledis Zeneli 3f282c660b Switch to using LOCK instead of lock_relation_if_exists in TRUNCATE (#5930)
Breaking down #5899 into smaller PR-s

This particular PR changes the way TRUNCATE acquires distributed locks on the relations it is truncating to use the LOCK command instead of lock_relation_if_exists. This has the benefit of using pg's recursive locking logic it implements for the LOCK command instead of us having to resolve relation dependencies and lock them explicitly. While this does not directly affect truncate, it will allow us to generalize this locking logic to then log different relations where the pg recursive locking will become useful (e.g. locking views).

This implementation is a bit more complex that it needs to be due to pg not supporting locking foreign tables. We can however, still lock foreign tables with lock_relation_if_exists. So for a command:

TRUNCATE dist_table_1, dist_table_2, foreign_table_1, foreign_table_2, dist_table_3;

We generate and send the following command to all the workers in metadata:
```sql
SEL citus.enable_ddl_propagation TO FALSE;
LOCK dist_table_1, dist_table_2 IN ACCESS EXCLUSIVE MODE;
SELECT lock_relation_if_exists('foreign_table_1', 'ACCESS EXCLUSIVE');
SELECT lock_relation_if_exists('foreign_table_2', 'ACCESS EXCLUSIVE');
LOCK dist_table_3 IN ACCESS EXCLUSIVE MODE;
SEL citus.enable_ddl_propagation TO TRUE;
```

Note that we need to alternate between the lock command and lock_table_if_exists in order to preserve the TRUNCATE order of relations.
When pg supports locking foreign tables, we will be able to massive simplify this logic and send a single LOCK command.

(cherry picked from commit 4c6f62efc6)
2022-05-20 17:24:44 +03:00
Marco Slot 73fd4f7ded Allow distributed execution from run_command_on_* functions 2022-05-20 15:42:50 +02:00
Burak Velioglu 8229d4b7ee Add ALTER VIEW support
Adds support for propagation ALTER VIEW commands to
- Change owner of view
- SET/RESET option
- Rename view and view's column name
- Change schema of the view

Since PG also supports targeting views with ALTER TABLE
commands, related code also added to direct such ALTER TABLE
commands to ALTER VIEW commands while sending them to workers.
2022-05-20 12:18:14 +03:00
Burak Velioglu 0cf769c43a Introduce CREATE/DROP VIEW
Adds support for propagating create/drop view commands and views to
worker node while scaling out the cluster. Since views are dropped while
converting the table type, metadata connection will be used while
propagating view commands to not switch to sequential mode.
2022-05-20 12:18:02 +03:00
Burak Velioglu 591f2565cc Use object address instead of relation id on DDLJob to decide on syncing metadata 2022-05-20 12:17:56 +03:00
Ahmet Gedemenli ddfcbfdca1 Add tests for materialized views 2022-05-20 12:17:48 +03:00
Ahmet Gedemenli 16071fac1d Add view tests to arbitrary configs 2022-05-20 12:17:41 +03:00
Onder Kalaci 9c4e3329f6 Rename metadata sync to node metadata sync where applicable 2022-05-19 11:00:51 +02:00
Onder Kalaci 36f641c586 Serialize reference table modifications with node changes & restore point
With Citus MX enabled, when a reference table is modified, it does
some operations on the first worker node(e.g., acquire locks).

If node metadata is locked (via add node or create restore point),
the changes to the reference tables should be blocked.
2022-05-19 11:00:51 +02:00
Onder Kalaci 5fe384329e Adds "sync" option to citus_disable_node() UDF 2022-05-19 11:00:51 +02:00
Marco Slot c20732142e Add a run_command_on_coordinator function 2022-05-19 10:41:10 +02:00
Marco Slot 082a14656d Fix downgrade scripts and add new downgrade tests 2022-05-19 10:37:56 +02:00
Marco Slot 33dede5b75 Add a citus_is_coordinator function 2022-05-19 10:36:22 +02:00
Nils Dijk 5e4c0e4bea
Merge pull request #5931 from citusdata/refactor/dedupe-object-propagation
Refactor: reduce complexity and code duplication for Object Propagation
2022-05-18 18:06:24 +02:00
Ahmet Gedemenli c2d9e88bf5
Fix schema name bug for sequences (#5937) 2022-05-18 17:29:30 +02:00
Ahmet Gedemenli 88369b6b23
Merge pull request #5934 from citusdata/fix-alter-statistics-nspname
Fix alter statistics namespace name
2022-05-18 17:29:30 +02:00
Onder Kalaci b7a39a232d Refrain reading the metadata cache for all tables during upgrade
First, it is not needed. Second, in the past we had issues regarding
this: https://github.com/citusdata/citus/pull/4344

When I create 10k tables, ~120K shards, this saves
40Mb of memory during ALTER EXTENSION citus UPDATE.

Before the change:  MetadataCacheMemoryContext: 41943040 ~ 40MB
After the change:  MetadataCacheMemoryContext: 8192

(cherry picked from commit f193e16a01)
2022-05-06 13:53:43 +02:00
Marco Slot e8b41d1e5b Convert citus.hide_shards_from_app_name_prefixes to citus.show_shards_for_app_name_prefixes 2022-05-05 13:24:23 +02:00
Onder Kalaci b4a65b9c45 Do not set coordinator's metadatasynced column to false
After a disable_node

(cherry picked from commit 5fc7661169)
2022-04-25 09:35:00 +02:00
Onder Kalaci 6ca3478c8d Do not assign distributed transaction ids for local execution
In the past, for all modifications on the local execution,
we enabled 2PC (with 6a7ed7b309).

This also required us to enable coordinated transactions
via https://github.com/citusdata/citus/pull/4831 .

However, it does have a very substantial impact on the
distributed deadlock detection. The distributed deadlock
detection is designed to avoid single-statement transactions
because they cannot lead to any actual deadlocks.

The implementation is to skip backends without distributed
transactions are assigned. Now that we assign single
statement local executions in the lock graphs, we are
conflicting with the design of distributed deadlock
detection.

In general, we should fix it. However, one might
think that it is not a big deal, even if the processes
show up in the lock graphs, the deadlock detection
should not be causing any false positives. That is
false, unless https://github.com/citusdata/citus/issues/1803
is fixed. Now that local processes are considered as a single
distributed backend, the lock graphs might find:

    local execution 1 [tx id: 1] -> any local process [tx id: 0]
    any local process [tx id: 0] -> local execution 2 [tx id: 2]

And, decides that there is a distributed deadlock.

This commit is:
   (a) right thing to do, as local execuion should not need any
       distributed tx id
   (b) Eliminates performance issues that might come up with
       deadlock detection does a lot of unncessary checks
   (c) After moving local execution after the remote execution
       via https://github.com/citusdata/citus/pull/4301, the
       vauge requirement for assigning distributed tx ids are
       already gone.

(cherry picked from commit a2debe0f02)
2022-04-25 09:34:32 +02:00
Hanefi Onaldi 86df61cae8
Bump Citus to 11.0.1_beta 2022-04-11 16:09:11 +03:00
Hanefi Onaldi e20a6dcd78
Add changelog entries for 11.0.1_beta
(cherry picked from commit 3ec1fc48fc)
2022-04-11 16:08:16 +03:00
Burak Velioglu 6eed51b75c
Create function in transaction according to create object propagation guc
(cherry picked from commit 5d9599f964)
2022-04-11 13:01:14 +03:00
Nils Dijk 675ba65f22
Implement DOMAIN propagation for citus 2022-04-08 16:18:02 +02:00
Marco Slot d611a50a80 Allow adding a unique constraint with an index 2022-04-07 16:41:10 +02:00
Marco Slot c5797030de Fix EXPLAIN ANALYZE JSON format for subplans 2022-04-07 16:00:12 +02:00
Marco Slot a74d991445 Handle user-defined type parameters in EXPLAIN ANALYZE 2022-04-07 11:37:43 +02:00
Marco Slot cb9e510e40 Add TABLESAMPLE support 2022-04-01 16:48:29 +02:00
Onder Kalaci e336b92552 Only hide shards from client backends and pg bg workers
The aim of hiding shards is to hide shards from client applications.

Certain bg workers (such as pg_cron or Citus maintanince daemon)
should be treated like client applications because users can run
queries from such bg workers. And, these bg workers should follow
the similar application_name checks as client backeends.

Certain other bg workers, such as logical replication or postgres'
parallel workers, should never hide shards. They are internal
operations.

Similarly the other backend types like the walsender or
checkpointer or autovacuum should never hide shards.

(cherry picked from commit 9043a1ed3f)
2022-03-30 17:44:03 +02:00
Hanefi Onaldi 4784d5579b
Bump Citus to 11.0.0_beta 2022-03-24 16:17:47 +03:00
532 changed files with 51078 additions and 5831 deletions

View File

@ -468,7 +468,7 @@ jobs:
when: on_fail
- store_artifacts:
name: 'Save tap logs'
path: /home/circleci/project/src/test/recovery/tmp_check/log
path: /home/circleci/project/src/test/<< parameters.suite >>/tmp_check/log
- store_artifacts:
name: 'Save core dumps'
path: /tmp/core_dumps
@ -520,12 +520,6 @@ workflows:
version: 2
build_and_test:
jobs:
- check-merge-to-enterprise:
filters:
branches:
ignore:
- /release-[0-9]+\.[0-9]+.*/ # match with releaseX.Y.*
- build:
name: build-13
pg_major: 13
@ -604,6 +598,12 @@ workflows:
image_tag: '<< pipeline.parameters.pg13_version >>'
suite: recovery
requires: [build-13]
- tap-test-citus:
name: 'test-13_tap-columnar-freezing'
pg_major: 13
image_tag: '<< pipeline.parameters.pg13_version >>'
suite: columnar_freezing
requires: [build-13]
- test-citus:
name: 'test-13_check-failure'
pg_major: 13
@ -612,6 +612,81 @@ workflows:
make: check-failure
requires: [build-13]
- test-citus:
name: 'test-13_check-enterprise'
pg_major: 13
image_tag: '<< pipeline.parameters.pg13_version >>'
make: check-enterprise
requires: [build-13]
- test-citus:
name: 'test-13_check-enterprise-isolation'
pg_major: 13
image_tag: '<< pipeline.parameters.pg13_version >>'
make: check-enterprise-isolation
requires: [build-13]
- test-citus:
name: 'test-13_check-enterprise-isolation-logicalrep-1'
pg_major: 13
image_tag: '<< pipeline.parameters.pg13_version >>'
make: check-enterprise-isolation-logicalrep-1
requires: [build-13]
- test-citus:
name: 'test-13_check-enterprise-isolation-logicalrep-2'
pg_major: 13
image_tag: '<< pipeline.parameters.pg13_version >>'
make: check-enterprise-isolation-logicalrep-2
requires: [build-13]
- test-citus:
name: 'test-13_check-enterprise-isolation-logicalrep-3'
pg_major: 13
image_tag: '<< pipeline.parameters.pg13_version >>'
make: check-enterprise-isolation-logicalrep-3
requires: [build-13]
- test-citus:
name: 'test-13_check-enterprise-failure'
pg_major: 13
image: citus/failtester
image_tag: '<< pipeline.parameters.pg13_version >>'
make: check-enterprise-failure
requires: [build-13]
- test-citus:
name: 'test-14_check-enterprise'
pg_major: 14
image_tag: '<< pipeline.parameters.pg14_version >>'
make: check-enterprise
requires: [build-14]
- test-citus:
name: 'test-14_check-enterprise-isolation'
pg_major: 14
image_tag: '<< pipeline.parameters.pg14_version >>'
make: check-enterprise-isolation
requires: [build-14]
- test-citus:
name: 'test-14_check-enterprise-isolation-logicalrep-1'
pg_major: 14
image_tag: '<< pipeline.parameters.pg14_version >>'
make: check-enterprise-isolation-logicalrep-1
requires: [build-14]
- test-citus:
name: 'test-14_check-enterprise-isolation-logicalrep-2'
pg_major: 14
image_tag: '<< pipeline.parameters.pg14_version >>'
make: check-enterprise-isolation-logicalrep-2
requires: [build-14]
- test-citus:
name: 'test-14_check-enterprise-isolation-logicalrep-3'
pg_major: 14
image_tag: '<< pipeline.parameters.pg14_version >>'
make: check-enterprise-isolation-logicalrep-3
requires: [build-14]
- test-citus:
name: 'test-14_check-enterprise-failure'
pg_major: 14
image: citus/failtester
image_tag: '<< pipeline.parameters.pg14_version >>'
make: check-enterprise-failure
requires: [build-14]
- test-citus:
name: 'test-14_check-multi'
pg_major: 14
@ -678,6 +753,12 @@ workflows:
image_tag: '<< pipeline.parameters.pg14_version >>'
suite: recovery
requires: [build-14]
- tap-test-citus:
name: 'test-14_tap-columnar-freezing'
pg_major: 14
image_tag: '<< pipeline.parameters.pg14_version >>'
suite: columnar_freezing
requires: [build-14]
- test-citus:
name: 'test-14_check-failure'
pg_major: 14

View File

@ -1,11 +1,58 @@
### citus v11.0.0_beta (March 22, 2022) ###
### citus v11.0.4 (July 13, 2022) ###
* Fixes a bug that prevents promoting read-replicas as primaries
### citus v11.0.3 (July 5, 2022) ###
* Fixes a bug that prevents adding local tables with materialized views to
Citus metadata
* Fixes a bug that prevents using `COMPRESSION` and `CONSTRAINT` on a column
* Fixes upgrades to Citus 11 when there are no nodes in the metadata
### citus v11.0.2 (June 15, 2022) ###
* Drops support for PostgreSQL 12
* Open sources enterprise features, see the rest of changelog items
* Turns metadata syncing on by default
* Adds `citus_finalize_upgrade_to_citus11()` which is necessary to upgrade to
Citus 11+ from earlier versions
* Introduces `citus_finish_citus_upgrade()` procedure which is necessary to
upgrade from earlier versions
* Open sources non-blocking shard moves/shard rebalancer
(`citus.logical_replication_timeout`)
* Open sources propagation of `CREATE/DROP/ALTER ROLE` statements
* Open sources propagation of `GRANT` statements
* Open sources propagation of `CLUSTER` statements
* Open sources propagation of `ALTER DATABASE ... OWNER TO ...`
* Open sources optimization for `COPY` when loading `JSON` to avoid double
parsing of the `JSON` object (`citus.skip_jsonb_validation_in_copy`)
* Open sources support for row level security
* Open sources support for `pg_dist_authinfo`, which allows storing different
authentication options for different users, e.g. you can store
passwords or certificates here.
* Open sources support for `pg_dist_poolinfo`, which allows using connection
poolers in between coordinator and workers
* Open sources tracking distributed query execution times using
citus_stat_statements (`citus.stat_statements_max`,
`citus.stat_statements_purge_interval`,
`citus.stat_statements_track`). This is disabled by default.
* Open sources tenant_isolation
* Open sources support for `sslkey` and `sslcert` in `citus.node_conninfo`
* Adds `citus.max_client_connections` GUC to limit non-Citus connections
@ -33,6 +80,8 @@
* Adds propagation for foreign server commands
* Adds propagation of `DOMAIN` objects
* Adds propagation of `TEXT SEARCH CONFIGURATION` objects
* Adds propagation of `TEXT SEARCH DICTIONARY` objects
@ -41,6 +90,8 @@
* Adds support for `CREATE SCHEMA AUTHORIZATION` statements without schema name
* Adds support for `CREATE/DROP/ALTER VIEW` commands
* Adds support for `TRUNCATE` for foreign tables
* Adds support for adding local tables to metadata using
@ -60,6 +111,12 @@
* Adds support for shard replication > 1 hash distributed tables on Citus MX
* Adds support for `LOCK` commands on distributed tables from worker nodes
* Adds support for `TABLESAMPLE`
* Adds support for propagating views when syncing Citus table metadata
* Improves handling of `IN`, `OUT` and `INOUT` parameters for functions
* Introduces `citus_backend_gpid()` UDF to get global pid of the current backend
@ -79,12 +136,23 @@
* Introduces a new flag `force_delegation` in `create_distributed_function()`
* Introduces `run_command_on_coordinator` UDF
* Introduces `synchronous` option to `citus_disable_node()` UDF
* Introduces `citus_is_coordinator` UDF to check whether a node is the
coordinator
* Allows adding a unique constraint with an index
* Allows `create_distributed_function()` on a function owned by an extension
* Allows creating distributed tables in sequential mode
* Allows disabling nodes when multiple failures happen
* Allows `lock_table_if_exits` to be called outside of a transaction blocks
* Adds support for pushing procedures with `OUT` arguments down to the worker
nodes
@ -108,6 +176,8 @@
* `citus_shards_on_worker` shows all local shards regardless of `search_path`
* Enables distributed execution from `run_command_on_*` functions
* Deprecates inactive shard state, never marks any placement inactive
* Disables distributed & reference foreign tables
@ -130,6 +200,20 @@
* Avoids unnecessary errors for `ALTER STATISTICS IF EXISTS` when the statistics
does not exist
* Fixes a bug that prevents dropping/altering indexes
* Fixes a bug that prevents non-client backends from accessing shards
* Fixes columnar freezing/wraparound bug
* Fixes `invalid read of size 1` memory error with `citus_add_node`
* Fixes schema name qualification for `ALTER/DROP SEQUENCE`
* Fixes schema name qualification for `ALTER/DROP STATISTICS`
* Fixes schema name qualification for `CREATE STATISTICS`
* Fixes a bug that causes columnar storage pages to have zero LSN
* Fixes a bug that causes issues while create dependencies from multiple
@ -157,6 +241,26 @@
* Fixes a bug that could cause re-partition joins involving local shards to fail
* Fixes a bug that could cause false positive distributed deadlocks due to local
execution
* Fixes a bug that could cause leaking files when materialized views are
refreshed
* Fixes a bug that could cause unqualified `DROP DOMAIN IF EXISTS` to fail
* Fixes a bug that could cause wrong schema and ownership after
`alter_distributed_table`
* Fixes a bug that could cause `EXPLAIN ANALYZE` to fail for prepared statements
with custom type
* Fixes a bug that could cause Citus not to create function in transaction block
properly
* Fixes a bug that could cause returning invalid JSON when running
`EXPLAIN ANALYZE` with subplans
* Fixes a bug that limits usage of sequences in non-int columns
* Fixes a bug that prevents `DROP SCHEMA CASCADE`
@ -188,17 +292,28 @@
* Fixes naming issues of newly created partitioned indexes
* Honors `enable_metadata_sync` in node operations
* Improves nested execution checks and adds GUC to control
(`citus.allow_nested_distributed_execution`)
* Improves self-deadlock prevention for `CREATE INDEX / REINDEX CONCURRENTLY`
commands for builds using PG14 or higher
* Moves `pg_dist_object` to `pg_catalog` schema
* Parallelizes metadata syncing on node activation
* Partitions shards to be co-located with the parent shards
* Prevents Citus table functions from being called on shards
* Prevents creating distributed functions when there are out of sync nodes
* Prevents alter table functions from dropping extensions
* Refrains reading the metadata cache for all tables during upgrade
* Provides notice message for idempotent `create_distributed_function` calls
* Reinstates optimisation for uniform shard interval ranges

View File

@ -11,7 +11,7 @@ endif
include Makefile.global
all: extension
all: extension pg_send_cancellation
# build extension
extension: $(citus_top_builddir)/src/include/citus_version.h
@ -30,14 +30,24 @@ clean-extension:
clean-full:
$(MAKE) -C src/backend/distributed/ clean-full
.PHONY: extension install-extension clean-extension clean-full
# Add to generic targets
install: install-extension install-headers
install-downgrades:
$(MAKE) -C src/backend/distributed/ install-downgrades
install-all: install-headers
install-all: install-headers install-pg_send_cancellation
$(MAKE) -C src/backend/distributed/ install-all
clean: clean-extension
# build citus_send_cancellation binary
pg_send_cancellation:
$(MAKE) -C src/bin/pg_send_cancellation/ all
install-pg_send_cancellation: pg_send_cancellation
$(MAKE) -C src/bin/pg_send_cancellation/ install
clean-pg_send_cancellation:
$(MAKE) -C src/bin/pg_send_cancellation/ clean
.PHONY: pg_send_cancellation install-pg_send_cancellation clean-pg_send_cancellation
# Add to generic targets
install: install-extension install-headers install-pg_send_cancellation
clean: clean-extension clean-pg_send_cancellation
# apply or check style
reindent:

18
configure vendored
View File

@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for Citus 11.0devel.
# Generated by GNU Autoconf 2.69 for Citus 11.0.4.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@ -579,8 +579,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='Citus'
PACKAGE_TARNAME='citus'
PACKAGE_VERSION='11.0devel'
PACKAGE_STRING='Citus 11.0devel'
PACKAGE_VERSION='11.0.4'
PACKAGE_STRING='Citus 11.0.4'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
@ -1260,7 +1260,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures Citus 11.0devel to adapt to many kinds of systems.
\`configure' configures Citus 11.0.4 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1322,7 +1322,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of Citus 11.0devel:";;
short | recursive ) echo "Configuration of Citus 11.0.4:";;
esac
cat <<\_ACEOF
@ -1425,7 +1425,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
Citus configure 11.0devel
Citus configure 11.0.4
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@ -1908,7 +1908,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by Citus $as_me 11.0devel, which was
It was created by Citus $as_me 11.0.4, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@ -5360,7 +5360,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by Citus $as_me 11.0devel, which was
This file was extended by Citus $as_me 11.0.4, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@ -5422,7 +5422,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
Citus config.status 11.0devel
Citus config.status 11.0.4
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"

View File

@ -5,7 +5,7 @@
# everyone needing autoconf installed, the resulting files are checked
# into the SCM.
AC_INIT([Citus], [11.0devel])
AC_INIT([Citus], [11.0.4])
AC_COPYRIGHT([Copyright (c) Citus Data, Inc.])
# we'll need sed and awk for some of the version commands

View File

@ -1033,6 +1033,27 @@ NeededColumnsList(TupleDesc tupdesc, Bitmapset *attr_needed)
}
/*
* ColumnarTableTupleCount returns the number of tuples that columnar
* table with relationId has by using stripe metadata.
*/
static uint64
ColumnarTableTupleCount(Relation relation)
{
List *stripeList = StripesForRelfilenode(relation->rd_node);
uint64 tupleCount = 0;
ListCell *lc = NULL;
foreach(lc, stripeList)
{
StripeMetadata *stripe = lfirst(lc);
tupleCount += stripe->rowCount;
}
return tupleCount;
}
/*
* columnar_vacuum_rel implements VACUUM without FULL option.
*/
@ -1049,6 +1070,9 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
return;
}
pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
RelationGetRelid(rel));
/*
* If metapage version of relation is older, then we hint users to VACUUM
* the relation in ColumnarMetapageCheckVersion. So if needed, upgrade
@ -1072,6 +1096,79 @@ columnar_vacuum_rel(Relation rel, VacuumParams *params,
{
TruncateColumnar(rel, elevel);
}
RelationOpenSmgr(rel);
BlockNumber new_rel_pages = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
/* get the number of indexes */
List *indexList = RelationGetIndexList(rel);
int nindexes = list_length(indexList);
TransactionId oldestXmin;
TransactionId freezeLimit;
MultiXactId multiXactCutoff;
/* initialize xids */
#if PG_VERSION_NUM >= PG_VERSION_15
MultiXactId oldestMxact;
vacuum_set_xid_limits(rel,
params->freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_min_age,
params->multixact_freeze_table_age,
&oldestXmin, &oldestMxact,
&freezeLimit, &multiXactCutoff);
Assert(MultiXactIdPrecedesOrEquals(multiXactCutoff, oldestMxact));
#else
TransactionId xidFullScanLimit;
MultiXactId mxactFullScanLimit;
vacuum_set_xid_limits(rel,
params->freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_min_age,
params->multixact_freeze_table_age,
&oldestXmin, &freezeLimit, &xidFullScanLimit,
&multiXactCutoff, &mxactFullScanLimit);
#endif
Assert(TransactionIdPrecedesOrEquals(freezeLimit, oldestXmin));
/*
* Columnar storage doesn't hold any transaction IDs, so we can always
* just advance to the most aggressive value.
*/
TransactionId newRelFrozenXid = oldestXmin;
#if PG_VERSION_NUM >= PG_VERSION_15
MultiXactId newRelminMxid = oldestMxact;
#else
MultiXactId newRelminMxid = multiXactCutoff;
#endif
double new_live_tuples = ColumnarTableTupleCount(rel);
/* all visible pages are always 0 */
BlockNumber new_rel_allvisible = 0;
#if PG_VERSION_NUM >= PG_VERSION_15
bool frozenxid_updated;
bool minmulti_updated;
vac_update_relstats(rel, new_rel_pages, new_live_tuples,
new_rel_allvisible, nindexes > 0,
newRelFrozenXid, newRelminMxid,
&frozenxid_updated, &minmulti_updated, false);
#else
vac_update_relstats(rel, new_rel_pages, new_live_tuples,
new_rel_allvisible, nindexes > 0,
newRelFrozenXid, newRelminMxid, false);
#endif
pgstat_report_vacuum(RelationGetRelid(rel),
rel->rd_rel->relisshared,
Max(new_live_tuples, 0),
0);
pgstat_progress_end_command();
}

View File

@ -0,0 +1 @@
-- no changes needed

View File

@ -0,0 +1 @@
-- no changes needed

View File

@ -22,7 +22,7 @@ SUBDIRS = . commands connection ddl deparser executor metadata operations planne
# columnar modules
SUBDIRS += ../columnar
# enterprise modules
SUBDIRS +=
SUBDIRS += replication
# Symlinks are not copied over to the build directory if a separete build
# directory is used during configure (such as on CI)

View File

@ -1,6 +1,6 @@
# Citus extension
comment = 'Citus distributed database'
default_version = '11.0-1'
default_version = '11.0-3'
module_pathname = '$libdir/citus'
relocatable = false
schema = pg_catalog

View File

@ -12,8 +12,8 @@ they are often moved to files that are named after the command.
| `create_distributed_table.c` | Implementation of UDF's for creating distributed tables |
| `drop_distributed_table.c` | Implementation for dropping metadata for partitions of distributed tables |
| `extension.c` | Implementation of `CREATE EXTENSION` commands for citus specific checks |
| `foreign_constraint.c` | Implementation of helper functions for foreign key constraints |
| `grant.c` | Placeholder for code granting users access to relations, implemented as enterprise feature |
| `foreign_constraint.c` | Implementation of and helper functions for foreign key constraints |
| `grant.c` | Implementation of `GRANT` commands for roles/users on relations |
| `index.c` | Implementation of commands specific to indices on distributed tables |
| `multi_copy.c` | Implementation of `COPY` command. There are multiple different copy modes which are described in detail below |
| `policy.c` | Implementation of `CREATE\ALTER POLICY` commands. |

View File

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

View File

@ -32,6 +32,8 @@
#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/pg_am.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_rewrite_d.h"
#include "columnar/columnar.h"
#include "columnar/columnar_tableam.h"
#include "commands/defrem.h"
@ -193,7 +195,6 @@ static void EnsureTableNotPartition(Oid relationId);
static TableConversionState * CreateTableConversion(TableConversionParameters *params);
static void CreateDistributedTableLike(TableConversionState *con);
static void CreateCitusTableLike(TableConversionState *con);
static List * GetViewCreationCommandsOfTable(Oid relationId);
static void ReplaceTable(Oid sourceId, Oid targetId, List *justBeforeDropCommands,
bool suppressNoticeMessages);
static bool HasAnyGeneratedStoredColumns(Oid relationId);
@ -205,16 +206,25 @@ static char * CreateWorkerChangeSequenceDependencyCommand(char *sequenceSchemaNa
char *sourceName,
char *targetSchemaName,
char *targetName);
static void ErrorIfMatViewSizeExceedsTheLimit(Oid matViewOid);
static char * CreateMaterializedViewDDLCommand(Oid matViewOid);
static char * GetAccessMethodForMatViewIfExists(Oid viewOid);
static bool WillRecreateForeignKeyToReferenceTable(Oid relationId,
CascadeToColocatedOption cascadeOption);
static void WarningsForDroppingForeignKeysWithDistributedTables(Oid relationId);
static void ErrorIfUnsupportedCascadeObjects(Oid relationId);
static bool DoesCascadeDropUnsupportedObject(Oid classId, Oid id, HTAB *nodeMap);
PG_FUNCTION_INFO_V1(undistribute_table);
PG_FUNCTION_INFO_V1(alter_distributed_table);
PG_FUNCTION_INFO_V1(alter_table_set_access_method);
PG_FUNCTION_INFO_V1(worker_change_sequence_dependency);
/* global variable keeping track of whether we are in a table type conversion function */
bool InTableTypeConversionFunctionCall = false;
/* controlled by GUC, in MB */
int MaxMatViewSizeToAutoRecreate = 1024;
/*
* undistribute_table gets a distributed table name and
@ -385,6 +395,8 @@ UndistributeTable(TableConversionParameters *params)
ErrorIfAnyPartitionRelationInvolvedInNonInheritedFKey(partitionList);
}
ErrorIfUnsupportedCascadeObjects(params->relationId);
params->conversionType = UNDISTRIBUTE_TABLE;
params->shardCountIsNull = true;
TableConversionState *con = CreateTableConversion(params);
@ -416,6 +428,8 @@ AlterDistributedTable(TableConversionParameters *params)
EnsureTableNotPartition(params->relationId);
EnsureHashDistributedTable(params->relationId);
ErrorIfUnsupportedCascadeObjects(params->relationId);
params->conversionType = ALTER_DISTRIBUTED_TABLE;
TableConversionState *con = CreateTableConversion(params);
CheckAlterDistributedTableConversionParameters(con);
@ -472,6 +486,8 @@ AlterTableSetAccessMethod(TableConversionParameters *params)
}
}
ErrorIfUnsupportedCascadeObjects(params->relationId);
params->conversionType = ALTER_TABLE_SET_ACCESS_METHOD;
params->shardCountIsNull = true;
TableConversionState *con = CreateTableConversion(params);
@ -503,10 +519,16 @@ AlterTableSetAccessMethod(TableConversionParameters *params)
*
* The function returns a TableConversionReturn object that can stores variables that
* can be used at the caller operations.
*
* To be able to provide more meaningful messages while converting a table type,
* Citus keeps InTableTypeConversionFunctionCall flag. Don't forget to set it properly
* in case you add a new way to return from this function.
*/
TableConversionReturn *
ConvertTable(TableConversionState *con)
{
InTableTypeConversionFunctionCall = true;
/*
* We undistribute citus local tables that are not chained with any reference
* tables via foreign keys at the end of the utility hook.
@ -535,6 +557,7 @@ ConvertTable(TableConversionState *con)
* subgraph including itself, so return here.
*/
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
InTableTypeConversionFunctionCall = false;
return NULL;
}
char *newAccessMethod = con->accessMethod ? con->accessMethod :
@ -562,8 +585,9 @@ ConvertTable(TableConversionState *con)
List *justBeforeDropCommands = NIL;
List *attachPartitionCommands = NIL;
postLoadCommands = list_concat(postLoadCommands,
GetViewCreationCommandsOfTable(con->relationId));
postLoadCommands =
list_concat(postLoadCommands,
GetViewCreationTableDDLCommandsOfTable(con->relationId));
List *foreignKeyCommands = NIL;
if (con->conversionType == ALTER_DISTRIBUTED_TABLE)
@ -819,6 +843,7 @@ ConvertTable(TableConversionState *con)
SetLocalEnableLocalReferenceForeignKeys(oldEnableLocalReferenceForeignKeys);
InTableTypeConversionFunctionCall = false;
return ret;
}
@ -1237,6 +1262,94 @@ CreateCitusTableLike(TableConversionState *con)
}
/*
* ErrorIfUnsupportedCascadeObjects gets oid of a relation, finds the objects
* that dropping this relation cascades into and errors if there are any extensions
* that would be dropped.
*/
static void
ErrorIfUnsupportedCascadeObjects(Oid relationId)
{
HASHCTL info;
memset(&info, 0, sizeof(info));
info.keysize = sizeof(Oid);
info.entrysize = sizeof(Oid);
info.hash = oid_hash;
uint32 hashFlags = (HASH_ELEM | HASH_FUNCTION);
HTAB *nodeMap = hash_create("object dependency map (oid)", 64, &info, hashFlags);
bool unsupportedObjectInDepGraph =
DoesCascadeDropUnsupportedObject(RelationRelationId, relationId, nodeMap);
if (unsupportedObjectInDepGraph)
{
ereport(ERROR, (errmsg("cannot alter table because an extension depends on it")));
}
}
/*
* DoesCascadeDropUnsupportedObject walks through the objects that depend on the
* object with object id and returns true if it finds any unsupported objects.
*
* This function only checks extensions as unsupported objects.
*
* Extension dependency is different than the rest. If an object depends on an extension
* dropping the object would drop the extension too.
* So we check with IsObjectAddressOwnedByExtension function.
*/
static bool
DoesCascadeDropUnsupportedObject(Oid classId, Oid objectId, HTAB *nodeMap)
{
bool found = false;
hash_search(nodeMap, &objectId, HASH_ENTER, &found);
if (found)
{
return false;
}
ObjectAddress objectAddress = { 0 };
ObjectAddressSet(objectAddress, classId, objectId);
if (IsObjectAddressOwnedByExtension(&objectAddress, NULL))
{
return true;
}
Oid targetObjectClassId = classId;
Oid targetObjectId = objectId;
List *dependencyTupleList = GetPgDependTuplesForDependingObjects(targetObjectClassId,
targetObjectId);
HeapTuple depTup = NULL;
foreach_ptr(depTup, dependencyTupleList)
{
Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
Oid dependingOid = InvalidOid;
Oid dependingClassId = InvalidOid;
if (pg_depend->classid == RewriteRelationId)
{
dependingOid = GetDependingView(pg_depend);
dependingClassId = RelationRelationId;
}
else
{
dependingOid = pg_depend->objid;
dependingClassId = pg_depend->classid;
}
if (DoesCascadeDropUnsupportedObject(dependingClassId, dependingOid, nodeMap))
{
return true;
}
}
return false;
}
/*
* GetViewCreationCommandsOfTable takes a table oid generates the CREATE VIEW
* commands for views that depend to the given table. This includes the views
@ -1246,46 +1359,152 @@ List *
GetViewCreationCommandsOfTable(Oid relationId)
{
List *views = GetDependingViews(relationId);
List *commands = NIL;
Oid viewOid = InvalidOid;
foreach_oid(viewOid, views)
{
Datum viewDefinitionDatum = DirectFunctionCall1(pg_get_viewdef,
ObjectIdGetDatum(viewOid));
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
StringInfo query = makeStringInfo();
char *viewName = get_rel_name(viewOid);
char *schemaName = get_namespace_name(get_rel_namespace(viewOid));
char *qualifiedViewName = quote_qualified_identifier(schemaName, viewName);
bool isMatView = get_rel_relkind(viewOid) == RELKIND_MATVIEW;
/* here we need to get the access method of the view to recreate it */
char *accessMethodName = GetAccessMethodForMatViewIfExists(viewOid);
appendStringInfoString(query, "CREATE ");
if (isMatView)
/* See comments on CreateMaterializedViewDDLCommand for its limitations */
if (get_rel_relkind(viewOid) == RELKIND_MATVIEW)
{
appendStringInfoString(query, "MATERIALIZED ");
ErrorIfMatViewSizeExceedsTheLimit(viewOid);
char *matViewCreateCommands = CreateMaterializedViewDDLCommand(viewOid);
appendStringInfoString(query, matViewCreateCommands);
}
else
{
char *viewCreateCommand = CreateViewDDLCommand(viewOid);
appendStringInfoString(query, viewCreateCommand);
}
appendStringInfo(query, "VIEW %s ", qualifiedViewName);
char *alterViewCommmand = AlterViewOwnerCommand(viewOid);
appendStringInfoString(query, alterViewCommmand);
if (accessMethodName)
{
appendStringInfo(query, "USING %s ", accessMethodName);
}
appendStringInfo(query, "AS %s", viewDefinition);
commands = lappend(commands, makeTableDDLCommandString(query->data));
commands = lappend(commands, query->data);
}
return commands;
}
/*
* GetViewCreationTableDDLCommandsOfTable is the same as GetViewCreationCommandsOfTable,
* but the returned list includes objects of TableDDLCommand's, not strings.
*/
List *
GetViewCreationTableDDLCommandsOfTable(Oid relationId)
{
List *commands = GetViewCreationCommandsOfTable(relationId);
List *tableDDLCommands = NIL;
char *command = NULL;
foreach_ptr(command, commands)
{
tableDDLCommands = lappend(tableDDLCommands, makeTableDDLCommandString(command));
}
return tableDDLCommands;
}
/*
* ErrorIfMatViewSizeExceedsTheLimit takes the oid of a materialized view and errors
* out if the size of the matview exceeds the limit set by the GUC
* citus.max_matview_size_to_auto_recreate.
*/
static void
ErrorIfMatViewSizeExceedsTheLimit(Oid matViewOid)
{
if (MaxMatViewSizeToAutoRecreate >= 0)
{
/* if it's below 0, it means the user has removed the limit */
Datum relSizeDatum = DirectFunctionCall1(pg_total_relation_size,
ObjectIdGetDatum(matViewOid));
uint64 matViewSize = DatumGetInt64(relSizeDatum);
/* convert from MB to bytes */
uint64 limitSizeInBytes = MaxMatViewSizeToAutoRecreate * 1024L * 1024L;
if (matViewSize > limitSizeInBytes)
{
ereport(ERROR, (errmsg("size of the materialized view %s exceeds "
"citus.max_matview_size_to_auto_recreate "
"(currently %d MB)", get_rel_name(matViewOid),
MaxMatViewSizeToAutoRecreate),
errdetail("Citus restricts automatically recreating "
"materialized views that are larger than the "
"limit, because it could take too long."),
errhint(
"Consider increasing the size limit by setting "
"citus.max_matview_size_to_auto_recreate; "
"or you can remove the limit by setting it to -1")));
}
}
}
/*
* CreateMaterializedViewDDLCommand creates the command to create materialized view.
* Note that this function doesn't support
* - Aliases
* - Storage parameters
* - Tablespace
* - WITH [NO] DATA
* options for the given materialized view. Parser functions for materialized views
* should be added to handle them.
*
* Related issue: https://github.com/citusdata/citus/issues/5968
*/
static char *
CreateMaterializedViewDDLCommand(Oid matViewOid)
{
StringInfo query = makeStringInfo();
char *viewName = get_rel_name(matViewOid);
char *schemaName = get_namespace_name(get_rel_namespace(matViewOid));
char *qualifiedViewName = quote_qualified_identifier(schemaName, viewName);
/* here we need to get the access method of the view to recreate it */
char *accessMethodName = GetAccessMethodForMatViewIfExists(matViewOid);
appendStringInfo(query, "CREATE MATERIALIZED VIEW %s ", qualifiedViewName);
if (accessMethodName)
{
appendStringInfo(query, "USING %s ", accessMethodName);
}
/*
* Set search_path to NIL so that all objects outside of pg_catalog will be
* schema-prefixed.
*/
OverrideSearchPath *overridePath = GetOverrideSearchPath(CurrentMemoryContext);
overridePath->schemas = NIL;
overridePath->addCatalog = true;
PushOverrideSearchPath(overridePath);
/*
* Push the transaction snapshot to be able to get vief definition with pg_get_viewdef
*/
PushActiveSnapshot(GetTransactionSnapshot());
Datum viewDefinitionDatum = DirectFunctionCall1(pg_get_viewdef,
ObjectIdGetDatum(matViewOid));
char *viewDefinition = TextDatumGetCString(viewDefinitionDatum);
PopActiveSnapshot();
PopOverrideSearchPath();
appendStringInfo(query, "AS %s", viewDefinition);
return query->data;
}
/*
* ReplaceTable replaces the source table with the target table.
* It moves all the rows of the source table to target table with INSERT SELECT.
@ -1789,6 +2008,19 @@ ExecuteQueryViaSPI(char *query, int SPIOK)
}
/*
* ExecuteAndLogQueryViaSPI is a wrapper around ExecuteQueryViaSPI, that logs
* the query to be executed, with the given log level.
*/
void
ExecuteAndLogQueryViaSPI(char *query, int SPIOK, int logLevel)
{
ereport(logLevel, (errmsg("executing \"%s\"", query)));
ExecuteQueryViaSPI(query, SPIOK);
}
/*
* SwitchToSequentialAndLocalExecutionIfRelationNameTooLong generates the longest shard name
* on the shards of a distributed table, and if exceeds the limit switches to sequential and

View File

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

View File

@ -26,6 +26,7 @@
#include "distributed/reference_table_utils.h"
#include "distributed/relation_access_tracking.h"
#include "distributed/worker_protocol.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
@ -512,6 +513,51 @@ ExecuteCascadeOperationForRelationIdList(List *relationIdList,
}
/*
* ExecuteAndLogUtilityCommandListInTableTypeConversionViaSPI is a wrapper function
* around ExecuteAndLogQueryViaSPI, that executes view creation commands
* with the flag InTableTypeConversionFunctionCall set to true.
*/
void
ExecuteAndLogUtilityCommandListInTableTypeConversionViaSPI(List *utilityCommandList)
{
bool oldValue = InTableTypeConversionFunctionCall;
InTableTypeConversionFunctionCall = true;
MemoryContext savedMemoryContext = CurrentMemoryContext;
PG_TRY();
{
char *utilityCommand = NULL;
foreach_ptr(utilityCommand, utilityCommandList)
{
/*
* CREATE MATERIALIZED VIEW commands need to be parsed/transformed,
* which SPI does for us.
*/
ExecuteAndLogQueryViaSPI(utilityCommand, SPI_OK_UTILITY, DEBUG1);
}
}
PG_CATCH();
{
InTableTypeConversionFunctionCall = oldValue;
MemoryContextSwitchTo(savedMemoryContext);
ErrorData *errorData = CopyErrorData();
FlushErrorState();
if (errorData->elevel != ERROR)
{
PG_RE_THROW();
}
ThrowErrorData(errorData);
}
PG_END_TRY();
InTableTypeConversionFunctionCall = oldValue;
}
/*
* ExecuteAndLogUtilityCommandList takes a list of utility commands and calls
* ExecuteAndLogUtilityCommand function for each of them.

View File

@ -81,7 +81,9 @@ static char * GetRenameShardTriggerCommand(Oid shardRelationId, char *triggerNam
uint64 shardId);
static void DropRelationTruncateTriggers(Oid relationId);
static char * GetDropTriggerCommand(Oid relationId, char *triggerName);
static void DropViewsOnTable(Oid relationId);
static List * GetRenameStatsCommandList(List *statsOidList, uint64 shardId);
static List * ReversedOidList(List *oidList);
static void AppendExplicitIndexIdsToList(Form_pg_index indexForm,
List **explicitIndexIdList,
int flags);
@ -328,6 +330,7 @@ CreateCitusLocalTable(Oid relationId, bool cascadeViaForeignKeys, bool autoConve
EnsureReferenceTablesExistOnAllNodes();
List *shellTableDDLEvents = GetShellTableDDLEventsForCitusLocalTable(relationId);
List *tableViewCreationCommands = GetViewCreationCommandsOfTable(relationId);
char *relationName = get_rel_name(relationId);
Oid relationSchemaId = get_rel_namespace(relationId);
@ -342,6 +345,12 @@ CreateCitusLocalTable(Oid relationId, bool cascadeViaForeignKeys, bool autoConve
*/
ExecuteAndLogUtilityCommandList(shellTableDDLEvents);
/*
* Execute the view creation commands with the shell table.
* Views will be distributed via FinalizeCitusLocalTableCreation below.
*/
ExecuteAndLogUtilityCommandListInTableTypeConversionViaSPI(tableViewCreationCommands);
/*
* Set shellRelationId as the relation with relationId now points
* to the shard relation.
@ -699,6 +708,9 @@ ConvertLocalTableToShard(Oid relationId)
*/
DropRelationTruncateTriggers(relationId);
/* drop views that depend on the shard table */
DropViewsOnTable(relationId);
/*
* We create INSERT|DELETE|UPDATE triggers on shard relation too.
* This is because citus prevents postgres executor to fire those
@ -1019,6 +1031,55 @@ GetDropTriggerCommand(Oid relationId, char *triggerName)
}
/*
* DropViewsOnTable drops the views that depend on the given relation.
*/
static void
DropViewsOnTable(Oid relationId)
{
List *views = GetDependingViews(relationId);
/*
* GetDependingViews returns views in the dependency order. We should drop views
* in the reversed order since dropping views can cascade to other views below.
*/
List *reverseOrderedViews = ReversedOidList(views);
Oid viewId = InvalidOid;
foreach_oid(viewId, reverseOrderedViews)
{
char *viewName = get_rel_name(viewId);
char *schemaName = get_namespace_name(get_rel_namespace(viewId));
char *qualifiedViewName = quote_qualified_identifier(schemaName, viewName);
StringInfo dropCommand = makeStringInfo();
appendStringInfo(dropCommand, "DROP %sVIEW IF EXISTS %s",
get_rel_relkind(viewId) == RELKIND_MATVIEW ? "MATERIALIZED " :
"",
qualifiedViewName);
ExecuteAndLogUtilityCommand(dropCommand->data);
}
}
/*
* ReversedOidList takes a list of oids and returns the reverse ordered version of it.
*/
static List *
ReversedOidList(List *oidList)
{
List *reversed = NIL;
Oid oid = InvalidOid;
foreach_oid(oid, oidList)
{
reversed = lcons_oid(oid, reversed);
}
return reversed;
}
/*
* GetExplicitIndexOidList returns a list of index oids defined "explicitly"
* on the relation with relationId by the "CREATE INDEX" commands. That means,

View File

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

View File

@ -10,41 +10,105 @@
#include "postgres.h"
#include "distributed/pg_version_constants.h"
#include "commands/defrem.h"
#include "catalog/namespace.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/listutils.h"
#include "distributed/metadata_cache.h"
/* placeholder for PreprocessClusterStmt */
static bool IsClusterStmtVerbose_compat(ClusterStmt *clusterStmt);
/*
* PreprocessClusterStmt first determines whether a given cluster statement involves
* a distributed table. If so (and if it is supported, i.e. no verbose), it
* creates a DDLJob to encapsulate information needed during the worker node
* portion of DDL execution before returning that DDLJob in a List. If no
* distributed table is involved, this function returns NIL.
*/
List *
PreprocessClusterStmt(Node *node, const char *clusterCommand,
ProcessUtilityContext processUtilityContext)
{
ClusterStmt *clusterStmt = castNode(ClusterStmt, node);
bool showPropagationWarning = false;
bool missingOK = false;
DDLJob *ddlJob = NULL;
/* CLUSTER all */
if (clusterStmt->relation == NULL)
{
showPropagationWarning = true;
ereport(WARNING, (errmsg("not propagating CLUSTER command to worker nodes"),
errhint("Provide a specific table in order to CLUSTER "
"distributed tables.")));
return NIL;
}
else
/* PostgreSQL uses access exclusive lock for CLUSTER command */
Oid relationId = RangeVarGetRelid(clusterStmt->relation, AccessExclusiveLock,
missingOK);
/*
* If the table does not exist, don't do anything here to allow PostgreSQL
* to throw the appropriate error or notice message later.
*/
if (!OidIsValid(relationId))
{
bool missingOK = false;
return NIL;
}
Oid relationId = RangeVarGetRelid(clusterStmt->relation, AccessShareLock,
missingOK);
/* we have no planning to do unless the table is distributed */
bool isCitusRelation = IsCitusTable(relationId);
if (!isCitusRelation)
{
return NIL;
}
if (OidIsValid(relationId))
#if PG_VERSION_NUM >= 120000
if (IsClusterStmtVerbose_compat(clusterStmt))
#else
if (clusterStmt->verbose)
#endif
{
ereport(ERROR, (errmsg("cannot run CLUSTER command"),
errdetail("VERBOSE option is currently unsupported "
"for distributed tables.")));
}
ddlJob = palloc0(sizeof(DDLJob));
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = clusterCommand;
ddlJob->taskList = DDLTaskList(relationId, clusterCommand);
return list_make1(ddlJob);
}
/*
* IsClusterStmtVerbose_compat returns true if the given statement
* is a cluster statement with verbose option.
*/
static bool
IsClusterStmtVerbose_compat(ClusterStmt *clusterStmt)
{
#if PG_VERSION_NUM < PG_VERSION_14
if (clusterStmt->options & CLUOPT_VERBOSE)
{
return true;
}
return false;
#else
DefElem *opt = NULL;
foreach_ptr(opt, clusterStmt->params)
{
if (strcmp(opt->defname, "verbose") == 0)
{
showPropagationWarning = IsCitusTable(relationId);
return defGetBoolean(opt);
}
}
if (showPropagationWarning)
{
ereport(WARNING, (errmsg("not propagating CLUSTER command to worker nodes")));
}
return NIL;
return false;
#endif
}

View File

@ -36,9 +36,6 @@
static char * CreateCollationDDLInternal(Oid collationId, Oid *collowner,
char **quotedCollationName);
static List * FilterNameListForDistributedCollations(List *objects, bool missing_ok,
List **addresses);
static bool ShouldPropagateDefineCollationStmt(void);
/*
* GetCreateCollationDDLInternal returns a CREATE COLLATE sql string for the
@ -162,267 +159,6 @@ AlterCollationOwnerObjectAddress(Node *node, bool missing_ok)
}
/*
* FilterNameListForDistributedCollations takes a list of objects to delete.
* This list is filtered against the collations that are distributed.
*
* The original list will not be touched, a new list will be created with only the objects
* in there.
*
* objectAddresses is replaced with a list of object addresses for the filtered objects.
*/
static List *
FilterNameListForDistributedCollations(List *objects, bool missing_ok,
List **objectAddresses)
{
List *result = NIL;
*objectAddresses = NIL;
List *collName = NULL;
foreach_ptr(collName, objects)
{
Oid collOid = get_collation_oid(collName, true);
ObjectAddress collAddress = { 0 };
if (!OidIsValid(collOid))
{
continue;
}
ObjectAddressSet(collAddress, CollationRelationId, collOid);
if (IsObjectDistributed(&collAddress))
{
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
*address = collAddress;
*objectAddresses = lappend(*objectAddresses, address);
result = lappend(result, collName);
}
}
return result;
}
List *
PreprocessDropCollationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
/*
* We swap the list of objects to remove during deparse so we need a reference back to
* the old list to put back
*/
List *distributedTypeAddresses = NIL;
if (!ShouldPropagate())
{
return NIL;
}
QualifyTreeNode((Node *) stmt);
List *oldCollations = stmt->objects;
List *distributedCollations =
FilterNameListForDistributedCollations(oldCollations, stmt->missing_ok,
&distributedTypeAddresses);
if (list_length(distributedCollations) <= 0)
{
/* no distributed types to drop */
return NIL;
}
/*
* managing collations can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here. MX workers don't have a notion of distributed
* collations, so we block the call.
*/
EnsureCoordinator();
/*
* remove the entries for the distributed objects on dropping
*/
ObjectAddress *addressItem = NULL;
foreach_ptr(addressItem, distributedTypeAddresses)
{
UnmarkObjectDistributed(addressItem);
}
/*
* temporary swap the lists of objects to delete with the distributed objects and
* deparse to an executable sql statement for the workers
*/
stmt->objects = distributedCollations;
char *dropStmtSql = DeparseTreeNode((Node *) stmt);
stmt->objects = oldCollations;
EnsureSequentialMode(OBJECT_COLLATION);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterCollationOwnerStmt is called for change of ownership of collations
* before the ownership is changed on the local instance.
*
* If the type for which the owner is changed is distributed we execute the change on all
* the workers to keep the type in sync across the cluster.
*/
List *
PreprocessAlterCollationOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
char *sql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_COLLATION);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterCollationOwnerStmt is invoked after the owner has been changed locally.
* Since changing the owner could result in new dependencies being found for this object
* we re-ensure all the dependencies for the collation do exist.
*
* This is solely to propagate the new owner (and all its dependencies) if it was not
* already distributed in the cluster.
*/
List *
PostprocessAlterCollationOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&collationAddress);
return NIL;
}
/*
* PreprocessRenameCollationStmt is called when the user is renaming the collation. The invocation happens
* before the statement is applied locally.
*
* As the collation already exists we have access to the ObjectAddress for the collation, this is
* used to check if the collation is distributed. If the collation is distributed the rename is
* executed on all the workers to keep the collation in sync across the cluster.
*/
List *
PreprocessRenameCollationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureCoordinator();
/* fully qualify */
QualifyTreeNode((Node *) stmt);
/* deparse sql*/
char *renameStmtSql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_COLLATION);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) renameStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterCollationSchemaStmt is executed before the statement is applied to the local
* postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers.
*/
List *
PreprocessAlterCollationSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
char *sql = DeparseTreeNode((Node *) stmt);
EnsureSequentialMode(OBJECT_COLLATION);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterCollationSchemaStmt is executed after the change has been applied locally, we
* can now use the new dependencies of the type to ensure all its dependencies exist on
* the workers before we apply the commands remotely.
*/
List *
PostprocessAlterCollationSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_COLLATION);
ObjectAddress collationAddress = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateObject(&collationAddress))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&collationAddress);
return NIL;
}
/*
* RenameCollationStmtObjectAddress returns the ObjectAddress of the type that is the object
* of the RenameStmt. Errors if missing_ok is false.
@ -544,89 +280,3 @@ DefineCollationStmtObjectAddress(Node *node, bool missing_ok)
return address;
}
/*
* PreprocessDefineCollationStmt executed before the collation has been
* created locally to ensure that if the collation create statement will
* be propagated, the node is a coordinator node
*/
List *
PreprocessDefineCollationStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
Assert(castNode(DefineStmt, node)->kind == OBJECT_COLLATION);
if (!ShouldPropagateDefineCollationStmt())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_COLLATION);
return NIL;
}
/*
* PostprocessDefineCollationStmt executed after the collation has been
* created locally and before we create it on the worker nodes.
* As we now have access to ObjectAddress of the collation that is just
* created, we can mark it as distributed to make sure that its
* dependencies exist on all nodes.
*/
List *
PostprocessDefineCollationStmt(Node *node, const char *queryString)
{
Assert(castNode(DefineStmt, node)->kind == OBJECT_COLLATION);
if (!ShouldPropagateDefineCollationStmt())
{
return NIL;
}
ObjectAddress collationAddress =
DefineCollationStmtObjectAddress(node, false);
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(
&collationAddress);
if (errMsg != NULL)
{
RaiseDeferredError(errMsg, WARNING);
return NIL;
}
EnsureDependenciesExistOnAllNodes(&collationAddress);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make1(DISABLE_DDL_PROPAGATION);
commands = list_concat(commands, CreateCollationDDLsIdempotent(
collationAddress.objectId));
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* ShouldPropagateDefineCollationStmt checks if collation define
* statement should be propagated. Don't propagate if:
* - metadata syncing if off
* - create statement should be propagated according the the ddl propagation policy
*/
static bool
ShouldPropagateDefineCollationStmt()
{
if (!ShouldPropagate())
{
return false;
}
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return false;
}
return true;
}

View File

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

View File

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

View File

@ -34,7 +34,6 @@ typedef bool (*AddressPredicate)(const ObjectAddress *);
static void EnsureDependenciesCanBeDistributed(const ObjectAddress *relationAddress);
static void ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress);
static int ObjectAddressComparator(const void *a, const void *b);
static List * GetDependencyCreateDDLCommands(const ObjectAddress *dependency);
static List * FilterObjectAddressListByPredicate(List *objectAddressList,
AddressPredicate predicate);
@ -166,11 +165,28 @@ EnsureDependenciesCanBeDistributed(const ObjectAddress *objectAddress)
/*
* ErrorIfCircularDependencyExists checks whether given object has circular dependency
* with itself via existing objects of pg_dist_object.
* ErrorIfCircularDependencyExists is a wrapper around
* DeferErrorIfCircularDependencyExists(), and throws error
* if circular dependency exists.
*/
static void
ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress)
{
DeferredErrorMessage *depError =
DeferErrorIfCircularDependencyExists(objectAddress);
if (depError != NULL)
{
RaiseDeferredError(depError, ERROR);
}
}
/*
* DeferErrorIfCircularDependencyExists checks whether given object has
* circular dependency with itself via existing objects of pg_dist_object.
*/
DeferredErrorMessage *
DeferErrorIfCircularDependencyExists(const ObjectAddress *objectAddress)
{
List *dependencies = GetAllSupportedDependenciesForObject(objectAddress);
@ -189,13 +205,18 @@ ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress)
objectDescription = getObjectDescription(objectAddress);
#endif
ereport(ERROR, (errmsg("Citus can not handle circular dependencies "
"between distributed objects"),
errdetail("\"%s\" circularly depends itself, resolve "
"circular dependency first",
objectDescription)));
StringInfo detailInfo = makeStringInfo();
appendStringInfo(detailInfo, "\"%s\" circularly depends itself, resolve "
"circular dependency first", objectDescription);
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
"Citus can not handle circular dependencies "
"between distributed objects", detailInfo->data,
NULL);
}
}
return NULL;
}
@ -289,7 +310,7 @@ GetDistributableDependenciesForObject(const ObjectAddress *target)
* GetDependencyCreateDDLCommands returns a list (potentially empty or NIL) of ddl
* commands to execute on a worker to create the object.
*/
static List *
List *
GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
{
switch (getObjectClass(dependency))
@ -349,6 +370,14 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
return DDLCommandsForSequence(dependency->objectId, sequenceOwnerName);
}
if (relKind == RELKIND_VIEW)
{
char *createViewCommand = CreateViewDDLCommand(dependency->objectId);
char *alterViewOwnerCommand = AlterViewOwnerCommand(dependency->objectId);
return list_make2(createViewCommand, alterViewOwnerCommand);
}
/* if this relation is not supported, break to the error at the end */
break;
}
@ -358,6 +387,15 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
return CreateCollationDDLsIdempotent(dependency->objectId);
}
case OCLASS_CONSTRAINT:
{
/*
* Constraints can only be reached by domains, they resolve functions.
* Constraints themself are recreated by the domain recreation.
*/
return NIL;
}
case OCLASS_DATABASE:
{
List *databaseDDLCommands = NIL;
@ -374,7 +412,10 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
case OCLASS_PROC:
{
return CreateFunctionDDLCommandsIdempotent(dependency);
List *DDLCommands = CreateFunctionDDLCommandsIdempotent(dependency);
List *grantDDLCommands = GrantOnFunctionDDLCommands(dependency->objectId);
DDLCommands = list_concat(DDLCommands, grantDDLCommands);
return DDLCommands;
}
case OCLASS_ROLE:
@ -417,7 +458,13 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
case OCLASS_FOREIGN_SERVER:
{
return GetForeignServerCreateDDLCommand(dependency->objectId);
Oid serverId = dependency->objectId;
List *DDLCommands = GetForeignServerCreateDDLCommand(serverId);
List *grantDDLCommands = GrantOnForeignServerDDLCommands(serverId);
DDLCommands = list_concat(DDLCommands, grantDDLCommands);
return DDLCommands;
}
default:

View File

@ -16,6 +16,7 @@
#include "distributed/deparser.h"
#include "distributed/pg_version_constants.h"
#include "distributed/version_compat.h"
#include "distributed/commands/utility_hook.h"
static DistributeObjectOps NoDistributeOps = {
.deparse = NULL,
@ -28,31 +29,34 @@ static DistributeObjectOps NoDistributeOps = {
static DistributeObjectOps Aggregate_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Aggregate_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Aggregate_Define = {
.deparse = NULL,
.qualify = QualifyDefineAggregateStmt,
.preprocess = PreprocessDefineAggregateStmt,
.postprocess = PostprocessDefineAggregateStmt,
.preprocess = NULL,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_AGGREGATE,
.address = DefineAggregateStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Aggregate_Drop = {
.deparse = DeparseDropFunctionStmt,
.qualify = NULL,
.preprocess = PreprocessDropFunctionStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -60,16 +64,18 @@ static DistributeObjectOps Aggregate_Drop = {
static DistributeObjectOps Aggregate_Rename = {
.deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Any_AlterEnum = {
.deparse = DeparseAlterEnumStmt,
.qualify = QualifyAlterEnumStmt,
.preprocess = PreprocessAlterEnumStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TYPE,
.address = AlterEnumStmtObjectAddress,
.markDistributed = false,
};
@ -92,9 +98,10 @@ static DistributeObjectOps Any_AlterExtensionContents = {
static DistributeObjectOps Any_AlterForeignServer = {
.deparse = DeparseAlterForeignServerStmt,
.qualify = NULL,
.preprocess = PreprocessAlterForeignServerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.objectType = OBJECT_FOREIGN_SERVER,
.address = AlterForeignServerStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Any_AlterFunction = {
@ -148,16 +155,29 @@ static DistributeObjectOps Any_Cluster = {
static DistributeObjectOps Any_CompositeType = {
.deparse = DeparseCompositeTypeStmt,
.qualify = QualifyCompositeTypeStmt,
.preprocess = PreprocessCompositeTypeStmt,
.postprocess = PostprocessCompositeTypeStmt,
.preprocess = NULL,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TYPE,
.featureFlag = &EnableCreateTypePropagation,
.address = CompositeTypeStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Any_CreateDomain = {
.deparse = DeparseCreateDomainStmt,
.qualify = QualifyCreateDomainStmt,
.preprocess = NULL,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_DOMAIN,
.address = CreateDomainStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Any_CreateEnum = {
.deparse = DeparseCreateEnumStmt,
.qualify = QualifyCreateEnumStmt,
.preprocess = PreprocessCreateEnumStmt,
.postprocess = PostprocessCreateEnumStmt,
.preprocess = NULL,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TYPE,
.featureFlag = &EnableCreateTypePropagation,
.address = CreateEnumStmtObjectAddress,
.markDistributed = true,
};
@ -177,10 +197,34 @@ static DistributeObjectOps Any_CreateFunction = {
.address = CreateFunctionStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Any_View = {
.deparse = NULL,
.qualify = NULL,
.preprocess = PreprocessViewStmt,
.postprocess = PostprocessViewStmt,
.address = ViewStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Any_CreatePolicy = {
.deparse = NULL,
.qualify = NULL,
.preprocess = PreprocessCreatePolicyStmt,
.preprocess = NULL,
.postprocess = PostprocessCreatePolicyStmt,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Any_CreateRole = {
.deparse = DeparseCreateRoleStmt,
.qualify = NULL,
.preprocess = PreprocessCreateRoleStmt,
.postprocess = NULL,
.address = CreateRoleStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Any_DropRole = {
.deparse = DeparseDropRoleStmt,
.qualify = NULL,
.preprocess = PreprocessDropRoleStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -188,8 +232,9 @@ static DistributeObjectOps Any_CreatePolicy = {
static DistributeObjectOps Any_CreateForeignServer = {
.deparse = DeparseCreateForeignServerStmt,
.qualify = NULL,
.preprocess = PreprocessCreateForeignServerStmt,
.postprocess = PostprocessCreateForeignServerStmt,
.preprocess = NULL,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_FOREIGN_SERVER,
.address = CreateForeignServerStmtObjectAddress,
.markDistributed = true,
};
@ -225,6 +270,14 @@ static DistributeObjectOps Any_Grant = {
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Any_GrantRole = {
.deparse = DeparseGrantRoleStmt,
.qualify = NULL,
.preprocess = PreprocessGrantRoleStmt,
.postprocess = PostprocessGrantRoleStmt,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Any_Index = {
.deparse = NULL,
.qualify = NULL,
@ -260,31 +313,34 @@ static DistributeObjectOps Attribute_Rename = {
static DistributeObjectOps Collation_AlterObjectSchema = {
.deparse = DeparseAlterCollationSchemaStmt,
.qualify = QualifyAlterCollationSchemaStmt,
.preprocess = PreprocessAlterCollationSchemaStmt,
.postprocess = PostprocessAlterCollationSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_COLLATION,
.address = AlterCollationSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Collation_AlterOwner = {
.deparse = DeparseAlterCollationOwnerStmt,
.qualify = QualifyAlterCollationOwnerStmt,
.preprocess = PreprocessAlterCollationOwnerStmt,
.postprocess = PostprocessAlterCollationOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_COLLATION,
.address = AlterCollationOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Collation_Define = {
.deparse = NULL,
.qualify = NULL,
.preprocess = PreprocessDefineCollationStmt,
.postprocess = PostprocessDefineCollationStmt,
.preprocess = NULL,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_COLLATION,
.address = DefineCollationStmtObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps Collation_Drop = {
.deparse = DeparseDropCollationStmt,
.qualify = QualifyDropCollationStmt,
.preprocess = PreprocessDropCollationStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -292,19 +348,76 @@ static DistributeObjectOps Collation_Drop = {
static DistributeObjectOps Collation_Rename = {
.deparse = DeparseRenameCollationStmt,
.qualify = QualifyRenameCollationStmt,
.preprocess = PreprocessRenameCollationStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_COLLATION,
.address = RenameCollationStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Database_AlterOwner = {
.deparse = DeparseAlterDatabaseOwnerStmt,
.qualify = NULL,
.preprocess = PreprocessAlterDatabaseOwnerStmt,
.postprocess = PostprocessAlterDatabaseOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DATABASE,
.featureFlag = &EnableAlterDatabaseOwner,
.address = AlterDatabaseOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Domain_Alter = {
.deparse = DeparseAlterDomainStmt,
.qualify = QualifyAlterDomainStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DOMAIN,
.address = AlterDomainStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Domain_AlterObjectSchema = {
.deparse = DeparseAlterDomainSchemaStmt,
.qualify = QualifyAlterDomainSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DOMAIN,
.address = AlterTypeSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Domain_AlterOwner = {
.deparse = DeparseAlterDomainOwnerStmt,
.qualify = QualifyAlterDomainOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_DOMAIN,
.address = AlterDomainOwnerStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Domain_Drop = {
.deparse = DeparseDropDomainStmt,
.qualify = QualifyDropDomainStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Domain_Rename = {
.deparse = DeparseRenameDomainStmt,
.qualify = QualifyRenameDomainStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_DOMAIN,
.address = RenameDomainStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Domain_RenameConstraint = {
.deparse = DeparseDomainRenameConstraintStmt,
.qualify = QualifyDomainRenameConstraintStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_DOMAIN,
.address = DomainRenameConstraintStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Extension_AlterObjectSchema = {
.deparse = DeparseAlterExtensionSchemaStmt,
.qualify = NULL,
@ -321,10 +434,26 @@ static DistributeObjectOps Extension_Drop = {
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps FDW_Grant = {
.deparse = DeparseGrantOnFDWStmt,
.qualify = NULL,
.preprocess = PreprocessGrantOnFDWStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps ForeignServer_Drop = {
.deparse = DeparseDropForeignServerStmt,
.qualify = NULL,
.preprocess = PreprocessDropForeignServerStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps ForeignServer_Grant = {
.deparse = DeparseGrantOnForeignServerStmt,
.qualify = NULL,
.preprocess = PreprocessGrantOnForeignServerStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -332,16 +461,18 @@ static DistributeObjectOps ForeignServer_Drop = {
static DistributeObjectOps ForeignServer_Rename = {
.deparse = DeparseAlterForeignServerRenameStmt,
.qualify = NULL,
.preprocess = PreprocessRenameForeignServerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.objectType = OBJECT_FOREIGN_SERVER,
.address = RenameForeignServerStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps ForeignServer_AlterOwner = {
.deparse = DeparseAlterForeignServerOwnerStmt,
.qualify = NULL,
.preprocess = PreprocessAlterForeignServerOwnerStmt,
.postprocess = PostprocessAlterForeignServerOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FOREIGN_SERVER,
.address = AlterForeignServerOwnerStmtObjectAddress,
.markDistributed = false,
};
@ -364,23 +495,41 @@ static DistributeObjectOps Function_AlterObjectDepends = {
static DistributeObjectOps Function_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Function_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Function_Drop = {
.deparse = DeparseDropFunctionStmt,
.qualify = NULL,
.preprocess = PreprocessDropFunctionStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Function_Grant = {
.deparse = DeparseGrantOnFunctionStmt,
.qualify = NULL,
.preprocess = PreprocessGrantOnFunctionStmt,
.postprocess = PostprocessGrantOnFunctionStmt,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps View_Drop = {
.deparse = DeparseDropViewStmt,
.qualify = QualifyDropViewStmt,
.preprocess = PreprocessDropViewStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -388,8 +537,9 @@ static DistributeObjectOps Function_Drop = {
static DistributeObjectOps Function_Rename = {
.deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress,
.markDistributed = false,
};
@ -428,32 +578,43 @@ static DistributeObjectOps Procedure_AlterObjectDepends = {
static DistributeObjectOps Procedure_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Procedure_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Procedure_Drop = {
.deparse = DeparseDropFunctionStmt,
.qualify = NULL,
.preprocess = PreprocessDropFunctionStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Procedure_Grant = {
.deparse = DeparseGrantOnFunctionStmt,
.qualify = NULL,
.preprocess = PreprocessGrantOnFunctionStmt,
.postprocess = PostprocessGrantOnFunctionStmt,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Procedure_Rename = {
.deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress,
.markDistributed = false,
};
@ -491,12 +652,20 @@ static DistributeObjectOps Sequence_AlterOwner = {
};
static DistributeObjectOps Sequence_Drop = {
.deparse = DeparseDropSequenceStmt,
.qualify = NULL,
.qualify = QualifyDropSequenceStmt,
.preprocess = PreprocessDropSequenceStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Sequence_Grant = {
.deparse = DeparseGrantOnSequenceStmt,
.qualify = QualifyGrantOnSequenceStmt,
.preprocess = PreprocessGrantOnSequenceStmt,
.postprocess = PostprocessGrantOnSequenceStmt,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Sequence_Rename = {
.deparse = DeparseRenameSequenceStmt,
.qualify = QualifyRenameSequenceStmt,
@ -508,32 +677,36 @@ static DistributeObjectOps Sequence_Rename = {
static DistributeObjectOps TextSearchConfig_Alter = {
.deparse = DeparseAlterTextSearchConfigurationStmt,
.qualify = QualifyAlterTextSearchConfigurationStmt,
.preprocess = PreprocessAlterTextSearchConfigurationStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TSCONFIGURATION,
.address = AlterTextSearchConfigurationStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps TextSearchConfig_AlterObjectSchema = {
.deparse = DeparseAlterTextSearchConfigurationSchemaStmt,
.qualify = QualifyAlterTextSearchConfigurationSchemaStmt,
.preprocess = PreprocessAlterTextSearchConfigurationSchemaStmt,
.postprocess = PostprocessAlterTextSearchConfigurationSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSCONFIGURATION,
.address = AlterTextSearchConfigurationSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps TextSearchConfig_AlterOwner = {
.deparse = DeparseAlterTextSearchConfigurationOwnerStmt,
.qualify = QualifyAlterTextSearchConfigurationOwnerStmt,
.preprocess = PreprocessAlterTextSearchConfigurationOwnerStmt,
.postprocess = PostprocessAlterTextSearchConfigurationOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSCONFIGURATION,
.address = AlterTextSearchConfigurationOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps TextSearchConfig_Comment = {
.deparse = DeparseTextSearchConfigurationCommentStmt,
.qualify = QualifyTextSearchConfigurationCommentStmt,
.preprocess = PreprocessTextSearchConfigurationCommentStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TSCONFIGURATION,
.address = TextSearchConfigurationCommentObjectAddress,
.markDistributed = false,
};
@ -541,14 +714,15 @@ static DistributeObjectOps TextSearchConfig_Define = {
.deparse = DeparseCreateTextSearchConfigurationStmt,
.qualify = NULL,
.preprocess = NULL,
.postprocess = PostprocessCreateTextSearchConfigurationStmt,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TSCONFIGURATION,
.address = CreateTextSearchConfigurationObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps TextSearchConfig_Drop = {
.deparse = DeparseDropTextSearchConfigurationStmt,
.qualify = QualifyDropTextSearchConfigurationStmt,
.preprocess = PreprocessDropTextSearchConfigurationStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -556,40 +730,45 @@ static DistributeObjectOps TextSearchConfig_Drop = {
static DistributeObjectOps TextSearchConfig_Rename = {
.deparse = DeparseRenameTextSearchConfigurationStmt,
.qualify = QualifyRenameTextSearchConfigurationStmt,
.preprocess = PreprocessRenameTextSearchConfigurationStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TSCONFIGURATION,
.address = RenameTextSearchConfigurationStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps TextSearchDict_Alter = {
.deparse = DeparseAlterTextSearchDictionaryStmt,
.qualify = QualifyAlterTextSearchDictionaryStmt,
.preprocess = PreprocessAlterTextSearchDictionaryStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TSDICTIONARY,
.address = AlterTextSearchDictionaryStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps TextSearchDict_AlterObjectSchema = {
.deparse = DeparseAlterTextSearchDictionarySchemaStmt,
.qualify = QualifyAlterTextSearchDictionarySchemaStmt,
.preprocess = PreprocessAlterTextSearchDictionarySchemaStmt,
.postprocess = PostprocessAlterTextSearchDictionarySchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSDICTIONARY,
.address = AlterTextSearchDictionarySchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps TextSearchDict_AlterOwner = {
.deparse = DeparseAlterTextSearchDictionaryOwnerStmt,
.qualify = QualifyAlterTextSearchDictionaryOwnerStmt,
.preprocess = PreprocessAlterTextSearchDictionaryOwnerStmt,
.postprocess = PostprocessAlterTextSearchDictionaryOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TSDICTIONARY,
.address = AlterTextSearchDictOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps TextSearchDict_Comment = {
.deparse = DeparseTextSearchDictionaryCommentStmt,
.qualify = QualifyTextSearchDictionaryCommentStmt,
.preprocess = PreprocessTextSearchDictionaryCommentStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TSDICTIONARY,
.address = TextSearchDictCommentObjectAddress,
.markDistributed = false,
};
@ -597,14 +776,15 @@ static DistributeObjectOps TextSearchDict_Define = {
.deparse = DeparseCreateTextSearchDictionaryStmt,
.qualify = NULL,
.preprocess = NULL,
.postprocess = PostprocessCreateTextSearchDictionaryStmt,
.postprocess = PostprocessCreateDistributedObjectFromCatalogStmt,
.objectType = OBJECT_TSDICTIONARY,
.address = CreateTextSearchDictObjectAddress,
.markDistributed = true,
};
static DistributeObjectOps TextSearchDict_Drop = {
.deparse = DeparseDropTextSearchDictionaryStmt,
.qualify = QualifyDropTextSearchDictionaryStmt,
.preprocess = PreprocessDropTextSearchDictionaryStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -612,8 +792,9 @@ static DistributeObjectOps TextSearchDict_Drop = {
static DistributeObjectOps TextSearchDict_Rename = {
.deparse = DeparseRenameTextSearchDictionaryStmt,
.qualify = QualifyRenameTextSearchDictionaryStmt,
.preprocess = PreprocessRenameTextSearchDictionaryStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TSDICTIONARY,
.address = RenameTextSearchDictionaryStmtObjectAddress,
.markDistributed = false,
};
@ -628,32 +809,43 @@ static DistributeObjectOps Trigger_AlterObjectDepends = {
static DistributeObjectOps Routine_AlterObjectSchema = {
.deparse = DeparseAlterFunctionSchemaStmt,
.qualify = QualifyAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterFunctionSchemaStmt,
.postprocess = PostprocessAlterFunctionSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Routine_AlterOwner = {
.deparse = DeparseAlterFunctionOwnerStmt,
.qualify = QualifyAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterFunctionOwnerStmt,
.postprocess = PostprocessAlterFunctionOwnerStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_FUNCTION,
.address = AlterFunctionOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Routine_Drop = {
.deparse = DeparseDropFunctionStmt,
.qualify = NULL,
.preprocess = PreprocessDropFunctionStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Routine_Grant = {
.deparse = DeparseGrantOnFunctionStmt,
.qualify = NULL,
.preprocess = PreprocessGrantOnFunctionStmt,
.postprocess = PostprocessGrantOnFunctionStmt,
.address = NULL,
.markDistributed = false,
};
static DistributeObjectOps Routine_Rename = {
.deparse = DeparseRenameFunctionStmt,
.qualify = QualifyRenameFunctionStmt,
.preprocess = PreprocessRenameFunctionStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_FUNCTION,
.address = RenameFunctionStmtObjectAddress,
.markDistributed = false,
};
@ -676,8 +868,9 @@ static DistributeObjectOps Schema_Grant = {
static DistributeObjectOps Schema_Rename = {
.deparse = DeparseAlterSchemaRenameStmt,
.qualify = NULL,
.preprocess = PreprocessAlterSchemaRenameStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_SCHEMA,
.address = AlterSchemaRenameStmtObjectAddress,
.markDistributed = false,
};
@ -750,31 +943,66 @@ static DistributeObjectOps Table_Drop = {
static DistributeObjectOps Type_AlterObjectSchema = {
.deparse = DeparseAlterTypeSchemaStmt,
.qualify = QualifyAlterTypeSchemaStmt,
.preprocess = PreprocessAlterTypeSchemaStmt,
.postprocess = PostprocessAlterTypeSchemaStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TYPE,
.address = AlterTypeSchemaStmtObjectAddress,
.markDistributed = false,
};
/*
* PreprocessAlterViewSchemaStmt and PostprocessAlterViewSchemaStmt functions can be called
* internally by ALTER TABLE view_name SET SCHEMA ... if the ALTER TABLE command targets a
* view. In other words ALTER VIEW view_name SET SCHEMA will use the View_AlterObjectSchema
* but ALTER TABLE view_name SET SCHEMA will use Table_AlterObjectSchema but call process
* functions of View_AlterObjectSchema internally.
*/
static DistributeObjectOps View_AlterObjectSchema = {
.deparse = DeparseAlterViewSchemaStmt,
.qualify = QualifyAlterViewSchemaStmt,
.preprocess = PreprocessAlterViewSchemaStmt,
.postprocess = PostprocessAlterViewSchemaStmt,
.address = AlterViewSchemaStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Type_AlterOwner = {
.deparse = DeparseAlterTypeOwnerStmt,
.qualify = QualifyAlterTypeOwnerStmt,
.preprocess = PreprocessAlterTypeOwnerStmt,
.postprocess = NULL,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = PostprocessAlterDistributedObjectStmt,
.objectType = OBJECT_TYPE,
.address = AlterTypeOwnerObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Type_AlterTable = {
.deparse = DeparseAlterTypeStmt,
.qualify = QualifyAlterTypeStmt,
.preprocess = PreprocessAlterTypeStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TYPE,
.address = AlterTypeStmtObjectAddress,
.markDistributed = false,
};
/*
* PreprocessAlterViewStmt and PostprocessAlterViewStmt functions can be called internally
* by ALTER TABLE view_name SET/RESET ... if the ALTER TABLE command targets a view. In
* other words ALTER VIEW view_name SET/RESET will use the View_AlterView
* but ALTER TABLE view_name SET/RESET will use Table_AlterTable but call process
* functions of View_AlterView internally.
*/
static DistributeObjectOps View_AlterView = {
.deparse = DeparseAlterViewStmt,
.qualify = QualifyAlterViewStmt,
.preprocess = PreprocessAlterViewStmt,
.postprocess = PostprocessAlterViewStmt,
.address = AlterViewStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Type_Drop = {
.deparse = DeparseDropTypeStmt,
.qualify = NULL,
.preprocess = PreprocessDropTypeStmt,
.preprocess = PreprocessDropDistributedObjectStmt,
.postprocess = NULL,
.address = NULL,
.markDistributed = false,
@ -790,11 +1018,27 @@ static DistributeObjectOps Trigger_Drop = {
static DistributeObjectOps Type_Rename = {
.deparse = DeparseRenameTypeStmt,
.qualify = QualifyRenameTypeStmt,
.preprocess = PreprocessRenameTypeStmt,
.preprocess = PreprocessAlterDistributedObjectStmt,
.postprocess = NULL,
.objectType = OBJECT_TYPE,
.address = RenameTypeStmtObjectAddress,
.markDistributed = false,
};
/*
* PreprocessRenameViewStmt function can be called internally by ALTER TABLE view_name
* RENAME ... if the ALTER TABLE command targets a view or a view's column. In other words
* ALTER VIEW view_name RENAME will use the View_Rename but ALTER TABLE view_name RENAME
* will use Any_Rename but call process functions of View_Rename internally.
*/
static DistributeObjectOps View_Rename = {
.deparse = DeparseRenameViewStmt,
.qualify = QualifyRenameViewStmt,
.preprocess = PreprocessRenameViewStmt,
.postprocess = NULL,
.address = RenameViewStmtObjectAddress,
.markDistributed = false,
};
static DistributeObjectOps Trigger_Rename = {
.deparse = NULL,
.qualify = NULL,
@ -815,6 +1059,11 @@ GetDistributeObjectOps(Node *node)
{
switch (nodeTag(node))
{
case T_AlterDomainStmt:
{
return &Domain_Alter;
}
case T_AlterEnumStmt:
{
return &Any_AlterEnum;
@ -887,6 +1136,11 @@ GetDistributeObjectOps(Node *node)
return &Collation_AlterObjectSchema;
}
case OBJECT_DOMAIN:
{
return &Domain_AlterObjectSchema;
}
case OBJECT_EXTENSION:
{
return &Extension_AlterObjectSchema;
@ -938,6 +1192,11 @@ GetDistributeObjectOps(Node *node)
return &Type_AlterObjectSchema;
}
case OBJECT_VIEW:
{
return &View_AlterObjectSchema;
}
default:
{
return &NoDistributeOps;
@ -965,6 +1224,11 @@ GetDistributeObjectOps(Node *node)
return &Database_AlterOwner;
}
case OBJECT_DOMAIN:
{
return &Domain_AlterOwner;
}
case OBJECT_FOREIGN_SERVER:
{
return &ForeignServer_AlterOwner;
@ -1069,6 +1333,11 @@ GetDistributeObjectOps(Node *node)
return &Sequence_AlterOwner;
}
case OBJECT_VIEW:
{
return &View_AlterView;
}
default:
{
return &NoDistributeOps;
@ -1123,6 +1392,11 @@ GetDistributeObjectOps(Node *node)
return &Any_CompositeType;
}
case T_CreateDomainStmt:
{
return &Any_CreateDomain;
}
case T_CreateEnumStmt:
{
return &Any_CreateEnum;
@ -1148,6 +1422,11 @@ GetDistributeObjectOps(Node *node)
return &Any_CreatePolicy;
}
case T_CreateRoleStmt:
{
return &Any_CreateRole;
}
case T_CreateSchemaStmt:
{
return &Any_CreateSchema;
@ -1195,6 +1474,11 @@ GetDistributeObjectOps(Node *node)
}
}
case T_DropRoleStmt:
{
return &Any_DropRole;
}
case T_DropStmt:
{
DropStmt *stmt = castNode(DropStmt, node);
@ -1210,6 +1494,11 @@ GetDistributeObjectOps(Node *node)
return &Collation_Drop;
}
case OBJECT_DOMAIN:
{
return &Domain_Drop;
}
case OBJECT_EXTENSION:
{
return &Extension_Drop;
@ -1285,6 +1574,11 @@ GetDistributeObjectOps(Node *node)
return &Trigger_Drop;
}
case OBJECT_VIEW:
{
return &View_Drop;
}
default:
{
return &NoDistributeOps;
@ -1292,6 +1586,11 @@ GetDistributeObjectOps(Node *node)
}
}
case T_GrantRoleStmt:
{
return &Any_GrantRole;
}
case T_GrantStmt:
{
GrantStmt *stmt = castNode(GrantStmt, node);
@ -1302,6 +1601,36 @@ GetDistributeObjectOps(Node *node)
return &Schema_Grant;
}
case OBJECT_SEQUENCE:
{
return &Sequence_Grant;
}
case OBJECT_FDW:
{
return &FDW_Grant;
}
case OBJECT_FOREIGN_SERVER:
{
return &ForeignServer_Grant;
}
case OBJECT_FUNCTION:
{
return &Function_Grant;
}
case OBJECT_PROCEDURE:
{
return &Procedure_Grant;
}
case OBJECT_ROUTINE:
{
return &Routine_Grant;
}
default:
{
return &Any_Grant;
@ -1314,6 +1643,11 @@ GetDistributeObjectOps(Node *node)
return &Any_Index;
}
case T_ViewStmt:
{
return &Any_View;
}
case T_ReindexStmt:
{
return &Any_Reindex;
@ -1339,6 +1673,16 @@ GetDistributeObjectOps(Node *node)
return &Collation_Rename;
}
case OBJECT_DOMAIN:
{
return &Domain_Rename;
}
case OBJECT_DOMCONSTRAINT:
{
return &Domain_RenameConstraint;
}
case OBJECT_FOREIGN_SERVER:
{
return &ForeignServer_Rename;
@ -1394,6 +1738,27 @@ GetDistributeObjectOps(Node *node)
return &Trigger_Rename;
}
case OBJECT_VIEW:
{
return &View_Rename;
}
case OBJECT_COLUMN:
{
switch (stmt->relationType)
{
case OBJECT_VIEW:
{
return &View_Rename;
}
default:
{
return &Any_Rename;
}
}
}
default:
{
return &Any_Rename;

View File

@ -0,0 +1,328 @@
/*-------------------------------------------------------------------------
*
* domain.c
* Hooks to handle the creation, altering and removal of domains.
* These hooks are responsible for duplicating the changes to the
* workers nodes.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/regproc.h"
#include "utils/syscache.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/metadata/distobject.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata_utility.h"
#include "distributed/multi_executor.h"
#include "distributed/worker_create_or_replace.h"
#include "distributed/worker_transaction.h"
static CollateClause * MakeCollateClauseFromOid(Oid collationOid);
static ObjectAddress GetDomainAddressByName(TypeName *domainName, bool missing_ok);
/*
* GetDomainAddressByName returns the ObjectAddress of the domain identified by
* domainName. When missing_ok is true the object id part of the ObjectAddress can be
* InvalidOid. When missing_ok is false this function will raise an error instead when the
* domain can't be found.
*/
static ObjectAddress
GetDomainAddressByName(TypeName *domainName, bool missing_ok)
{
ObjectAddress address = { 0 };
Oid domainOid = LookupTypeNameOid(NULL, domainName, missing_ok);
ObjectAddressSet(address, TypeRelationId, domainOid);
return address;
}
/*
* RecreateDomainStmt returns a CreateDomainStmt pointer where the statement represents
* the creation of the domain to recreate the domain on a different postgres node based on
* the current representation in the local catalog.
*/
CreateDomainStmt *
RecreateDomainStmt(Oid domainOid)
{
CreateDomainStmt *stmt = makeNode(CreateDomainStmt);
stmt->domainname = stringToQualifiedNameList(format_type_be_qualified(domainOid));
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainOid));
if (!HeapTupleIsValid(tup))
{
elog(ERROR, "cache lookup failed for type %u", domainOid);
}
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
if (typTup->typtype != TYPTYPE_DOMAIN)
{
elog(ERROR, "type is not a domain type");
}
stmt->typeName = makeTypeNameFromOid(typTup->typbasetype, typTup->typtypmod);
if (OidIsValid(typTup->typcollation))
{
stmt->collClause = MakeCollateClauseFromOid(typTup->typcollation);
}
/*
* typdefault and typdefaultbin are potentially null, so don't try to
* access 'em as struct fields. Must do it the hard way with
* SysCacheGetAttr.
*/
bool isNull = false;
Datum typeDefaultDatum = SysCacheGetAttr(TYPEOID,
tup,
Anum_pg_type_typdefaultbin,
&isNull);
if (!isNull)
{
/* when not null there is default value which we should add as a constraint */
Constraint *constraint = makeNode(Constraint);
constraint->contype = CONSTR_DEFAULT;
constraint->cooked_expr = TextDatumGetCString(typeDefaultDatum);
stmt->constraints = lappend(stmt->constraints, constraint);
}
/* NOT NULL constraints are non-named on the actual type */
if (typTup->typnotnull)
{
Constraint *constraint = makeNode(Constraint);
constraint->contype = CONSTR_NOTNULL;
stmt->constraints = lappend(stmt->constraints, constraint);
}
/* lookup and look all constraints to add them to the CreateDomainStmt */
Relation conRel = table_open(ConstraintRelationId, AccessShareLock);
/* Look for CHECK Constraints on this domain */
ScanKeyData key[1];
ScanKeyInit(&key[0],
Anum_pg_constraint_contypid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(domainOid));
SysScanDesc scan = systable_beginscan(conRel, ConstraintTypidIndexId, true, NULL, 1,
key);
HeapTuple conTup = NULL;
while (HeapTupleIsValid(conTup = systable_getnext(scan)))
{
Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup);
if (c->contype != CONSTRAINT_CHECK)
{
/* Ignore non-CHECK constraints, shouldn't be any */
continue;
}
/*
* We create a constraint, completely ignoring c->convalidated because we can't
* create a domain with an invalidated constraint. Once a constraint is added to
* a domain -even non valid-, all new data is validated. Meaning, creating a
* domain with a non-valid constraint doesn't make any sense.
*
* Given it will be too hard to defer the creation of a constraint till we
* validate the constraint on the coordinator we will simply create the
* non-validated constraint to ad hear to validating all new data.
*
* An edgecase here would be when moving existing data, that hasn't been validated
* before to an other node. This behaviour is consistent with sending it to an
* already existing node (that has the constraint created but not validated) and a
* new node.
*/
Constraint *constraint = makeNode(Constraint);
constraint->conname = pstrdup(NameStr(c->conname));
constraint->contype = CONSTR_CHECK; /* we only come here with check constraints */
/* Not expecting conbin to be NULL, but we'll test for it anyway */
Datum conbin = heap_getattr(conTup, Anum_pg_constraint_conbin, conRel->rd_att,
&isNull);
if (isNull)
{
elog(ERROR, "domain \"%s\" constraint \"%s\" has NULL conbin",
NameStr(typTup->typname), NameStr(c->conname));
}
/*
* The conbin containes the cooked expression from when the constraint was
* inserted into the catalog. We store it here for the deparser to distinguish
* between cooked expressions and raw expressions.
*
* There is no supported way to go from a cooked expression to a raw expression.
*/
constraint->cooked_expr = TextDatumGetCString(conbin);
stmt->constraints = lappend(stmt->constraints, constraint);
}
systable_endscan(scan);
table_close(conRel, NoLock);
ReleaseSysCache(tup);
QualifyTreeNode((Node *) stmt);
return stmt;
}
/*
* MakeCollateClauseFromOid returns a CollateClause describing the COLLATE segment of a
* CREATE DOMAIN statement based on the Oid of the collation used for the domain.
*/
static CollateClause *
MakeCollateClauseFromOid(Oid collationOid)
{
CollateClause *collateClause = makeNode(CollateClause);
ObjectAddress collateAddress = { 0 };
ObjectAddressSet(collateAddress, CollationRelationId, collationOid);
List *objName = NIL;
List *objArgs = NIL;
#if PG_VERSION_NUM >= PG_VERSION_14
getObjectIdentityParts(&collateAddress, &objName, &objArgs, false);
#else
getObjectIdentityParts(&collateAddress, &objName, &objArgs);
#endif
char *name = NULL;
foreach_ptr(name, objName)
{
collateClause->collname = lappend(collateClause->collname, makeString(name));
}
collateClause->location = -1;
return collateClause;
}
/*
* CreateDomainStmtObjectAddress returns the ObjectAddress of the domain that would be
* created by the statement. When missing_ok is false the function will raise an error if
* the domain cannot be found in the local catalog.
*/
ObjectAddress
CreateDomainStmtObjectAddress(Node *node, bool missing_ok)
{
CreateDomainStmt *stmt = castNode(CreateDomainStmt, node);
TypeName *typeName = makeTypeNameFromNameList(stmt->domainname);
Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok);
ObjectAddress address = { 0 };
ObjectAddressSet(address, TypeRelationId, typeOid);
return address;
}
/*
* AlterDomainStmtObjectAddress returns the ObjectAddress of the domain being altered.
* When missing_ok is false this function will raise an error when the domain is not
* found.
*/
ObjectAddress
AlterDomainStmtObjectAddress(Node *node, bool missing_ok)
{
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
TypeName *domainName = makeTypeNameFromNameList(stmt->typeName);
return GetDomainAddressByName(domainName, missing_ok);
}
/*
* DomainRenameConstraintStmtObjectAddress returns the ObjectAddress of the domain for
* which the constraint is being renamed. When missing_ok this function will raise an
* error if the domain cannot be found.
*/
ObjectAddress
DomainRenameConstraintStmtObjectAddress(Node *node, bool missing_ok)
{
RenameStmt *stmt = castNode(RenameStmt, node);
TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object));
return GetDomainAddressByName(domainName, missing_ok);
}
/*
* AlterDomainOwnerStmtObjectAddress returns the ObjectAddress for which the owner is
* being changed. When missing_ok is false this function will raise an error if the domain
* cannot be found.
*/
ObjectAddress
AlterDomainOwnerStmtObjectAddress(Node *node, bool missing_ok)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_DOMAIN);
TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object));
return GetDomainAddressByName(domainName, missing_ok);
}
/*
* RenameDomainStmtObjectAddress returns the ObjectAddress of the domain being renamed.
* When missing_ok is false this function will raise an error when the domain cannot be
* found.
*/
ObjectAddress
RenameDomainStmtObjectAddress(Node *node, bool missing_ok)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_DOMAIN);
TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object));
return GetDomainAddressByName(domainName, missing_ok);
}
/*
* get_constraint_typid returns the contypid of a constraint. This field is only set for
* constraints on domain types. Returns InvalidOid if conoid is an invalid constraint, as
* well as for constraints that are not on domain types.
*/
Oid
get_constraint_typid(Oid conoid)
{
HeapTuple tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conoid));
if (HeapTupleIsValid(tp))
{
Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
Oid result = contup->contypid;
ReleaseSysCache(tp);
return result;
}
else
{
return InvalidOid;
}
}

View File

@ -10,8 +10,12 @@
#include "postgres.h"
#include "access/genam.h"
#include "citus_version.h"
#include "catalog/dependency.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_extension_d.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "commands/defrem.h"
#include "commands/extension.h"
#include "distributed/citus_ruleutils.h"
@ -26,9 +30,12 @@
#include "distributed/multi_executor.h"
#include "distributed/relation_access_tracking.h"
#include "distributed/transaction_management.h"
#include "foreign/foreign.h"
#include "nodes/makefuncs.h"
#include "utils/lsyscache.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
/* Local functions forward declarations for helper functions */
@ -37,9 +44,11 @@ static void AddSchemaFieldIfMissing(CreateExtensionStmt *stmt);
static List * FilterDistributedExtensions(List *extensionObjectList);
static List * ExtensionNameListToObjectAddressList(List *extensionObjectList);
static void MarkExistingObjectDependenciesDistributedIfSupported(void);
static List * GetAllViews(void);
static bool ShouldPropagateExtensionCommand(Node *parseTree);
static bool IsAlterExtensionSetSchemaCitus(Node *parseTree);
static Node * RecreateExtensionStmt(Oid extensionOid);
static List * GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId);
/*
@ -510,26 +519,78 @@ MarkExistingObjectDependenciesDistributedIfSupported()
Oid citusTableId = InvalidOid;
foreach_oid(citusTableId, citusTableIdList)
{
ObjectAddress tableAddress = { 0 };
ObjectAddressSet(tableAddress, RelationRelationId, citusTableId);
if (ShouldSyncTableMetadata(citusTableId))
if (!ShouldMarkRelationDistributed(citusTableId))
{
/* we need to pass pointer allocated in the heap */
ObjectAddress *addressPointer = palloc0(sizeof(ObjectAddress));
*addressPointer = tableAddress;
/* as of Citus 11, tables that should be synced are also considered object */
resultingObjectAddresses = lappend(resultingObjectAddresses, addressPointer);
continue;
}
List *distributableDependencyObjectAddresses =
GetDistributableDependenciesForObject(&tableAddress);
/* refrain reading the metadata cache for all tables */
if (ShouldSyncTableMetadataViaCatalog(citusTableId))
{
ObjectAddress tableAddress = { 0 };
ObjectAddressSet(tableAddress, RelationRelationId, citusTableId);
resultingObjectAddresses = list_concat(resultingObjectAddresses,
distributableDependencyObjectAddresses);
/*
* We mark tables distributed immediately because we also need to mark
* views as distributed. We check whether the views that depend on
* the table has any auto-distirbutable dependencies below. Citus
* currently cannot "auto" distribute tables as dependencies, so we
* mark them distributed immediately.
*/
MarkObjectDistributedLocally(&tableAddress);
/*
* All the distributable dependencies of a table should be marked as
* distributed.
*/
List *distributableDependencyObjectAddresses =
GetDistributableDependenciesForObject(&tableAddress);
resultingObjectAddresses =
list_concat(resultingObjectAddresses,
distributableDependencyObjectAddresses);
}
}
/*
* As of Citus 11, views on Citus tables that do not have any unsupported
* dependency should also be distributed.
*
* In general, we mark views distributed as long as it does not have
* any unsupported dependencies.
*/
List *viewList = GetAllViews();
Oid viewOid = InvalidOid;
foreach_oid(viewOid, viewList)
{
if (!ShouldMarkRelationDistributed(viewOid))
{
continue;
}
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
/*
* If a view depends on multiple views, that view will be marked
* as distributed while it is processed for the last view
* table.
*/
MarkObjectDistributedLocally(&viewAddress);
/* we need to pass pointer allocated in the heap */
ObjectAddress *addressPointer = palloc0(sizeof(ObjectAddress));
*addressPointer = viewAddress;
List *distributableDependencyObjectAddresses =
GetDistributableDependenciesForObject(&viewAddress);
resultingObjectAddresses =
list_concat(resultingObjectAddresses,
distributableDependencyObjectAddresses);
}
/* resolve dependencies of the objects in pg_dist_object*/
List *distributedObjectAddressList = GetDistributedObjectAddressList();
@ -565,6 +626,40 @@ MarkExistingObjectDependenciesDistributedIfSupported()
}
/*
* GetAllViews returns list of view oids that exists on this server.
*/
static List *
GetAllViews(void)
{
List *viewOidList = NIL;
Relation pgClass = table_open(RelationRelationId, AccessShareLock);
SysScanDesc scanDescriptor = systable_beginscan(pgClass, InvalidOid, false, NULL,
0, NULL);
HeapTuple heapTuple = systable_getnext(scanDescriptor);
while (HeapTupleIsValid(heapTuple))
{
Form_pg_class relationForm = (Form_pg_class) GETSTRUCT(heapTuple);
/* we're only interested in views */
if (relationForm->relkind == RELKIND_VIEW)
{
viewOidList = lappend_oid(viewOidList, relationForm->oid);
}
heapTuple = systable_getnext(scanDescriptor);
}
systable_endscan(scanDescriptor);
table_close(pgClass, NoLock);
return viewOidList;
}
/*
* PreprocessAlterExtensionContentsStmt issues a notice. It does not propagate.
*/
@ -732,6 +827,12 @@ CreateExtensionDDLCommand(const ObjectAddress *extensionAddress)
List *ddlCommands = list_make1((void *) ddlCommand);
/* any privilege granted on FDWs that belong to the extension should be included */
List *FDWGrants =
GenerateGrantCommandsOnExtesionDependentFDWs(extensionAddress->objectId);
ddlCommands = list_concat(ddlCommands, FDWGrants);
return ddlCommands;
}
@ -790,6 +891,88 @@ RecreateExtensionStmt(Oid extensionOid)
}
/*
* GenerateGrantCommandsOnExtesionDependentFDWs returns a list of commands that GRANTs
* the privileges on FDWs that are depending on the given extension.
*/
static List *
GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId)
{
List *commands = NIL;
List *FDWOids = GetDependentFDWsToExtension(extensionId);
Oid FDWOid = InvalidOid;
foreach_oid(FDWOid, FDWOids)
{
Acl *aclEntry = GetPrivilegesForFDW(FDWOid);
if (aclEntry == NULL)
{
continue;
}
AclItem *privileges = ACL_DAT(aclEntry);
int numberOfPrivsGranted = ACL_NUM(aclEntry);
for (int i = 0; i < numberOfPrivsGranted; i++)
{
commands = list_concat(commands,
GenerateGrantOnFDWQueriesFromAclItem(FDWOid,
&privileges[i]));
}
}
return commands;
}
/*
* GetDependentFDWsToExtension gets an extension oid and returns the list of oids of FDWs
* that are depending on the given extension.
*/
List *
GetDependentFDWsToExtension(Oid extensionId)
{
List *extensionFDWs = NIL;
ScanKeyData key[3];
int scanKeyCount = 3;
HeapTuple tup;
Relation pgDepend = table_open(DependRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_depend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(ExtensionRelationId));
ScanKeyInit(&key[1],
Anum_pg_depend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(extensionId));
ScanKeyInit(&key[2],
Anum_pg_depend_classid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(ForeignDataWrapperRelationId));
SysScanDesc scan = systable_beginscan(pgDepend, InvalidOid, false,
NULL, scanKeyCount, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
Form_pg_depend pgDependEntry = (Form_pg_depend) GETSTRUCT(tup);
if (pgDependEntry->deptype == DEPENDENCY_EXTENSION)
{
extensionFDWs = lappend_oid(extensionFDWs, pgDependEntry->objid);
}
}
systable_endscan(scan);
table_close(pgDepend, AccessShareLock);
return extensionFDWs;
}
/*
* AlterExtensionSchemaStmtObjectAddress returns the ObjectAddress of the extension that is
* the subject of the AlterObjectSchemaStmt. Errors if missing_ok is false.

View File

@ -0,0 +1,144 @@
/*-------------------------------------------------------------------------
*
* foreign_data_wrapper.c
* Commands for FOREIGN DATA WRAPPER statements.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/commands.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata/distobject.h"
#include "foreign/foreign.h"
#include "nodes/makefuncs.h"
#include "nodes/parsenodes.h"
#include "utils/syscache.h"
static bool NameListHasFDWOwnedByDistributedExtension(List *FDWNames);
static ObjectAddress GetObjectAddressByFDWName(char *FDWName, bool missing_ok);
/*
* PreprocessGrantOnFDWStmt is executed before the statement is applied to the
* local postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers to grant
* on foreign data wrappers.
*/
List *
PreprocessGrantOnFDWStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_FDW);
if (!NameListHasFDWOwnedByDistributedExtension(stmt->objects))
{
/*
* We propagate granted privileges on a FDW only if it belongs to a distributed
* extension. For now, we skip for custom FDWs, as most of the users prefer
* extension FDWs.
*/
return NIL;
}
if (list_length(stmt->objects) > 1)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot grant on FDW with other FDWs"),
errhint("Try granting on each object in separate commands")));
}
if (!ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
Assert(list_length(stmt->objects) == 1);
char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* NameListHasFDWOwnedByDistributedExtension takes a namelist of FDWs and returns true
* if at least one of them depends on a distributed extension. Returns false otherwise.
*/
static bool
NameListHasFDWOwnedByDistributedExtension(List *FDWNames)
{
Value *FDWValue = NULL;
foreach_ptr(FDWValue, FDWNames)
{
/* captures the extension address during lookup */
ObjectAddress extensionAddress = { 0 };
ObjectAddress FDWAddress = GetObjectAddressByFDWName(strVal(FDWValue), false);
if (IsObjectAddressOwnedByExtension(&FDWAddress, &extensionAddress))
{
if (IsObjectDistributed(&extensionAddress))
{
return true;
}
}
}
return false;
}
/*
* GetObjectAddressByFDWName takes a FDW name and returns the object address.
*/
static ObjectAddress
GetObjectAddressByFDWName(char *FDWName, bool missing_ok)
{
ForeignDataWrapper *FDW = GetForeignDataWrapperByName(FDWName, missing_ok);
Oid FDWId = FDW->fdwid;
ObjectAddress address = { 0 };
ObjectAddressSet(address, ForeignDataWrapperRelationId, FDWId);
return address;
}
/*
* GetPrivilegesForFDW takes a FDW object id and returns the privileges granted
* on that FDW as a Acl object. Returns NULL if there is no privilege granted.
*/
Acl *
GetPrivilegesForFDW(Oid FDWOid)
{
HeapTuple fdwtup = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(FDWOid));
bool isNull = true;
Datum aclDatum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, fdwtup,
Anum_pg_foreign_data_wrapper_fdwacl, &isNull);
if (isNull)
{
ReleaseSysCache(fdwtup);
return NULL;
}
Acl *aclEntry = DatumGetAclPCopy(aclDatum);
ReleaseSysCache(fdwtup);
return aclEntry;
}

View File

@ -9,6 +9,7 @@
*/
#include "postgres.h"
#include "miscadmin.h"
#include "catalog/pg_foreign_server.h"
#include "distributed/commands/utility_hook.h"
@ -23,240 +24,14 @@
#include "nodes/makefuncs.h"
#include "nodes/parsenodes.h"
#include "nodes/primnodes.h"
#include "utils/builtins.h"
static char * GetForeignServerAlterOwnerCommand(Oid serverId);
static Node * RecreateForeignServerStmt(Oid serverId);
static bool NameListHasDistributedServer(List *serverNames);
static ObjectAddress GetObjectAddressByServerName(char *serverName, bool missing_ok);
/*
* PreprocessCreateForeignServerStmt is called during the planning phase for
* CREATE SERVER.
*/
List *
PreprocessCreateForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FOREIGN_SERVER);
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterForeignServerStmt is called during the planning phase for
* ALTER SERVER .. OPTIONS ..
*/
List *
PreprocessAlterForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterForeignServerStmt *stmt = castNode(AlterForeignServerStmt, node);
ObjectAddress address = GetObjectAddressByServerName(stmt->servername, false);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessRenameForeignServerStmt is called during the planning phase for
* ALTER SERVER RENAME.
*/
List *
PreprocessRenameForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_FOREIGN_SERVER);
ObjectAddress address = GetObjectAddressByServerName(strVal(stmt->object), false);
/* filter distributed servers */
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterForeignServerOwnerStmt is called during the planning phase for
* ALTER SERVER .. OWNER TO.
*/
List *
PreprocessAlterForeignServerOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_FOREIGN_SERVER);
ObjectAddress address = GetObjectAddressByServerName(strVal(stmt->object), false);
/* filter distributed servers */
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureCoordinator();
char *sql = DeparseTreeNode(node);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessDropForeignServerStmt is called during the planning phase for
* DROP SERVER.
*/
List *
PreprocessDropForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
Assert(stmt->removeType == OBJECT_FOREIGN_SERVER);
bool includesDistributedServer = NameListHasDistributedServer(stmt->objects);
if (!includesDistributedServer)
{
return NIL;
}
if (list_length(stmt->objects) > 1)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot drop distributed server with other servers"),
errhint("Try dropping each object in a separate DROP command")));
}
if (!ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
Assert(list_length(stmt->objects) == 1);
Value *serverValue = linitial(stmt->objects);
ObjectAddress address = GetObjectAddressByServerName(strVal(serverValue), false);
/* unmark distributed server */
UnmarkObjectDistributed(&address);
const char *deparsedStmt = DeparseTreeNode((Node *) stmt);
/*
* To prevent recursive propagation in mx architecture, we disable ddl
* propagation before sending the command to workers.
*/
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) deparsedStmt,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessCreateForeignServerStmt is called after a CREATE SERVER command has
* been executed by standard process utility.
*/
List *
PostprocessCreateForeignServerStmt(Node *node, const char *queryString)
{
if (!ShouldPropagate())
{
return NIL;
}
/* check creation against multi-statement transaction policy */
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return NIL;
}
const bool missingOk = false;
ObjectAddress address = GetObjectAddressFromParseTree(node, missingOk);
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PostprocessAlterForeignServerOwnerStmt is called after a ALTER SERVER OWNER command
* has been executed by standard process utility.
*/
List *
PostprocessAlterForeignServerOwnerStmt(Node *node, const char *queryString)
{
const bool missingOk = false;
ObjectAddress address = GetObjectAddressFromParseTree(node, missingOk);
if (!ShouldPropagateObject(&address))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* CreateForeignServerStmtObjectAddress finds the ObjectAddress for the server
* that is created by given CreateForeignServerStmt. If missingOk is false and if
@ -274,6 +49,88 @@ CreateForeignServerStmtObjectAddress(Node *node, bool missing_ok)
}
/*
* AlterForeignServerStmtObjectAddress finds the ObjectAddress for the server that is
* changed by given AlterForeignServerStmt. If missingOk is false and if
* the server does not exist, then it errors out.
*
* Never returns NULL, but the objid in the address can be invalid if missingOk
* was set to true.
*/
ObjectAddress
AlterForeignServerStmtObjectAddress(Node *node, bool missing_ok)
{
AlterForeignServerStmt *stmt = castNode(AlterForeignServerStmt, node);
return GetObjectAddressByServerName(stmt->servername, missing_ok);
}
/*
* PreprocessGrantOnForeignServerStmt is executed before the statement is applied to the
* local postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers to grant
* on servers.
*/
List *
PreprocessGrantOnForeignServerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_FOREIGN_SERVER);
bool includesDistributedServer = NameListHasDistributedServer(stmt->objects);
if (!includesDistributedServer)
{
return NIL;
}
if (list_length(stmt->objects) > 1)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot grant on distributed server with other servers"),
errhint("Try granting on each object in separate commands")));
}
if (!ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
Assert(list_length(stmt->objects) == 1);
char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* RenameForeignServerStmtObjectAddress finds the ObjectAddress for the server that is
* renamed by given RenmaeStmt. If missingOk is false and if the server does not exist,
* then it errors out.
*
* Never returns NULL, but the objid in the address can be invalid if missingOk
* was set to true.
*/
ObjectAddress
RenameForeignServerStmtObjectAddress(Node *node, bool missing_ok)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_FOREIGN_SERVER);
return GetObjectAddressByServerName(strVal(stmt->object), missing_ok);
}
/*
* AlterForeignServerOwnerStmtObjectAddress finds the ObjectAddress for the server
* given in AlterOwnerStmt. If missingOk is false and if
@ -303,14 +160,37 @@ GetForeignServerCreateDDLCommand(Oid serverId)
Node *stmt = RecreateForeignServerStmt(serverId);
/* capture ddl command for the create statement */
const char *ddlCommand = DeparseTreeNode(stmt);
const char *createCommand = DeparseTreeNode(stmt);
const char *alterOwnerCommand = GetForeignServerAlterOwnerCommand(serverId);
List *ddlCommands = list_make1((void *) ddlCommand);
List *ddlCommands = list_make2((void *) createCommand,
(void *) alterOwnerCommand);
return ddlCommands;
}
/*
* GetForeignServerAlterOwnerCommand returns "ALTER SERVER .. OWNER TO .." statement
* for the specified foreign server.
*/
static char *
GetForeignServerAlterOwnerCommand(Oid serverId)
{
ForeignServer *server = GetForeignServer(serverId);
Oid ownerId = server->owner;
char *ownerName = GetUserNameFromId(ownerId, false);
StringInfo alterCommand = makeStringInfo();
appendStringInfo(alterCommand, "ALTER SERVER %s OWNER TO %s;",
quote_identifier(server->servername),
quote_identifier(ownerName));
return alterCommand->data;
}
/*
* RecreateForeignServerStmt returns a parsetree for a CREATE SERVER statement
* that would recreate the given server on a new node.

View File

@ -6,7 +6,9 @@
* We currently support replicating function definitions on the
* coordinator in all the worker nodes in the form of
*
* CREATE OR REPLACE FUNCTION ... queries.
* CREATE OR REPLACE FUNCTION ... queries and
* GRANT ... ON FUNCTION queries
*
*
* ALTER or DROP operations are not yet propagated.
*
@ -104,6 +106,7 @@ static void DistributeFunctionColocatedWithDistributedTable(RegProcedure funcOid
functionAddress);
static void DistributeFunctionColocatedWithReferenceTable(const
ObjectAddress *functionAddress);
static List * FilterDistributedFunctions(GrantStmt *grantStmt);
static void EnsureExtensionFunctionCanBeDistributed(const ObjectAddress functionAddress,
const ObjectAddress extensionAddress,
@ -239,8 +242,17 @@ create_distributed_function(PG_FUNCTION_ARGS)
const char *createFunctionSQL = GetFunctionDDLCommand(funcOid, true);
const char *alterFunctionOwnerSQL = GetFunctionAlterOwnerCommand(funcOid);
initStringInfo(&ddlCommand);
appendStringInfo(&ddlCommand, "%s;%s;%s;%s", DISABLE_METADATA_SYNC,
createFunctionSQL, alterFunctionOwnerSQL, ENABLE_METADATA_SYNC);
appendStringInfo(&ddlCommand, "%s;%s;%s", DISABLE_METADATA_SYNC,
createFunctionSQL, alterFunctionOwnerSQL);
List *grantDDLCommands = GrantOnFunctionDDLCommands(funcOid);
char *grantOnFunctionSQL = NULL;
foreach_ptr(grantOnFunctionSQL, grantDDLCommands)
{
appendStringInfo(&ddlCommand, ";%s", grantOnFunctionSQL);
}
appendStringInfo(&ddlCommand, ";%s", ENABLE_METADATA_SYNC);
SendCommandToWorkersAsUser(NON_COORDINATOR_NODES, CurrentUserName(),
ddlCommand.data);
}
@ -1263,12 +1275,7 @@ ShouldPropagateCreateFunction(CreateFunctionStmt *stmt)
return false;
}
/*
* If the create command is a part of a multi-statement transaction that is not in
* sequential mode, don't propagate.
*/
if (IsMultiStatementTransaction() &&
MultiShardConnectionType != SEQUENTIAL_CONNECTION)
if (!ShouldPropagateCreateInCoordinatedTransction())
{
return false;
}
@ -1507,234 +1514,6 @@ PreprocessAlterFunctionStmt(Node *node, const char *queryString,
}
/*
* PreprocessRenameFunctionStmt is called when the user is renaming a function. The invocation
* happens before the statement is applied locally.
*
* As the function already exists we have access to the ObjectAddress, this is used to
* check if it is distributed. If so the rename is executed on all the workers to keep the
* types in sync across the cluster.
*/
List *
PreprocessRenameFunctionStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
RenameStmt *stmt = castNode(RenameStmt, node);
AssertObjectTypeIsFunctional(stmt->renameType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterFunctionSchemaStmt is executed before the statement is applied to the local
* postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers.
*/
List *
PreprocessAlterFunctionSchemaStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterFunctionOwnerStmt is called for change of owner ship of functions before the owner
* ship is changed on the local instance.
*
* If the function for which the owner is changed is distributed we execute the change on
* all the workers to keep the type in sync across the cluster.
*/
List *
PreprocessAlterFunctionOwnerStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessAlterFunctionOwnerStmt is invoked after the owner has been changed locally.
* Since changing the owner could result in new dependencies being found for this object
* we re-ensure all the dependencies for the function do exist.
*
* This is solely to propagate the new owner (and all its dependencies) if it was not
* already distributed in the cluster.
*/
List *
PostprocessAlterFunctionOwnerStmt(Node *node, const char *queryString)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* PreprocessDropFunctionStmt gets called during the planning phase of a DROP FUNCTION statement
* and returns a list of DDLJob's that will drop any distributed functions from the
* workers.
*
* The DropStmt could have multiple objects to drop, the list of objects will be filtered
* to only keep the distributed functions for deletion on the workers. Non-distributed
* functions will still be dropped locally but not on the workers.
*/
List *
PreprocessDropFunctionStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
List *deletingObjectWithArgsList = stmt->objects;
List *distributedObjectWithArgsList = NIL;
List *distributedFunctionAddresses = NIL;
AssertObjectTypeIsFunctional(stmt->removeType);
if (creating_extension)
{
/*
* extensions should be created separately on the workers, types cascading from an
* extension should therefore not be propagated here.
*/
return NIL;
}
if (!EnableMetadataSync)
{
/*
* we are configured to disable object propagation, should not propagate anything
*/
return NIL;
}
/*
* Our statements need to be fully qualified so we can drop them from the right schema
* on the workers
*/
QualifyTreeNode((Node *) stmt);
/*
* iterate over all functions to be dropped and filter to keep only distributed
* functions.
*/
ObjectWithArgs *func = NULL;
foreach_ptr(func, deletingObjectWithArgsList)
{
ObjectAddress address = FunctionToObjectAddress(stmt->removeType, func,
stmt->missing_ok);
if (!IsObjectDistributed(&address))
{
continue;
}
/* collect information for all distributed functions */
ObjectAddress *addressp = palloc(sizeof(ObjectAddress));
*addressp = address;
distributedFunctionAddresses = lappend(distributedFunctionAddresses, addressp);
distributedObjectWithArgsList = lappend(distributedObjectWithArgsList, func);
}
if (list_length(distributedObjectWithArgsList) <= 0)
{
/* no distributed functions to drop */
return NIL;
}
/*
* managing types can only be done on the coordinator if ddl propagation is on. when
* it is off we will never get here. MX workers don't have a notion of distributed
* types, so we block the call.
*/
EnsureCoordinator();
EnsureSequentialMode(OBJECT_FUNCTION);
/* remove the entries for the distributed objects on dropping */
ObjectAddress *address = NULL;
foreach_ptr(address, distributedFunctionAddresses)
{
UnmarkObjectDistributed(address);
}
/*
* Swap the list of objects before deparsing and restore the old list after. This
* ensures we only have distributed functions in the deparsed drop statement.
*/
DropStmt *stmtCopy = copyObject(stmt);
stmtCopy->objects = distributedObjectWithArgsList;
const char *dropStmtSql = DeparseTreeNode((Node *) stmtCopy);
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) dropStmtSql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PreprocessAlterFunctionDependsStmt is called during the planning phase of an
* ALTER FUNCION ... DEPENDS ON EXTENSION ... statement. Since functions depending on
@ -1808,30 +1587,6 @@ AlterFunctionDependsStmtObjectAddress(Node *node, bool missing_ok)
}
/*
* PostprocessAlterFunctionSchemaStmt is executed after the change has been applied locally,
* we can now use the new dependencies of the function to ensure all its dependencies
* exist on the workers before we apply the commands remotely.
*/
List *
PostprocessAlterFunctionSchemaStmt(Node *node, const char *queryString)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
AssertObjectTypeIsFunctional(stmt->objectType);
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
if (!ShouldPropagateAlterFunction(&address))
{
return NIL;
}
/* dependencies have changed (schema) let's ensure they exist */
EnsureDependenciesExistOnAllNodes(&address);
return NIL;
}
/*
* AlterFunctionStmtObjectAddress returns the ObjectAddress of the subject in the
* AlterFunctionStmt. If missing_ok is set to false an error will be raised if postgres
@ -2177,3 +1932,162 @@ EnsureExtensionFunctionCanBeDistributed(const ObjectAddress functionAddress,
EnsureDependenciesExistOnAllNodes(&functionAddress);
}
/*
* PreprocessGrantOnFunctionStmt is executed before the statement is applied to the local
* postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers to grant
* on distributed functions, procedures, routines.
*/
List *
PreprocessGrantOnFunctionStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(isFunction(stmt->objtype));
List *distributedFunctions = FilterDistributedFunctions(stmt);
if (list_length(distributedFunctions) == 0 || !ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
List *grantFunctionList = NIL;
ObjectAddress *functionAddress = NULL;
foreach_ptr(functionAddress, distributedFunctions)
{
ObjectWithArgs *distFunction = ObjectWithArgsFromOid(
functionAddress->objectId);
grantFunctionList = lappend(grantFunctionList, distFunction);
}
List *originalObjects = stmt->objects;
GrantTargetType originalTargtype = stmt->targtype;
stmt->objects = grantFunctionList;
stmt->targtype = ACL_TARGET_OBJECT;
char *sql = DeparseTreeNode((Node *) stmt);
stmt->objects = originalObjects;
stmt->targtype = originalTargtype;
List *commandList = list_make3(DISABLE_DDL_PROPAGATION,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commandList);
}
/*
* PostprocessGrantOnFunctionStmt makes sure dependencies of each
* distributed function in the statement exist on all nodes
*/
List *
PostprocessGrantOnFunctionStmt(Node *node, const char *queryString)
{
GrantStmt *stmt = castNode(GrantStmt, node);
List *distributedFunctions = FilterDistributedFunctions(stmt);
if (list_length(distributedFunctions) == 0)
{
return NIL;
}
ObjectAddress *functionAddress = NULL;
foreach_ptr(functionAddress, distributedFunctions)
{
EnsureDependenciesExistOnAllNodes(functionAddress);
}
return NIL;
}
/*
* FilterDistributedFunctions determines and returns a list of distributed functions
* ObjectAddress-es from given grant statement.
*/
static List *
FilterDistributedFunctions(GrantStmt *grantStmt)
{
List *grantFunctionList = NIL;
bool grantOnFunctionCommand = (grantStmt->targtype == ACL_TARGET_OBJECT &&
isFunction(grantStmt->objtype));
bool grantAllFunctionsOnSchemaCommand = (grantStmt->targtype ==
ACL_TARGET_ALL_IN_SCHEMA &&
isFunction(grantStmt->objtype));
/* we are only interested in function/procedure/routine level grants */
if (!grantOnFunctionCommand && !grantAllFunctionsOnSchemaCommand)
{
return NIL;
}
if (grantAllFunctionsOnSchemaCommand)
{
List *distributedFunctionList = DistributedFunctionList();
ObjectAddress *distributedFunction = NULL;
List *namespaceOidList = NIL;
/* iterate over all namespace names provided to get their oid's */
Value *namespaceValue = NULL;
foreach_ptr(namespaceValue, grantStmt->objects)
{
char *nspname = strVal(namespaceValue);
bool missing_ok = false;
Oid namespaceOid = get_namespace_oid(nspname, missing_ok);
namespaceOidList = list_append_unique_oid(namespaceOidList, namespaceOid);
}
/*
* iterate over all distributed functions to filter the ones
* that belong to one of the namespaces from above
*/
foreach_ptr(distributedFunction, distributedFunctionList)
{
Oid namespaceOid = get_func_namespace(distributedFunction->objectId);
/*
* if this distributed function's schema is one of the schemas
* specified in the GRANT .. ALL FUNCTIONS IN SCHEMA ..
* add it to the list
*/
if (list_member_oid(namespaceOidList, namespaceOid))
{
grantFunctionList = lappend(grantFunctionList, distributedFunction);
}
}
}
else
{
bool missingOk = false;
ObjectWithArgs *objectWithArgs = NULL;
foreach_ptr(objectWithArgs, grantStmt->objects)
{
ObjectAddress *functionAddress = palloc0(sizeof(ObjectAddress));
functionAddress->classId = ProcedureRelationId;
functionAddress->objectId = LookupFuncWithArgs(grantStmt->objtype,
objectWithArgs,
missingOk);
functionAddress->objectSubId = 0;
/*
* if this function from GRANT .. ON FUNCTION .. is a distributed
* function, add it to the list
*/
if (IsObjectDistributed(functionAddress))
{
grantFunctionList = lappend(grantFunctionList, functionAddress);
}
}
}
return grantFunctionList;
}

View File

@ -8,13 +8,244 @@
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/metadata/distobject.h"
#include "distributed/metadata_cache.h"
#include "distributed/version_compat.h"
#include "lib/stringinfo.h"
#include "nodes/parsenodes.h"
#include "utils/lsyscache.h"
/* placeholder for PreprocessGrantStmt */
/* Local functions forward declarations for helper functions */
static List * CollectGrantTableIdList(GrantStmt *grantStmt);
/*
* PreprocessGrantStmt determines whether a given GRANT/REVOKE statement involves
* a distributed table. If so, it creates DDLJobs to encapsulate information
* needed during the worker node portion of DDL execution before returning the
* DDLJobs in a List. If no distributed table is involved, this returns NIL.
*
* NB: So far column level privileges are not supported.
*/
List *
PreprocessGrantStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
return NIL;
GrantStmt *grantStmt = castNode(GrantStmt, node);
StringInfoData privsString;
StringInfoData granteesString;
StringInfoData targetString;
StringInfoData ddlString;
ListCell *granteeCell = NULL;
ListCell *tableListCell = NULL;
bool isFirst = true;
List *ddlJobs = NIL;
initStringInfo(&privsString);
initStringInfo(&granteesString);
initStringInfo(&targetString);
initStringInfo(&ddlString);
/*
* So far only table level grants are supported. Most other types of
* grants aren't interesting anyway.
*/
if (grantStmt->objtype != OBJECT_TABLE)
{
return NIL;
}
List *tableIdList = CollectGrantTableIdList(grantStmt);
/* nothing to do if there is no distributed table in the grant list */
if (tableIdList == NIL)
{
return NIL;
}
/* deparse the privileges */
if (grantStmt->privileges == NIL)
{
appendStringInfo(&privsString, "ALL");
}
else
{
ListCell *privilegeCell = NULL;
isFirst = true;
foreach(privilegeCell, grantStmt->privileges)
{
AccessPriv *priv = lfirst(privilegeCell);
if (!isFirst)
{
appendStringInfoString(&privsString, ", ");
}
isFirst = false;
if (priv->cols != NIL)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("grant/revoke on column list is currently "
"unsupported")));
}
Assert(priv->priv_name != NULL);
appendStringInfo(&privsString, "%s", priv->priv_name);
}
}
/* deparse the grantees */
isFirst = true;
foreach(granteeCell, grantStmt->grantees)
{
RoleSpec *spec = lfirst(granteeCell);
if (!isFirst)
{
appendStringInfoString(&granteesString, ", ");
}
isFirst = false;
appendStringInfoString(&granteesString, RoleSpecString(spec, true));
}
/*
* Deparse the target objects, and issue the deparsed statements to
* workers, if applicable. That's so we easily can replicate statements
* only to distributed relations.
*/
isFirst = true;
foreach(tableListCell, tableIdList)
{
Oid relationId = lfirst_oid(tableListCell);
const char *grantOption = "";
resetStringInfo(&targetString);
appendStringInfo(&targetString, "%s", generate_relation_name(relationId, NIL));
if (grantStmt->is_grant)
{
if (grantStmt->grant_option)
{
grantOption = " WITH GRANT OPTION";
}
appendStringInfo(&ddlString, "GRANT %s ON %s TO %s%s",
privsString.data, targetString.data, granteesString.data,
grantOption);
}
else
{
if (grantStmt->grant_option)
{
grantOption = "GRANT OPTION FOR ";
}
appendStringInfo(&ddlString, "REVOKE %s%s ON %s FROM %s",
grantOption, privsString.data, targetString.data,
granteesString.data);
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = pstrdup(ddlString.data);
ddlJob->taskList = NIL;
if (IsCitusTable(relationId))
{
ddlJob->taskList = DDLTaskList(relationId, ddlString.data);
}
ddlJobs = lappend(ddlJobs, ddlJob);
resetStringInfo(&ddlString);
}
return ddlJobs;
}
/*
* CollectGrantTableIdList determines and returns a list of distributed table
* Oids from grant statement.
* Grant statement may appear in two forms
* 1 - grant on table:
* each distributed table oid in grant object list is added to returned list.
* 2 - grant all tables in schema:
* Collect namespace oid list from grant statement
* Add each distributed table oid in the target namespace list to the returned list.
*/
static List *
CollectGrantTableIdList(GrantStmt *grantStmt)
{
List *grantTableList = NIL;
bool grantOnTableCommand = (grantStmt->targtype == ACL_TARGET_OBJECT &&
grantStmt->objtype == OBJECT_TABLE);
bool grantAllTablesOnSchemaCommand = (grantStmt->targtype ==
ACL_TARGET_ALL_IN_SCHEMA &&
grantStmt->objtype == OBJECT_TABLE);
/* we are only interested in table level grants */
if (!grantOnTableCommand && !grantAllTablesOnSchemaCommand)
{
return NIL;
}
if (grantAllTablesOnSchemaCommand)
{
List *citusTableIdList = CitusTableTypeIdList(ANY_CITUS_TABLE_TYPE);
ListCell *citusTableIdCell = NULL;
List *namespaceOidList = NIL;
ListCell *objectCell = NULL;
foreach(objectCell, grantStmt->objects)
{
char *nspname = strVal(lfirst(objectCell));
bool missing_ok = false;
Oid namespaceOid = get_namespace_oid(nspname, missing_ok);
Assert(namespaceOid != InvalidOid);
namespaceOidList = list_append_unique_oid(namespaceOidList, namespaceOid);
}
foreach(citusTableIdCell, citusTableIdList)
{
Oid relationId = lfirst_oid(citusTableIdCell);
Oid namespaceOid = get_rel_namespace(relationId);
if (list_member_oid(namespaceOidList, namespaceOid))
{
grantTableList = lappend_oid(grantTableList, relationId);
}
}
}
else
{
ListCell *objectCell = NULL;
foreach(objectCell, grantStmt->objects)
{
RangeVar *relvar = (RangeVar *) lfirst(objectCell);
Oid relationId = RangeVarGetRelid(relvar, NoLock, false);
if (IsCitusTable(relationId))
{
grantTableList = lappend_oid(grantTableList, relationId);
continue;
}
/* check for distributed sequences included in GRANT ON TABLE statement */
ObjectAddress sequenceAddress = { 0 };
ObjectAddressSet(sequenceAddress, RelationRelationId, relationId);
if (IsObjectDistributed(&sequenceAddress))
{
grantTableList = lappend_oid(grantTableList, relationId);
}
}
}
return grantTableList;
}

View File

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

View File

@ -73,10 +73,12 @@
#include "distributed/commands/multi_copy.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/intermediate_results.h"
#include "distributed/listutils.h"
#include "distributed/local_executor.h"
#include "distributed/log_utils.h"
#include "distributed/coordinator_protocol.h"
#include "distributed/metadata_cache.h"
#include "distributed/multi_executor.h"
#include "distributed/multi_partitioning_utils.h"
#include "distributed/multi_physical_planner.h"
#include "distributed/multi_router_planner.h"
@ -102,6 +104,7 @@
#include "libpq/pqformat.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#if PG_VERSION_NUM >= PG_VERSION_13
#include "tcop/cmdtag.h"
@ -117,6 +120,9 @@
/* constant used in binary protocol */
static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
/* if true, skip validation of JSONB columns during COPY */
bool SkipJsonbValidationInCopy = true;
/* custom Citus option for appending to a shard */
#define APPEND_TO_SHARD_OPTION "append_to_shard"
@ -242,6 +248,9 @@ typedef enum LocalCopyStatus
/* Local functions forward declarations */
static void CopyToExistingShards(CopyStmt *copyStatement,
QueryCompletionCompat *completionTag);
static bool IsCopyInBinaryFormat(CopyStmt *copyStatement);
static List * FindJsonbInputColumns(TupleDesc tupleDescriptor,
List *inputColumnNameList);
static List * RemoveOptionFromList(List *optionList, char *optionName);
static bool BinaryOutputFunctionDefined(Oid typeId);
static bool BinaryInputFunctionDefined(Oid typeId);
@ -452,6 +461,7 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletionCompat *completionT
List *columnNameList = NIL;
int partitionColumnIndex = INVALID_PARTITION_COLUMN_INDEX;
bool isInputFormatBinary = IsCopyInBinaryFormat(copyStatement);
uint64 processedRowCount = 0;
ErrorContextCallback errorCallback;
@ -543,6 +553,72 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletionCompat *completionT
copiedDistributedRelationTuple->relkind = RELKIND_RELATION;
}
/*
* We make an optimisation to skip JSON parsing for JSONB columns, because many
* Citus users have large objects in this column and parsing it on the coordinator
* causes significant CPU overhead. We do this by forcing BeginCopyFrom and
* NextCopyFrom to parse the column as text and then encoding it as JSON again
* by using citus_text_send_as_jsonb as the binary output function.
*
* The main downside of enabling this optimisation is that it defers validation
* until the object is parsed by the worker, which is unable to give an accurate
* line number.
*/
if (SkipJsonbValidationInCopy && !isInputFormatBinary)
{
CopyOutState copyOutState = copyDest->copyOutState;
ListCell *jsonbColumnIndexCell = NULL;
/* get the column indices for all JSONB columns that appear in the input */
List *jsonbColumnIndexList = FindJsonbInputColumns(
copiedDistributedRelation->rd_att,
copyStatement->attlist);
foreach(jsonbColumnIndexCell, jsonbColumnIndexList)
{
int jsonbColumnIndex = lfirst_int(jsonbColumnIndexCell);
Form_pg_attribute currentColumn =
TupleDescAttr(copiedDistributedRelation->rd_att, jsonbColumnIndex);
if (jsonbColumnIndex == partitionColumnIndex)
{
/*
* In the curious case of using a JSONB column as partition column,
* we leave it as is because we want to make sure the hashing works
* correctly.
*/
continue;
}
ereport(DEBUG1, (errmsg("parsing JSONB column %s as text",
NameStr(currentColumn->attname))));
/* parse the column as text instead of JSONB */
currentColumn->atttypid = TEXTOID;
if (copyOutState->binary)
{
Oid textSendAsJsonbFunctionId = CitusTextSendAsJsonbFunctionId();
/*
* If we're using binary encoding between coordinator and workers
* then we should honour the format expected by jsonb_recv, which
* is a version number followed by text. We therefore use an output
* function which sends the text as if it were jsonb, namely by
* prepending a version number.
*/
fmgr_info(textSendAsJsonbFunctionId,
&copyDest->columnOutputFunctions[jsonbColumnIndex]);
}
else
{
Oid textoutFunctionId = TextOutFunctionId();
fmgr_info(textoutFunctionId,
&copyDest->columnOutputFunctions[jsonbColumnIndex]);
}
}
}
/* initialize copy state to read from COPY data source */
CopyFromState copyState = BeginCopyFrom_compat(NULL,
copiedDistributedRelation,
@ -610,6 +686,82 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletionCompat *completionT
}
/*
* IsCopyInBinaryFormat determines whether the given COPY statement has the
* WITH (format binary) option.
*/
static bool
IsCopyInBinaryFormat(CopyStmt *copyStatement)
{
ListCell *optionCell = NULL;
foreach(optionCell, copyStatement->options)
{
DefElem *defel = lfirst_node(DefElem, optionCell);
if (strcmp(defel->defname, "format") == 0 &&
strcmp(defGetString(defel), "binary") == 0)
{
return true;
}
}
return false;
}
/*
* FindJsonbInputColumns finds columns in the tuple descriptor that have
* the JSONB type and appear in inputColumnNameList. If the list is empty then
* all JSONB columns are returned.
*/
static List *
FindJsonbInputColumns(TupleDesc tupleDescriptor, List *inputColumnNameList)
{
List *jsonbColumnIndexList = NIL;
int columnCount = tupleDescriptor->natts;
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
Form_pg_attribute currentColumn = TupleDescAttr(tupleDescriptor, columnIndex);
if (currentColumn->attisdropped)
{
continue;
}
if (currentColumn->atttypid != JSONBOID)
{
continue;
}
if (inputColumnNameList != NIL)
{
ListCell *inputColumnCell = NULL;
bool isInputColumn = false;
foreach(inputColumnCell, inputColumnNameList)
{
char *inputColumnName = strVal(lfirst(inputColumnCell));
if (namestrcmp(&currentColumn->attname, inputColumnName) == 0)
{
isInputColumn = true;
break;
}
}
if (!isInputColumn)
{
continue;
}
}
jsonbColumnIndexList = lappend_int(jsonbColumnIndexList, columnIndex);
}
return jsonbColumnIndexList;
}
static void
CompleteCopyQueryTagCompat(QueryCompletionCompat *completionTag, uint64 processedRowCount)
{
@ -3430,10 +3582,7 @@ InitializeCopyShardState(CopyShardState *shardState,
ereport(ERROR, (errmsg("could not connect to any active placements")));
}
if (hasRemoteCopy)
{
EnsureRemoteTaskExecutionAllowed();
}
EnsureTaskExecutionAllowed(hasRemoteCopy);
/*
* We just error out and code execution should never reach to this

View File

@ -12,112 +12,650 @@
#include "catalog/namespace.h"
#include "commands/policy.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/coordinator_protocol.h"
#include "distributed/listutils.h"
#include "distributed/metadata_cache.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_clause.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rowsecurity.h"
#include "utils/builtins.h"
#include "utils/ruleutils.h"
/* placeholder for CreatePolicyCommands */
static const char * unparse_policy_command(const char aclchar);
static void AddRangeTableEntryToQueryCompat(ParseState *parseState, Relation relation);
static RowSecurityPolicy * GetPolicyByName(Oid relationId, const char *policyName);
static List * GetPolicyListForRelation(Oid relationId);
static char * CreatePolicyCommandForPolicy(Oid relationId, RowSecurityPolicy *policy);
/*
* CreatePolicyCommands takes in a relationId, and returns the list of create policy
* commands needed to reconstruct the policies of that table.
*/
List *
CreatePolicyCommands(Oid relationId)
{
/* placeholder for future implementation */
return NIL;
}
List *commands = NIL;
List *policyList = GetPolicyListForRelation(relationId);
/* placeholder for PreprocessCreatePolicyStmt */
List *
PreprocessCreatePolicyStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
CreatePolicyStmt *stmt = castNode(CreatePolicyStmt, node);
Oid relationId = RangeVarGetRelid(stmt->table,
AccessExclusiveLock,
false);
if (IsCitusTable(relationId))
RowSecurityPolicy *policy;
foreach_ptr(policy, policyList)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("policies on distributed tables are only supported in "
"Citus Enterprise")));
char *createPolicyCommand = CreatePolicyCommandForPolicy(relationId, policy);
commands = lappend(commands, makeTableDDLCommandString(createPolicyCommand));
}
/* placeholder for future implementation */
return NIL;
return commands;
}
/* placeholder for PreprocessAlterPolicyStmt */
/*
* GetPolicyListForRelation returns a list of RowSecurityPolicy objects identifying
* the policies on the relation with relationId. Note that this function acquires
* AccessShareLock on relation and does not release it in the end to make sure that
* caller will process valid policies through the transaction.
*/
static List *
GetPolicyListForRelation(Oid relationId)
{
Relation relation = table_open(relationId, AccessShareLock);
if (!relation_has_policies(relation))
{
table_close(relation, NoLock);
return NIL;
}
if (relation->rd_rsdesc == NULL)
{
/*
* there are policies, but since RLS is not enabled they are not loaded into
* cache, we will do so here for us to access
*/
RelationBuildRowSecurity(relation);
}
List *policyList = NIL;
RowSecurityPolicy *policy;
foreach_ptr(policy, relation->rd_rsdesc->policies)
{
policyList = lappend(policyList, policy);
}
table_close(relation, NoLock);
return policyList;
}
/*
* CreatePolicyCommandForPolicy takes a relationId and a policy, returns
* the CREATE POLICY command needed to reconstruct the policy identified
* by the "policy" object on the relation with relationId.
*/
static char *
CreatePolicyCommandForPolicy(Oid relationId, RowSecurityPolicy *policy)
{
char *relationName = generate_qualified_relation_name(relationId);
List *relationContext = deparse_context_for(relationName, relationId);
StringInfo createPolicyCommand = makeStringInfo();
appendStringInfo(createPolicyCommand, "CREATE POLICY %s ON %s FOR %s",
quote_identifier(policy->policy_name),
relationName,
unparse_policy_command(policy->polcmd));
appendStringInfoString(createPolicyCommand, " TO ");
/*
* iterate over all roles and append them to the ddl command with commas
* separating the role names
*/
Oid *roles = (Oid *) ARR_DATA_PTR(policy->roles);
for (int roleIndex = 0; roleIndex < ARR_DIMS(policy->roles)[0]; roleIndex++)
{
const char *roleName;
if (roleIndex > 0)
{
appendStringInfoString(createPolicyCommand, ", ");
}
if (roles[roleIndex] == ACL_ID_PUBLIC)
{
roleName = "PUBLIC";
}
else
{
roleName = quote_identifier(GetUserNameFromId(roles[roleIndex], false));
}
appendStringInfoString(createPolicyCommand, roleName);
}
if (policy->qual)
{
char *qualString = deparse_expression((Node *) (policy->qual),
relationContext, false, false);
appendStringInfo(createPolicyCommand, " USING (%s)", qualString);
}
if (policy->with_check_qual)
{
char *withCheckQualString = deparse_expression(
(Node *) (policy->with_check_qual), relationContext, false, false);
appendStringInfo(createPolicyCommand, " WITH CHECK (%s)",
withCheckQualString);
}
return createPolicyCommand->data;
}
/*
* unparse_policy_command takes the type of a policy command and converts it to its full
* command string. This function is the exact inverse of parse_policy_command that is in
* postgres.
*/
static const char *
unparse_policy_command(const char aclchar)
{
switch (aclchar)
{
case '*':
{
return "ALL";
}
case ACL_SELECT_CHR:
{
return "SELECT";
}
case ACL_INSERT_CHR:
{
return "INSERT";
}
case ACL_UPDATE_CHR:
{
return "UPDATE";
}
case ACL_DELETE_CHR:
{
return "DELETE";
}
default:
{
elog(ERROR, "unrecognized aclchar: %d", aclchar);
return NULL;
}
}
}
/*
* PostprocessCreatePolicyStmt determines when a CREATE POLICY statement involves
* a distributed table. If so, it creates DDLJobs to encapsulate information
* needed during the worker node portion of DDL execution before returning the
* DDLJobs in a List. If no distributed table is involved, this returns NIL.
*/
List *
PostprocessCreatePolicyStmt(Node *node, const char *queryString)
{
CreatePolicyStmt *stmt = castNode(CreatePolicyStmt, node);
/* load relation information */
RangeVar *relvar = stmt->table;
Oid relationId = RangeVarGetRelid(relvar, NoLock, false);
if (!IsCitusTable(relationId))
{
return NIL;
}
Relation relation = table_open(relationId, AccessShareLock);
ParseState *qual_pstate = make_parsestate(NULL);
AddRangeTableEntryToQueryCompat(qual_pstate, relation);
Node *qual = transformWhereClause(qual_pstate,
copyObject(stmt->qual),
EXPR_KIND_POLICY,
"POLICY");
if (qual)
{
ErrorIfUnsupportedPolicyExpr(qual);
}
ParseState *with_check_pstate = make_parsestate(NULL);
AddRangeTableEntryToQueryCompat(with_check_pstate, relation);
Node *with_check_qual = transformWhereClause(with_check_pstate,
copyObject(stmt->with_check),
EXPR_KIND_POLICY,
"POLICY");
if (with_check_qual)
{
ErrorIfUnsupportedPolicyExpr(with_check_qual);
}
RowSecurityPolicy *policy = GetPolicyByName(relationId, stmt->policy_name);
if (policy == NULL)
{
/*
* As this function is executed after standard process utility created the
* policy, we should be able to find & deparse the policy with policy_name.
* But to be more safe, error out here.
*/
ereport(ERROR, (errmsg("cannot create policy, policy does not exist.")));
}
EnsureCoordinator();
char *ddlCommand = CreatePolicyCommandForPolicy(relationId, policy);
/*
* create the DDLJob that needs to be executed both on the local relation and all its
* placements.
*/
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = pstrdup(ddlCommand);
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
relation_close(relation, NoLock);
return list_make1(ddlJob);
}
/*
* AddRangeTableEntryToQueryCompat adds the given relation to query.
* This method is a compatibility wrapper.
*/
static void
AddRangeTableEntryToQueryCompat(ParseState *parseState, Relation relation)
{
#if PG_VERSION_NUM >= PG_VERSION_13
ParseNamespaceItem *rte = NULL;
#else
RangeTblEntry *rte = NULL;
#endif
rte = addRangeTableEntryForRelation(parseState, relation,
#if PG_VERSION_NUM >= PG_VERSION_12
AccessShareLock,
#endif
NULL, false, false);
#if PG_VERSION_NUM >= PG_VERSION_13
addNSItemToQuery(parseState, rte, false, true, true);
#else
addRTEtoQuery(parseState, rte, false, true, true);
#endif
}
/*
* GetPolicyByName takes a relationId and a policyName, returns RowSecurityPolicy
* object which identifies the policy with name "policyName" on the relation
* with relationId. If there does not exist such a policy, then this function
* returns NULL.
*/
static RowSecurityPolicy *
GetPolicyByName(Oid relationId, const char *policyName)
{
List *policyList = GetPolicyListForRelation(relationId);
RowSecurityPolicy *policy = NULL;
foreach_ptr(policy, policyList)
{
if (strncmp(policy->policy_name, policyName, NAMEDATALEN) == 0)
{
return policy;
}
}
return NULL;
}
/*
* PreprocessAlterPolicyStmt determines whether a given ALTER POLICY statement involves a
* distributed table. If so, it creates DDLJobs to encapsulate information needed during
* the worker node portion of DDL execution before returning the DDLJobs in a list. If no
* distributed table is involved this returns NIL.
*/
List *
PreprocessAlterPolicyStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
/* placeholder for future implementation */
return NIL;
AlterPolicyStmt *stmt = castNode(AlterPolicyStmt, node);
StringInfoData ddlString;
ListCell *roleCell = NULL;
/* load relation information */
RangeVar *relvar = stmt->table;
Oid relOid = RangeVarGetRelid(relvar, NoLock, false);
if (!IsCitusTable(relOid))
{
return NIL;
}
initStringInfo(&ddlString);
Relation relation = relation_open(relOid, AccessShareLock);
char *relationName = generate_relation_name(relOid, NIL);
appendStringInfo(&ddlString, "ALTER POLICY %s ON %s",
quote_identifier(stmt->policy_name),
relationName
);
if (stmt->roles)
{
appendStringInfoString(&ddlString, " TO ");
foreach(roleCell, stmt->roles)
{
RoleSpec *roleSpec = (RoleSpec *) lfirst(roleCell);
appendStringInfoString(&ddlString, RoleSpecString(roleSpec, true));
if (lnext_compat(stmt->roles, roleCell) != NULL)
{
appendStringInfoString(&ddlString, ", ");
}
}
}
List *relationContext = deparse_context_for(relationName, relOid);
ParseState *qual_pstate = make_parsestate(NULL);
AddRangeTableEntryToQueryCompat(qual_pstate, relation);
Node *qual = transformWhereClause(qual_pstate,
copyObject(stmt->qual),
EXPR_KIND_POLICY,
"POLICY");
if (qual)
{
ErrorIfUnsupportedPolicyExpr(qual);
char *qualString = deparse_expression(qual, relationContext, false, false);
appendStringInfo(&ddlString, " USING (%s)", qualString);
}
ParseState *with_check_pstate = make_parsestate(NULL);
AddRangeTableEntryToQueryCompat(with_check_pstate, relation);
Node *with_check_qual = transformWhereClause(with_check_pstate,
copyObject(stmt->with_check),
EXPR_KIND_POLICY,
"POLICY");
if (with_check_qual)
{
ErrorIfUnsupportedPolicyExpr(with_check_qual);
char *withCheckString = deparse_expression(with_check_qual, relationContext,
false,
false);
appendStringInfo(&ddlString, " WITH CHECK (%s)", withCheckString);
}
/*
* create the DDLJob that needs to be executed both on the local relation and all its
* placements.
*/
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relOid);
ddlJob->metadataSyncCommand = pstrdup(ddlString.data);
ddlJob->taskList = DDLTaskList(relOid, ddlString.data);
relation_close(relation, NoLock);
return list_make1(ddlJob);
}
/* placeholder for ErrorIfUnsupportedPolicy */
/*
* ErrorIfUnsupportedPolicy runs checks related to a Relation their Policies and errors
* out if it is not possible to create one of the policies in a distributed environment.
*
* To support policies we require that:
* - Policy expressions do not contain subqueries.
*/
void
ErrorIfUnsupportedPolicy(Relation relation)
{
if (relation_has_policies(relation))
ListCell *policyCell = NULL;
if (!relation_has_policies(relation))
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("policies on distributed tables are only supported in "
"Citus Enterprise"),
errhint("Remove any policies on a table before distributing")));
return;
}
/*
* even if a relation has policies they might not be loaded on the Relation yet. This
* happens if policies are on a Relation without Row Level Security enabled. We need
* to make sure the policies installed are valid for distribution if RLS gets enabled
* after the table has been distributed. Therefore we force a build of the policies on
* the cached Relation
*/
if (relation->rd_rsdesc == NULL)
{
RelationBuildRowSecurity(relation);
}
foreach(policyCell, relation->rd_rsdesc->policies)
{
RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(policyCell);
ErrorIfUnsupportedPolicyExpr((Node *) policy->qual);
ErrorIfUnsupportedPolicyExpr((Node *) policy->with_check_qual);
}
}
/* placeholder for PreprocessDropPolicyStmt */
/*
* ErrorIfUnsupportedPolicyExpr tests if the provided expression for a policy is
* supported on a distributed table.
*/
void
ErrorIfUnsupportedPolicyExpr(Node *expr)
{
/*
* We do not allow any sublink to prevent expressions with subqueries to be used as an
* expression in policies on distributed tables.
*/
if (checkExprHasSubLink(expr))
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create policy"),
errdetail("Subqueries are not supported in policies on distributed "
"tables")));
}
}
/*
* PreprocessDropPolicyStmt determines whether a given DROP POLICY statement involves a
* distributed table. If so it creates DDLJobs to encapsulate information needed during
* the worker node portion of DDL execution before returning the DDLJobs in a List. If no
* distributed table is involved this returns NIL.
*/
List *
PreprocessDropPolicyStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
/* placeholder for future implementation */
return NIL;
DropStmt *stmt = castNode(DropStmt, node);
List *ddlJobs = NIL;
ListCell *dropObjectCell = NULL;
Assert(stmt->removeType == OBJECT_POLICY);
foreach(dropObjectCell, stmt->objects)
{
List *names = (List *) lfirst(dropObjectCell);
/*
* the last element in the list of names is the name of the policy. The ones
* before are describing the relation. By removing the last item from the list we
* can use makeRangeVarFromNameList to get to the relation. As list_truncate
* changes the list in place we make a copy before.
*/
names = list_copy(names);
names = list_truncate(names, list_length(names) - 1);
RangeVar *relation = makeRangeVarFromNameList(names);
Oid relOid = RangeVarGetRelid(relation, NoLock, false);
if (!IsCitusTable(relOid))
{
continue;
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relOid);
ddlJob->metadataSyncCommand = queryString;
ddlJob->taskList = DDLTaskList(relOid, queryString);
ddlJobs = lappend(ddlJobs, ddlJob);
}
return ddlJobs;
}
/* placeholder for IsPolicyRenameStmt */
/*
* IsPolicyRenameStmt returns wherher the passed-in RenameStmt is one of the following
* forms:
*
* - ALTER POLICY ... ON ... RENAME TO ...
*/
bool
IsPolicyRenameStmt(RenameStmt *stmt)
{
/* placeholder for future implementation */
return false;
return stmt->renameType == OBJECT_POLICY;
}
/* placeholder for CreatePolicyEventExtendNames */
/*
* CreatePolicyEventExtendNames extends relation names in the given CreatePolicyStmt tree.
* This function has side effects on the tree as the names are replaced inplace.
*/
void
CreatePolicyEventExtendNames(CreatePolicyStmt *stmt, const char *schemaName, uint64
shardId)
{
/* placeholder for future implementation */
RangeVar *relation = stmt->table;
char **relationName = &(relation->relname);
char **relationSchemaName = &(relation->schemaname);
/* prefix with schema name if it is not added already */
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
AppendShardIdToName(relationName, shardId);
}
/* placeholder for AlterPolicyEventExtendNames */
/*
* AlterPolicyEventExtendNames extends relation names in the given AlterPolicyStatement
* tree. This function has side effects on the tree as the names are replaced inplace.
*/
void
AlterPolicyEventExtendNames(AlterPolicyStmt *stmt, const char *schemaName, uint64 shardId)
{
/* placeholder for future implementation */
RangeVar *relation = stmt->table;
char **relationName = &(relation->relname);
char **relationSchemaName = &(relation->schemaname);
/* prefix with schema name if it is not added already */
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
AppendShardIdToName(relationName, shardId);
}
/* placeholder for RenamePolicyEventExtendNames */
/*
* RenamePolicyEventExtendNames extends relation names in the given RenameStmt tree. This
* function has side effects on the tree as the names are replaced inline.
*/
void
RenamePolicyEventExtendNames(RenameStmt *stmt, const char *schemaName, uint64 shardId)
{
/* placeholder for future implementation */
char **relationName = &(stmt->relation->relname);
char **objectSchemaName = &(stmt->relation->schemaname);
/* prefix with schema name if it is not added already */
SetSchemaNameIfNotExist(objectSchemaName, schemaName);
AppendShardIdToName(relationName, shardId);
}
/* placeholder for DropPolicyEventExtendNames */
/*
* DropPolicyEventExtendNames extends relation names in the given DropStmt tree specific
* to policies. This function has side effects on the tree as the names are replaced
* inplace.
*/
void
DropPolicyEventExtendNames(DropStmt *dropStmt, const char *schemaName, uint64 shardId)
{
/* placeholder for future implementation */
Value *relationSchemaNameValue = NULL;
Value *relationNameValue = NULL;
uint32 dropCount = list_length(dropStmt->objects);
if (dropCount > 1)
{
ereport(ERROR, (errmsg("cannot extend name for multiple drop objects")));
}
List *relationNameList = (List *) linitial(dropStmt->objects);
int relationNameListLength = list_length(relationNameList);
switch (relationNameListLength)
{
case 2:
{
relationNameValue = linitial(relationNameList);
break;
}
case 3:
{
relationSchemaNameValue = linitial(relationNameList);
relationNameValue = lsecond(relationNameList);
break;
}
default:
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper policy name: \"%s\"",
NameListToString(relationNameList))));
break;
}
}
/* prefix with schema name if it is not added already */
if (relationSchemaNameValue == NULL)
{
Value *schemaNameValue = makeString(pstrdup(schemaName));
relationNameList = lcons(schemaNameValue, relationNameList);
}
char **relationName = &(relationNameValue->val.str);
AppendShardIdToName(relationName, shardId);
}

View File

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

View File

@ -14,7 +14,9 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/genam.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
@ -31,6 +33,9 @@
#include "distributed/coordinator_protocol.h"
#include "distributed/metadata/distobject.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata/distobject.h"
#include "distributed/multi_executor.h"
#include "distributed/relation_access_tracking.h"
#include "distributed/version_compat.h"
#include "distributed/worker_transaction.h"
#include "miscadmin.h"
@ -40,6 +45,7 @@
#include "parser/scansup.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc_tables.h"
#include "utils/guc.h"
#include "utils/rel.h"
@ -54,6 +60,9 @@ static char * CreateCreateOrAlterRoleCommand(const char *roleName,
AlterRoleStmt *alterRoleStmt);
static DefElem * makeDefElemInt(char *name, int value);
static List * GenerateRoleOptionsList(HeapTuple tuple);
static List * GenerateGrantRoleStmtsFromOptions(RoleSpec *roleSpec, List *options);
static List * GenerateGrantRoleStmtsOfRole(Oid roleid);
static void EnsureSequentialModeForRoleDDL(void);
static char * GetRoleNameFromDbRoleSetting(HeapTuple tuple,
TupleDesc DbRoleSettingDescription);
@ -68,6 +77,7 @@ static int ConfigGenericNameCompare(const void *lhs, const void *rhs);
static ObjectAddress RoleSpecToObjectAddress(RoleSpec *role, bool missing_ok);
/* controlled via GUC */
bool EnableCreateRolePropagation = true;
bool EnableAlterRolePropagation = true;
bool EnableAlterRoleSetPropagation = true;
@ -133,11 +143,13 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
return NIL;
}
if (!EnableAlterRolePropagation || !IsCoordinator())
if (!EnableAlterRolePropagation)
{
return NIL;
}
EnsureCoordinator();
AlterRoleStmt *stmt = castNode(AlterRoleStmt, node);
DefElem *option = NULL;
@ -161,7 +173,9 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
break;
}
}
List *commands = list_make1((void *) CreateAlterRoleIfExistsCommand(stmt));
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) CreateAlterRoleIfExistsCommand(stmt),
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
@ -206,14 +220,7 @@ PreprocessAlterRoleSetStmt(Node *node, const char *queryString,
return NIL;
}
/*
* Since roles need to be handled manually on community, we need to support such queries
* by handling them locally on worker nodes
*/
if (!IsCoordinator())
{
return NIL;
}
EnsureCoordinator();
QualifyTreeNode((Node *) stmt);
const char *sql = DeparseTreeNode((Node *) stmt);
@ -337,6 +344,7 @@ ExtractEncryptedPassword(Oid roleOid)
Datum passwordDatum = heap_getattr(tuple, Anum_pg_authid_rolpassword,
pgAuthIdDescription, &isNull);
char *passwordCstring = TextDatumGetCString(passwordDatum);
table_close(pgAuthId, AccessShareLock);
ReleaseSysCache(tuple);
@ -346,7 +354,7 @@ ExtractEncryptedPassword(Oid roleOid)
return NULL;
}
return pstrdup(TextDatumGetCString(passwordDatum));
return pstrdup(passwordCstring);
}
@ -492,6 +500,14 @@ GenerateCreateOrAlterRoleCommand(Oid roleOid)
Form_pg_authid role = ((Form_pg_authid) GETSTRUCT(roleTuple));
CreateRoleStmt *createRoleStmt = NULL;
if (EnableCreateRolePropagation)
{
createRoleStmt = makeNode(CreateRoleStmt);
createRoleStmt->stmt_type = ROLESTMT_ROLE;
createRoleStmt->role = pstrdup(NameStr(role->rolname));
createRoleStmt->options = GenerateRoleOptionsList(roleTuple);
}
AlterRoleStmt *alterRoleStmt = NULL;
if (EnableAlterRolePropagation)
{
@ -525,6 +541,16 @@ GenerateCreateOrAlterRoleCommand(Oid roleOid)
completeRoleList = list_concat(completeRoleList, alterRoleSetCommands);
}
if (EnableCreateRolePropagation)
{
List *grantRoleStmts = GenerateGrantRoleStmtsOfRole(roleOid);
Node *stmt = NULL;
foreach_ptr(stmt, grantRoleStmts)
{
completeRoleList = lappend(completeRoleList, DeparseTreeNode(stmt));
}
}
return completeRoleList;
}
@ -731,6 +757,157 @@ MakeSetStatementArguments(char *configurationName, char *configurationValue)
}
/*
* GenerateGrantRoleStmtsFromOptions gets a RoleSpec of a role that is being
* created and a list of options of CreateRoleStmt to generate GrantRoleStmts
* for the role's memberships.
*/
static List *
GenerateGrantRoleStmtsFromOptions(RoleSpec *roleSpec, List *options)
{
List *stmts = NIL;
DefElem *option = NULL;
foreach_ptr(option, options)
{
if (strcmp(option->defname, "adminmembers") != 0 &&
strcmp(option->defname, "rolemembers") != 0 &&
strcmp(option->defname, "addroleto") != 0)
{
continue;
}
GrantRoleStmt *grantRoleStmt = makeNode(GrantRoleStmt);
grantRoleStmt->is_grant = true;
if (strcmp(option->defname, "adminmembers") == 0 || strcmp(option->defname,
"rolemembers") == 0)
{
grantRoleStmt->granted_roles = list_make1(roleSpec);
grantRoleStmt->grantee_roles = (List *) option->arg;
}
else
{
grantRoleStmt->granted_roles = (List *) option->arg;
grantRoleStmt->grantee_roles = list_make1(roleSpec);
}
if (strcmp(option->defname, "adminmembers") == 0)
{
grantRoleStmt->admin_opt = true;
}
stmts = lappend(stmts, grantRoleStmt);
}
return stmts;
}
/*
* GenerateGrantRoleStmtsOfRole generates the GrantRoleStmts for the memberships
* of the role whose oid is roleid.
*/
static List *
GenerateGrantRoleStmtsOfRole(Oid roleid)
{
Relation pgAuthMembers = table_open(AuthMemRelationId, AccessShareLock);
HeapTuple tuple = NULL;
List *stmts = NIL;
ScanKeyData skey[1];
ScanKeyInit(&skey[0], Anum_pg_auth_members_member, BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(roleid));
SysScanDesc scan = systable_beginscan(pgAuthMembers, AuthMemMemRoleIndexId, true,
NULL, 1, &skey[0]);
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
{
Form_pg_auth_members membership = (Form_pg_auth_members) GETSTRUCT(tuple);
GrantRoleStmt *grantRoleStmt = makeNode(GrantRoleStmt);
grantRoleStmt->is_grant = true;
RoleSpec *grantedRole = makeNode(RoleSpec);
grantedRole->roletype = ROLESPEC_CSTRING;
grantedRole->location = -1;
grantedRole->rolename = GetUserNameFromId(membership->roleid, true);
grantRoleStmt->granted_roles = list_make1(grantedRole);
RoleSpec *granteeRole = makeNode(RoleSpec);
granteeRole->roletype = ROLESPEC_CSTRING;
granteeRole->location = -1;
granteeRole->rolename = GetUserNameFromId(membership->member, true);
grantRoleStmt->grantee_roles = list_make1(granteeRole);
grantRoleStmt->grantor = NULL;
grantRoleStmt->admin_opt = membership->admin_option;
stmts = lappend(stmts, grantRoleStmt);
}
systable_endscan(scan);
table_close(pgAuthMembers, AccessShareLock);
return stmts;
}
/*
* PreprocessCreateRoleStmt creates a worker_create_or_alter_role query for the
* role that is being created. With that query we can create the role in the
* workers or if they exist we alter them to the way they are being created
* right now.
*/
List *
PreprocessCreateRoleStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!EnableCreateRolePropagation || !ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialModeForRoleDDL();
LockRelationOid(DistNodeRelationId(), RowShareLock);
CreateRoleStmt *createRoleStmt = castNode(CreateRoleStmt, node);
AlterRoleStmt *alterRoleStmt = makeNode(AlterRoleStmt);
alterRoleStmt->role = makeNode(RoleSpec);
alterRoleStmt->role->roletype = ROLESPEC_CSTRING;
alterRoleStmt->role->location = -1;
alterRoleStmt->role->rolename = pstrdup(createRoleStmt->role);
alterRoleStmt->action = 1;
alterRoleStmt->options = createRoleStmt->options;
List *grantRoleStmts = GenerateGrantRoleStmtsFromOptions(alterRoleStmt->role,
createRoleStmt->options);
char *createOrAlterRoleQuery = CreateCreateOrAlterRoleCommand(createRoleStmt->role,
createRoleStmt,
alterRoleStmt);
List *commands = NIL;
commands = lappend(commands, DISABLE_DDL_PROPAGATION);
commands = lappend(commands, createOrAlterRoleQuery);
/* deparse all grant statements and add them to the to commands list */
Node *stmt = NULL;
foreach_ptr(stmt, grantRoleStmts)
{
commands = lappend(commands, DeparseTreeNode(stmt));
}
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* makeStringConst creates a Const Node that stores a given string
*
@ -785,6 +962,178 @@ makeFloatConst(char *str, int location)
}
/*
* PreprocessDropRoleStmt finds the distributed role out of the ones
* being dropped and unmarks them distributed and creates the drop statements
* for the workers.
*/
List *
PreprocessDropRoleStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropRoleStmt *stmt = castNode(DropRoleStmt, node);
List *allDropRoles = stmt->roles;
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
if (list_length(distributedDropRoles) <= 0)
{
return NIL;
}
if (!EnableCreateRolePropagation || !ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
EnsureSequentialModeForRoleDDL();
stmt->roles = distributedDropRoles;
char *sql = DeparseTreeNode((Node *) stmt);
stmt->roles = allDropRoles;
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* UnmarkRolesDistributed unmarks the roles in the RoleSpec list distributed.
*/
void
UnmarkRolesDistributed(List *roles)
{
Node *roleNode = NULL;
foreach_ptr(roleNode, roles)
{
RoleSpec *role = castNode(RoleSpec, roleNode);
ObjectAddress roleAddress = { 0 };
Oid roleOid = get_rolespec_oid(role, true);
if (roleOid == InvalidOid)
{
/*
* If the role is dropped (concurrently), we might get an inactive oid for the
* role. If it is invalid oid, skip.
*/
continue;
}
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
UnmarkObjectDistributed(&roleAddress);
}
}
/*
* FilterDistributedRoles filters the list of RoleSpecs and returns the ones
* that are distributed.
*/
List *
FilterDistributedRoles(List *roles)
{
List *distributedRoles = NIL;
Node *roleNode = NULL;
foreach_ptr(roleNode, roles)
{
RoleSpec *role = castNode(RoleSpec, roleNode);
ObjectAddress roleAddress = { 0 };
Oid roleOid = get_rolespec_oid(role, true);
if (roleOid == InvalidOid)
{
/*
* Non-existing roles are ignored silently here. Postgres will
* handle to give an error or not for these roles.
*/
continue;
}
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
if (IsObjectDistributed(&roleAddress))
{
distributedRoles = lappend(distributedRoles, role);
}
}
return distributedRoles;
}
/*
* PreprocessGrantRoleStmt finds the distributed grantee roles and creates the
* query to run on the workers.
*/
List *
PreprocessGrantRoleStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
if (!EnableCreateRolePropagation || !ShouldPropagate())
{
return NIL;
}
EnsureCoordinator();
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
List *allGranteeRoles = stmt->grantee_roles;
RoleSpec *grantor = stmt->grantor;
List *distributedGranteeRoles = FilterDistributedRoles(allGranteeRoles);
if (list_length(distributedGranteeRoles) <= 0)
{
return NIL;
}
/*
* Postgres don't seem to use the grantor. Even dropping the grantor doesn't
* seem to affect the membership. If this changes, we might need to add grantors
* to the dependency resolution too. For now we just don't propagate it.
*/
stmt->grantor = NULL;
stmt->grantee_roles = distributedGranteeRoles;
char *sql = DeparseTreeNode((Node *) stmt);
stmt->grantee_roles = allGranteeRoles;
stmt->grantor = grantor;
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}
/*
* PostprocessGrantRoleStmt actually creates the plan we need to execute for grant
* role statement.
*/
List *
PostprocessGrantRoleStmt(Node *node, const char *queryString)
{
if (!EnableCreateRolePropagation || !IsCoordinator() || !ShouldPropagate())
{
return NIL;
}
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
RoleSpec *role = NULL;
foreach_ptr(role, stmt->grantee_roles)
{
ObjectAddress roleAddress = { 0 };
Oid roleOid = get_rolespec_oid(role, false);
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
if (IsObjectDistributed(&roleAddress))
{
EnsureDependenciesExistOnAllNodes(&roleAddress);
}
}
return NIL;
}
/*
* ConfigGenericNameCompare compares two config_generic structs based on their
* name fields. If the name fields contain the same strings two structs are
@ -805,3 +1154,64 @@ ConfigGenericNameCompare(const void *a, const void *b)
*/
return pg_strcasecmp(confa->name, confb->name);
}
/*
* CreateRoleStmtObjectAddress finds the ObjectAddress for the role described
* by the CreateRoleStmt. If missing_ok is false this function throws an error if the
* role does not exist.
*
* Never returns NULL, but the objid in the address could be invalid if missing_ok was set
* to true.
*/
ObjectAddress
CreateRoleStmtObjectAddress(Node *node, bool missing_ok)
{
CreateRoleStmt *stmt = castNode(CreateRoleStmt, node);
Oid roleOid = get_role_oid(stmt->role, missing_ok);
ObjectAddress roleAddress = { 0 };
ObjectAddressSet(roleAddress, AuthIdRelationId, roleOid);
return roleAddress;
}
/*
* EnsureSequentialModeForRoleDDL makes sure that the current transaction is already in
* sequential mode, or can still safely be put in sequential mode, it errors if that is
* not possible. The error contains information for the user to retry the transaction with
* sequential mode set from the begining.
*
* As roles are node scoped objects there exists only 1 instance of the role used by
* potentially multiple shards. To make sure all shards in the transaction can interact
* with the role the role needs to be visible on all connections used by the transaction,
* meaning we can only use 1 connection per node.
*/
static void
EnsureSequentialModeForRoleDDL(void)
{
if (!IsTransactionBlock())
{
/* we do not need to switch to sequential mode if we are not in a transaction */
return;
}
if (ParallelQueryExecutedInTransaction())
{
ereport(ERROR, (errmsg("cannot create or modify role because there was a "
"parallel operation on a distributed table in the "
"transaction"),
errdetail("When creating or altering a role, Citus needs to "
"perform all operations over a single connection per "
"node to ensure consistency."),
errhint("Try re-running the transaction with "
"\"SET LOCAL citus.multi_shard_modify_mode TO "
"\'sequential\';\"")));
}
ereport(DEBUG1, (errmsg("switching to sequential query execution mode"),
errdetail("Role is created or altered. To make sure subsequent "
"commands see the role correctly we need to make sure to "
"use only one connection for all future commands")));
SetLocalMultiShardModifyModeToSequential();
}

View File

@ -161,14 +161,7 @@ PreprocessGrantOnSchemaStmt(Node *node, const char *queryString,
return NIL;
}
/*
* Since access control needs to be handled manually on community, we need to support
* such queries by handling them locally on worker nodes.
*/
if (!IsCoordinator())
{
return NIL;
}
EnsureCoordinator();
List *originalObjects = stmt->objects;
@ -178,41 +171,8 @@ PreprocessGrantOnSchemaStmt(Node *node, const char *queryString,
stmt->objects = originalObjects;
return NodeDDLTaskList(NON_COORDINATOR_NODES, list_make1(sql));
}
/*
* PreprocessAlterSchemaRenameStmt is called when the user is renaming a schema.
* The invocation happens before the statement is applied locally.
*
* As the schema already exists we have access to the ObjectAddress for the schema, this
* is used to check if the schmea is distributed. If the schema is distributed the rename
* is executed on all the workers to keep the schemas in sync across the cluster.
*/
List *
PreprocessAlterSchemaRenameStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
ObjectAddress schemaAddress = GetObjectAddressFromParseTree(node, false);
if (!ShouldPropagateObject(&schemaAddress))
{
return NIL;
}
EnsureCoordinator();
/* fully qualify */
QualifyTreeNode(node);
/* deparse sql*/
const char *renameStmtSql = DeparseTreeNode(node);
EnsureSequentialMode(OBJECT_SCHEMA);
/* to prevent recursion with mx we disable ddl propagation */
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
(void *) renameStmtSql,
(void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);

View File

@ -24,6 +24,7 @@
#include "distributed/metadata/distobject.h"
#include "distributed/metadata_cache.h"
#include "distributed/metadata_sync.h"
#include "nodes/makefuncs.h"
#include "distributed/worker_create_or_replace.h"
#include "nodes/parsenodes.h"
#include "utils/builtins.h"
@ -32,6 +33,7 @@
/* Local functions forward declarations for helper functions */
static bool OptionsSpecifyOwnedBy(List *optionList, Oid *ownedByTableId);
static Oid SequenceUsedInDistributedTable(const ObjectAddress *sequenceAddress);
static List * FilterDistributedSequences(GrantStmt *stmt);
/*
@ -226,7 +228,6 @@ PreprocessDropSequenceStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
DropStmt *stmt = castNode(DropStmt, node);
List *deletingSequencesList = stmt->objects;
List *distributedSequencesList = NIL;
List *distributedSequenceAddresses = NIL;
@ -259,6 +260,7 @@ PreprocessDropSequenceStmt(Node *node, const char *queryString,
* iterate over all sequences to be dropped and filter to keep only distributed
* sequences.
*/
List *deletingSequencesList = stmt->objects;
List *objectNameList = NULL;
foreach_ptr(objectNameList, deletingSequencesList)
{
@ -660,6 +662,97 @@ PostprocessAlterSequenceOwnerStmt(Node *node, const char *queryString)
}
/*
* PreprocessGrantOnSequenceStmt is executed before the statement is applied to the local
* postgres instance.
*
* In this stage we can prepare the commands that need to be run on all workers to grant
* on distributed sequences.
*/
List *
PreprocessGrantOnSequenceStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_SEQUENCE);
if (creating_extension)
{
/*
* extensions should be created separately on the workers, sequences cascading
* from an extension should therefore not be propagated here.
*/
return NIL;
}
if (!EnableMetadataSync)
{
/*
* we are configured to disable object propagation, should not propagate anything
*/
return NIL;
}
List *distributedSequences = FilterDistributedSequences(stmt);
if (list_length(distributedSequences) == 0)
{
return NIL;
}
EnsureCoordinator();
GrantStmt *stmtCopy = copyObject(stmt);
stmtCopy->objects = distributedSequences;
/*
* if the original command was targeting schemas, we have expanded to the distributed
* sequences in these schemas through FilterDistributedSequences.
*/
stmtCopy->targtype = ACL_TARGET_OBJECT;
QualifyTreeNode((Node *) stmtCopy);
char *sql = DeparseTreeNode((Node *) stmtCopy);
List *commands = list_make3(DISABLE_DDL_PROPAGATION, (void *) sql,
ENABLE_DDL_PROPAGATION);
return NodeDDLTaskList(NON_COORDINATOR_METADATA_NODES, commands);
}
/*
* PostprocessGrantOnSequenceStmt makes sure dependencies of each
* distributed sequence in the statement exist on all nodes
*/
List *
PostprocessGrantOnSequenceStmt(Node *node, const char *queryString)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_SEQUENCE);
List *distributedSequences = FilterDistributedSequences(stmt);
if (list_length(distributedSequences) == 0)
{
return NIL;
}
EnsureCoordinator();
RangeVar *sequence = NULL;
foreach_ptr(sequence, distributedSequences)
{
ObjectAddress sequenceAddress = { 0 };
Oid sequenceOid = RangeVarGetRelid(sequence, NoLock, false);
ObjectAddressSet(sequenceAddress, RelationRelationId, sequenceOid);
EnsureDependenciesExistOnAllNodes(&sequenceAddress);
}
return NIL;
}
/*
* GenerateBackupNameForSequenceCollision generates a new sequence name for an existing
* sequence. The name is generated in such a way that the new name doesn't overlap with
@ -702,6 +795,96 @@ GenerateBackupNameForSequenceCollision(const ObjectAddress *address)
}
/*
* FilterDistributedSequences determines and returns a list of distributed sequences
* RangeVar-s from given grant statement.
* - If the stmt's targtype is ACL_TARGET_OBJECT, i.e. of the form GRANT ON SEQUENCE ...
* it returns the distributed sequences in the list of sequences in the statement
* - If targtype is ACL_TARGET_ALL_IN_SCHEMA, i.e. GRANT ON ALL SEQUENCES IN SCHEMA ...
* it expands the ALL IN SCHEMA to the actual sequences, and returns the distributed
* sequences from those.
*/
static List *
FilterDistributedSequences(GrantStmt *stmt)
{
bool grantOnSequenceCommand = (stmt->targtype == ACL_TARGET_OBJECT &&
stmt->objtype == OBJECT_SEQUENCE);
bool grantOnAllSequencesInSchemaCommand = (stmt->targtype ==
ACL_TARGET_ALL_IN_SCHEMA &&
stmt->objtype == OBJECT_SEQUENCE);
/* we are only interested in sequence level grants */
if (!grantOnSequenceCommand && !grantOnAllSequencesInSchemaCommand)
{
return NIL;
}
List *grantSequenceList = NIL;
if (grantOnAllSequencesInSchemaCommand)
{
/* iterate over all namespace names provided to get their oid's */
List *namespaceOidList = NIL;
Value *namespaceValue = NULL;
foreach_ptr(namespaceValue, stmt->objects)
{
char *nspname = strVal(namespaceValue);
bool missing_ok = false;
Oid namespaceOid = get_namespace_oid(nspname, missing_ok);
namespaceOidList = list_append_unique_oid(namespaceOidList, namespaceOid);
}
/*
* iterate over all distributed sequences to filter the ones
* that belong to one of the namespaces from above
*/
List *distributedSequenceList = DistributedSequenceList();
ObjectAddress *sequenceAddress = NULL;
foreach_ptr(sequenceAddress, distributedSequenceList)
{
Oid namespaceOid = get_rel_namespace(sequenceAddress->objectId);
/*
* if this distributed sequence's schema is one of the schemas
* specified in the GRANT .. ALL SEQUENCES IN SCHEMA ..
* add it to the list
*/
if (list_member_oid(namespaceOidList, namespaceOid))
{
RangeVar *distributedSequence = makeRangeVar(get_namespace_name(
namespaceOid),
get_rel_name(
sequenceAddress->objectId),
-1);
grantSequenceList = lappend(grantSequenceList, distributedSequence);
}
}
}
else
{
bool missing_ok = false;
RangeVar *sequenceRangeVar = NULL;
foreach_ptr(sequenceRangeVar, stmt->objects)
{
ObjectAddress sequenceAddress = { 0 };
Oid sequenceOid = RangeVarGetRelid(sequenceRangeVar, NoLock, missing_ok);
ObjectAddressSet(sequenceAddress, RelationRelationId, sequenceOid);
/*
* if this sequence from GRANT .. ON SEQUENCE .. is a distributed
* sequence, add it to the list
*/
if (IsObjectDistributed(&sequenceAddress))
{
grantSequenceList = lappend(grantSequenceList, sequenceRangeVar);
}
}
}
return grantSequenceList;
}
/*
* RenameExistingSequenceWithDifferentTypeIfExists renames the sequence's type if
* that sequence exists and the desired sequence type is different than it's type.

View File

@ -92,7 +92,7 @@ PreprocessCreateStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -197,7 +197,7 @@ PreprocessDropStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -236,7 +236,7 @@ PreprocessAlterStatisticsRenameStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -274,7 +274,7 @@ PreprocessAlterStatisticsSchemaStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -376,7 +376,7 @@ PreprocessAlterStatisticsStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);
@ -416,7 +416,7 @@ PreprocessAlterStatisticsOwnerStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->startNewTransaction = false;
ddlJob->metadataSyncCommand = ddlCommand;
ddlJob->taskList = DDLTaskList(relationId, ddlCommand);

View File

@ -10,13 +10,129 @@
#include "postgres.h"
#include "safe_lib.h"
#include <string.h>
#include "commands/defrem.h"
#include "distributed/commands.h"
#include "distributed/connection_management.h"
#include "distributed/pg_version_constants.h"
#include "distributed/version_compat.h"
#include "libpq-fe.h"
#include "nodes/parsenodes.h"
#include "utils/builtins.h"
/* placeholder for ProcessCreateSubscriptionStmt */
static char * GenerateConninfoWithAuth(char *conninfo);
/*
* ProcessCreateSubscriptionStmt looks for a special citus_use_authinfo option.
* If it is set to true, then we'll expand the node's authinfo into the create
* statement (see GenerateConninfoWithAuth).
*/
Node *
ProcessCreateSubscriptionStmt(CreateSubscriptionStmt *createSubStmt)
{
ListCell *currCell = NULL;
#if PG_VERSION_NUM < PG_VERSION_13
ListCell *prevCell = NULL;
#endif
bool useAuthinfo = false;
foreach(currCell, createSubStmt->options)
{
DefElem *defElem = (DefElem *) lfirst(currCell);
if (strcmp(defElem->defname, "citus_use_authinfo") == 0)
{
useAuthinfo = defGetBoolean(defElem);
createSubStmt->options = list_delete_cell_compat(createSubStmt->options,
currCell,
prevCell);
break;
}
#if PG_VERSION_NUM < PG_VERSION_13
prevCell = currCell;
#endif
}
if (useAuthinfo)
{
createSubStmt->conninfo = GenerateConninfoWithAuth(createSubStmt->conninfo);
}
return (Node *) createSubStmt;
}
/*
* GenerateConninfoWithAuth extracts the host and port from the provided libpq
* conninfo string, using them to find an appropriate authinfo for the target
* host. If such an authinfo is found, it is added to the (repalloc'd) string,
* which is then returned.
*/
static char *
GenerateConninfoWithAuth(char *conninfo)
{
StringInfo connInfoWithAuth = makeStringInfo();
char *host = NULL, *user = NULL;
int32 port = -1;
PQconninfoOption *option = NULL, *optionArray = NULL;
optionArray = PQconninfoParse(conninfo, NULL);
if (optionArray == NULL)
{
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("not a valid libpq connection info string: %s",
conninfo)));
}
for (option = optionArray; option->keyword != NULL; option++)
{
if (option->val == NULL || option->val[0] == '\0')
{
continue;
}
if (strcmp(option->keyword, "host") == 0)
{
host = option->val;
}
else if (strcmp(option->keyword, "port") == 0)
{
port = pg_atoi(option->val, 4, 0);
}
else if (strcmp(option->keyword, "user") == 0)
{
user = option->val;
}
}
/*
* In case of repetition of parameters in connection strings, last value
* wins. So first add the provided connection string, then global
* connection parameters, then node specific ones.
*
* Note that currently lists of parameters in pg_dist_authnode and
* citus.node_conninfo do not overlap.
*
* The only overlapping parameter between these three lists is
* connect_timeout, which is assigned in conninfo (generated
* by CreateShardMoveSubscription) and is also allowed in
* citus.node_conninfo. Prioritizing the value in citus.node_conninfo
* over conninfo gives user the power to control this value.
*/
appendStringInfo(connInfoWithAuth, "%s %s", conninfo, NodeConninfo);
if (host != NULL && port > 0 && user != NULL)
{
char *nodeAuthInfo = GetAuthinfo(host, port, user);
appendStringInfo(connInfoWithAuth, " %s", nodeAuthInfo);
}
PQconninfoFree(optionArray);
return connInfoWithAuth->data;
}

View File

@ -651,12 +651,21 @@ PostprocessAlterTableSchemaStmt(Node *node, const char *queryString)
*/
ObjectAddress tableAddress = GetObjectAddressFromParseTree((Node *) stmt, true);
/* check whether we are dealing with a sequence here */
if (get_rel_relkind(tableAddress.objectId) == RELKIND_SEQUENCE)
/*
* Check whether we are dealing with a sequence or view here and route queries
* accordingly to the right processor function.
*/
char relKind = get_rel_relkind(tableAddress.objectId);
if (relKind == RELKIND_SEQUENCE)
{
stmt->objectType = OBJECT_SEQUENCE;
return PostprocessAlterSequenceSchemaStmt((Node *) stmt, queryString);
}
else if (relKind == RELKIND_VIEW)
{
stmt->objectType = OBJECT_VIEW;
return PostprocessAlterViewSchemaStmt((Node *) stmt, queryString);
}
if (!ShouldPropagate() || !IsCitusTable(tableAddress.objectId))
{
@ -699,18 +708,26 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
}
/*
* check whether we are dealing with a sequence here
* check whether we are dealing with a sequence or view here
* if yes, it must be ALTER TABLE .. OWNER TO .. command
* since this is the only ALTER command of a sequence that
* since this is the only ALTER command of a sequence or view that
* passes through an AlterTableStmt
*/
if (get_rel_relkind(leftRelationId) == RELKIND_SEQUENCE)
char relKind = get_rel_relkind(leftRelationId);
if (relKind == RELKIND_SEQUENCE)
{
AlterTableStmt *stmtCopy = copyObject(alterTableStatement);
AlterTableStmtObjType_compat(stmtCopy) = OBJECT_SEQUENCE;
return PreprocessAlterSequenceOwnerStmt((Node *) stmtCopy, alterTableCommand,
processUtilityContext);
}
else if (relKind == RELKIND_VIEW)
{
AlterTableStmt *stmtCopy = copyObject(alterTableStatement);
AlterTableStmtObjType_compat(stmtCopy) = OBJECT_VIEW;
return PreprocessAlterViewStmt((Node *) stmtCopy, alterTableCommand,
processUtilityContext);
}
/*
* AlterTableStmt applies also to INDEX relations, and we have support for
@ -1102,7 +1119,7 @@ PreprocessAlterTableStmt(Node *node, const char *alterTableCommand,
/* fill them here as it is possible to use them in some conditional blocks below */
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = leftRelationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, leftRelationId);
const char *sqlForTaskList = alterTableCommand;
if (deparseAT)
@ -1758,18 +1775,31 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString,
{
return NIL;
}
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
stmt->missing_ok);
Oid relationId = address.objectId;
/* check whether we are dealing with a sequence here */
if (get_rel_relkind(relationId) == RELKIND_SEQUENCE)
/*
* Check whether we are dealing with a sequence or view here and route queries
* accordingly to the right processor function. We need to check both objects here
* since PG supports targeting sequences and views with ALTER TABLE commands.
*/
char relKind = get_rel_relkind(relationId);
if (relKind == RELKIND_SEQUENCE)
{
AlterObjectSchemaStmt *stmtCopy = copyObject(stmt);
stmtCopy->objectType = OBJECT_SEQUENCE;
return PreprocessAlterSequenceSchemaStmt((Node *) stmtCopy, queryString,
processUtilityContext);
}
else if (relKind == RELKIND_VIEW)
{
AlterObjectSchemaStmt *stmtCopy = copyObject(stmt);
stmtCopy->objectType = OBJECT_VIEW;
return PreprocessAlterViewSchemaStmt((Node *) stmtCopy, queryString,
processUtilityContext);
}
/* first check whether a distributed relation is affected */
if (!OidIsValid(relationId) || !IsCitusTable(relationId))
@ -1779,7 +1809,7 @@ PreprocessAlterTableSchemaStmt(Node *node, const char *queryString,
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
QualifyTreeNode((Node *) stmt);
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = DeparseTreeNode((Node *) stmt);
ddlJob->taskList = DDLTaskList(relationId, ddlJob->metadataSyncCommand);
return list_make1(ddlJob);
@ -1939,12 +1969,19 @@ PostprocessAlterTableStmt(AlterTableStmt *alterTableStatement)
* since this is the only ALTER command of a sequence that
* passes through an AlterTableStmt
*/
if (get_rel_relkind(relationId) == RELKIND_SEQUENCE)
char relKind = get_rel_relkind(relationId);
if (relKind == RELKIND_SEQUENCE)
{
AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_SEQUENCE;
PostprocessAlterSequenceOwnerStmt((Node *) alterTableStatement, NULL);
return;
}
else if (relKind == RELKIND_VIEW)
{
AlterTableStmtObjType_compat(alterTableStatement) = OBJECT_VIEW;
PostprocessAlterViewStmt((Node *) alterTableStatement, NULL);
return;
}
/*
* Before ensuring each dependency exist, update dependent sequences
@ -2563,6 +2600,7 @@ ErrorIfUnsupportedConstraint(Relation relation, char distributionMethod,
* ALTER TABLE ADD|DROP CONSTRAINT
* ALTER TABLE REPLICA IDENTITY
* ALTER TABLE SET ()
* ALTER TABLE ENABLE|DISABLE|NO FORCE|FORCE ROW LEVEL SECURITY
* ALTER TABLE RESET ()
* ALTER TABLE ENABLE/DISABLE TRIGGER (if enable_unsafe_triggers is not set, we only support triggers for citus local tables)
*/
@ -2906,6 +2944,10 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement)
case AT_SetNotNull:
case AT_ReplicaIdentity:
case AT_ChangeOwner:
case AT_EnableRowSecurity:
case AT_DisableRowSecurity:
case AT_ForceRowSecurity:
case AT_NoForceRowSecurity:
case AT_ValidateConstraint:
case AT_DropConstraint: /* we do the check for invalidation in AlterTableDropsForeignKey */
#if PG_VERSION_NUM >= PG_VERSION_14
@ -2950,6 +2992,7 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement)
errdetail("Only ADD|DROP COLUMN, SET|DROP NOT NULL, "
"SET|DROP DEFAULT, ADD|DROP|VALIDATE CONSTRAINT, "
"SET (), RESET (), "
"ENABLE|DISABLE|NO FORCE|FORCE ROW LEVEL SECURITY, "
"ATTACH|DETACH PARTITION and TYPE subcommands "
"are supported.")));
}

View File

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

View File

@ -712,7 +712,7 @@ CitusCreateTriggerCommandDDLJob(Oid relationId, char *triggerName,
const char *queryString)
{
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = queryString;
if (!triggerName)

View File

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

View File

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

View File

@ -42,6 +42,7 @@
#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "distributed/adaptive_executor.h"
#include "distributed/backend_data.h"
#include "distributed/colocation_utils.h"
#include "distributed/commands.h"
#include "distributed/commands/multi_copy.h"
@ -53,6 +54,7 @@
#include "distributed/listutils.h"
#include "distributed/local_executor.h"
#include "distributed/maintenanced.h"
#include "distributed/multi_logical_replication.h"
#include "distributed/multi_partitioning_utils.h"
#if PG_VERSION_NUM < 140000
#include "distributed/metadata_cache.h"
@ -64,6 +66,7 @@
#include "distributed/multi_physical_planner.h"
#include "distributed/reference_table_utils.h"
#include "distributed/resource_lock.h"
#include "distributed/string_utils.h"
#include "distributed/transmit.h"
#include "distributed/version_compat.h"
#include "distributed/worker_shard_visibility.h"
@ -77,6 +80,7 @@
#include "utils/lsyscache.h"
#include "utils/syscache.h"
bool EnableDDLPropagation = true; /* ddl propagation is enabled */
int CreateObjectPropagationMode = CREATE_OBJECT_PROPAGATION_IMMEDIATE;
PropSetCmdBehavior PropagateSetCommands = PROPSETCMD_NONE; /* SET prop off */
@ -164,7 +168,6 @@ multi_ProcessUtility(PlannedStmt *pstmt,
parsetree = pstmt->utilityStmt;
if (IsA(parsetree, TransactionStmt) ||
IsA(parsetree, LockStmt) ||
IsA(parsetree, ListenStmt) ||
IsA(parsetree, NotifyStmt) ||
IsA(parsetree, ExecuteStmt) ||
@ -409,6 +412,31 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
parsetree = ProcessCreateSubscriptionStmt(createSubStmt);
}
if (IsA(parsetree, AlterSubscriptionStmt))
{
AlterSubscriptionStmt *alterSubStmt = (AlterSubscriptionStmt *) parsetree;
if (!superuser() &&
StringStartsWith(alterSubStmt->subname,
SHARD_MOVE_SUBSCRIPTION_PREFIX))
{
ereport(ERROR, (
errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("Only superusers can alter shard move subscriptions")));
}
}
if (IsA(parsetree, DropSubscriptionStmt))
{
DropSubscriptionStmt *dropSubStmt = (DropSubscriptionStmt *) parsetree;
if (!superuser() &&
StringStartsWith(dropSubStmt->subname, SHARD_MOVE_SUBSCRIPTION_PREFIX))
{
ereport(ERROR, (
errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("Only superusers can drop shard move subscriptions")));
}
}
/*
* Process SET LOCAL and SET TRANSACTION statements in multi-statement
* transactions.
@ -505,6 +533,18 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
PreprocessTruncateStatement((TruncateStmt *) parsetree);
}
if (IsA(parsetree, LockStmt))
{
/*
* PreprocessLockStatement might lock the relations locally if the
* node executing the command is in pg_dist_node. Even though the process
* utility will re-acquire the locks across the same relations if the node
* is in the metadata (in the pg_dist_node table) that should not be a problem,
* plus it ensures consistent locking order between the nodes.
*/
PreprocessLockStatement((LockStmt *) parsetree, context);
}
/*
* We only process ALTER TABLE ... ATTACH PARTITION commands in the function below
* and distribute the partition if necessary.
@ -525,6 +565,20 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
parsetree = pstmt->utilityStmt;
ops = GetDistributeObjectOps(parsetree);
/*
* For some statements Citus defines a Qualify function. The goal of this function
* is to take any ambiguity from the statement that is contextual on either the
* search_path or current settings.
* Instead of relying on the search_path and settings we replace any deduced bits
* and fill them out how postgres would resolve them. This makes subsequent
* deserialize calls for the statement portable to other postgres servers, the
* workers in our case.
*/
if (ops && ops->qualify)
{
ops->qualify(parsetree);
}
if (ops && ops->preprocess)
{
ddlJobs = ops->preprocess(parsetree, queryString, context);
@ -575,7 +629,7 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
errhint("You can manually create a database and its "
"extensions on workers.")));
}
else if (IsA(parsetree, CreateRoleStmt))
else if (IsA(parsetree, CreateRoleStmt) && !EnableCreateRolePropagation)
{
ereport(NOTICE, (errmsg("not propagating CREATE ROLE/USER commands to worker"
" nodes"),
@ -605,6 +659,24 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
StopMaintenanceDaemon(MyDatabaseId);
}
/*
* Make sure that dropping the role deletes the pg_dist_object entries. There is a
* separate logic for roles, since roles are not included as dropped objects in the
* drop event trigger. To handle it both on worker and coordinator nodes, it is not
* implemented as a part of process functions but here.
*/
if (IsA(parsetree, DropRoleStmt))
{
DropRoleStmt *stmt = castNode(DropRoleStmt, parsetree);
List *allDropRoles = stmt->roles;
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
if (list_length(distributedDropRoles) > 0)
{
UnmarkRolesDistributed(distributedDropRoles);
}
}
pstmt->utilityStmt = parsetree;
PG_TRY();
@ -714,6 +786,21 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
{
PostprocessAlterTableStmt(castNode(AlterTableStmt, parsetree));
}
if (IsA(parsetree, GrantStmt))
{
GrantStmt *grantStmt = (GrantStmt *) parsetree;
if (grantStmt->targtype == ACL_TARGET_ALL_IN_SCHEMA)
{
/*
* Grant .. IN SCHEMA causes a deadlock if we don't use local execution
* because standard process utility processes the shard placements as well
* and the row-level locks in pg_class will not be released until the current
* transaction commits. We could skip the local shard placements after standard
* process utility, but for simplicity we just prefer using local execution.
*/
SetLocalExecutionStatus(LOCAL_EXECUTION_REQUIRED);
}
}
DDLJob *ddlJob = NULL;
foreach_ptr(ddlJob, ddlJobs)
@ -1081,16 +1168,20 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
EnsureCoordinator();
Oid targetRelationId = ddlJob->targetRelationId;
ObjectAddress targetObjectAddress = ddlJob->targetObjectAddress;
if (OidIsValid(targetRelationId))
if (OidIsValid(targetObjectAddress.classId))
{
/*
* Only for ddlJobs that are targetting a relation (table) we want to sync
* its metadata and verify some properties around the table.
* Only for ddlJobs that are targetting an object we want to sync
* its metadata.
*/
shouldSyncMetadata = ShouldSyncTableMetadata(targetRelationId);
EnsurePartitionTableNotReplicated(targetRelationId);
shouldSyncMetadata = ShouldSyncUserCommandForObject(targetObjectAddress);
if (targetObjectAddress.classId == RelationRelationId)
{
EnsurePartitionTableNotReplicated(targetObjectAddress.objectId);
}
}
bool localExecutionSupported = true;
@ -1341,7 +1432,7 @@ CreateCustomDDLTaskList(Oid relationId, TableDDLCommand *command)
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = relationId;
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = GetTableDDLCommand(command);
ddlJob->taskList = taskList;
@ -1592,10 +1683,9 @@ NodeDDLTaskList(TargetWorkerSet targets, List *commands)
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ddlJob->targetRelationId = InvalidOid;
ddlJob->targetObjectAddress = InvalidObjectAddress;
ddlJob->metadataSyncCommand = NULL;
ddlJob->taskList = list_make1(task);
return list_make1(ddlJob);
}

View File

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

View File

@ -10,9 +10,12 @@
#include "postgres.h"
#include "access/transam.h"
#include "access/xact.h"
#include "distributed/backend_data.h"
#include "distributed/citus_safe_lib.h"
#include "distributed/connection_management.h"
#include "distributed/intermediate_result_pruning.h"
#include "distributed/metadata_cache.h"
#include "distributed/worker_manager.h"
@ -40,6 +43,7 @@ typedef struct ConnParamsInfo
static ConnParamsInfo ConnParams;
/* helper functions for processing connection info */
static ConnectionHashKey * GetEffectiveConnKey(ConnectionHashKey *key);
static Size CalculateMaxSize(void);
static int uri_prefix_length(const char *connstr);
@ -232,6 +236,7 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
* already we can add a pointer to the runtimeValues.
*/
char nodePortString[12] = "";
ConnectionHashKey *effectiveKey = GetEffectiveConnKey(key);
StringInfo applicationName = makeStringInfo();
appendStringInfo(applicationName, "%s%ld", CITUS_APPLICATION_NAME_PREFIX,
@ -260,10 +265,10 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
"application_name"
};
const char *runtimeValues[] = {
key->hostname,
effectiveKey->hostname,
nodePortString,
key->database,
key->user,
effectiveKey->database,
effectiveKey->user,
GetDatabaseEncodingName(),
applicationName->data
};
@ -300,7 +305,7 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
errmsg("too many connParams entries")));
}
pg_ltoa(key->port, nodePortString); /* populate node port string with port */
pg_ltoa(effectiveKey->port, nodePortString); /* populate node port string with port */
/* first step: copy global parameters to beginning of array */
for (Size paramIndex = 0; paramIndex < ConnParams.size; paramIndex++)
@ -322,6 +327,58 @@ GetConnParams(ConnectionHashKey *key, char ***keywords, char ***values,
MemoryContextStrdup(context, runtimeValues[runtimeParamIndex]);
}
/* we look up authinfo by original key, not effective one */
char *authinfo = GetAuthinfo(key->hostname, key->port, key->user);
char *pqerr = NULL;
PQconninfoOption *optionArray = PQconninfoParse(authinfo, &pqerr);
if (optionArray == NULL)
{
/* PQconninfoParse failed, it's unsafe to continue as this has caused segfaults in production */
if (pqerr == NULL)
{
/* parse failed without an error message, treat as OOM error */
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory"),
errdetail("Failed to parse authentication information via libpq")));
}
else
{
/*
* Parse error, should not be possible as the validity is checked upon insert into pg_dist_authinfo,
* however, better safe than sorry
*/
/*
* errmsg is populated by PQconninfoParse which requires us to free the message. Since we want to
* incorporate the parse error into the detail of our message we need to copy the error message before
* freeing it. Not freeing the message will leak memory.
*/
char *pqerrcopy = pstrdup(pqerr);
PQfreemem(pqerr);
ereport(ERROR, (errmsg(
"failed to parse node authentication information for %s@%s:%d",
key->user, key->hostname, key->port),
errdetail("%s", pqerrcopy)));
}
}
for (PQconninfoOption *option = optionArray; option->keyword != NULL; option++)
{
if (option->val == NULL || option->val[0] == '\0')
{
continue;
}
connKeywords[authParamsIdx] = MemoryContextStrdup(context, option->keyword);
connValues[authParamsIdx] = MemoryContextStrdup(context, option->val);
authParamsIdx++;
}
PQconninfoFree(optionArray);
/* final step: add terminal NULL, required by libpq */
connKeywords[authParamsIdx] = connValues[authParamsIdx] = NULL;
}
@ -346,6 +403,116 @@ GetConnParam(const char *keyword)
}
/*
* GetEffectiveConnKey checks whether there is any pooler configuration for the
* provided key (host/port combination). The one case where this logic is not
* applied is for loopback connections originating within the task tracker. If
* a corresponding row is found in the poolinfo table, a modified (effective)
* key is returned with the node, port, and dbname overridden, as applicable,
* otherwise, the original key is returned unmodified.
*/
ConnectionHashKey *
GetEffectiveConnKey(ConnectionHashKey *key)
{
PQconninfoOption *option = NULL, *optionArray = NULL;
if (!IsTransactionState())
{
/* we're in the task tracker, so should only see loopback */
Assert(strncmp(LOCAL_HOST_NAME, key->hostname, MAX_NODE_LENGTH) == 0 &&
PostPortNumber == key->port);
return key;
}
WorkerNode *worker = FindWorkerNode(key->hostname, key->port);
if (worker == NULL)
{
/* this can be hit when the key references an unknown node */
return key;
}
char *poolinfo = GetPoolinfoViaCatalog(worker->nodeId);
if (poolinfo == NULL)
{
return key;
}
/* copy the key to provide defaults for all fields */
ConnectionHashKey *effectiveKey = palloc(sizeof(ConnectionHashKey));
*effectiveKey = *key;
optionArray = PQconninfoParse(poolinfo, NULL);
for (option = optionArray; option->keyword != NULL; option++)
{
if (option->val == NULL || option->val[0] == '\0')
{
continue;
}
if (strcmp(option->keyword, "host") == 0)
{
strlcpy(effectiveKey->hostname, option->val, MAX_NODE_LENGTH);
}
else if (strcmp(option->keyword, "port") == 0)
{
effectiveKey->port = pg_atoi(option->val, 4, 0);
}
else if (strcmp(option->keyword, "dbname") == 0)
{
/* permit dbname for poolers which can key pools based on dbname */
strlcpy(effectiveKey->database, option->val, NAMEDATALEN);
}
else
{
ereport(FATAL, (errmsg("unrecognized poolinfo keyword")));
}
}
PQconninfoFree(optionArray);
return effectiveKey;
}
/*
* GetAuthinfo simply returns the string representation of authentication info
* for a specified hostname/port/user combination. If the current transaction
* is valid, then we use the catalog, otherwise a shared memory hash is used,
* a mode that is currently only useful for getting authentication information
* to the Task Tracker, which lacks a database connection and transaction.
*/
char *
GetAuthinfo(char *hostname, int32 port, char *user)
{
char *authinfo = NULL;
bool isLoopback = (strncmp(LOCAL_HOST_NAME, hostname, MAX_NODE_LENGTH) == 0 &&
PostPortNumber == port);
if (IsTransactionState())
{
int64 nodeId = WILDCARD_NODE_ID;
/* -1 is a special value for loopback connections (task tracker) */
if (isLoopback)
{
nodeId = LOCALHOST_NODE_ID;
}
else
{
WorkerNode *worker = FindWorkerNode(hostname, port);
if (worker != NULL)
{
nodeId = worker->nodeId;
}
}
authinfo = GetAuthinfoViaCatalog(user, nodeId);
}
return (authinfo != NULL) ? authinfo : "";
}
/*
* CalculateMaxSize simply counts the number of elements returned by
* PQconnDefaults, including the final NULL. This helps us know how space would

View File

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

View File

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

View File

@ -79,8 +79,8 @@ static void deparse_index_columns(StringInfo buffer, List *indexParameterList,
List *deparseContext);
static void AppendStorageParametersToString(StringInfo stringBuffer,
List *optionList);
static const char * convert_aclright_to_string(int aclright);
static void simple_quote_literal(StringInfo buf, const char *val);
static char * flatten_reloptions(Oid relid);
static void AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer);
@ -377,6 +377,14 @@ pg_get_tableschemadef_string(Oid tableRelationId, IncludeSequenceDefaults
atttypmod);
appendStringInfoString(&buffer, attributeTypeName);
#if PG_VERSION_NUM >= PG_VERSION_14
if (CompressionMethodIsValid(attributeForm->attcompression))
{
appendStringInfo(&buffer, " COMPRESSION %s",
GetCompressionMethodName(attributeForm->attcompression));
}
#endif
/* if this column has a default value, append the default value */
if (attributeForm->atthasdef)
{
@ -448,14 +456,6 @@ pg_get_tableschemadef_string(Oid tableRelationId, IncludeSequenceDefaults
appendStringInfoString(&buffer, " NOT NULL");
}
#if PG_VERSION_NUM >= PG_VERSION_14
if (CompressionMethodIsValid(attributeForm->attcompression))
{
appendStringInfo(&buffer, " COMPRESSION %s",
GetCompressionMethodName(attributeForm->attcompression));
}
#endif
if (attributeForm->attcollation != InvalidOid &&
attributeForm->attcollation != DEFAULT_COLLATION_OID)
{
@ -1063,6 +1063,138 @@ pg_get_indexclusterdef_string(Oid indexRelationId)
}
/*
* pg_get_table_grants returns a list of sql statements which recreate the
* permissions for a specific table.
*
* This function is modeled after aclexplode(), don't change too heavily.
*/
List *
pg_get_table_grants(Oid relationId)
{
/* *INDENT-OFF* */
StringInfoData buffer;
List *defs = NIL;
bool isNull = false;
Relation relation = relation_open(relationId, AccessShareLock);
char *relationName = generate_relation_name(relationId, NIL);
initStringInfo(&buffer);
/* lookup all table level grants */
HeapTuple classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
if (!HeapTupleIsValid(classTuple))
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation with OID %u does not exist",
relationId)));
}
Datum aclDatum = SysCacheGetAttr(RELOID, classTuple, Anum_pg_class_relacl,
&isNull);
ReleaseSysCache(classTuple);
if (!isNull)
{
/*
* First revoke all default permissions, so we can start adding the
* exact permissions from the master. Note that we only do so if there
* are any actual grants; an empty grant set signals default
* permissions.
*
* Note: This doesn't work correctly if default permissions have been
* changed with ALTER DEFAULT PRIVILEGES - but that's hard to fix
* properly currently.
*/
appendStringInfo(&buffer, "REVOKE ALL ON %s FROM PUBLIC",
relationName);
defs = lappend(defs, pstrdup(buffer.data));
resetStringInfo(&buffer);
/* iterate through the acl datastructure, emit GRANTs */
Acl *acl = DatumGetAclP(aclDatum);
AclItem *aidat = ACL_DAT(acl);
int offtype = -1;
int i = 0;
while (i < ACL_NUM(acl))
{
AclItem *aidata = NULL;
AclMode priv_bit = 0;
offtype++;
if (offtype == N_ACL_RIGHTS)
{
offtype = 0;
i++;
if (i >= ACL_NUM(acl)) /* done */
{
break;
}
}
aidata = &aidat[i];
priv_bit = 1 << offtype;
if (ACLITEM_GET_PRIVS(*aidata) & priv_bit)
{
const char *roleName = NULL;
const char *withGrant = "";
if (aidata->ai_grantee != 0)
{
HeapTuple htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(aidata->ai_grantee));
if (HeapTupleIsValid(htup))
{
Form_pg_authid authForm = ((Form_pg_authid) GETSTRUCT(htup));
roleName = quote_identifier(NameStr(authForm->rolname));
ReleaseSysCache(htup);
}
else
{
elog(ERROR, "cache lookup failed for role %u", aidata->ai_grantee);
}
}
else
{
roleName = "PUBLIC";
}
if ((ACLITEM_GET_GOPTIONS(*aidata) & priv_bit) != 0)
{
withGrant = " WITH GRANT OPTION";
}
appendStringInfo(&buffer, "GRANT %s ON %s TO %s%s",
convert_aclright_to_string(priv_bit),
relationName,
roleName,
withGrant);
defs = lappend(defs, pstrdup(buffer.data));
resetStringInfo(&buffer);
}
}
}
resetStringInfo(&buffer);
relation_close(relation, NoLock);
return defs;
/* *INDENT-ON* */
}
/*
* generate_qualified_relation_name computes the schema-qualified name to display for a
* relation specified by OID.
@ -1157,6 +1289,45 @@ AppendStorageParametersToString(StringInfo stringBuffer, List *optionList)
}
/* copy of postgresql's function, which is static as well */
static const char *
convert_aclright_to_string(int aclright)
{
/* *INDENT-OFF* */
switch (aclright)
{
case ACL_INSERT:
return "INSERT";
case ACL_SELECT:
return "SELECT";
case ACL_UPDATE:
return "UPDATE";
case ACL_DELETE:
return "DELETE";
case ACL_TRUNCATE:
return "TRUNCATE";
case ACL_REFERENCES:
return "REFERENCES";
case ACL_TRIGGER:
return "TRIGGER";
case ACL_EXECUTE:
return "EXECUTE";
case ACL_USAGE:
return "USAGE";
case ACL_CREATE:
return "CREATE";
case ACL_CREATE_TEMP:
return "TEMPORARY";
case ACL_CONNECT:
return "CONNECT";
default:
elog(ERROR, "unrecognized aclright: %d", aclright);
return NULL;
}
/* *INDENT-ON* */
}
/*
* contain_nextval_expression_walker walks over expression tree and returns
* true if it contains call to 'nextval' function.
@ -1225,13 +1396,53 @@ pg_get_replica_identity_command(Oid tableRelationId)
}
/*
* pg_get_row_level_security_commands function returns the required ALTER .. TABLE
* commands to define the row level security settings for a relation.
*/
List *
pg_get_row_level_security_commands(Oid relationId)
{
StringInfoData buffer;
List *commands = NIL;
initStringInfo(&buffer);
Relation relation = table_open(relationId, AccessShareLock);
if (relation->rd_rel->relrowsecurity)
{
char *relationName = generate_qualified_relation_name(relationId);
appendStringInfo(&buffer, "ALTER TABLE %s ENABLE ROW LEVEL SECURITY",
relationName);
commands = lappend(commands, pstrdup(buffer.data));
resetStringInfo(&buffer);
}
if (relation->rd_rel->relforcerowsecurity)
{
char *relationName = generate_qualified_relation_name(relationId);
appendStringInfo(&buffer, "ALTER TABLE %s FORCE ROW LEVEL SECURITY",
relationName);
commands = lappend(commands, pstrdup(buffer.data));
resetStringInfo(&buffer);
}
table_close(relation, AccessShareLock);
return commands;
}
/*
* Generate a C string representing a relation's reloptions, or NULL if none.
*
* This function comes from PostgreSQL source code in
* src/backend/utils/adt/ruleutils.c
*/
static char *
char *
flatten_reloptions(Oid relid)
{
char *result = NULL;

View File

@ -0,0 +1,626 @@
/*-------------------------------------------------------------------------
*
* deparse_domain_stmts.c
* Functions to turn all Statement structures related to domains back
* into sql.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/parsenodes.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_node.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/namespace_utils.h"
static void AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
TypeName *typeName);
static Node * replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref);
static Node * TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName);
static Node * TransformConstraintExpr(Node *expr, TypeName *typeName);
static CoerceToDomainValue * GetCoerceDomainValue(TypeName *typeName);
static char * TypeNameAsIdentifier(TypeName *typeName);
static Oid DomainGetBaseTypeOid(List *names, int32 *baseTypeMod);
static void AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt);
static void AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt);
static void AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt);
/*
* DeparseCreateDomainStmt returns the sql representation for the CREATE DOMAIN statement.
*/
char *
DeparseCreateDomainStmt(Node *node)
{
CreateDomainStmt *stmt = castNode(CreateDomainStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
const char *domainIdentifier = NameListToQuotedString(stmt->domainname);
const char *typeIdentifier = TypeNameAsIdentifier(stmt->typeName);
appendStringInfo(&buf, "CREATE DOMAIN %s AS %s", domainIdentifier, typeIdentifier);
if (stmt->collClause)
{
const char *collateIdentifier =
NameListToQuotedString(stmt->collClause->collname);
appendStringInfo(&buf, " COLLATE %s", collateIdentifier);
}
Constraint *constraint = NULL;
foreach_ptr(constraint, stmt->constraints)
{
AppendConstraint(&buf, constraint, stmt->domainname, stmt->typeName);
}
appendStringInfoString(&buf, ";");
return buf.data;
}
/*
* TypeNameAsIdentifier returns the sql identifier of a TypeName. This is more complex
* than concatenating the schema name and typename since certain types contain modifiers
* that need to be correctly represented.
*/
static char *
TypeNameAsIdentifier(TypeName *typeName)
{
int32 typmod = 0;
Oid typeOid = InvalidOid;
bits16 formatFlags = FORMAT_TYPE_TYPEMOD_GIVEN | FORMAT_TYPE_FORCE_QUALIFY;
typenameTypeIdAndMod(NULL, typeName, &typeOid, &typmod);
return format_type_extended(typeOid, typmod, formatFlags);
}
/*
* DeparseDropDomainStmt returns the sql for teh DROP DOMAIN statement.
*/
char *
DeparseDropDomainStmt(Node *node)
{
DropStmt *stmt = castNode(DropStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
appendStringInfoString(&buf, "DROP DOMAIN ");
if (stmt->missing_ok)
{
appendStringInfoString(&buf, "IF EXISTS ");
}
TypeName *domainName = NULL;
bool first = true;
foreach_ptr(domainName, stmt->objects)
{
if (!first)
{
appendStringInfoString(&buf, ", ");
}
first = false;
const char *identifier = NameListToQuotedString(domainName->names);
appendStringInfoString(&buf, identifier);
}
if (stmt->behavior == DROP_CASCADE)
{
appendStringInfoString(&buf, " CASCADE");
}
appendStringInfoString(&buf, ";");
return buf.data;
}
/*
* DeparseAlterDomainStmt returns the sql representation of the DOMAIN specific ALTER
* statements.
*/
char *
DeparseAlterDomainStmt(Node *node)
{
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
appendStringInfo(&buf, "ALTER DOMAIN %s ", NameListToQuotedString(stmt->typeName));
switch (stmt->subtype)
{
case 'T': /* SET DEFAULT */
{
AppendAlterDomainStmtSetDefault(&buf, stmt);
break;
}
case 'N': /* DROP NOT NULL */
{
appendStringInfoString(&buf, "DROP NOT NULL");
break;
}
case 'O': /* SET NOT NULL */
{
appendStringInfoString(&buf, "SET NOT NULL");
break;
}
case 'C': /* ADD [CONSTRAINT name] */
{
AppendAlterDomainStmtAddConstraint(&buf, stmt);
break;
}
case 'X': /* DROP CONSTRAINT */
{
AppendAlterDomainStmtDropConstraint(&buf, stmt);
break;
}
case 'V': /* VALIDATE CONSTRAINT */
{
appendStringInfo(&buf, "VALIDATE CONSTRAINT %s",
quote_identifier(stmt->name));
break;
}
default:
{
elog(ERROR, "unsupported alter domain statement for distribution");
}
}
appendStringInfoChar(&buf, ';');
return buf.data;
}
/*
* DeparseDomainRenameConstraintStmt returns the sql representation of the domain
* constraint renaming.
*/
char *
DeparseDomainRenameConstraintStmt(Node *node)
{
RenameStmt *stmt = castNode(RenameStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
char *domainIdentifier = NameListToQuotedString(castNode(List, stmt->object));
appendStringInfo(&buf, "ALTER DOMAIN %s RENAME CONSTRAINT %s TO %s;",
domainIdentifier,
quote_identifier(stmt->subname),
quote_identifier(stmt->newname));
return buf.data;
}
/*
* DeparseAlterDomainOwnerStmt returns the sql representation of the ALTER DOMAIN OWNER
* statement.
*/
char *
DeparseAlterDomainOwnerStmt(Node *node)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
List *domainName = castNode(List, stmt->object);
char *domainIdentifier = NameListToQuotedString(domainName);
appendStringInfo(&buf, "ALTER DOMAIN %s OWNER TO %s;",
domainIdentifier,
RoleSpecString(stmt->newowner, true));
return buf.data;
}
/*
* DeparseRenameDomainStmt returns the sql representation of the ALTER DOMAIN RENAME
* statement.
*/
char *
DeparseRenameDomainStmt(Node *node)
{
RenameStmt *stmt = castNode(RenameStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
List *domainName = castNode(List, stmt->object);
char *domainIdentifier = NameListToQuotedString(domainName);
appendStringInfo(&buf, "ALTER DOMAIN %s RENAME TO %s;",
domainIdentifier,
quote_identifier(stmt->newname));
return buf.data;
}
/*
* DeparseAlterDomainSchemaStmt returns the sql representation of the ALTER DOMAIN SET
* SCHEMA statement.
*/
char *
DeparseAlterDomainSchemaStmt(Node *node)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
List *domainName = castNode(List, stmt->object);
char *domainIdentifier = NameListToQuotedString(domainName);
appendStringInfo(&buf, "ALTER DOMAIN %s SET SCHEMA %s;",
domainIdentifier,
quote_identifier(stmt->newschema));
return buf.data;
}
/*
* DomainGetBaseTypeOid returns the type Oid and the type modifiers of the type underlying
* a domain addresses by the namelist provided as the names argument. The type modifier is
* only provided if the baseTypeMod pointer is a valid pointer on where to write the
* modifier (not a NULL pointer).
*
* If the type cannot be found this function will raise a non-userfacing error. Care needs
* to be taken by the caller that the domain is actually existing.
*/
static Oid
DomainGetBaseTypeOid(List *names, int32 *baseTypeMod)
{
TypeName *domainName = makeTypeNameFromNameList(names);
Oid domainoid = typenameTypeId(NULL, domainName);
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainoid));
if (!HeapTupleIsValid(tup))
{
elog(ERROR, "cache lookup failed for type %u", domainoid);
}
Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup);
Oid baseTypeOid = typTup->typbasetype;
if (baseTypeMod)
{
*baseTypeMod = typTup->typtypmod;
}
ReleaseSysCache(tup);
return baseTypeOid;
}
/*
* AppendAlterDomainStmtSetDefault is a helper function that appends the default value
* portion of an ALTER DOMAIN statement that is changing the default value of the domain.
*/
static void
AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt)
{
if (stmt->def == NULL)
{
/* no default expression is a DROP DEFAULT statment */
appendStringInfoString(buf, "DROP DEFAULT");
return;
}
int32 baseTypMod = 0;
Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod);
TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod);
/* cook the default expression, without cooking we can't deparse */
Node *expr = stmt->def;
expr = TransformDefaultExpr(expr, stmt->typeName, baseTypeName);
/* deparse while the searchpath is cleared to force qualification of identifiers */
PushOverrideEmptySearchPath(CurrentMemoryContext);
char *exprSql = deparse_expression(expr, NIL, true, true);
PopOverrideSearchPath();
appendStringInfo(buf, "SET DEFAULT %s", exprSql);
}
/*
* AppendAlterDomainStmtAddConstraint is a helper function that appends the constraint
* specification for an ALTER DOMAIN statement that adds a constraint to the domain.
*/
static void
AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt)
{
if (stmt->def == NULL || !IsA(stmt->def, Constraint))
{
ereport(ERROR, (errmsg("unable to deparse ALTER DOMAIN statement due to "
"unexpected contents")));
}
Constraint *constraint = castNode(Constraint, stmt->def);
appendStringInfoString(buf, "ADD");
int32 baseTypMod = 0;
Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod);
TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod);
AppendConstraint(buf, constraint, stmt->typeName, baseTypeName);
if (!constraint->initially_valid)
{
appendStringInfoString(buf, " NOT VALID");
}
}
/*
* AppendAlterDomainStmtDropConstraint is a helper function that appends the DROP
* CONSTRAINT part of an ALTER DOMAIN statement for an alter statement that drops a
* constraint.
*/
static void
AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt)
{
appendStringInfoString(buf, "DROP CONSTRAINT ");
if (stmt->missing_ok)
{
appendStringInfoString(buf, "IF EXISTS ");
}
appendStringInfoString(buf, quote_identifier(stmt->name));
if (stmt->behavior == DROP_CASCADE)
{
appendStringInfoString(buf, " CASCADE");
}
}
/*
* AppendConstraint is a helper function that appends a constraint specification to a sql
* string that is adding a constraint.
*
* There are multiple places where a constraint specification is appended to sql strings.
*
* Given the complexities of serializing a constraint they all use this routine.
*/
static void
AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName,
TypeName *typeName)
{
if (constraint->conname)
{
appendStringInfo(buf, " CONSTRAINT %s", quote_identifier(constraint->conname));
}
switch (constraint->contype)
{
case CONSTR_CHECK:
{
Node *expr = NULL;
if (constraint->raw_expr)
{
/* the expression was parsed from sql, still needs to transform */
expr = TransformConstraintExpr(constraint->raw_expr, typeName);
}
else if (constraint->cooked_expr)
{
/* expression was read from the catalog, no cooking required just parse */
expr = stringToNode(constraint->cooked_expr);
}
else
{
elog(ERROR, "missing expression for domain constraint");
}
PushOverrideEmptySearchPath(CurrentMemoryContext);
char *exprSql = deparse_expression(expr, NIL, true, true);
PopOverrideSearchPath();
appendStringInfo(buf, " CHECK (%s)", exprSql);
return;
}
case CONSTR_DEFAULT:
{
Node *expr = NULL;
if (constraint->raw_expr)
{
/* the expression was parsed from sql, still needs to transform */
expr = TransformDefaultExpr(constraint->raw_expr, domainName, typeName);
}
else if (constraint->cooked_expr)
{
/* expression was read from the catalog, no cooking required just parse */
expr = stringToNode(constraint->cooked_expr);
}
else
{
elog(ERROR, "missing expression for domain default");
}
PushOverrideEmptySearchPath(CurrentMemoryContext);
char *exprSql = deparse_expression(expr, NIL, true, true);
PopOverrideSearchPath();
appendStringInfo(buf, " DEFAULT %s", exprSql);
return;
}
case CONSTR_NOTNULL:
{
appendStringInfoString(buf, " NOT NULL");
return;
}
case CONSTR_NULL:
{
appendStringInfoString(buf, " NULL");
return;
}
default:
{
ereport(ERROR, (errmsg("unsupported constraint for distributed domain")));
}
}
}
/*
* TransformDefaultExpr transforms a default expression from the expression passed on the
* AST to a cooked version that postgres uses internally.
*
* Only the cooked version can be easily turned back into a sql string, hence its use in
* the deparser. This is only called for default expressions that don't have a cooked
* variant stored.
*/
static Node *
TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName)
{
const char *domainNameStr = NameListToQuotedString(domainName);
int32 basetypeMod = 0; /* capture typeMod during lookup */
Type tup = typenameType(NULL, typeName, &basetypeMod);
Oid basetypeoid = typeTypeId(tup);
ReleaseSysCache(tup);
ParseState *pstate = make_parsestate(NULL);
Node *defaultExpr = cookDefault(pstate, expr,
basetypeoid,
basetypeMod,
domainNameStr,
0);
return defaultExpr;
}
/*
* TransformConstraintExpr transforms a constraint expression from the expression passed
* on the AST to a cooked version that postgres uses internally.
*
* Only the cooked version can be easily turned back into a sql string, hence its use in
* the deparser. This is only called for default expressions that don't have a cooked
* variant stored.
*/
static Node *
TransformConstraintExpr(Node *expr, TypeName *typeName)
{
/*
* Convert the A_EXPR in raw_expr into an EXPR
*/
ParseState *pstate = make_parsestate(NULL);
/*
* Set up a CoerceToDomainValue to represent the occurrence of VALUE in
* the expression. Note that it will appear to have the type of the base
* type, not the domain. This seems correct since within the check
* expression, we should not assume the input value can be considered a
* member of the domain.
*/
CoerceToDomainValue *domVal = GetCoerceDomainValue(typeName);
pstate->p_pre_columnref_hook = replace_domain_constraint_value;
pstate->p_ref_hook_state = (void *) domVal;
expr = transformExpr(pstate, expr, EXPR_KIND_DOMAIN_CHECK);
/*
* Make sure it yields a boolean result.
*/
expr = coerce_to_boolean(pstate, expr, "CHECK");
/*
* Fix up collation information.
*/
assign_expr_collations(pstate, expr);
return expr;
}
/*
* GetCoerceDomainValue creates a stub CoerceToDomainValue struct representing the type
* referenced by the typeName.
*/
static CoerceToDomainValue *
GetCoerceDomainValue(TypeName *typeName)
{
int32 typMod = 0; /* capture typeMod during lookup */
Type tup = LookupTypeName(NULL, typeName, &typMod, false);
if (tup == NULL)
{
elog(ERROR, "unable to lookup type information for %s",
NameListToQuotedString(typeName->names));
}
CoerceToDomainValue *domVal = makeNode(CoerceToDomainValue);
domVal->typeId = typeTypeId(tup);
domVal->typeMod = typMod;
domVal->collation = typeTypeCollation(tup);
domVal->location = -1;
ReleaseSysCache(tup);
return domVal;
}
/* Parser pre_columnref_hook for domain CHECK constraint parsing */
static Node *
replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref)
{
/*
* Check for a reference to "value", and if that's what it is, replace
* with a CoerceToDomainValue as prepared for us by domainAddConstraint.
* (We handle VALUE as a name, not a keyword, to avoid breaking a lot of
* applications that have used VALUE as a column name in the past.)
*/
if (list_length(cref->fields) == 1)
{
Node *field1 = (Node *) linitial(cref->fields);
Assert(IsA(field1, String));
char *colname = strVal(field1);
if (strcmp(colname, "value") == 0)
{
CoerceToDomainValue *domVal = copyObject(pstate->p_ref_hook_state);
/* Propagate location knowledge, if any */
domVal->location = cref->location;
return (Node *) domVal;
}
}
return NULL;
}

View File

@ -0,0 +1,93 @@
/*-------------------------------------------------------------------------
*
* deparse_foreign_data_wrapper_stmts.c
* All routines to deparse foreign data wrapper statements.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "commands/defrem.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/relay_utility.h"
#include "lib/stringinfo.h"
#include "nodes/nodes.h"
#include "utils/builtins.h"
static void AppendGrantOnFDWStmt(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnFDWNames(StringInfo buf, GrantStmt *stmt);
char *
DeparseGrantOnFDWStmt(Node *node)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_FDW);
StringInfoData str = { 0 };
initStringInfo(&str);
AppendGrantOnFDWStmt(&str, stmt);
return str.data;
}
static void
AppendGrantOnFDWStmt(StringInfo buf, GrantStmt *stmt)
{
Assert(stmt->objtype == OBJECT_FDW);
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
if (!stmt->is_grant && stmt->grant_option)
{
appendStringInfo(buf, "GRANT OPTION FOR ");
}
AppendGrantPrivileges(buf, stmt);
AppendGrantOnFDWNames(buf, stmt);
AppendGrantGrantees(buf, stmt);
if (stmt->is_grant && stmt->grant_option)
{
appendStringInfo(buf, " WITH GRANT OPTION");
}
if (!stmt->is_grant)
{
if (stmt->behavior == DROP_RESTRICT)
{
appendStringInfo(buf, " RESTRICT");
}
else if (stmt->behavior == DROP_CASCADE)
{
appendStringInfo(buf, " CASCADE");
}
}
appendStringInfo(buf, ";");
}
static void
AppendGrantOnFDWNames(StringInfo buf, GrantStmt *stmt)
{
ListCell *cell = NULL;
appendStringInfo(buf, " ON FOREIGN DATA WRAPPER ");
foreach(cell, stmt->objects)
{
char *fdwname = strVal(lfirst(cell));
appendStringInfoString(buf, quote_identifier(fdwname));
if (cell != list_tail(stmt->objects))
{
appendStringInfo(buf, ", ");
}
}
}

View File

@ -27,6 +27,8 @@ static void AppendDropForeignServerStmt(StringInfo buf, DropStmt *stmt);
static void AppendServerNames(StringInfo buf, DropStmt *stmt);
static void AppendBehavior(StringInfo buf, DropStmt *stmt);
static char * GetDefElemActionString(DefElemAction action);
static void AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt *stmt);
char *
DeparseCreateForeignServerStmt(Node *node)
@ -104,6 +106,21 @@ DeparseDropForeignServerStmt(Node *node)
}
char *
DeparseGrantOnForeignServerStmt(Node *node)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_FOREIGN_SERVER);
StringInfoData str = { 0 };
initStringInfo(&str);
AppendGrantOnForeignServerStmt(&str, stmt);
return str.data;
}
static void
AppendCreateForeignServerStmt(StringInfo buf, CreateForeignServerStmt *stmt)
{
@ -275,3 +292,58 @@ GetDefElemActionString(DefElemAction action)
return "";
}
}
static void
AppendGrantOnForeignServerStmt(StringInfo buf, GrantStmt *stmt)
{
Assert(stmt->objtype == OBJECT_FOREIGN_SERVER);
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
if (!stmt->is_grant && stmt->grant_option)
{
appendStringInfo(buf, "GRANT OPTION FOR ");
}
AppendGrantPrivileges(buf, stmt);
AppendGrantOnForeignServerServers(buf, stmt);
AppendGrantGrantees(buf, stmt);
if (stmt->is_grant && stmt->grant_option)
{
appendStringInfo(buf, " WITH GRANT OPTION");
}
if (!stmt->is_grant)
{
if (stmt->behavior == DROP_RESTRICT)
{
appendStringInfo(buf, " RESTRICT");
}
else if (stmt->behavior == DROP_CASCADE)
{
appendStringInfo(buf, " CASCADE");
}
}
appendStringInfo(buf, ";");
}
static void
AppendGrantOnForeignServerServers(StringInfo buf, GrantStmt *stmt)
{
ListCell *cell = NULL;
appendStringInfo(buf, " ON FOREIGN SERVER ");
foreach(cell, stmt->objects)
{
char *servername = strVal(lfirst(cell));
appendStringInfoString(buf, quote_identifier(servername));
if (cell != list_tail(stmt->objects))
{
appendStringInfo(buf, ", ");
}
}
}

View File

@ -67,6 +67,9 @@ static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt
static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
static void AppendAlterFunctionDependsStmt(StringInfo buf, AlterObjectDependsStmt *stmt);
static void AppendGrantOnFunctionStmt(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnFunctionFunctions(StringInfo buf, GrantStmt *stmt);
static char * CopyAndConvertToUpperCase(const char *str);
/*
@ -711,3 +714,113 @@ CopyAndConvertToUpperCase(const char *str)
return result;
}
/*
* DeparseGrantOnFunctionStmt builds and returns a string representing the GrantOnFunctionStmt
*/
char *
DeparseGrantOnFunctionStmt(Node *node)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(isFunction(stmt->objtype));
StringInfoData str = { 0 };
initStringInfo(&str);
AppendGrantOnFunctionStmt(&str, stmt);
return str.data;
}
/*
* AppendGrantOnFunctionStmt builds and returns an SQL command representing a
* GRANT .. ON FUNCTION command from given GrantStmt object.
*/
static void
AppendGrantOnFunctionStmt(StringInfo buf, GrantStmt *stmt)
{
Assert(isFunction(stmt->objtype));
if (stmt->targtype == ACL_TARGET_ALL_IN_SCHEMA)
{
elog(ERROR,
"GRANT .. ALL FUNCTIONS/PROCEDURES IN SCHEMA is not supported for formatting.");
}
appendStringInfoString(buf, stmt->is_grant ? "GRANT " : "REVOKE ");
if (!stmt->is_grant && stmt->grant_option)
{
appendStringInfoString(buf, "GRANT OPTION FOR ");
}
AppendGrantPrivileges(buf, stmt);
AppendGrantOnFunctionFunctions(buf, stmt);
AppendGrantGrantees(buf, stmt);
if (stmt->is_grant && stmt->grant_option)
{
appendStringInfoString(buf, " WITH GRANT OPTION");
}
if (!stmt->is_grant)
{
if (stmt->behavior == DROP_RESTRICT)
{
appendStringInfoString(buf, " RESTRICT");
}
else if (stmt->behavior == DROP_CASCADE)
{
appendStringInfoString(buf, " CASCADE");
}
}
appendStringInfoString(buf, ";");
}
/*
* AppendGrantOnFunctionFunctions appends the function names along with their arguments
* to the given StringInfo from the given GrantStmt
*/
static void
AppendGrantOnFunctionFunctions(StringInfo buf, GrantStmt *stmt)
{
ListCell *cell = NULL;
appendStringInfo(buf, " ON %s ", ObjectTypeToKeyword(stmt->objtype));
foreach(cell, stmt->objects)
{
/*
* GrantOnFunction statement keeps its objects (functions) as
* a list of ObjectWithArgs
*/
ObjectWithArgs *function = (ObjectWithArgs *) lfirst(cell);
appendStringInfoString(buf, NameListToString(function->objname));
if (!function->args_unspecified)
{
/* if args are specified, we should append "(arg1, arg2, ...)" to the function name */
const char *args = TypeNameListToString(function->objargs);
appendStringInfo(buf, "(%s)", args);
}
if (cell != list_tail(stmt->objects))
{
appendStringInfoString(buf, ", ");
}
}
}
/*
* isFunction returns true if the given ObjectType is a function, a procedure or a routine
* otherwise returns false
*/
bool
isFunction(ObjectType objectType)
{
return (objectType == OBJECT_FUNCTION || objectType == OBJECT_PROCEDURE ||
objectType == OBJECT_ROUTINE);
}

View File

@ -21,7 +21,11 @@
static void AppendAlterRoleStmt(StringInfo buf, AlterRoleStmt *stmt);
static void AppendAlterRoleSetStmt(StringInfo buf, AlterRoleSetStmt *stmt);
static void AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt);
static void AppendRoleOption(StringInfo buf, ListCell *optionCell);
static void AppendRoleList(StringInfo buf, List *roleList);
static void AppendDropRoleStmt(StringInfo buf, DropRoleStmt *stmt);
static void AppendGrantRoleStmt(StringInfo buf, GrantRoleStmt *stmt);
/*
@ -173,6 +177,213 @@ AppendRoleOption(StringInfo buf, ListCell *optionCell)
}
/*
* DeparseCreateRoleStmt builds and returns a string representing of the
* CreateRoleStmt for application on a remote server.
*/
char *
DeparseCreateRoleStmt(Node *node)
{
CreateRoleStmt *stmt = castNode(CreateRoleStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
AppendCreateRoleStmt(&buf, stmt);
return buf.data;
}
/*
* AppendCreateRoleStmt generates the string representation of the
* CreateRoleStmt and appends it to the buffer.
*/
static void
AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt)
{
ListCell *optionCell = NULL;
appendStringInfo(buf, "CREATE ");
switch (stmt->stmt_type)
{
case ROLESTMT_ROLE:
{
appendStringInfo(buf, "ROLE ");
break;
}
case ROLESTMT_USER:
{
appendStringInfo(buf, "USER ");
break;
}
case ROLESTMT_GROUP:
{
appendStringInfo(buf, "GROUP ");
break;
}
}
appendStringInfo(buf, "%s", quote_identifier(stmt->role));
foreach(optionCell, stmt->options)
{
AppendRoleOption(buf, optionCell);
DefElem *option = (DefElem *) lfirst(optionCell);
if (strcmp(option->defname, "sysid") == 0)
{
appendStringInfo(buf, " SYSID %s", quote_literal_cstr(strVal(option->arg)));
}
else if (strcmp(option->defname, "adminmembers") == 0)
{
appendStringInfo(buf, " ADMIN ");
AppendRoleList(buf, (List *) option->arg);
}
else if (strcmp(option->defname, "rolemembers") == 0)
{
appendStringInfo(buf, " ROLE ");
AppendRoleList(buf, (List *) option->arg);
}
else if (strcmp(option->defname, "addroleto") == 0)
{
appendStringInfo(buf, " IN ROLE ");
AppendRoleList(buf, (List *) option->arg);
}
}
}
/*
* DeparseDropRoleStmt builds and returns a string representing of the
* DropRoleStmt for application on a remote server.
*/
char *
DeparseDropRoleStmt(Node *node)
{
DropRoleStmt *stmt = castNode(DropRoleStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
AppendDropRoleStmt(&buf, stmt);
return buf.data;
}
/*
* AppendDropRoleStmt generates the string representation of the
* DropRoleStmt and appends it to the buffer.
*/
static void
AppendDropRoleStmt(StringInfo buf, DropRoleStmt *stmt)
{
appendStringInfo(buf, "DROP ROLE ");
if (stmt->missing_ok)
{
appendStringInfo(buf, "IF EXISTS ");
}
AppendRoleList(buf, stmt->roles);
}
static void
AppendRoleList(StringInfo buf, List *roleList)
{
ListCell *cell = NULL;
foreach(cell, roleList)
{
Node *roleNode = (Node *) lfirst(cell);
Assert(IsA(roleNode, RoleSpec) || IsA(roleNode, AccessPriv));
char const *rolename = NULL;
if (IsA(roleNode, RoleSpec))
{
rolename = RoleSpecString((RoleSpec *) roleNode, true);
}
if (IsA(roleNode, AccessPriv))
{
rolename = quote_identifier(((AccessPriv *) roleNode)->priv_name);
}
appendStringInfoString(buf, rolename);
if (cell != list_tail(roleList))
{
appendStringInfo(buf, ", ");
}
}
}
/*
* DeparseGrantRoleStmt builds and returns a string representing of the
* GrantRoleStmt for application on a remote server.
*/
char *
DeparseGrantRoleStmt(Node *node)
{
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
StringInfoData buf = { 0 };
initStringInfo(&buf);
AppendGrantRoleStmt(&buf, stmt);
return buf.data;
}
/*
* AppendGrantRoleStmt generates the string representation of the
* GrantRoleStmt and appends it to the buffer.
*/
static void
AppendGrantRoleStmt(StringInfo buf, GrantRoleStmt *stmt)
{
appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE");
if (!stmt->is_grant && stmt->admin_opt)
{
appendStringInfo(buf, "ADMIN OPTION FOR ");
}
AppendRoleList(buf, stmt->granted_roles);
appendStringInfo(buf, "%s ", stmt->is_grant ? " TO " : " FROM ");
AppendRoleList(buf, stmt->grantee_roles);
if (stmt->is_grant)
{
if (stmt->admin_opt)
{
appendStringInfo(buf, " WITH ADMIN OPTION");
}
if (stmt->grantor)
{
appendStringInfo(buf, " GRANTED BY %s", RoleSpecString(stmt->grantor, true));
}
}
else
{
if (stmt->behavior == DROP_RESTRICT)
{
appendStringInfo(buf, " RESTRICT");
}
else if (stmt->behavior == DROP_CASCADE)
{
appendStringInfo(buf, " CASCADE");
}
}
}
/*
* AppendAlterRoleSetStmt generates the string representation of the
* AlterRoleSetStmt and appends it to the buffer.

View File

@ -22,9 +22,7 @@
static void AppendCreateSchemaStmt(StringInfo buf, CreateSchemaStmt *stmt);
static void AppendDropSchemaStmt(StringInfo buf, DropStmt *stmt);
static void AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnSchemaPrivileges(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnSchemaSchemas(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnSchemaGrantees(StringInfo buf, GrantStmt *stmt);
static void AppendAlterSchemaRenameStmt(StringInfo buf, RenameStmt *stmt);
char *
@ -161,11 +159,11 @@ AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt)
appendStringInfo(buf, "GRANT OPTION FOR ");
}
AppendGrantOnSchemaPrivileges(buf, stmt);
AppendGrantPrivileges(buf, stmt);
AppendGrantOnSchemaSchemas(buf, stmt);
AppendGrantOnSchemaGrantees(buf, stmt);
AppendGrantGrantees(buf, stmt);
if (stmt->is_grant && stmt->grant_option)
{
@ -186,8 +184,8 @@ AppendGrantOnSchemaStmt(StringInfo buf, GrantStmt *stmt)
}
static void
AppendGrantOnSchemaPrivileges(StringInfo buf, GrantStmt *stmt)
void
AppendGrantPrivileges(StringInfo buf, GrantStmt *stmt)
{
if (list_length(stmt->privileges) == 0)
{
@ -227,8 +225,8 @@ AppendGrantOnSchemaSchemas(StringInfo buf, GrantStmt *stmt)
}
static void
AppendGrantOnSchemaGrantees(StringInfo buf, GrantStmt *stmt)
void
AppendGrantGrantees(StringInfo buf, GrantStmt *stmt)
{
ListCell *cell = NULL;
appendStringInfo(buf, " %s ", stmt->is_grant ? "TO" : "FROM");

View File

@ -27,6 +27,8 @@ static void AppendSequenceNameList(StringInfo buf, List *objects, ObjectType obj
static void AppendRenameSequenceStmt(StringInfo buf, RenameStmt *stmt);
static void AppendAlterSequenceSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt);
static void AppendAlterSequenceOwnerStmt(StringInfo buf, AlterTableStmt *stmt);
static void AppendGrantOnSequenceStmt(StringInfo buf, GrantStmt *stmt);
static void AppendGrantOnSequenceSequences(StringInfo buf, GrantStmt *stmt);
/*
* DeparseDropSequenceStmt builds and returns a string representing the DropStmt
@ -86,12 +88,6 @@ AppendSequenceNameList(StringInfo buf, List *objects, ObjectType objtype)
RangeVar *seq = makeRangeVarFromNameList((List *) lfirst(objectCell));
if (seq->schemaname == NULL)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
}
char *qualifiedSequenceName = quote_qualified_identifier(seq->schemaname,
seq->relname);
appendStringInfoString(buf, qualifiedSequenceName);
@ -260,3 +256,107 @@ AppendAlterSequenceOwnerStmt(StringInfo buf, AlterTableStmt *stmt)
}
}
}
/*
* DeparseGrantOnSequenceStmt builds and returns a string representing the GrantOnSequenceStmt
*/
char *
DeparseGrantOnSequenceStmt(Node *node)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_SEQUENCE);
StringInfoData str = { 0 };
initStringInfo(&str);
AppendGrantOnSequenceStmt(&str, stmt);
return str.data;
}
/*
* AppendGrantOnSequenceStmt builds and returns an SQL command representing a
* GRANT .. ON SEQUENCE command from given GrantStmt object.
*/
static void
AppendGrantOnSequenceStmt(StringInfo buf, GrantStmt *stmt)
{
Assert(stmt->objtype == OBJECT_SEQUENCE);
if (stmt->targtype == ACL_TARGET_ALL_IN_SCHEMA)
{
/*
* Normally we shouldn't reach this
* We deparse a GrantStmt with OBJECT_SEQUENCE after setting targtype
* to ACL_TARGET_OBJECT
*/
elog(ERROR,
"GRANT .. ALL SEQUENCES IN SCHEMA is not supported for formatting.");
}
appendStringInfoString(buf, stmt->is_grant ? "GRANT " : "REVOKE ");
if (!stmt->is_grant && stmt->grant_option)
{
appendStringInfoString(buf, "GRANT OPTION FOR ");
}
AppendGrantPrivileges(buf, stmt);
AppendGrantOnSequenceSequences(buf, stmt);
AppendGrantGrantees(buf, stmt);
if (stmt->is_grant && stmt->grant_option)
{
appendStringInfoString(buf, " WITH GRANT OPTION");
}
if (!stmt->is_grant)
{
if (stmt->behavior == DROP_RESTRICT)
{
appendStringInfoString(buf, " RESTRICT");
}
else if (stmt->behavior == DROP_CASCADE)
{
appendStringInfoString(buf, " CASCADE");
}
}
appendStringInfoString(buf, ";");
}
/*
* AppendGrantOnSequenceSequences appends the sequence names along with their arguments
* to the given StringInfo from the given GrantStmt
*/
static void
AppendGrantOnSequenceSequences(StringInfo buf, GrantStmt *stmt)
{
Assert(stmt->objtype == OBJECT_SEQUENCE);
appendStringInfoString(buf, " ON SEQUENCE ");
ListCell *cell = NULL;
foreach(cell, stmt->objects)
{
/*
* GrantOnSequence statement keeps its objects (sequences) as
* a list of RangeVar-s
*/
RangeVar *sequence = (RangeVar *) lfirst(cell);
/*
* We have qualified the statement beforehand
*/
appendStringInfoString(buf, quote_qualified_identifier(sequence->schemaname,
sequence->relname));
if (cell != list_tail(stmt->objects))
{
appendStringInfoString(buf, ", ");
}
}
}

View File

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

View File

@ -0,0 +1,260 @@
/*-------------------------------------------------------------------------
*
* qualify_domain.c
* Functions to fully qualify, make the statements independent of
* search_path settings, for all domain related statements. This
* mostly consists of adding the schema name to all the domain
* names referencing domains.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "parser/parse_type.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
static void QualifyTypeName(TypeName *typeName, bool missing_ok);
static void QualifyCollate(CollateClause *collClause, bool missing_ok);
/*
* QualifyCreateDomainStmt modifies the CreateDomainStmt passed to become search_path
* independent.
*/
void
QualifyCreateDomainStmt(Node *node)
{
CreateDomainStmt *stmt = castNode(CreateDomainStmt, node);
char *schemaName = NULL;
char *domainName = NULL;
/* fully qualify domain name */
DeconstructQualifiedName(stmt->domainname, &schemaName, &domainName);
if (!schemaName)
{
RangeVar *var = makeRangeVarFromNameList(stmt->domainname);
Oid creationSchema = RangeVarGetCreationNamespace(var);
schemaName = get_namespace_name(creationSchema);
stmt->domainname = list_make2(makeString(schemaName), makeString(domainName));
}
/* referenced types should be fully qualified */
QualifyTypeName(stmt->typeName, false);
QualifyCollate(stmt->collClause, false);
}
/*
* QualifyDropDomainStmt modifies the DropStmt for DOMAIN's to be search_path independent.
*/
void
QualifyDropDomainStmt(Node *node)
{
DropStmt *stmt = castNode(DropStmt, node);
TypeName *domainName = NULL;
foreach_ptr(domainName, stmt->objects)
{
QualifyTypeName(domainName, stmt->missing_ok);
}
}
/*
* QualifyAlterDomainStmt modifies the AlterDomainStmt to be search_path independent.
*/
void
QualifyAlterDomainStmt(Node *node)
{
AlterDomainStmt *stmt = castNode(AlterDomainStmt, node);
if (list_length(stmt->typeName) == 1)
{
TypeName *typeName = makeTypeNameFromNameList(stmt->typeName);
QualifyTypeName(typeName, false);
stmt->typeName = typeName->names;
}
}
/*
* QualifyDomainRenameConstraintStmt modifies the RenameStmt for domain constraints to be
* search_path independent.
*/
void
QualifyDomainRenameConstraintStmt(Node *node)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_DOMCONSTRAINT);
List *domainName = castNode(List, stmt->object);
if (list_length(domainName) == 1)
{
TypeName *typeName = makeTypeNameFromNameList(domainName);
QualifyTypeName(typeName, false);
stmt->object = (Node *) typeName->names;
}
}
/*
* QualifyAlterDomainOwnerStmt modifies the AlterOwnerStmt for DOMAIN's to be search_oath
* independent.
*/
void
QualifyAlterDomainOwnerStmt(Node *node)
{
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
Assert(stmt->objectType == OBJECT_DOMAIN);
List *domainName = castNode(List, stmt->object);
if (list_length(domainName) == 1)
{
TypeName *typeName = makeTypeNameFromNameList(domainName);
QualifyTypeName(typeName, false);
stmt->object = (Node *) typeName->names;
}
}
/*
* QualifyRenameDomainStmt modifies the RenameStmt for the Domain to be search_path
* independent.
*/
void
QualifyRenameDomainStmt(Node *node)
{
RenameStmt *stmt = castNode(RenameStmt, node);
Assert(stmt->renameType == OBJECT_DOMAIN);
List *domainName = castNode(List, stmt->object);
if (list_length(domainName) == 1)
{
TypeName *typeName = makeTypeNameFromNameList(domainName);
QualifyTypeName(typeName, false);
stmt->object = (Node *) typeName->names;
}
}
/*
* QualifyAlterDomainSchemaStmt modifies the AlterObjectSchemaStmt to be search_path
* independent.
*/
void
QualifyAlterDomainSchemaStmt(Node *node)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
Assert(stmt->objectType == OBJECT_DOMAIN);
List *domainName = castNode(List, stmt->object);
if (list_length(domainName) == 1)
{
TypeName *typeName = makeTypeNameFromNameList(domainName);
QualifyTypeName(typeName, false);
stmt->object = (Node *) typeName->names;
}
}
/*
* QualifyTypeName qualifies a TypeName object in place. When missing_ok is false it might
* throw an error if the type can't be found based on its name. When an oid is provided
* missing_ok is ignored and treated as false. Meaning, even if missing_ok is true the
* function might raise an error for non-existing types if the oid can't be found.
*/
static void
QualifyTypeName(TypeName *typeName, bool missing_ok)
{
if (OidIsValid(typeName->typeOid))
{
/*
* When the typeName is provided as oid, fill in the names.
* missing_ok is ignored for oid's
*/
Type typeTup = typeidType(typeName->typeOid);
char *name = typeTypeName(typeTup);
Oid namespaceOid = TypeOidGetNamespaceOid(typeName->typeOid);
char *schemaName = get_namespace_name(namespaceOid);
typeName->names = list_make2(makeString(schemaName), makeString(name));
ReleaseSysCache(typeTup);
}
else
{
char *name = NULL;
char *schemaName = NULL;
DeconstructQualifiedName(typeName->names, &schemaName, &name);
if (!schemaName)
{
Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok);
if (OidIsValid(typeOid))
{
Oid namespaceOid = TypeOidGetNamespaceOid(typeOid);
schemaName = get_namespace_name(namespaceOid);
typeName->names = list_make2(makeString(schemaName), makeString(name));
}
}
}
}
/*
* QualifyCollate qualifies any given CollateClause by adding any missing schema name to
* the collation being identified.
*
* If collClause is a NULL pointer this function is a no-nop.
*/
static void
QualifyCollate(CollateClause *collClause, bool missing_ok)
{
if (collClause == NULL)
{
/* no collate clause, nothing to qualify*/
return;
}
if (list_length(collClause->collname) != 1)
{
/* already qualified */
return;
}
Oid collOid = get_collation_oid(collClause->collname, missing_ok);
ObjectAddress collationAddress = { 0 };
ObjectAddressSet(collationAddress, CollationRelationId, collOid);
List *objName = NIL;
List *objArgs = NIL;
#if PG_VERSION_NUM >= PG_VERSION_14
getObjectIdentityParts(&collationAddress, &objName, &objArgs, false);
#else
getObjectIdentityParts(&collationAddress, &objName, &objArgs);
#endif
collClause->collname = NIL;
char *name = NULL;
foreach_ptr(name, objName)
{
collClause->collname = lappend(collClause->collname, makeString(name));
}
}

View File

@ -17,7 +17,9 @@
#include "postgres.h"
#include "distributed/commands.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/version_compat.h"
#include "parser/parse_func.h"
#include "utils/lsyscache.h"
@ -38,8 +40,13 @@ QualifyAlterSequenceOwnerStmt(Node *node)
if (seq->schemaname == NULL)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
if (OidIsValid(seqOid))
{
Oid schemaOid = get_rel_namespace(seqOid);
seq->schemaname = get_namespace_name(schemaOid);
}
}
}
@ -59,12 +66,53 @@ QualifyAlterSequenceSchemaStmt(Node *node)
if (seq->schemaname == NULL)
{
Oid schemaOid = RangeVarGetCreationNamespace(seq);
seq->schemaname = get_namespace_name(schemaOid);
Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
if (OidIsValid(seqOid))
{
Oid schemaOid = get_rel_namespace(seqOid);
seq->schemaname = get_namespace_name(schemaOid);
}
}
}
/*
* QualifyDropSequenceStmt transforms a DROP SEQUENCE
* statement in place and makes the sequence name fully qualified.
*/
void
QualifyDropSequenceStmt(Node *node)
{
DropStmt *stmt = castNode(DropStmt, node);
Assert(stmt->removeType == OBJECT_SEQUENCE);
List *objectNameListWithSchema = NIL;
List *objectNameList = NULL;
foreach_ptr(objectNameList, stmt->objects)
{
RangeVar *seq = makeRangeVarFromNameList(objectNameList);
if (seq->schemaname == NULL)
{
Oid seqOid = RangeVarGetRelid(seq, NoLock, stmt->missing_ok);
if (OidIsValid(seqOid))
{
Oid schemaOid = get_rel_namespace(seqOid);
seq->schemaname = get_namespace_name(schemaOid);
}
}
objectNameListWithSchema = lappend(objectNameListWithSchema,
MakeNameListFromRangeVar(seq));
}
stmt->objects = objectNameListWithSchema;
}
/*
* QualifyRenameSequenceStmt transforms a
* ALTER SEQUENCE .. RENAME TO ..
@ -84,3 +132,41 @@ QualifyRenameSequenceStmt(Node *node)
seq->schemaname = get_namespace_name(schemaOid);
}
}
/*
* QualifyGrantOnSequenceStmt transforms a
* GRANT ON SEQUENCE ...
* statement in place and makes the sequence names fully qualified.
*/
void
QualifyGrantOnSequenceStmt(Node *node)
{
GrantStmt *stmt = castNode(GrantStmt, node);
Assert(stmt->objtype == OBJECT_SEQUENCE);
/*
* The other option would be GRANT ALL SEQUENCES ON SCHEMA ...
* For that we don't need to qualify
*/
if (stmt->targtype != ACL_TARGET_OBJECT)
{
return;
}
List *qualifiedSequenceRangeVars = NIL;
RangeVar *sequenceRangeVar = NULL;
foreach_ptr(sequenceRangeVar, stmt->objects)
{
if (sequenceRangeVar->schemaname == NULL)
{
Oid seqOid = RangeVarGetRelid(sequenceRangeVar, NoLock, false);
Oid schemaOid = get_rel_namespace(seqOid);
sequenceRangeVar->schemaname = get_namespace_name(schemaOid);
}
qualifiedSequenceRangeVars = lappend(qualifiedSequenceRangeVars,
sequenceRangeVar);
}
stmt->objects = qualifiedSequenceRangeVars;
}

View File

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

View File

@ -31,13 +31,10 @@
#include "utils/syscache.h"
#include "utils/lsyscache.h"
static char * GetTypeNamespaceNameByNameList(List *names);
static Oid TypeOidGetNamespaceOid(Oid typeOid);
/*
* GetTypeNamespaceNameByNameList resolved the schema name of a type by its namelist.
*/
static char *
char *
GetTypeNamespaceNameByNameList(List *names)
{
TypeName *typeName = makeTypeNameFromNameList(names);
@ -51,7 +48,7 @@ GetTypeNamespaceNameByNameList(List *names)
/*
* TypeOidGetNamespaceOid resolves the namespace oid for a type identified by its type oid
*/
static Oid
Oid
TypeOidGetNamespaceOid(Oid typeOid)
{
HeapTuple typeTuple = SearchSysCache1(TYPEOID, typeOid);

View File

@ -0,0 +1,116 @@
/*-------------------------------------------------------------------------
*
* qualify_view_stmt.c
* Functions specialized in fully qualifying all view statements. These
* functions are dispatched from qualify.c
*
* Copyright (c), Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "nodes/nodes.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
static void QualifyViewRangeVar(RangeVar *view);
/*
* QualifyDropViewStmt quailifies the view names of the DROP VIEW statement.
*/
void
QualifyDropViewStmt(Node *node)
{
DropStmt *stmt = castNode(DropStmt, node);
List *qualifiedViewNames = NIL;
List *possiblyQualifiedViewName = NULL;
foreach_ptr(possiblyQualifiedViewName, stmt->objects)
{
char *viewName = NULL;
char *schemaName = NULL;
List *viewNameToAdd = possiblyQualifiedViewName;
DeconstructQualifiedName(possiblyQualifiedViewName, &schemaName, &viewName);
if (schemaName == NULL)
{
RangeVar *viewRangeVar = makeRangeVarFromNameList(possiblyQualifiedViewName);
Oid viewOid = RangeVarGetRelid(viewRangeVar, AccessExclusiveLock,
stmt->missing_ok);
/*
* If DROP VIEW IF EXISTS called and the view doesn't exist, oid can be invalid.
* Do not try to qualify it.
*/
if (OidIsValid(viewOid))
{
Oid schemaOid = get_rel_namespace(viewOid);
schemaName = get_namespace_name(schemaOid);
List *qualifiedViewName = list_make2(makeString(schemaName),
makeString(viewName));
viewNameToAdd = qualifiedViewName;
}
}
qualifiedViewNames = lappend(qualifiedViewNames, viewNameToAdd);
}
stmt->objects = qualifiedViewNames;
}
/*
* QualifyAlterViewStmt quailifies the view name of the ALTER VIEW statement.
*/
void
QualifyAlterViewStmt(Node *node)
{
AlterTableStmt *stmt = castNode(AlterTableStmt, node);
RangeVar *view = stmt->relation;
QualifyViewRangeVar(view);
}
/*
* QualifyRenameViewStmt quailifies the view name of the ALTER VIEW ... RENAME statement.
*/
void
QualifyRenameViewStmt(Node *node)
{
RenameStmt *stmt = castNode(RenameStmt, node);
RangeVar *view = stmt->relation;
QualifyViewRangeVar(view);
}
/*
* QualifyAlterViewSchemaStmt quailifies the view name of the ALTER VIEW ... SET SCHEMA statement.
*/
void
QualifyAlterViewSchemaStmt(Node *node)
{
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
RangeVar *view = stmt->relation;
QualifyViewRangeVar(view);
}
/*
* QualifyViewRangeVar qualifies the given view RangeVar if it is not qualified.
*/
static void
QualifyViewRangeVar(RangeVar *view)
{
if (view->schemaname == NULL)
{
Oid viewOid = RelnameGetRelid(view->relname);
Oid schemaOid = get_rel_namespace(viewOid);
view->schemaname = get_namespace_name(schemaOid);
}
}

View File

@ -1321,7 +1321,8 @@ StartDistributedExecution(DistributedExecution *execution)
/* make sure we are not doing remote execution from within a task */
if (execution->remoteTaskList != NIL)
{
EnsureRemoteTaskExecutionAllowed();
bool isRemote = true;
EnsureTaskExecutionAllowed(isRemote);
}
}

View File

@ -40,6 +40,7 @@
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
#include "optimizer/clauses.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/datum.h"
@ -674,7 +675,10 @@ CitusEndScan(CustomScanState *node)
partitionKeyConst = workerJob->partitionKeyValue;
}
/* queryId is not set if pg_stat_statements is not installed */
/*
* queryId is not set if pg_stat_statements is not installed,
* it can be set with as of pg14: set compute_query_id to on;
*/
if (queryId != 0)
{
if (partitionKeyConst != NULL && executorType == MULTI_EXECUTOR_ADAPTIVE)

View File

@ -45,7 +45,7 @@
#include "utils/syscache.h"
static bool CreatedResultsDirectory = false;
static List *CreatedResultsDirectories = NIL;
/* CopyDestReceiver can be used to stream results into a distributed table */
@ -594,26 +594,28 @@ CreateIntermediateResultsDirectory(void)
{
char *resultDirectory = IntermediateResultsDirectory();
if (!CreatedResultsDirectory)
int makeOK = mkdir(resultDirectory, S_IRWXU);
if (makeOK != 0)
{
int makeOK = mkdir(resultDirectory, S_IRWXU);
if (makeOK != 0)
if (errno == EEXIST)
{
if (errno == EEXIST)
{
/* someone else beat us to it, that's ok */
return resultDirectory;
}
ereport(ERROR, (errcode_for_file_access(),
errmsg("could not create intermediate results directory "
"\"%s\": %m",
resultDirectory)));
/* someone else beat us to it, that's ok */
return resultDirectory;
}
CreatedResultsDirectory = true;
ereport(ERROR, (errcode_for_file_access(),
errmsg("could not create intermediate results directory "
"\"%s\": %m",
resultDirectory)));
}
MemoryContext oldContext = MemoryContextSwitchTo(TopTransactionContext);
CreatedResultsDirectories =
lappend(CreatedResultsDirectories, pstrdup(resultDirectory));
MemoryContextSwitchTo(oldContext);
return resultDirectory;
}
@ -693,13 +695,14 @@ IntermediateResultsDirectory(void)
/*
* RemoveIntermediateResultsDirectory removes the intermediate result directory
* RemoveIntermediateResultsDirectories removes the intermediate result directory
* for the current distributed transaction, if any was created.
*/
void
RemoveIntermediateResultsDirectory(void)
RemoveIntermediateResultsDirectories(void)
{
if (CreatedResultsDirectory)
char *directoryElement = NULL;
foreach_ptr(directoryElement, CreatedResultsDirectories)
{
/*
* The shared directory is renamed before deleting it. Otherwise it
@ -708,7 +711,7 @@ RemoveIntermediateResultsDirectory(void)
* that's not possible. The current PID is included in the new
* filename, so there can be no collisions with other backends.
*/
char *sharedName = IntermediateResultsDirectory();
char *sharedName = directoryElement;
StringInfo privateName = makeStringInfo();
appendStringInfo(privateName, "%s.removed-by-%d", sharedName, MyProcPid);
if (rename(sharedName, privateName->data))
@ -728,9 +731,12 @@ RemoveIntermediateResultsDirectory(void)
{
PathNameDeleteTemporaryDir(privateName->data);
}
CreatedResultsDirectory = false;
}
/* cleanup */
list_free_deep(CreatedResultsDirectories);
CreatedResultsDirectories = NIL;
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
#include "postgres.h"
#include "distributed/commands.h"
#include "distributed/pg_version_constants.h"
#include "access/genam.h"
@ -20,8 +21,13 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid_d.h"
#include "catalog/pg_class.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_extension_d.h"
#include "catalog/pg_foreign_data_wrapper_d.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc_d.h"
#include "catalog/pg_rewrite.h"
@ -43,6 +49,7 @@
#include "utils/fmgroids.h"
#include "utils/hsearch.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
/*
* ObjectAddressCollector keeps track of collected ObjectAddresses. This can be used
@ -127,6 +134,7 @@ static List * GetRelationTriggerFunctionDependencyList(Oid relationId);
static List * GetRelationStatsSchemaDependencyList(Oid relationId);
static List * GetRelationIndicesDependencyList(Oid relationId);
static DependencyDefinition * CreateObjectAddressDependencyDef(Oid classId, Oid objectId);
static List * GetTypeConstraintDependencyDefinition(Oid typeId);
static List * CreateObjectAddressDependencyDefList(Oid classId, List *objectIdList);
static ObjectAddress DependencyDefinitionObjectAddress(DependencyDefinition *definition);
@ -162,10 +170,12 @@ static bool FollowAllDependencies(ObjectAddressCollector *collector,
DependencyDefinition *definition);
static void ApplyAddToDependencyList(ObjectAddressCollector *collector,
DependencyDefinition *definition);
static List * GetViewRuleReferenceDependencyList(Oid relationId);
static List * ExpandCitusSupportedTypes(ObjectAddressCollector *collector,
ObjectAddress target);
static List * GetDependentRoleIdsFDW(Oid FDWOid);
static List * ExpandRolesToGroups(Oid roleid);
static ViewDependencyNode * BuildViewDependencyGraph(Oid relationId, HTAB *nodeMap);
static Oid GetDependingView(Form_pg_depend pg_depend);
/*
@ -422,7 +432,7 @@ DependencyDefinitionFromPgDepend(ObjectAddress target)
/*
* DependencyDefinitionFromPgDepend loads all pg_shdepend records describing the
* DependencyDefinitionFromPgShDepend loads all pg_shdepend records describing the
* dependencies of target.
*/
static List *
@ -630,6 +640,15 @@ SupportedDependencyByCitus(const ObjectAddress *address)
return IsObjectAddressOwnedByExtension(address, NULL);
}
case OCLASS_CONSTRAINT:
{
/*
* Constraints are only supported when on domain types. Other constraints have
* their typid set to InvalidOid.
*/
return OidIsValid(get_constraint_typid(address->objectId));
}
case OCLASS_COLLATION:
{
return true;
@ -658,16 +677,13 @@ SupportedDependencyByCitus(const ObjectAddress *address)
case OCLASS_ROLE:
{
/*
* Community only supports the extension owner as a distributed object to
* propagate alter statements for this user
*/
if (address->objectId == CitusExtensionOwner())
/* if it is a reserved role do not propagate */
if (IsReservedName(GetUserNameFromId(address->objectId, false)))
{
return true;
return false;
}
return false;
return true;
}
case OCLASS_EXTENSION:
@ -691,6 +707,7 @@ SupportedDependencyByCitus(const ObjectAddress *address)
{
case TYPTYPE_ENUM:
case TYPTYPE_COMPOSITE:
case TYPTYPE_DOMAIN:
{
return true;
}
@ -734,7 +751,8 @@ SupportedDependencyByCitus(const ObjectAddress *address)
relKind == RELKIND_FOREIGN_TABLE ||
relKind == RELKIND_SEQUENCE ||
relKind == RELKIND_INDEX ||
relKind == RELKIND_PARTITIONED_INDEX)
relKind == RELKIND_PARTITIONED_INDEX ||
relKind == RELKIND_VIEW)
{
return true;
}
@ -751,6 +769,58 @@ SupportedDependencyByCitus(const ObjectAddress *address)
}
/*
* ErrorOrWarnIfObjectHasUnsupportedDependency returns false without throwing any message if
* object doesn't have any unsupported dependency, else throws a message with proper level
* (except the cluster doesn't have any node) and return true.
*/
bool
ErrorOrWarnIfObjectHasUnsupportedDependency(ObjectAddress *objectAddress)
{
DeferredErrorMessage *errMsg = DeferErrorIfHasUnsupportedDependency(objectAddress);
if (errMsg != NULL)
{
/*
* Don't need to give any messages if there is no worker nodes in
* the cluster as user's experience won't be affected on the single node even
* if the object won't be distributed.
*/
if (!HasAnyNodes())
{
return true;
}
/*
* Since Citus drops and recreates some object while converting a table type
* giving a DEBUG1 message is enough if the process in table type conversion
* function call
*/
if (InTableTypeConversionFunctionCall)
{
RaiseDeferredError(errMsg, DEBUG1);
}
/*
* If the view is object distributed, we should provide an error to not have
* different definition of object on coordinator and worker nodes. If the object
* is not distributed yet, we can create it locally to not affect user's local
* usage experience.
*/
else if (IsObjectDistributed(objectAddress))
{
RaiseDeferredError(errMsg, ERROR);
}
else
{
RaiseDeferredError(errMsg, WARNING);
}
return true;
}
return false;
}
/*
* DeferErrorIfHasUnsupportedDependency returns deferred error message if the given
* object has any undistributable dependency.
@ -788,8 +858,11 @@ DeferErrorIfHasUnsupportedDependency(const ObjectAddress *objectAddress)
* Otherwise, callers are expected to throw the error returned from this
* function as a hard one by ignoring the detail part.
*/
appendStringInfo(detailInfo, "\"%s\" will be created only locally",
objectDescription);
if (!IsObjectDistributed(objectAddress))
{
appendStringInfo(detailInfo, "\"%s\" will be created only locally",
objectDescription);
}
if (SupportedDependencyByCitus(undistributableDependency))
{
@ -800,9 +873,19 @@ DeferErrorIfHasUnsupportedDependency(const ObjectAddress *objectAddress)
objectDescription,
dependencyDescription);
appendStringInfo(hintInfo, "Distribute \"%s\" first to distribute \"%s\"",
dependencyDescription,
objectDescription);
if (IsObjectDistributed(objectAddress))
{
appendStringInfo(hintInfo,
"Distribute \"%s\" first to modify \"%s\" on worker nodes",
dependencyDescription,
objectDescription);
}
else
{
appendStringInfo(hintInfo, "Distribute \"%s\" first to distribute \"%s\"",
dependencyDescription,
objectDescription);
}
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
errorInfo->data, detailInfo->data, hintInfo->data);
@ -880,7 +963,9 @@ GetUndistributableDependency(const ObjectAddress *objectAddress)
{
char relKind = get_rel_relkind(dependency->objectId);
if (relKind == RELKIND_SEQUENCE || relKind == RELKIND_COMPOSITE_TYPE)
if (relKind == RELKIND_SEQUENCE ||
relKind == RELKIND_COMPOSITE_TYPE ||
relKind == RELKIND_VIEW)
{
/* citus knows how to auto-distribute these dependencies */
continue;
@ -1194,19 +1279,74 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
switch (target.classId)
{
case TypeRelationId:
case AuthIdRelationId:
{
/*
* types depending on other types are not captured in pg_depend, instead they
* are described with their dependencies by the relation that describes the
* composite type.
* Roles are members of other roles. These relations are not recorded directly
* but can be deduced from pg_auth_members
*/
if (get_typtype(target.objectId) == TYPTYPE_COMPOSITE)
return ExpandRolesToGroups(target.objectId);
}
case ExtensionRelationId:
{
/*
* FDWs get propagated along with the extensions they belong to.
* In case there are GRANTed privileges on FDWs to roles, those
* GRANT statements will be propagated to. In order to make sure
* that those GRANT statements work, the privileged roles should
* exist on the worker nodes. Hence, here we find these dependent
* roles and add them as dependencies.
*/
Oid extensionId = target.objectId;
List *FDWOids = GetDependentFDWsToExtension(extensionId);
Oid FDWOid = InvalidOid;
foreach_oid(FDWOid, FDWOids)
{
Oid typeRelationId = get_typ_typrelid(target.objectId);
DependencyDefinition *dependency =
CreateObjectAddressDependencyDef(RelationRelationId, typeRelationId);
result = lappend(result, dependency);
List *dependentRoleIds = GetDependentRoleIdsFDW(FDWOid);
List *dependencies =
CreateObjectAddressDependencyDefList(AuthIdRelationId,
dependentRoleIds);
result = list_concat(result, dependencies);
}
break;
}
case TypeRelationId:
{
switch (get_typtype(target.objectId))
{
/*
* types depending on other types are not captured in pg_depend, instead
* they are described with their dependencies by the relation that
* describes the composite type.
*/
case TYPTYPE_COMPOSITE:
{
Oid typeRelationId = get_typ_typrelid(target.objectId);
DependencyDefinition *dependency =
CreateObjectAddressDependencyDef(RelationRelationId,
typeRelationId);
result = lappend(result, dependency);
break;
}
/*
* Domains can have constraints associated with them. Constraints themself
* can depend on things like functions. To support the propagation of
* these functions we will add the constraints to the list of objects to
* be created.
*/
case TYPTYPE_DOMAIN:
{
List *dependencies =
GetTypeConstraintDependencyDefinition(target.objectId);
result = list_concat(result, dependencies);
break;
}
}
/*
@ -1275,9 +1415,26 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
* create all objects required by the indices before we create the table
* including indices.
*/
List *indexDependencyList = GetRelationIndicesDependencyList(relationId);
result = list_concat(result, indexDependencyList);
/*
* Get the dependencies of the rule for the given view. PG keeps internal
* dependency between view and rule. As it is stated on the PG doc, if
* there is an internal dependency, dependencies of the dependent object
* behave much like they were dependencies of the referenced object.
*
* We need to expand dependencies by including dependencies of the rule
* internally dependent to the view. PG doesn't keep any dependencies
* from view to any object, but it keeps an internal dependency to the
* rule and that rule has dependencies to other objects.
*/
char relKind = get_rel_relkind(relationId);
if (relKind == RELKIND_VIEW)
{
List *ruleRefDepList = GetViewRuleReferenceDependencyList(relationId);
result = list_concat(result, ruleRefDepList);
}
}
default:
@ -1290,6 +1447,131 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
}
/*
* GetDependentRoleIdsFDW returns a list of role oids that has privileges on the
* FDW with the given object id.
*/
static List *
GetDependentRoleIdsFDW(Oid FDWOid)
{
List *roleIds = NIL;
Acl *aclEntry = GetPrivilegesForFDW(FDWOid);
if (aclEntry == NULL)
{
return NIL;
}
AclItem *privileges = ACL_DAT(aclEntry);
int numberOfPrivsGranted = ACL_NUM(aclEntry);
for (int i = 0; i < numberOfPrivsGranted; i++)
{
roleIds = lappend_oid(roleIds, privileges[i].ai_grantee);
}
return roleIds;
}
/*
* ExpandRolesToGroups returns a list of object addresses pointing to roles that roleid
* depends on.
*/
static List *
ExpandRolesToGroups(Oid roleid)
{
Relation pgAuthMembers = table_open(AuthMemRelationId, AccessShareLock);
HeapTuple tuple = NULL;
ScanKeyData scanKey[1];
const int scanKeyCount = 1;
/* scan pg_auth_members for member = $1 via index pg_auth_members_member_role_index */
ScanKeyInit(&scanKey[0], Anum_pg_auth_members_member, BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(roleid));
SysScanDesc scanDescriptor = systable_beginscan(pgAuthMembers, AuthMemMemRoleIndexId,
true, NULL, scanKeyCount, scanKey);
List *roles = NIL;
while ((tuple = systable_getnext(scanDescriptor)) != NULL)
{
Form_pg_auth_members membership = (Form_pg_auth_members) GETSTRUCT(tuple);
DependencyDefinition *definition = palloc0(sizeof(DependencyDefinition));
definition->mode = DependencyObjectAddress;
ObjectAddressSet(definition->data.address, AuthIdRelationId, membership->roleid);
roles = lappend(roles, definition);
}
systable_endscan(scanDescriptor);
table_close(pgAuthMembers, AccessShareLock);
return roles;
}
/*
* GetViewRuleReferenceDependencyList returns the dependencies of the view's
* internal rule dependencies.
*/
static List *
GetViewRuleReferenceDependencyList(Oid viewId)
{
List *dependencyTupleList = GetPgDependTuplesForDependingObjects(RelationRelationId,
viewId);
List *nonInternalDependenciesOfDependingRules = NIL;
HeapTuple depTup = NULL;
foreach_ptr(depTup, dependencyTupleList)
{
Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
/*
* Dependencies of the internal rule dependency should be handled as the dependency
* of referenced view object.
*
* PG doesn't keep dependency relation between views and dependent objects directly
* but it keeps an internal dependency relation between the view and the rule, then
* keeps the dependent objects of the view as non-internal dependencies of the
* internally dependent rule object.
*/
if (pg_depend->deptype == DEPENDENCY_INTERNAL && pg_depend->classid ==
RewriteRelationId)
{
ObjectAddress ruleAddress = { 0 };
ObjectAddressSet(ruleAddress, RewriteRelationId, pg_depend->objid);
/* Expand results with the noninternal dependencies of it */
List *ruleDependencies = DependencyDefinitionFromPgDepend(ruleAddress);
DependencyDefinition *dependencyDef = NULL;
foreach_ptr(dependencyDef, ruleDependencies)
{
/*
* Follow all dependencies of the internally dependent rule dependencies
* except it is an internal dependency of view itself.
*/
if (dependencyDef->data.pg_depend.deptype == DEPENDENCY_INTERNAL ||
(dependencyDef->data.pg_depend.refclassid == RelationRelationId &&
dependencyDef->data.pg_depend.refobjid == viewId))
{
continue;
}
nonInternalDependenciesOfDependingRules =
lappend(nonInternalDependenciesOfDependingRules, dependencyDef);
}
}
}
return nonInternalDependenciesOfDependingRules;
}
/*
* GetRelationSequenceDependencyList returns the sequence dependency definition
* list for the given relation.
@ -1381,6 +1663,49 @@ GetRelationTriggerFunctionDependencyList(Oid relationId)
}
/*
* GetTypeConstraintDependencyDefinition creates a list of constraint dependency
* definitions for a given type
*/
static List *
GetTypeConstraintDependencyDefinition(Oid typeId)
{
/* lookup and look all constraints to add them to the CreateDomainStmt */
Relation conRel = table_open(ConstraintRelationId, AccessShareLock);
/* Look for CHECK Constraints on this domain */
ScanKeyData key[1];
ScanKeyInit(&key[0],
Anum_pg_constraint_contypid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(typeId));
SysScanDesc scan = systable_beginscan(conRel, ConstraintTypidIndexId, true, NULL, 1,
key);
List *dependencies = NIL;
HeapTuple conTup = NULL;
while (HeapTupleIsValid(conTup = systable_getnext(scan)))
{
Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup);
if (c->contype != CONSTRAINT_CHECK)
{
/* Ignore non-CHECK constraints, shouldn't be any */
continue;
}
dependencies = lappend(dependencies, CreateObjectAddressDependencyDef(
ConstraintRelationId, c->oid));
}
systable_endscan(scan);
table_close(conRel, NoLock);
return dependencies;
}
/*
* CreateObjectAddressDependencyDef returns DependencyDefinition object that
* stores the ObjectAddress for the database object identified by classId and

View File

@ -28,8 +28,11 @@
#include "catalog/pg_type.h"
#include "citus_version.h"
#include "commands/extension.h"
#include "distributed/listutils.h"
#include "distributed/colocation_utils.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/metadata/dependency.h"
#include "distributed/metadata/distobject.h"
#include "distributed/metadata/pg_dist_object.h"
#include "distributed/metadata_cache.h"
@ -42,11 +45,11 @@
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/rel.h"
static void MarkObjectDistributedLocally(const ObjectAddress *distAddress);
static char * CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress);
static int ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
Datum *paramValues);
@ -195,7 +198,7 @@ MarkObjectDistributedViaSuperUser(const ObjectAddress *distAddress)
* This function should never be called alone, MarkObjectDistributed() or
* MarkObjectDistributedViaSuperUser() should be called.
*/
static void
void
MarkObjectDistributedLocally(const ObjectAddress *distAddress)
{
int paramCount = 3;
@ -221,6 +224,52 @@ MarkObjectDistributedLocally(const ObjectAddress *distAddress)
}
/*
* ShouldMarkRelationDistributed is a helper function that
* decides whether the input relation should be marked as distributed.
*/
bool
ShouldMarkRelationDistributed(Oid relationId)
{
if (!EnableMetadataSync)
{
/*
* Just in case anything goes wrong, we should still be able
* to continue to the version upgrade.
*/
return false;
}
ObjectAddress relationAddress = { 0 };
ObjectAddressSet(relationAddress, RelationRelationId, relationId);
bool pgObject = (relationId < FirstNormalObjectId);
bool isObjectSupported = SupportedDependencyByCitus(&relationAddress);
bool ownedByExtension = IsTableOwnedByExtension(relationId);
bool alreadyDistributed = IsObjectDistributed(&relationAddress);
bool hasUnsupportedDependency =
DeferErrorIfHasUnsupportedDependency(&relationAddress) != NULL;
bool hasCircularDependency =
DeferErrorIfCircularDependencyExists(&relationAddress) != NULL;
/*
* pgObject: Citus never marks pg objects as distributed
* isObjectSupported: Citus does not support propagation of some objects
* ownedByExtension: let extensions manage its own objects
* alreadyDistributed: most likely via earlier versions
* hasUnsupportedDependency: Citus doesn't know how to distribute its dependencies
* hasCircularDependency: Citus cannot handle circular dependencies
*/
if (pgObject || !isObjectSupported || ownedByExtension || alreadyDistributed ||
hasUnsupportedDependency || hasCircularDependency)
{
return false;
}
return true;
}
/*
* CreatePgDistObjectEntryCommand creates command to insert pg_dist_object tuple
* for the given object address.
@ -472,3 +521,82 @@ UpdateDistributedObjectColocationId(uint32 oldColocationId,
table_close(pgDistObjectRel, NoLock);
CommandCounterIncrement();
}
/*
* DistributedFunctionList returns the list of ObjectAddress-es of all the
* distributed functions found in pg_dist_object
*/
List *
DistributedFunctionList(void)
{
List *distributedFunctionList = NIL;
ScanKeyData key[1];
Relation pgDistObjectRel = table_open(DistObjectRelationId(), AccessShareLock);
/* scan pg_dist_object for classid = ProcedureRelationId via index */
ScanKeyInit(&key[0], Anum_pg_dist_object_classid, BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(ProcedureRelationId));
SysScanDesc pgDistObjectScan = systable_beginscan(pgDistObjectRel,
DistObjectPrimaryKeyIndexId(),
true, NULL, 1, key);
HeapTuple pgDistObjectTup = NULL;
while (HeapTupleIsValid(pgDistObjectTup = systable_getnext(pgDistObjectScan)))
{
Form_pg_dist_object pg_dist_object =
(Form_pg_dist_object) GETSTRUCT(pgDistObjectTup);
ObjectAddress *functionAddress = palloc0(sizeof(ObjectAddress));
functionAddress->classId = ProcedureRelationId;
functionAddress->objectId = pg_dist_object->objid;
functionAddress->objectSubId = pg_dist_object->objsubid;
distributedFunctionList = lappend(distributedFunctionList, functionAddress);
}
systable_endscan(pgDistObjectScan);
relation_close(pgDistObjectRel, AccessShareLock);
return distributedFunctionList;
}
/*
* DistributedSequenceList returns the list of ObjectAddress-es of all the
* distributed sequences found in pg_dist_object
*/
List *
DistributedSequenceList(void)
{
List *distributedSequenceList = NIL;
ScanKeyData key[1];
Relation pgDistObjectRel = table_open(DistObjectRelationId(), AccessShareLock);
/* scan pg_dist_object for classid = RelationRelationId via index */
ScanKeyInit(&key[0], Anum_pg_dist_object_classid, BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationRelationId));
SysScanDesc pgDistObjectScan = systable_beginscan(pgDistObjectRel,
DistObjectPrimaryKeyIndexId(),
true, NULL, 1, key);
HeapTuple pgDistObjectTup = NULL;
while (HeapTupleIsValid(pgDistObjectTup = systable_getnext(pgDistObjectScan)))
{
Form_pg_dist_object pg_dist_object =
(Form_pg_dist_object) GETSTRUCT(pgDistObjectTup);
if (get_rel_relkind(pg_dist_object->objid) == RELKIND_SEQUENCE)
{
ObjectAddress *sequenceAddress = palloc0(sizeof(ObjectAddress));
sequenceAddress->classId = RelationRelationId;
sequenceAddress->objectId = pg_dist_object->objid;
sequenceAddress->objectSubId = pg_dist_object->objsubid;
distributedSequenceList = lappend(distributedSequenceList, sequenceAddress);
}
}
systable_endscan(pgDistObjectScan);
relation_close(pgDistObjectRel, AccessShareLock);
return distributedSequenceList;
}

View File

@ -20,6 +20,7 @@
#include "access/nbtree.h"
#include "access/xact.h"
#include "access/sysattr.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
@ -37,6 +38,7 @@
#include "distributed/citus_ruleutils.h"
#include "distributed/multi_executor.h"
#include "distributed/function_utils.h"
#include "distributed/listutils.h"
#include "distributed/foreign_key_relationship.h"
#include "distributed/listutils.h"
#include "distributed/metadata_utility.h"
@ -62,11 +64,13 @@
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/datum.h"
#include "utils/elog.h"
#include "utils/hsearch.h"
#include "utils/jsonb.h"
#if PG_VERSION_NUM >= PG_VERSION_13
#include "common/hashfn.h"
#endif
@ -149,6 +153,7 @@ typedef struct MetadataCacheData
Oid distShardShardidIndexId;
Oid distPlacementShardidIndexId;
Oid distPlacementPlacementidIndexId;
Oid distColocationidIndexId;
Oid distPlacementGroupidIndexId;
Oid distTransactionRelationId;
Oid distTransactionGroupIndexId;
@ -160,6 +165,7 @@ typedef struct MetadataCacheData
Oid workerHashFunctionId;
Oid anyValueFunctionId;
Oid textSendAsJsonbFunctionId;
Oid textoutFunctionId;
Oid extensionOwner;
Oid binaryCopyFormatId;
Oid textCopyFormatId;
@ -167,6 +173,10 @@ typedef struct MetadataCacheData
Oid secondaryNodeRoleId;
Oid pgTableIsVisibleFuncId;
Oid citusTableIsVisibleFuncId;
Oid distAuthinfoRelationId;
Oid distAuthinfoIndexId;
Oid distPoolinfoRelationId;
Oid distPoolinfoIndexId;
Oid relationIsAKnownShardFuncId;
Oid jsonbExtractPathFuncId;
Oid jsonbExtractPathTextFuncId;
@ -229,6 +239,7 @@ static void InitializeWorkerNodeCache(void);
static void RegisterForeignKeyGraphCacheCallbacks(void);
static void RegisterWorkerNodeCacheCallbacks(void);
static void RegisterLocalGroupIdCacheCallbacks(void);
static void RegisterAuthinfoCacheCallbacks(void);
static void RegisterCitusTableCacheEntryReleaseCallbacks(void);
static uint32 WorkerNodeHashCode(const void *key, Size keySize);
static void ResetCitusTableCacheEntry(CitusTableCacheEntry *cacheEntry);
@ -240,6 +251,7 @@ static void InvalidateForeignRelationGraphCacheCallback(Datum argument, Oid rela
static void InvalidateDistRelationCacheCallback(Datum argument, Oid relationId);
static void InvalidateNodeRelationCacheCallback(Datum argument, Oid relationId);
static void InvalidateLocalGroupIdRelationCacheCallback(Datum argument, Oid relationId);
static void InvalidateConnParamsCacheCallback(Datum argument, Oid relationId);
static void CitusTableCacheEntryReleaseCallback(ResourceReleasePhase phase, bool isCommit,
bool isTopLevel, void *arg);
static HeapTuple LookupDistPartitionTuple(Relation pgDistPartition, Oid relationId);
@ -267,6 +279,10 @@ static bool IsCitusTableTypeInternal(char partitionMethod, char replicationModel
CitusTableType tableType);
static bool RefreshTableCacheEntryIfInvalid(ShardIdCacheEntry *shardEntry);
static Oid DistAuthinfoRelationId(void);
static Oid DistAuthinfoIndexId(void);
static Oid DistPoolinfoRelationId(void);
static Oid DistPoolinfoIndexId(void);
/* exports for SQL callable functions */
PG_FUNCTION_INFO_V1(citus_dist_partition_cache_invalidate);
@ -719,6 +735,24 @@ ReferenceTableShardId(uint64 shardId)
}
/*
* DistributedTableShardId returns true if the given shardId belongs to
* a distributed table.
*/
bool
DistributedTableShardId(uint64 shardId)
{
if (shardId == INVALID_SHARD_ID)
{
return false;
}
ShardIdCacheEntry *shardIdEntry = LookupShardIdCacheEntry(shardId);
CitusTableCacheEntry *tableEntry = shardIdEntry->tableEntry;
return IsCitusTableTypeCacheEntry(tableEntry, DISTRIBUTED_TABLE);
}
/*
* LoadGroupShardPlacement returns the cached shard placement metadata
*
@ -2503,6 +2537,17 @@ DistPlacementPlacementidIndexId(void)
}
/* return oid of pg_dist_colocation_pkey */
Oid
DistColocationIndexId(void)
{
CachedRelationLookup("pg_dist_colocation_pkey",
&MetadataCache.distColocationidIndexId);
return MetadataCache.distColocationidIndexId;
}
/* return oid of pg_dist_transaction relation */
Oid
DistTransactionRelationId(void)
@ -2536,6 +2581,50 @@ DistPlacementGroupidIndexId(void)
}
/* return oid of pg_dist_authinfo relation */
static Oid
DistAuthinfoRelationId(void)
{
CachedRelationLookup("pg_dist_authinfo",
&MetadataCache.distAuthinfoRelationId);
return MetadataCache.distAuthinfoRelationId;
}
/* return oid of pg_dist_authinfo identification index */
static Oid
DistAuthinfoIndexId(void)
{
CachedRelationLookup("pg_dist_authinfo_identification_index",
&MetadataCache.distAuthinfoIndexId);
return MetadataCache.distAuthinfoIndexId;
}
/* return oid of pg_dist_poolinfo relation */
static Oid
DistPoolinfoRelationId(void)
{
CachedRelationLookup("pg_dist_poolinfo",
&MetadataCache.distPoolinfoRelationId);
return MetadataCache.distPoolinfoRelationId;
}
/* return oid of pg_dist_poolinfo primary key index */
static Oid
DistPoolinfoIndexId(void)
{
CachedRelationLookup("pg_dist_poolinfo_pkey",
&MetadataCache.distPoolinfoIndexId);
return MetadataCache.distPoolinfoIndexId;
}
/* return oid of the read_intermediate_result(text,citus_copy_format) function */
Oid
CitusReadIntermediateResultFuncId(void)
@ -2655,6 +2744,42 @@ CitusAnyValueFunctionId(void)
}
/* return oid of the citus_text_send_as_jsonb(text) function */
Oid
CitusTextSendAsJsonbFunctionId(void)
{
if (MetadataCache.textSendAsJsonbFunctionId == InvalidOid)
{
List *nameList = list_make2(makeString("pg_catalog"),
makeString("citus_text_send_as_jsonb"));
Oid paramOids[1] = { TEXTOID };
MetadataCache.textSendAsJsonbFunctionId =
LookupFuncName(nameList, 1, paramOids, false);
}
return MetadataCache.textSendAsJsonbFunctionId;
}
/* return oid of the textout(text) function */
Oid
TextOutFunctionId(void)
{
if (MetadataCache.textoutFunctionId == InvalidOid)
{
List *nameList = list_make2(makeString("pg_catalog"),
makeString("textout"));
Oid paramOids[1] = { TEXTOID };
MetadataCache.textoutFunctionId =
LookupFuncName(nameList, 1, paramOids, false);
}
return MetadataCache.textoutFunctionId;
}
/*
* PgTableVisibleFuncId returns oid of the pg_table_is_visible function.
*/
@ -3243,7 +3368,7 @@ citus_conninfo_cache_invalidate(PG_FUNCTION_ARGS)
errmsg("must be called as trigger")));
}
/* no-op in community edition */
CitusInvalidateRelcacheByRelid(DistAuthinfoRelationId());
PG_RETURN_DATUM(PointerGetDatum(NULL));
}
@ -3371,6 +3496,7 @@ InitializeCaches(void)
RegisterForeignKeyGraphCacheCallbacks();
RegisterWorkerNodeCacheCallbacks();
RegisterLocalGroupIdCacheCallbacks();
RegisterAuthinfoCacheCallbacks();
RegisterCitusTableCacheEntryReleaseCallbacks();
}
PG_CATCH();
@ -3776,6 +3902,18 @@ RegisterLocalGroupIdCacheCallbacks(void)
}
/*
* RegisterAuthinfoCacheCallbacks registers the callbacks required to
* maintain cached connection parameters at fresh values.
*/
static void
RegisterAuthinfoCacheCallbacks(void)
{
/* Watch for invalidation events. */
CacheRegisterRelcacheCallback(InvalidateConnParamsCacheCallback, (Datum) 0);
}
/*
* WorkerNodeHashCode computes the hash code for a worker node from the node's
* host name and port number. Nodes that only differ by their rack locations
@ -4274,6 +4412,30 @@ InvalidateLocalGroupIdRelationCacheCallback(Datum argument, Oid relationId)
}
/*
* InvalidateConnParamsCacheCallback sets isValid flag to false for all entries
* in ConnParamsHash, a cache used during connection establishment.
*/
static void
InvalidateConnParamsCacheCallback(Datum argument, Oid relationId)
{
if (relationId == MetadataCache.distAuthinfoRelationId ||
relationId == MetadataCache.distPoolinfoRelationId ||
relationId == InvalidOid)
{
ConnParamsHashEntry *entry = NULL;
HASH_SEQ_STATUS status;
hash_seq_init(&status, ConnParamsHash);
while ((entry = (ConnParamsHashEntry *) hash_seq_search(&status)) != NULL)
{
entry->isValid = false;
}
}
}
/*
* CitusTableCacheFlushInvalidatedEntries frees invalidated cache entries.
* Invalidated entries aren't freed immediately as callers expect their lifetime
@ -4853,6 +5015,12 @@ DistNodeMetadata(void)
"could not find any entries in pg_dist_metadata")));
}
/*
* Copy the jsonb result before closing the table
* since that memory can be freed.
*/
metadata = JsonbPGetDatum(DatumGetJsonbPCopy(metadata));
systable_endscan(scanDescriptor);
table_close(pgDistNodeMetadata, AccessShareLock);
@ -4875,37 +5043,164 @@ role_exists(PG_FUNCTION_ARGS)
/*
* authinfo_valid is a check constraint which errors on all rows, intended for
* use in prohibiting writes to pg_dist_authinfo in Citus Community.
* GetPoolinfoViaCatalog searches the pg_dist_poolinfo table for a row matching
* the provided nodeId and returns the poolinfo field of this row if found.
* Otherwise, this function returns NULL.
*/
Datum
authinfo_valid(PG_FUNCTION_ARGS)
char *
GetPoolinfoViaCatalog(int64 nodeId)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot write to pg_dist_authinfo"),
errdetail(
"Citus Community Edition does not support the use of "
"custom authentication options."),
errhint(
"To learn more about using advanced authentication schemes "
"with Citus, please contact us at "
"https://citusdata.com/about/contact_us")));
ScanKeyData scanKey[1];
const int scanKeyCount = 1;
const AttrNumber nodeIdIdx = 1, poolinfoIdx = 2;
Relation pgDistPoolinfo = table_open(DistPoolinfoRelationId(), AccessShareLock);
bool indexOK = true;
char *poolinfo = NULL;
/* set scan arguments */
ScanKeyInit(&scanKey[0], nodeIdIdx, BTEqualStrategyNumber, F_INT4EQ,
Int32GetDatum(nodeId));
SysScanDesc scanDescriptor = systable_beginscan(pgDistPoolinfo, DistPoolinfoIndexId(),
indexOK,
NULL, scanKeyCount, scanKey);
HeapTuple heapTuple = systable_getnext(scanDescriptor);
if (HeapTupleIsValid(heapTuple))
{
TupleDesc tupleDescriptor = RelationGetDescr(pgDistPoolinfo);
bool isNull = false;
Datum poolinfoDatum = heap_getattr(heapTuple, poolinfoIdx, tupleDescriptor,
&isNull);
Assert(!isNull);
poolinfo = TextDatumGetCString(poolinfoDatum);
}
systable_endscan(scanDescriptor);
table_close(pgDistPoolinfo, AccessShareLock);
return poolinfo;
}
/*
* poolinfo_valid is a check constraint which errors on all rows, intended for
* use in prohibiting writes to pg_dist_poolinfo in Citus Community.
* GetAuthinfoViaCatalog searches pg_dist_authinfo for a row matching a pro-
* vided role and node id. Three types of rules are currently permitted: those
* matching a specific node (non-zero nodeid), those matching all nodes (a
* nodeid of zero), and those denoting a loopback connection (nodeid of -1).
* Rolename must always be specified. If both types of rules exist for a given
* user/host, the more specific (host-specific) rule wins. This means that when
* both a zero and non-zero row exist for a given rolename, the non-zero row
* has precedence.
*
* In short, this function will return a rule matching nodeId, or if that's
* absent the rule for 0, or if that's absent, an empty string. Callers can
* just use the returned authinfo and know the precedence has been honored.
*/
char *
GetAuthinfoViaCatalog(const char *roleName, int64 nodeId)
{
char *authinfo = "";
Datum nodeIdDatumArray[2] = {
Int32GetDatum(nodeId),
Int32GetDatum(WILDCARD_NODE_ID)
};
ArrayType *nodeIdArrayType = DatumArrayToArrayType(nodeIdDatumArray,
lengthof(nodeIdDatumArray),
INT4OID);
ScanKeyData scanKey[2];
const AttrNumber nodeIdIdx = 1, roleIdx = 2, authinfoIdx = 3;
/*
* Our index's definition ensures correct precedence for positive nodeIds,
* but when handling a negative value we need to traverse backwards to keep
* the invariant that the zero rule has lowest precedence.
*/
ScanDirection direction = (nodeId < 0) ? BackwardScanDirection : ForwardScanDirection;
if (ReindexIsProcessingIndex(DistAuthinfoIndexId()))
{
ereport(ERROR, (errmsg("authinfo is being reindexed; try again")));
}
memset(&scanKey, 0, sizeof(scanKey));
/* first column in index is rolename, need exact match there ... */
ScanKeyInit(&scanKey[0], roleIdx, BTEqualStrategyNumber,
F_NAMEEQ, CStringGetDatum(roleName));
/* second column is nodeId, match against array of nodeid and zero (any node) ... */
ScanKeyInit(&scanKey[1], nodeIdIdx, BTEqualStrategyNumber,
F_INT4EQ, PointerGetDatum(nodeIdArrayType));
scanKey[1].sk_flags |= SK_SEARCHARRAY;
/*
* It's important that we traverse the index in order: we need to ensure
* that rules with nodeid 0 are encountered last. We'll use the first tuple
* we find. This ordering defines the precedence order of authinfo rules.
*/
Relation pgDistAuthinfo = table_open(DistAuthinfoRelationId(), AccessShareLock);
Relation pgDistAuthinfoIdx = index_open(DistAuthinfoIndexId(), AccessShareLock);
SysScanDesc scanDescriptor = systable_beginscan_ordered(pgDistAuthinfo,
pgDistAuthinfoIdx,
NULL, lengthof(scanKey),
scanKey);
/* first tuple represents highest-precedence rule for this node */
HeapTuple authinfoTuple = systable_getnext_ordered(scanDescriptor, direction);
if (HeapTupleIsValid(authinfoTuple))
{
TupleDesc tupleDescriptor = RelationGetDescr(pgDistAuthinfo);
bool isNull = false;
Datum authinfoDatum = heap_getattr(authinfoTuple, authinfoIdx,
tupleDescriptor, &isNull);
Assert(!isNull);
authinfo = TextDatumGetCString(authinfoDatum);
}
systable_endscan_ordered(scanDescriptor);
index_close(pgDistAuthinfoIdx, AccessShareLock);
table_close(pgDistAuthinfo, AccessShareLock);
return authinfo;
}
/*
* authinfo_valid is a check constraint to verify that an inserted authinfo row
* uses only permitted libpq parameters.
*/
Datum
authinfo_valid(PG_FUNCTION_ARGS)
{
char *authinfo = TextDatumGetCString(PG_GETARG_DATUM(0));
/* this array _must_ be kept in an order usable by bsearch */
const char *allowList[] = { "password", "sslcert", "sslkey" };
bool authinfoValid = CheckConninfo(authinfo, allowList, lengthof(allowList), NULL);
PG_RETURN_BOOL(authinfoValid);
}
/*
* poolinfo_valid is a check constraint to verify that an inserted poolinfo row
* uses only permitted libpq parameters.
*/
Datum
poolinfo_valid(PG_FUNCTION_ARGS)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot write to pg_dist_poolinfo"),
errdetail(
"Citus Community Edition does not support the use of "
"pooler options."),
errhint("To learn more about using advanced pooling schemes "
"with Citus, please contact us at "
"https://citusdata.com/about/contact_us")));
char *poolinfo = TextDatumGetCString(PG_GETARG_DATUM(0));
/* this array _must_ be kept in an order usable by bsearch */
const char *allowList[] = { "dbname", "host", "port" };
bool poolinfoValid = CheckConninfo(poolinfo, allowList, lengthof(allowList), NULL);
PG_RETURN_BOOL(poolinfoValid);
}

View File

@ -50,6 +50,7 @@
#include "distributed/metadata_cache.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata_utility.h"
#include "distributed/metadata/dependency.h"
#include "distributed/metadata/distobject.h"
#include "distributed/metadata/pg_dist_object.h"
#include "distributed/multi_executor.h"
@ -70,6 +71,7 @@
#include "executor/spi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/pg_list.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
@ -95,6 +97,8 @@ static char * SchemaOwnerName(Oid objectId);
static bool HasMetadataWorkers(void);
static void CreateShellTableOnWorkers(Oid relationId);
static void CreateTableMetadataOnWorkers(Oid relationId);
static void CreateDependingViewsOnWorkers(Oid relationId);
static NodeMetadataSyncResult SyncNodeMetadataToNodesOptional(void);
static bool ShouldSyncTableMetadataInternal(bool hashDistributed,
bool citusTableWithNoDistKey);
static bool SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError);
@ -110,6 +114,11 @@ static List * GetObjectsForGrantStmt(ObjectType objectType, Oid objectId);
static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission);
static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid,
AclItem *aclItem);
static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid,
AclItem *aclItem);
static List * GrantOnSequenceDDLCommands(Oid sequenceOid);
static List * GenerateGrantOnSequenceQueriesFromAclItem(Oid sequenceOid,
AclItem *aclItem);
static void SetLocalReplicateReferenceTablesOnActivate(bool state);
static char * GenerateSetRoleQuery(Oid roleOid);
static void MetadataSyncSigTermHandler(SIGNAL_ARGS);
@ -136,6 +145,7 @@ static char * RemoteTypeIdExpression(Oid typeId);
static char * RemoteCollationIdExpression(Oid colocationId);
PG_FUNCTION_INFO_V1(start_metadata_sync_to_all_nodes);
PG_FUNCTION_INFO_V1(start_metadata_sync_to_node);
PG_FUNCTION_INFO_V1(stop_metadata_sync_to_node);
PG_FUNCTION_INFO_V1(worker_record_sequence_dependency);
@ -192,6 +202,33 @@ start_metadata_sync_to_node(PG_FUNCTION_ARGS)
}
/*
* start_metadata_sync_to_all_nodes function sets hasmetadata column of
* all the primary worker nodes to true, and then activate nodes without
* replicating reference tables.
*/
Datum
start_metadata_sync_to_all_nodes(PG_FUNCTION_ARGS)
{
CheckCitusVersion(ERROR);
EnsureSuperUser();
EnsureCoordinator();
List *workerNodes = ActivePrimaryNonCoordinatorNodeList(RowShareLock);
bool prevReplicateRefTablesOnActivate = ReplicateReferenceTablesOnActivate;
SetLocalReplicateReferenceTablesOnActivate(false);
ActivateNodeList(workerNodes);
TransactionModifiedNodeMetadata = true;
SetLocalReplicateReferenceTablesOnActivate(prevReplicateRefTablesOnActivate);
PG_RETURN_BOOL(true);
}
/*
* SyncNodeMetadataToNode is the internal API for
* start_metadata_sync_to_node().
@ -271,7 +308,8 @@ SyncNodeMetadataToNode(const char *nodeNameString, int32 nodePort)
* SyncCitusTableMetadata syncs citus table metadata to worker nodes with metadata.
* Our definition of metadata includes the shell table and its inter relations with
* other shell tables, corresponding pg_dist_object, pg_dist_partiton, pg_dist_shard
* and pg_dist_shard placement entries.
* and pg_dist_shard placement entries. This function also propagates the views that
* depend on the given relation, to the metadata workers.
*/
void
SyncCitusTableMetadata(Oid relationId)
@ -286,6 +324,51 @@ SyncCitusTableMetadata(Oid relationId)
ObjectAddressSet(relationAddress, RelationRelationId, relationId);
MarkObjectDistributed(&relationAddress);
}
CreateDependingViewsOnWorkers(relationId);
}
/*
* CreateDependingViewsOnWorkers takes a relationId and creates the views that depend on
* that relation on workers with metadata. Propagated views are marked as distributed.
*/
static void
CreateDependingViewsOnWorkers(Oid relationId)
{
List *views = GetDependingViews(relationId);
if (list_length(views) < 1)
{
/* no view to propagate */
return;
}
SendCommandToWorkersWithMetadata(DISABLE_DDL_PROPAGATION);
Oid viewOid = InvalidOid;
foreach_oid(viewOid, views)
{
if (!ShouldMarkRelationDistributed(viewOid))
{
continue;
}
ObjectAddress viewAddress = { 0 };
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
EnsureDependenciesExistOnAllNodes(&viewAddress);
char *createViewCommand = CreateViewDDLCommand(viewOid);
char *alterViewOwnerCommand = AlterViewOwnerCommand(viewOid);
SendCommandToWorkersWithMetadata(createViewCommand);
SendCommandToWorkersWithMetadata(alterViewOwnerCommand);
MarkObjectDistributed(&viewAddress);
}
SendCommandToWorkersWithMetadata(ENABLE_DDL_PROPAGATION);
}
@ -423,6 +506,25 @@ ClusterHasKnownMetadataWorkers()
}
/*
* ShouldSyncUserCommandForObject checks if the user command should be synced to the
* worker nodes for the given object.
*/
bool
ShouldSyncUserCommandForObject(ObjectAddress objectAddress)
{
if (objectAddress.classId == RelationRelationId)
{
Oid relOid = objectAddress.objectId;
return ShouldSyncTableMetadata(relOid) ||
ShouldSyncSequenceMetadata(relOid) ||
get_rel_relkind(relOid) == RELKIND_VIEW;
}
return false;
}
/*
* ShouldSyncTableMetadata checks if the metadata of a distributed table should be
* propagated to metadata workers, i.e. the table is a hash distributed table or
@ -488,6 +590,26 @@ ShouldSyncTableMetadataInternal(bool hashDistributed, bool citusTableWithNoDistK
/*
* ShouldSyncSequenceMetadata checks if the metadata of a sequence should be
* propagated to metadata workers, i.e. the sequence is marked as distributed
*/
bool
ShouldSyncSequenceMetadata(Oid relationId)
{
if (!OidIsValid(relationId) || !(get_rel_relkind(relationId) == RELKIND_SEQUENCE))
{
return false;
}
ObjectAddress sequenceAddress = { 0 };
ObjectAddressSet(sequenceAddress, RelationRelationId, relationId);
return IsObjectDistributed(&sequenceAddress);
}
/*
* SyncMetadataSnapshotToNode does the following:
* SyncNodeMetadataSnapshotToNode does the following:
* 1. Sets the localGroupId on the worker so the worker knows which tuple in
* pg_dist_node represents itself.
@ -522,10 +644,10 @@ SyncNodeMetadataSnapshotToNode(WorkerNode *workerNode, bool raiseOnError)
*/
if (raiseOnError)
{
SendMetadataCommandListToWorkerInCoordinatedTransaction(workerNode->workerName,
workerNode->workerPort,
currentUser,
recreateMetadataSnapshotCommandList);
SendMetadataCommandListToWorkerListInCoordinatedTransaction(list_make1(
workerNode),
currentUser,
recreateMetadataSnapshotCommandList);
return true;
}
else
@ -1249,6 +1371,23 @@ ShardListInsertCommand(List *shardIntervalList)
}
/*
* ShardListDeleteCommand generates a command list that can be executed to delete
* shard and shard placement metadata for the given shard.
*/
List *
ShardDeleteCommandList(ShardInterval *shardInterval)
{
uint64 shardId = shardInterval->shardId;
StringInfo deleteShardCommand = makeStringInfo();
appendStringInfo(deleteShardCommand,
"SELECT citus_internal_delete_shard_metadata(%ld);", shardId);
return list_make1(deleteShardCommand->data);
}
/*
* NodeDeleteCommand generate a command that can be
* executed to delete the metadata for a worker node.
@ -1383,6 +1522,8 @@ DDLCommandsForSequence(Oid sequenceOid, char *ownerName)
sequenceDDLList = lappend(sequenceDDLList, wrappedSequenceDef->data);
sequenceDDLList = lappend(sequenceDDLList, sequenceGrantStmt->data);
sequenceDDLList = list_concat(sequenceDDLList, GrantOnSequenceDDLCommands(
sequenceOid));
return sequenceDDLList;
}
@ -1842,7 +1983,7 @@ GrantOnSchemaDDLCommands(Oid schemaOid)
/*
* GenerateGrantOnSchemaQueryFromACL generates a query string for replicating a users permissions
* GenerateGrantOnSchemaQueryFromACLItem generates a query string for replicating a users permissions
* on a schema.
*/
List *
@ -1926,6 +2067,34 @@ GetObjectsForGrantStmt(ObjectType objectType, Oid objectId)
return list_make1(makeString(get_namespace_name(objectId)));
}
/* enterprise supported object types */
case OBJECT_FUNCTION:
case OBJECT_PROCEDURE:
{
ObjectWithArgs *owa = ObjectWithArgsFromOid(objectId);
return list_make1(owa);
}
case OBJECT_FDW:
{
ForeignDataWrapper *fdw = GetForeignDataWrapper(objectId);
return list_make1(makeString(fdw->fdwname));
}
case OBJECT_FOREIGN_SERVER:
{
ForeignServer *server = GetForeignServer(objectId);
return list_make1(makeString(server->servername));
}
case OBJECT_SEQUENCE:
{
Oid namespaceOid = get_rel_namespace(objectId);
RangeVar *sequence = makeRangeVar(get_namespace_name(namespaceOid),
get_rel_name(objectId), -1);
return list_make1(sequence);
}
default:
{
elog(ERROR, "unsupported object type for GRANT");
@ -1936,6 +2105,211 @@ GetObjectsForGrantStmt(ObjectType objectType, Oid objectId)
}
/*
* GrantOnFunctionDDLCommands creates a list of ddl command for replicating the permissions
* of roles on distributed functions.
*/
List *
GrantOnFunctionDDLCommands(Oid functionOid)
{
HeapTuple proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionOid));
bool isNull = true;
Datum aclDatum = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proacl,
&isNull);
if (isNull)
{
ReleaseSysCache(proctup);
return NIL;
}
Acl *acl = DatumGetAclPCopy(aclDatum);
AclItem *aclDat = ACL_DAT(acl);
int aclNum = ACL_NUM(acl);
List *commands = NIL;
ReleaseSysCache(proctup);
for (int i = 0; i < aclNum; i++)
{
commands = list_concat(commands,
GenerateGrantOnFunctionQueriesFromAclItem(
functionOid,
&aclDat[i]));
}
return commands;
}
/*
* GrantOnForeignServerDDLCommands creates a list of ddl command for replicating the
* permissions of roles on distributed foreign servers.
*/
List *
GrantOnForeignServerDDLCommands(Oid serverId)
{
HeapTuple servertup = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverId));
bool isNull = true;
Datum aclDatum = SysCacheGetAttr(FOREIGNSERVEROID, servertup,
Anum_pg_foreign_server_srvacl, &isNull);
if (isNull)
{
ReleaseSysCache(servertup);
return NIL;
}
Acl *aclEntry = DatumGetAclPCopy(aclDatum);
AclItem *privileges = ACL_DAT(aclEntry);
int numberOfPrivsGranted = ACL_NUM(aclEntry);
List *commands = NIL;
ReleaseSysCache(servertup);
for (int i = 0; i < numberOfPrivsGranted; i++)
{
commands = list_concat(commands,
GenerateGrantOnForeignServerQueriesFromAclItem(
serverId,
&privileges[i]));
}
return commands;
}
/*
* GenerateGrantOnForeignServerQueriesFromAclItem generates a query string for
* replicating a users permissions on a foreign server.
*/
List *
GenerateGrantOnForeignServerQueriesFromAclItem(Oid serverId, AclItem *aclItem)
{
/* privileges to be granted */
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_FOREIGN_SERVER;
/* WITH GRANT OPTION clause */
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_FOREIGN_SERVER;
/*
* seems unlikely but we check if there is a grant option in the list without the actual permission
*/
Assert(!(grants & ACL_USAGE) || (permissions & ACL_USAGE));
Oid granteeOid = aclItem->ai_grantee;
List *queries = NIL;
/* switch to the role which had granted acl */
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
/* generate the GRANT stmt that will be executed by the grantor role */
if (permissions & ACL_USAGE)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_FOREIGN_SERVER, granteeOid, serverId,
"USAGE", grants & ACL_USAGE));
queries = lappend(queries, query);
}
/* reset the role back */
queries = lappend(queries, "RESET ROLE");
return queries;
}
/*
* GenerateGrantOnFunctionQueryFromACLItem generates a query string for replicating a users permissions
* on a distributed function.
*/
List *
GenerateGrantOnFunctionQueriesFromAclItem(Oid functionOid, AclItem *aclItem)
{
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_FUNCTION;
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_FUNCTION;
/*
* seems unlikely but we check if there is a grant option in the list without the actual permission
*/
Assert(!(grants & ACL_EXECUTE) || (permissions & ACL_EXECUTE));
Oid granteeOid = aclItem->ai_grantee;
List *queries = NIL;
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
if (permissions & ACL_EXECUTE)
{
char prokind = get_func_prokind(functionOid);
ObjectType objectType;
if (prokind == PROKIND_FUNCTION)
{
objectType = OBJECT_FUNCTION;
}
else if (prokind == PROKIND_PROCEDURE)
{
objectType = OBJECT_PROCEDURE;
}
else
{
ereport(ERROR, (errmsg("unsupported prokind"),
errdetail("GRANT commands on procedures are propagated only "
"for procedures and functions.")));
}
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
objectType, granteeOid, functionOid, "EXECUTE",
grants & ACL_EXECUTE));
queries = lappend(queries, query);
}
queries = lappend(queries, "RESET ROLE");
return queries;
}
/*
* GenerateGrantOnFDWQueriesFromAclItem generates a query string for
* replicating a users permissions on a foreign data wrapper.
*/
List *
GenerateGrantOnFDWQueriesFromAclItem(Oid FDWId, AclItem *aclItem)
{
/* privileges to be granted */
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_FDW;
/* WITH GRANT OPTION clause */
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_FDW;
/*
* seems unlikely but we check if there is a grant option in the list without the actual permission
*/
Assert(!(grants & ACL_USAGE) || (permissions & ACL_USAGE));
Oid granteeOid = aclItem->ai_grantee;
List *queries = NIL;
/* switch to the role which had granted acl */
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
/* generate the GRANT stmt that will be executed by the grantor role */
if (permissions & ACL_USAGE)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_FDW, granteeOid, FDWId, "USAGE",
grants & ACL_USAGE));
queries = lappend(queries, query);
}
/* reset the role back */
queries = lappend(queries, "RESET ROLE");
return queries;
}
/*
* GetAccessPrivObjectForGrantStmt creates an AccessPriv object for the given permission.
* It will be used when creating GrantStmt objects.
@ -1951,6 +2325,93 @@ GetAccessPrivObjectForGrantStmt(char *permission)
}
/*
* GrantOnSequenceDDLCommands creates a list of ddl command for replicating the permissions
* of roles on distributed sequences.
*/
static List *
GrantOnSequenceDDLCommands(Oid sequenceOid)
{
HeapTuple seqtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceOid));
bool isNull = false;
Datum aclDatum = SysCacheGetAttr(RELOID, seqtup, Anum_pg_class_relacl,
&isNull);
if (isNull)
{
ReleaseSysCache(seqtup);
return NIL;
}
Acl *acl = DatumGetAclPCopy(aclDatum);
AclItem *aclDat = ACL_DAT(acl);
int aclNum = ACL_NUM(acl);
List *commands = NIL;
ReleaseSysCache(seqtup);
for (int i = 0; i < aclNum; i++)
{
commands = list_concat(commands,
GenerateGrantOnSequenceQueriesFromAclItem(
sequenceOid,
&aclDat[i]));
}
return commands;
}
/*
* GenerateGrantOnSequenceQueriesFromAclItem generates a query string for replicating a users permissions
* on a distributed sequence.
*/
static List *
GenerateGrantOnSequenceQueriesFromAclItem(Oid sequenceOid, AclItem *aclItem)
{
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_SEQUENCE;
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_SEQUENCE;
/*
* seems unlikely but we check if there is a grant option in the list without the actual permission
*/
Assert(!(grants & ACL_USAGE) || (permissions & ACL_USAGE));
Assert(!(grants & ACL_SELECT) || (permissions & ACL_SELECT));
Assert(!(grants & ACL_UPDATE) || (permissions & ACL_UPDATE));
Oid granteeOid = aclItem->ai_grantee;
List *queries = NIL;
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
if (permissions & ACL_USAGE)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_SEQUENCE, granteeOid, sequenceOid,
"USAGE", grants & ACL_USAGE));
queries = lappend(queries, query);
}
if (permissions & ACL_SELECT)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_SEQUENCE, granteeOid, sequenceOid,
"SELECT", grants & ACL_SELECT));
queries = lappend(queries, query);
}
if (permissions & ACL_UPDATE)
{
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
OBJECT_SEQUENCE, granteeOid, sequenceOid,
"UPDATE", grants & ACL_UPDATE));
queries = lappend(queries, query);
}
queries = lappend(queries, "RESET ROLE");
return queries;
}
/*
* SetLocalEnableMetadataSync sets the enable_metadata_sync locally
*/
@ -2042,7 +2503,7 @@ SchemaOwnerName(Oid objectId)
static bool
HasMetadataWorkers(void)
{
List *workerNodeList = ActivePrimaryNonCoordinatorNodeList(NoLock);
List *workerNodeList = ActiveReadableNonCoordinatorNodeList();
WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, workerNodeList)
@ -2217,16 +2678,16 @@ DetachPartitionCommandList(void)
/*
* SyncNodeMetadataToNodes tries recreating the metadata snapshot in the
* metadata workers that are out of sync. Returns the result of
* synchronization.
* SyncNodeMetadataToNodesOptional tries recreating the metadata
* snapshot in the metadata workers that are out of sync.
* Returns the result of synchronization.
*
* This function must be called within coordinated transaction
* since updates on the pg_dist_node metadata must be rollbacked if anything
* goes wrong.
*/
static NodeMetadataSyncResult
SyncNodeMetadataToNodes(void)
SyncNodeMetadataToNodesOptional(void)
{
NodeMetadataSyncResult result = NODE_METADATA_SYNC_SUCCESS;
if (!IsCoordinator())
@ -2286,6 +2747,46 @@ SyncNodeMetadataToNodes(void)
}
/*
* SyncNodeMetadataToNodes recreates the node metadata snapshot in all the
* metadata workers.
*
* This function runs within a coordinated transaction since updates on
* the pg_dist_node metadata must be rollbacked if anything
* goes wrong.
*/
void
SyncNodeMetadataToNodes(void)
{
EnsureCoordinator();
/*
* Request a RowExclusiveLock so we don't run concurrently with other
* functions updating pg_dist_node, but allow concurrency with functions
* which are just reading from pg_dist_node.
*/
if (!ConditionalLockRelationOid(DistNodeRelationId(), RowExclusiveLock))
{
ereport(ERROR, (errmsg("cannot sync metadata because a concurrent "
"metadata syncing operation is in progress")));
}
List *workerList = ActivePrimaryNonCoordinatorNodeList(NoLock);
WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, workerList)
{
if (workerNode->hasMetadata)
{
SetWorkerColumnLocalOnly(workerNode, Anum_pg_dist_node_metadatasynced,
BoolGetDatum(true));
bool raiseOnError = true;
SyncNodeMetadataSnapshotToNode(workerNode, raiseOnError);
}
}
}
/*
* SyncNodeMetadataToNodesMain is the main function for syncing node metadata to
* MX nodes. It retries until success and then exits.
@ -2332,7 +2833,7 @@ SyncNodeMetadataToNodesMain(Datum main_arg)
{
UseCoordinatedTransaction();
NodeMetadataSyncResult result = SyncNodeMetadataToNodes();
NodeMetadataSyncResult result = SyncNodeMetadataToNodesOptional();
syncedAllNodes = (result == NODE_METADATA_SYNC_SUCCESS);
/* we use LISTEN/NOTIFY to wait for metadata syncing in tests */
@ -3391,12 +3892,19 @@ ColocationGroupCreateCommandList(void)
"distributioncolumncollationschema) AS (VALUES ");
Relation pgDistColocation = table_open(DistColocationRelationId(), AccessShareLock);
Relation colocationIdIndexRel = index_open(DistColocationIndexId(), AccessShareLock);
bool indexOK = false;
SysScanDesc scanDescriptor = systable_beginscan(pgDistColocation, InvalidOid, indexOK,
NULL, 0, NULL);
/*
* It is not strictly necessary to read the tuples in order.
* However, it is useful to get consistent behavior, both for regression
* tests and also in production systems.
*/
SysScanDesc scanDescriptor =
systable_beginscan_ordered(pgDistColocation, colocationIdIndexRel,
NULL, 0, NULL);
HeapTuple colocationTuple = systable_getnext(scanDescriptor);
HeapTuple colocationTuple = systable_getnext_ordered(scanDescriptor,
ForwardScanDirection);
while (HeapTupleIsValid(colocationTuple))
{
@ -3454,10 +3962,11 @@ ColocationGroupCreateCommandList(void)
"NULL, NULL)");
}
colocationTuple = systable_getnext(scanDescriptor);
colocationTuple = systable_getnext_ordered(scanDescriptor, ForwardScanDirection);
}
systable_endscan(scanDescriptor);
systable_endscan_ordered(scanDescriptor);
index_close(colocationIdIndexRel, AccessShareLock);
table_close(pgDistColocation, AccessShareLock);
if (!hasColocations)

View File

@ -66,6 +66,9 @@
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#if PG_VERSION_NUM < 120000
#include "utils/tqual.h"
#endif
#define DISK_SPACE_FIELDS 2
@ -2175,11 +2178,8 @@ EnsureSuperUser(void)
}
/*
* Return a table's owner as a string.
*/
char *
TableOwner(Oid relationId)
Oid
TableOwnerOid(Oid relationId)
{
HeapTuple tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
if (!HeapTupleIsValid(tuple))
@ -2191,8 +2191,17 @@ TableOwner(Oid relationId)
Oid userId = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
ReleaseSysCache(tuple);
return userId;
}
return GetUserNameFromId(userId, false);
/*
* Return a table's owner as a string.
*/
char *
TableOwner(Oid relationId)
{
return GetUserNameFromId(TableOwnerOid(relationId), false);
}

View File

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

View File

@ -419,20 +419,10 @@ ErrorIfCurrentUserCanNotDistributeObject(ObjectType type, ObjectAddress *addr,
case OBJECT_TABLE:
case OBJECT_EXTENSION:
case OBJECT_COLLATION:
{
check_object_ownership(userId, type, *addr, node, *relation);
break;
}
case OBJECT_VIEW:
case OBJECT_ROLE:
{
/* Support only extension owner role with community */
if (addr->objectId != CitusExtensionOwner())
{
ereport(ERROR, (errmsg("Current user does not have required "
"access privileges on role %d with type %d",
addr->objectId, type)));
}
check_object_ownership(userId, type, *addr, node, *relation);
break;
}

View File

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

View File

@ -622,6 +622,17 @@ GetPreLoadTableCreationCommands(Oid relationId,
}
}
List *tableACLList = pg_get_table_grants(relationId);
if (tableACLList != NIL)
{
char *tableACLCommand = NULL;
foreach_ptr(tableACLCommand, tableACLList)
{
tableDDLEventList = lappend(tableDDLEventList,
makeTableDDLCommandString(tableACLCommand));
}
}
char *tableOwnerDef = TableOwnerResetCommand(relationId);
if (tableOwnerDef != NULL)
{
@ -629,6 +640,9 @@ GetPreLoadTableCreationCommands(Oid relationId,
tableOwnerDef));
}
List *tableRowLevelSecurityCommands = GetTableRowLevelSecurityCommands(relationId);
tableDDLEventList = list_concat(tableDDLEventList, tableRowLevelSecurityCommands);
List *policyCommands = CreatePolicyCommands(relationId);
tableDDLEventList = list_concat(tableDDLEventList, policyCommands);
@ -777,6 +791,29 @@ GatherIndexAndConstraintDefinitionList(Form_pg_index indexForm, List **indexDDLE
}
/*
* GetTableRowLevelSecurityCommands takes in a relationId, and returns the list of
* commands needed to reconstruct the row level security policy.
*/
List *
GetTableRowLevelSecurityCommands(Oid relationId)
{
List *rowLevelSecurityCommandList = NIL;
List *rowLevelSecurityEnableCommands = pg_get_row_level_security_commands(relationId);
char *rowLevelSecurityCommand = NULL;
foreach_ptr(rowLevelSecurityCommand, rowLevelSecurityEnableCommands)
{
rowLevelSecurityCommandList = lappend(
rowLevelSecurityCommandList,
makeTableDDLCommandString(rowLevelSecurityCommand));
}
return rowLevelSecurityCommandList;
}
/*
* IndexImpliedByAConstraint is a helper function to be used while scanning
* pg_index. It returns true if the index identified by the given indexForm is

View File

@ -32,6 +32,7 @@
#include "distributed/metadata_cache.h"
#include "distributed/metadata_sync.h"
#include "distributed/multi_join_order.h"
#include "distributed/multi_logical_replication.h"
#include "distributed/multi_partitioning_utils.h"
#include "distributed/reference_table_utils.h"
#include "distributed/remote_commands.h"
@ -53,6 +54,9 @@
#include "utils/syscache.h"
/* local function forward declarations */
static void VerifyTablesHaveReplicaIdentity(List *colocatedTableList);
static bool RelationCanPublishAllModifications(Oid relationId);
static bool CanUseLogicalReplication(Oid relationId, char shardReplicationMode);
static void ErrorIfTableCannotBeReplicated(Oid relationId);
static void RepairShardPlacement(int64 shardId, const char *sourceNodeName,
int32 sourceNodePort, const char *targetNodeName,
@ -64,6 +68,12 @@ static void ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName
static void CopyShardTables(List *shardIntervalList, char *sourceNodeName,
int32 sourceNodePort, char *targetNodeName,
int32 targetNodePort, bool useLogicalReplication);
static void CopyShardTablesViaLogicalReplication(List *shardIntervalList,
char *sourceNodeName,
int32 sourceNodePort,
char *targetNodeName,
int32 targetNodePort);
static void CopyShardTablesViaBlockWrites(List *shardIntervalList, char *sourceNodeName,
int32 sourceNodePort,
char *targetNodeName, int32 targetNodePort);
@ -146,11 +156,10 @@ citus_copy_shard_placement(PG_FUNCTION_ARGS)
char *targetNodeName = text_to_cstring(targetNodeNameText);
char shardReplicationMode = LookupShardTransferMode(shardReplicationModeOid);
if (shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
if (doRepair && shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the force_logical transfer mode is currently "
"unsupported")));
errmsg("logical replication cannot be used for repairs")));
}
ShardInterval *shardInterval = LoadShardInterval(shardId);
@ -282,8 +291,7 @@ CheckSpaceConstraints(MultiConnection *connection, uint64 colocationSizeInBytes)
* After that, there are two different paths. First one is blocking shard move in the
* sense that during shard move all modifications are paused to the shard. The second
* one relies on logical replication meaning that the writes blocked only for a very
* short duration almost only when the metadata is actually being updated. This option
* is currently only available in Citus Enterprise.
* short duration almost only when the metadata is actually being updated.
*
* After successful move operation, shards in the source node gets deleted. If the move
* fails at any point, this function throws an error, leaving the cluster without doing
@ -354,23 +362,52 @@ citus_move_shard_placement(PG_FUNCTION_ARGS)
}
char shardReplicationMode = LookupShardTransferMode(shardReplicationModeOid);
if (shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
if (shardReplicationMode == TRANSFER_MODE_AUTOMATIC)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the force_logical transfer mode is currently "
"unsupported")));
VerifyTablesHaveReplicaIdentity(colocatedTableList);
}
EnsureEnoughDiskSpaceForShardMove(colocatedShardList, sourceNodeName, sourceNodePort,
targetNodeName, targetNodePort);
BlockWritesToShardList(colocatedShardList);
/*
* At this point of the shard moves, we don't need to block the writes to
* shards when logical replication is used.
*/
bool useLogicalReplication = CanUseLogicalReplication(distributedTableId,
shardReplicationMode);
if (!useLogicalReplication)
{
BlockWritesToShardList(colocatedShardList);
}
else
{
/*
* We prevent multiple shard moves in a transaction that use logical
* replication. That's because the first call opens a transaction block
* on the worker to drop the old shard placement and replication slot
* creation waits for pending transactions to finish, which will not
* happen ever. In other words, we prevent a self-deadlock if both
* source shard placements are on the same node.
*/
if (PlacementMovedUsingLogicalReplicationInTX)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("moving multiple shard placements via logical "
"replication in the same transaction is currently "
"not supported"),
errhint("If you wish to move multiple shard placements "
"in a single transaction set the shard_transfer_mode "
"to 'block_writes'.")));
}
PlacementMovedUsingLogicalReplicationInTX = true;
}
/*
* CopyColocatedShardPlacement function copies given shard with its co-located
* shards.
*/
bool useLogicalReplication = false;
CopyShardTables(colocatedShardList, sourceNodeName, sourceNodePort, targetNodeName,
targetNodePort, useLogicalReplication);
@ -522,6 +559,74 @@ ErrorIfMoveUnsupportedTableType(Oid relationId)
}
/*
* VerifyTablesHaveReplicaIdentity throws an error if any of the tables
* do not have a replica identity, which is required for logical replication
* to replicate UPDATE and DELETE commands.
*/
static void
VerifyTablesHaveReplicaIdentity(List *colocatedTableList)
{
ListCell *colocatedTableCell = NULL;
foreach(colocatedTableCell, colocatedTableList)
{
Oid colocatedTableId = lfirst_oid(colocatedTableCell);
if (!RelationCanPublishAllModifications(colocatedTableId))
{
char *colocatedRelationName = get_rel_name(colocatedTableId);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot use logical replication to transfer shards of "
"the relation %s since it doesn't have a REPLICA "
"IDENTITY or PRIMARY KEY", colocatedRelationName),
errdetail("UPDATE and DELETE commands on the shard will "
"error out during logical replication unless "
"there is a REPLICA IDENTITY or PRIMARY KEY."),
errhint("If you wish to continue without a replica "
"identity set the shard_transfer_mode to "
"'force_logical' or 'block_writes'.")));
}
}
}
/*
* RelationCanPublishAllModifications returns true if the relation is safe to publish
* all modification while being replicated via logical replication.
*/
static bool
RelationCanPublishAllModifications(Oid relationId)
{
Relation relation = RelationIdGetRelation(relationId);
bool canPublish = false;
if (relation == NULL)
{
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("could not open relation with OID %u", relationId)));
}
/* if relation has replica identity we are always good */
if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
OidIsValid(RelationGetReplicaIndex(relation)))
{
canPublish = true;
}
/* partitioned tables do not contain any data themselves, can always replicate */
if (PartitionedTable(relationId))
{
canPublish = true;
}
RelationClose(relation);
return canPublish;
}
/*
* BlockWritesToShardList blocks writes to all shards in the given shard
* list. The function assumes that all the shards in the list are colocated.
@ -567,6 +672,49 @@ BlockWritesToShardList(List *shardList)
}
/*
* CanUseLogicalReplication returns true if the given table can be logically replicated.
*/
static bool
CanUseLogicalReplication(Oid relationId, char shardReplicationMode)
{
if (shardReplicationMode == TRANSFER_MODE_BLOCK_WRITES)
{
/* user explicitly chose not to use logical replication */
return false;
}
/*
* Logical replication doesn't support replicating foreign tables and views.
*/
if (!RegularTable(relationId))
{
ereport(LOG, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Cannot use logical replication for "
"shard move since the relation %s is not "
"a regular relation",
get_rel_name(relationId))));
return false;
}
/* Logical replication doesn't support inherited tables */
if (IsParentTable(relationId))
{
ereport(LOG, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Cannot use logical replication for "
"shard move since the relation %s is an "
"inherited relation",
get_rel_name(relationId))));
return false;
}
return true;
}
/*
* ErrorIfTableCannotBeReplicated function errors out if the given table is not suitable
* for its shard being replicated. There are 2 cases in which shard replication is not
@ -790,7 +938,16 @@ ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName,
*/
colocatedShardList = SortList(colocatedShardList, CompareShardIntervalsById);
BlockWritesToShardList(colocatedShardList);
/*
* At this point of the shard replication, we don't need to block the writes to
* shards when logical replication is used.
*/
bool useLogicalReplication = CanUseLogicalReplication(distributedTableId,
shardReplicationMode);
if (!useLogicalReplication)
{
BlockWritesToShardList(colocatedShardList);
}
ShardInterval *colocatedShard = NULL;
foreach_ptr(colocatedShard, colocatedShardList)
@ -805,6 +962,11 @@ ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName,
targetNodeName, targetNodePort);
}
if (shardReplicationMode == TRANSFER_MODE_AUTOMATIC)
{
VerifyTablesHaveReplicaIdentity(colocatedTableList);
}
if (!IsCitusTableType(distributedTableId, REFERENCE_TABLE))
{
/*
@ -818,7 +980,6 @@ ReplicateColocatedShardPlacement(int64 shardId, char *sourceNodeName,
EnsureReferenceTablesExistOnAllNodesExtended(shardReplicationMode);
}
bool useLogicalReplication = false;
CopyShardTables(colocatedShardList, sourceNodeName, sourceNodePort,
targetNodeName, targetNodePort, useLogicalReplication);
@ -912,7 +1073,9 @@ CopyShardTables(List *shardIntervalList, char *sourceNodeName, int32 sourceNodeP
if (useLogicalReplication)
{
/* only supported in Citus enterprise */
CopyShardTablesViaLogicalReplication(shardIntervalList, sourceNodeName,
sourceNodePort, targetNodeName,
targetNodePort);
}
else
{
@ -922,6 +1085,50 @@ CopyShardTables(List *shardIntervalList, char *sourceNodeName, int32 sourceNodeP
}
/*
* CopyShardTablesViaLogicalReplication copies a shard along with its co-located shards
* from a source node to target node via logical replication.
*/
static void
CopyShardTablesViaLogicalReplication(List *shardIntervalList, char *sourceNodeName,
int32 sourceNodePort, char *targetNodeName,
int32 targetNodePort)
{
MemoryContext localContext = AllocSetContextCreate(CurrentMemoryContext,
"CopyShardTablesViaLogicalReplication",
ALLOCSET_DEFAULT_SIZES);
MemoryContext oldContext = MemoryContextSwitchTo(localContext);
/*
* Iterate through the colocated shards and create them on the
* target node. We do not create the indexes yet.
*/
ShardInterval *shardInterval = NULL;
foreach_ptr(shardInterval, shardIntervalList)
{
Oid relationId = shardInterval->relationId;
uint64 shardId = shardInterval->shardId;
List *tableRecreationCommandList = RecreateTableDDLCommandList(relationId);
tableRecreationCommandList =
WorkerApplyShardDDLCommandList(tableRecreationCommandList, shardId);
char *tableOwner = TableOwner(shardInterval->relationId);
SendCommandListToWorkerOutsideTransaction(targetNodeName, targetNodePort,
tableOwner,
tableRecreationCommandList);
MemoryContextReset(localContext);
}
MemoryContextSwitchTo(oldContext);
/* data copy is done seperately when logical replication is used */
LogicallyReplicateShards(shardIntervalList, sourceNodeName,
sourceNodePort, targetNodeName, targetNodePort);
}
/*
* CopyShardTablesViaBlockWrites copies a shard along with its co-located shards
* from a source node to target node via COPY command. While the command is in
@ -989,12 +1196,12 @@ CopyShardTablesViaBlockWrites(List *shardIntervalList, char *sourceNodeName,
{
List *shardForeignConstraintCommandList = NIL;
List *referenceTableForeignConstraintList = NIL;
List *commandList = NIL;
CopyShardForeignConstraintCommandListGrouped(shardInterval,
&shardForeignConstraintCommandList,
&referenceTableForeignConstraintList);
List *commandList = NIL;
commandList = list_concat(commandList, shardForeignConstraintCommandList);
commandList = list_concat(commandList, referenceTableForeignConstraintList);

View File

@ -5,7 +5,7 @@
* left around. Shards that are left around are marked as state 4
* (SHARD_STATE_TO_DELETE) in pg_dist_placement.
*
* Copyright (c), Citus Data, Inc.
* Copyright (c) 2018, Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/

View File

@ -6,6 +6,8 @@
*
* Copyright (c) Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
@ -317,7 +319,7 @@ CheckRebalanceStateInvariants(const RebalanceState *state)
/* Check that utilization field is up to date. */
Assert(fillState->utilization == CalculateUtilization(fillState->totalCost,
fillState->capacity));
fillState->capacity)); /* lgtm[cpp/equality-on-floats] */
/*
* Check that fillState->totalCost is within 0.1% difference of
@ -698,14 +700,6 @@ ExecutePlacementUpdates(List *placementUpdateList, Oid shardReplicationModeOid,
ListCell *placementUpdateCell = NULL;
char shardReplicationMode = LookupShardTransferMode(shardReplicationModeOid);
if (shardReplicationMode == TRANSFER_MODE_FORCE_LOGICAL)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("the force_logical transfer mode is currently "
"unsupported")));
}
DropOrphanedShardsInSeparateTransaction();
foreach(placementUpdateCell, placementUpdateList)
@ -2341,7 +2335,7 @@ FindAndMoveShardCost(float4 utilizationLowerBound,
}
if (newTargetUtilization == sourceFillState->utilization &&
newSourceUtilization <= targetFillState->utilization
)
) /* lgtm[cpp/equality-on-floats] */
{
/*
* this can trigger when capacity of the nodes is not the

View File

@ -13,6 +13,7 @@
#include "postgres.h"
#include "c.h"
#include "fmgr.h"
#include "libpq-fe.h"
#include "catalog/pg_class.h"
#include "distributed/colocation_utils.h"
@ -20,14 +21,17 @@
#include "distributed/metadata_cache.h"
#include "distributed/metadata_sync.h"
#include "distributed/multi_join_order.h"
#include "distributed/multi_partitioning_utils.h"
#include "distributed/multi_router_planner.h"
#include "distributed/pg_dist_partition.h"
#include "distributed/pg_dist_shard.h"
#include "distributed/remote_commands.h"
#include "distributed/reference_table_utils.h"
#include "distributed/resource_lock.h"
#include "distributed/worker_manager.h"
#include "distributed/worker_protocol.h"
#include "distributed/worker_transaction.h"
#include "distributed/version_compat.h"
#include "nodes/pg_list.h"
#include "storage/lock.h"
#include "utils/builtins.h"
@ -42,6 +46,25 @@ PG_FUNCTION_INFO_V1(isolate_tenant_to_new_shard);
PG_FUNCTION_INFO_V1(worker_hash);
/* local function forward declarations */
static uint64 SplitShardByValue(ShardInterval *sourceShard, Datum distributionValueDatum);
static void ErrorIfCannotSplitShard(ShardInterval *sourceShard);
static void CreateSplitOffShards(ShardInterval *sourceShard, int hashedValue,
List **splitOffShardList, int *isolatedShardId);
static List * ShardTemplateList(ShardInterval *sourceShard, int hashedValue,
int *isolatedShardIndex);
static ShardInterval * CreateSplitOffShardFromTemplate(ShardInterval *shardTemplate,
Oid relationId);
static List * SplitOffCommandList(ShardInterval *sourceShard,
ShardInterval *splitOffShard);
static void ExecuteCommandListOnPlacements(List *commandList, List *placementList);
static void InsertSplitOffShardMetadata(List *splitOffShardList,
List *sourcePlacementList);
static void CreateForeignConstraints(List *splitOffShardList, List *sourcePlacementList);
static void ExecuteCommandListOnWorker(char *nodeName, int nodePort, List *commandList);
static void DropShardList(List *shardIntervalList);
/*
* isolate_tenant_to_new_shard isolates a tenant to its own shard by spliting
* the current matching shard.
@ -49,9 +72,100 @@ PG_FUNCTION_INFO_V1(worker_hash);
Datum
isolate_tenant_to_new_shard(PG_FUNCTION_ARGS)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("isolate_tenant_to_new_shard() is only supported on "
"Citus Enterprise")));
CheckCitusVersion(ERROR);
EnsureCoordinator();
Oid relationId = PG_GETARG_OID(0);
Datum inputDatum = PG_GETARG_DATUM(1);
text *cascadeOptionText = PG_GETARG_TEXT_P(2);
ListCell *colocatedTableCell = NULL;
EnsureTableOwner(relationId);
CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
char partitionMethod = cacheEntry->partitionMethod;
if (partitionMethod != DISTRIBUTE_BY_HASH)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot isolate tenant because tenant isolation "
"is only support for hash distributed tables")));
}
if (PartitionedTable(relationId))
{
char *sourceRelationName = get_rel_name(relationId);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot isolate shard placement of '%s', because it "
"is a partitioned table", sourceRelationName),
errdetail("Citus does not support isolating placements of "
"partitioned tables.")));
}
List *colocatedTableList = ColocatedTableList(relationId);
int colocatedTableCount = list_length(colocatedTableList);
foreach(colocatedTableCell, colocatedTableList)
{
Oid colocatedTableId = lfirst_oid(colocatedTableCell);
/*
* At the moment, Citus does not support copying a shard if that shard's
* relation is in a colocation group with a partitioned table or partition.
*/
if (colocatedTableId != relationId &&
PartitionedTable(colocatedTableId))
{
char *sourceRelationName = get_rel_name(relationId);
char *colocatedRelationName = get_rel_name(colocatedTableId);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot isolate shard placement of '%s', because it "
"is a partitioned table", colocatedRelationName),
errdetail("In colocation group of '%s', a partitioned "
"relation exists: '%s'. Citus does not support "
"isolating placements of partitioned tables.",
sourceRelationName, colocatedRelationName)));
}
}
Oid inputDataType = get_fn_expr_argtype(fcinfo->flinfo, 1);
char *tenantIdString = DatumToString(inputDatum, inputDataType);
char *cascadeOptionString = text_to_cstring(cascadeOptionText);
if (pg_strncasecmp(cascadeOptionString, "CASCADE", NAMEDATALEN) != 0 &&
colocatedTableCount > 1)
{
char *relationName = get_rel_name(relationId);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot isolate tenant because \"%s\" has colocated "
"tables", relationName),
errhint("Use CASCADE option to isolate tenants for the "
"colocated tables too. Example usage: "
"isolate_tenant_to_new_shard('%s', '%s', 'CASCADE')",
relationName, tenantIdString)));
}
EnsureReferenceTablesExistOnAllNodes();
Var *distributionColumn = DistPartitionKey(relationId);
/* earlier we checked that the table was hash partitioned, so there should be a distribution column */
Assert(distributionColumn != NULL);
Oid distributionColumnType = distributionColumn->vartype;
Datum tenantIdDatum = StringToDatum(tenantIdString, distributionColumnType);
ShardInterval *sourceShard = FindShardInterval(tenantIdDatum, cacheEntry);
if (sourceShard == NULL)
{
ereport(ERROR, (errmsg("tenant does not have a shard")));
}
uint64 isolatedShardId = SplitShardByValue(sourceShard, tenantIdDatum);
PG_RETURN_INT64(isolatedShardId);
}
@ -86,3 +200,623 @@ worker_hash(PG_FUNCTION_ARGS)
PG_RETURN_INT32(hashedValueDatum);
}
/*
* SplitShardByValue gets a shard and a value which is in the range of
* distribution column of this shard. Then, it splits this shard and all its
* colocated shards into three; the lower range, the given value itself, and
* the upper range. Finally, it returns the id of the shard which is created
* for the given value.
*/
static uint64
SplitShardByValue(ShardInterval *sourceShard, Datum distributionValueDatum)
{
Oid relationId = sourceShard->relationId;
int isolatedShardId = 0;
List *splitOffShardList = NIL;
if (XactModificationLevel > XACT_MODIFICATION_NONE)
{
ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("cannot isolate a tenant after other modifications "
"in the same transaction")));
}
/* sort the tables to avoid deadlocks */
List *colocatedTableList = ColocatedTableList(relationId);
colocatedTableList = SortList(colocatedTableList, CompareOids);
Oid colocatedTableId = InvalidOid;
foreach_oid(colocatedTableId, colocatedTableList)
{
/*
* Block concurrent DDL / TRUNCATE commands on the relation. Similarly,
* block concurrent citus_move_shard_placement()/isolate_tenant_to_new_shard()
* on any shard of the same relation. This is OK for now since
* we're executing shard moves/splits sequentially anyway.
*/
LockRelationOid(colocatedTableId, ShareUpdateExclusiveLock);
}
/* get colocated shard list */
List *colocatedShardList = ColocatedShardIntervalList(sourceShard);
/* get locks */
BlockWritesToShardList(colocatedShardList);
ErrorIfCannotSplitShard(sourceShard);
/* get hash function name */
CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
FmgrInfo *hashFunction = cacheEntry->hashFunction;
/* get hashed value of the distribution value */
Datum hashedValueDatum = FunctionCall1(hashFunction, distributionValueDatum);
int hashedValue = DatumGetInt32(hashedValueDatum);
/* create a list of nodes with source shard placements */
List *sourcePlacementList = ActiveShardPlacementList(sourceShard->shardId);
/* create new shards in a separate transaction and commit them */
CreateSplitOffShards(sourceShard, hashedValue, &splitOffShardList, &isolatedShardId);
/*
* Drop old shards and delete related metadata. Have to do that before
* creating the new shard metadata, because there's cross-checks
* preventing inconsistent metadata (like overlapping shards).
*/
DropShardList(colocatedShardList);
/* insert new metadata */
InsertSplitOffShardMetadata(splitOffShardList, sourcePlacementList);
/*
* Create foreign keys if exists after the metadata changes happening in
* DropShardList() and InsertSplitOffShardMetadata() because the foreign
* key creation depends on the new metadata.
*/
CreateForeignConstraints(splitOffShardList, sourcePlacementList);
CitusInvalidateRelcacheByRelid(DistShardRelationId());
return isolatedShardId;
}
/*
* CreateForeignConstraints creates the foreign constraints on the newly
* created shards via the tenant isolation.
*
* The function treats foreign keys to reference tables and foreign keys to
* co-located distributed tables differently. The former one needs to be
* executed over a single connection to prevent self-deadlocks. The latter
* one can be executed in parallel if there are multiple replicas.
*/
static void
CreateForeignConstraints(List *splitOffShardList, List *sourcePlacementList)
{
ListCell *splitOffShardCell = NULL;
List *colocatedShardForeignConstraintCommandList = NIL;
List *referenceTableForeignConstraintList = NIL;
foreach(splitOffShardCell, splitOffShardList)
{
ShardInterval *splitOffShard = (ShardInterval *) lfirst(splitOffShardCell);
List *currentColocatedForeignKeyList = NIL;
List *currentReferenceForeignKeyList = NIL;
CopyShardForeignConstraintCommandListGrouped(splitOffShard,
&currentColocatedForeignKeyList,
&currentReferenceForeignKeyList);
colocatedShardForeignConstraintCommandList =
list_concat(colocatedShardForeignConstraintCommandList,
currentColocatedForeignKeyList);
referenceTableForeignConstraintList =
list_concat(referenceTableForeignConstraintList,
currentReferenceForeignKeyList);
}
/*
* We can use parallel connections to while creating co-located foreign keys
* if the source placement .
* However, foreign keys to reference tables need to be created using a single
* connection per worker to prevent self-deadlocks.
*/
if (colocatedShardForeignConstraintCommandList != NIL)
{
ExecuteCommandListOnPlacements(colocatedShardForeignConstraintCommandList,
sourcePlacementList);
}
if (referenceTableForeignConstraintList != NIL)
{
ListCell *shardPlacementCell = NULL;
foreach(shardPlacementCell, sourcePlacementList)
{
ShardPlacement *shardPlacement =
(ShardPlacement *) lfirst(shardPlacementCell);
char *nodeName = shardPlacement->nodeName;
int32 nodePort = shardPlacement->nodePort;
/*
* We're using the connections that we've used for dropping the
* source placements within the same coordinated transaction.
*/
ExecuteCommandListOnWorker(nodeName, nodePort,
referenceTableForeignConstraintList);
}
}
}
/*
* ExecuteCommandListOnWorker executes the command on the given node within
* the coordinated 2PC.
*/
static void
ExecuteCommandListOnWorker(char *nodeName, int nodePort, List *commandList)
{
ListCell *commandCell = NULL;
foreach(commandCell, commandList)
{
char *command = (char *) lfirst(commandCell);
SendCommandToWorker(nodeName, nodePort, command);
}
}
/*
* ErrorIfCannotSplitShard checks relation kind and invalid shards. It errors
* out if we are not able to split the given shard.
*/
static void
ErrorIfCannotSplitShard(ShardInterval *sourceShard)
{
Oid relationId = sourceShard->relationId;
ListCell *colocatedTableCell = NULL;
ListCell *colocatedShardCell = NULL;
/* checks for table ownership and foreign tables */
List *colocatedTableList = ColocatedTableList(relationId);
foreach(colocatedTableCell, colocatedTableList)
{
Oid colocatedTableId = lfirst_oid(colocatedTableCell);
/* check that user has owner rights in all co-located tables */
EnsureTableOwner(colocatedTableId);
char relationKind = get_rel_relkind(colocatedTableId);
if (relationKind == RELKIND_FOREIGN_TABLE)
{
char *relationName = get_rel_name(colocatedTableId);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot isolate tenant because \"%s\" is a "
"foreign table", relationName),
errdetail("Isolating shards backed by foreign tables "
"is not supported.")));
}
}
/* check shards with inactive placements */
List *colocatedShardList = ColocatedShardIntervalList(sourceShard);
foreach(colocatedShardCell, colocatedShardList)
{
ShardInterval *shardInterval = (ShardInterval *) lfirst(colocatedShardCell);
uint64 shardId = shardInterval->shardId;
ListCell *shardPlacementCell = NULL;
List *shardPlacementList = ShardPlacementListWithoutOrphanedPlacements(shardId);
foreach(shardPlacementCell, shardPlacementList)
{
ShardPlacement *placement = (ShardPlacement *) lfirst(shardPlacementCell);
if (placement->shardState != SHARD_STATE_ACTIVE)
{
char *relationName = get_rel_name(shardInterval->relationId);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot isolate tenant because relation "
"\"%s\" has an inactive shard placement "
"for the shard %lu", relationName, shardId),
errhint("Use master_copy_shard_placement UDF to "
"repair the inactive shard placement.")));
}
}
}
}
/*
* CreateSplitOffShards gets a shard and a hashed value to pick the split point.
* First, it creates templates to create new shards. Then, for every colocated
* shard, it creates new split shards data and physically creates them on the
* worker nodes. This function returns newly created split off shards and the
* matching shard id for the source shard and hashed value via passed parameters.
*/
static void
CreateSplitOffShards(ShardInterval *sourceShard, int hashedValue,
List **splitOffShardList, int *isolatedShardId)
{
List *nodeCommandList = NIL;
ListCell *sourceColocatedShardCell = NULL;
int isolatedShardIndex = 0;
List *sourceColocatedShardList = ColocatedShardIntervalList(sourceShard);
List *shardTemplateList = ShardTemplateList(sourceShard, hashedValue,
&isolatedShardIndex);
foreach(sourceColocatedShardCell, sourceColocatedShardList)
{
ShardInterval *sourceColocatedShard =
(ShardInterval *) lfirst(sourceColocatedShardCell);
Oid relationId = sourceColocatedShard->relationId;
ListCell *templateShardCell = NULL;
int currentShardIndex = 0;
foreach(templateShardCell, shardTemplateList)
{
ShardInterval *templateShard = (ShardInterval *) lfirst(templateShardCell);
ShardInterval *splitOffShard = CreateSplitOffShardFromTemplate(templateShard,
relationId);
List *splitOffCommandList = SplitOffCommandList(sourceColocatedShard,
splitOffShard);
nodeCommandList = list_concat(nodeCommandList, splitOffCommandList);
/* check if this is the isolated shard for the given table */
if (splitOffShard->relationId == sourceShard->relationId &&
currentShardIndex == isolatedShardIndex)
{
(*isolatedShardId) = splitOffShard->shardId;
}
/* add newly created split off shards to list */
(*splitOffShardList) = lappend(*splitOffShardList, splitOffShard);
currentShardIndex++;
}
}
List *sourcePlacementList = ActiveShardPlacementList(sourceShard->shardId);
ExecuteCommandListOnPlacements(nodeCommandList, sourcePlacementList);
}
/*
* ShardTemplateList creates shard templates with new min and max values from
* the given shard and the split point which is the given hashed value.
* It returns the list of shard templates, and passes the isolated shard index
* via isolatedShardIndex parameter.
*/
static List *
ShardTemplateList(ShardInterval *sourceShard, int hashedValue, int *isolatedShardIndex)
{
List *shardTemplateList = NIL;
/* get min and max values of the source shard */
int32 shardMinValue = DatumGetInt32(sourceShard->minValue);
int32 shardMaxValue = DatumGetInt32(sourceShard->maxValue);
(*isolatedShardIndex) = 0;
/* add a shard template for lower range if exists */
if (shardMinValue < hashedValue)
{
ShardInterval *lowerRangeShard = CopyShardInterval(sourceShard);
lowerRangeShard->minValue = Int32GetDatum(shardMinValue);
lowerRangeShard->maxValue = Int32GetDatum(hashedValue - 1);
shardTemplateList = lappend(shardTemplateList, lowerRangeShard);
(*isolatedShardIndex) = 1;
}
/* add shard template for the isolated value */
ShardInterval *isolatedShard = CopyShardInterval(sourceShard);
isolatedShard->minValue = Int32GetDatum(hashedValue);
isolatedShard->maxValue = Int32GetDatum(hashedValue);
shardTemplateList = lappend(shardTemplateList, isolatedShard);
/* add a shard template for upper range if exists */
if (shardMaxValue > hashedValue)
{
ShardInterval *upperRangeShard = CopyShardInterval(sourceShard);
upperRangeShard->minValue = Int32GetDatum(hashedValue + 1);
upperRangeShard->maxValue = Int32GetDatum(shardMaxValue);
shardTemplateList = lappend(shardTemplateList, upperRangeShard);
}
if (list_length(shardTemplateList) == 1)
{
char *tableName = get_rel_name(sourceShard->relationId);
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("table \"%s\" has already been isolated for the "
"given value", tableName)));
}
return shardTemplateList;
}
/*
* CreateSplitOffShardFromTemplate creates a new split off shard from the given
* shard template by creating a new shard id and setting the relation id.
*/
static ShardInterval *
CreateSplitOffShardFromTemplate(ShardInterval *shardTemplate, Oid relationId)
{
ShardInterval *splitOffShard = CopyShardInterval(shardTemplate);
/* set new shard id and the relation id */
splitOffShard->shardId = GetNextShardId();
splitOffShard->relationId = relationId;
return splitOffShard;
}
/*
* SplitOffCommandList creates a command list to run on worker nodes to create
* new split off shard from the source shard.
*/
static List *
SplitOffCommandList(ShardInterval *sourceShard, ShardInterval *splitOffShard)
{
List *splitOffCommandList = NIL;
bool includeSequenceDefaults = false;
Oid relationId = sourceShard->relationId;
Var *partitionKey = DistPartitionKey(relationId);
Assert(partitionKey != NULL);
const char *partitionColumnName = get_attname(relationId,
partitionKey->varattno, false);
const char *quotedPartitionColumnName = quote_identifier(partitionColumnName);
char *splitOffShardName = ConstructQualifiedShardName(splitOffShard);
char *sourceShardName = ConstructQualifiedShardName(sourceShard);
int32 shardMinValue = DatumGetInt32(splitOffShard->minValue);
int32 shardMaxValue = DatumGetInt32(splitOffShard->maxValue);
List *tableCreationCommandList =
GetPreLoadTableCreationCommands(relationId, includeSequenceDefaults, NULL);
tableCreationCommandList = WorkerApplyShardDDLCommandList(tableCreationCommandList,
splitOffShard->shardId);
splitOffCommandList = list_concat(splitOffCommandList, tableCreationCommandList);
StringInfo splitOffShardCommand = makeStringInfo();
appendStringInfo(splitOffShardCommand,
"INSERT INTO %s SELECT * FROM %s WHERE "
"worker_hash(%s) >= %d AND worker_hash(%s) <= %d",
splitOffShardName, sourceShardName, quotedPartitionColumnName,
shardMinValue, quotedPartitionColumnName, shardMaxValue);
splitOffCommandList = lappend(splitOffCommandList, splitOffShardCommand->data);
List *indexCommandList = GetPostLoadTableCreationCommands(relationId, true, true);
indexCommandList = WorkerApplyShardDDLCommandList(indexCommandList,
splitOffShard->shardId);
splitOffCommandList = list_concat(splitOffCommandList, indexCommandList);
return splitOffCommandList;
}
/*
* ExecuteCommandListOnPlacements runs the given command list on the nodes of
* the given shard placement list. First, it creates connections. Then it sends
* commands one by one. For every command, first it send the command to all
* connections and then checks the results. This helps to run long running
* commands in parallel. Finally, it sends commit messages to all connections
* and close them.
*/
static void
ExecuteCommandListOnPlacements(List *commandList, List *placementList)
{
List *workerConnectionList = NIL;
ListCell *workerConnectionCell = NULL;
ListCell *shardPlacementCell = NULL;
ListCell *commandCell = NULL;
/* create connections and start transactions */
foreach(shardPlacementCell, placementList)
{
ShardPlacement *shardPlacement = (ShardPlacement *) lfirst(shardPlacementCell);
char *nodeName = shardPlacement->nodeName;
int32 nodePort = shardPlacement->nodePort;
int connectionFlags = FORCE_NEW_CONNECTION;
char *currentUser = CurrentUserName();
/* create a new connection */
MultiConnection *workerConnection = GetNodeUserDatabaseConnection(connectionFlags,
nodeName,
nodePort,
currentUser,
NULL);
/* mark connection as critical ans start transaction */
MarkRemoteTransactionCritical(workerConnection);
RemoteTransactionBegin(workerConnection);
/* add connection to the list */
workerConnectionList = lappend(workerConnectionList, workerConnection);
}
/* send and check results for every command one by one */
foreach(commandCell, commandList)
{
char *command = lfirst(commandCell);
/* first only send the command */
foreach(workerConnectionCell, workerConnectionList)
{
MultiConnection *workerConnection =
(MultiConnection *) lfirst(workerConnectionCell);
int querySent = SendRemoteCommand(workerConnection, command);
if (querySent == 0)
{
ReportConnectionError(workerConnection, ERROR);
}
}
/* then check the result separately to run long running commands in parallel */
foreach(workerConnectionCell, workerConnectionList)
{
MultiConnection *workerConnection =
(MultiConnection *) lfirst(workerConnectionCell);
bool raiseInterrupts = true;
PGresult *result = GetRemoteCommandResult(workerConnection, raiseInterrupts);
if (!IsResponseOK(result))
{
ReportResultError(workerConnection, result, ERROR);
}
PQclear(result);
ForgetResults(workerConnection);
}
}
/* finally commit each transaction and close connections */
foreach(workerConnectionCell, workerConnectionList)
{
MultiConnection *workerConnection =
(MultiConnection *) lfirst(workerConnectionCell);
RemoteTransactionCommit(workerConnection);
CloseConnection(workerConnection);
}
}
/*
* InsertSplitOffShardMetadata inserts new shard and shard placement data into
* catolog tables both the coordinator and mx nodes.
*/
static void
InsertSplitOffShardMetadata(List *splitOffShardList, List *sourcePlacementList)
{
List *syncedShardList = NIL;
ListCell *shardCell = NULL;
ListCell *commandCell = NULL;
/* add new metadata */
foreach(shardCell, splitOffShardList)
{
ShardInterval *splitOffShard = (ShardInterval *) lfirst(shardCell);
Oid relationId = splitOffShard->relationId;
uint64 shardId = splitOffShard->shardId;
char storageType = splitOffShard->storageType;
ListCell *shardPlacementCell = NULL;
int32 shardMinValue = DatumGetInt32(splitOffShard->minValue);
int32 shardMaxValue = DatumGetInt32(splitOffShard->maxValue);
text *shardMinValueText = IntegerToText(shardMinValue);
text *shardMaxValueText = IntegerToText(shardMaxValue);
InsertShardRow(relationId, shardId, storageType, shardMinValueText,
shardMaxValueText);
/* split off shard placement metadata */
foreach(shardPlacementCell, sourcePlacementList)
{
ShardPlacement *placement = (ShardPlacement *) lfirst(shardPlacementCell);
uint64 shardSize = 0;
InsertShardPlacementRow(shardId, INVALID_PLACEMENT_ID, SHARD_STATE_ACTIVE,
shardSize, placement->groupId);
}
if (ShouldSyncTableMetadata(relationId))
{
syncedShardList = lappend(syncedShardList, splitOffShard);
}
}
/* send commands to synced nodes one by one */
List *splitOffShardMetadataCommandList = ShardListInsertCommand(syncedShardList);
foreach(commandCell, splitOffShardMetadataCommandList)
{
char *command = (char *) lfirst(commandCell);
SendCommandToWorkersWithMetadata(command);
}
}
/*
* DropShardList drops shards and their metadata from both the coordinator and
* mx nodes.
*/
static void
DropShardList(List *shardIntervalList)
{
ListCell *shardIntervalCell = NULL;
foreach(shardIntervalCell, shardIntervalList)
{
ShardInterval *shardInterval = (ShardInterval *) lfirst(shardIntervalCell);
ListCell *shardPlacementCell = NULL;
Oid relationId = shardInterval->relationId;
uint64 oldShardId = shardInterval->shardId;
/* delete metadata from synced nodes */
if (ShouldSyncTableMetadata(relationId))
{
ListCell *commandCell = NULL;
/* send the commands one by one */
List *shardMetadataDeleteCommandList = ShardDeleteCommandList(shardInterval);
foreach(commandCell, shardMetadataDeleteCommandList)
{
char *command = (char *) lfirst(commandCell);
SendCommandToWorkersWithMetadata(command);
}
}
/* delete shard placements and drop shards */
List *shardPlacementList = ActiveShardPlacementList(oldShardId);
foreach(shardPlacementCell, shardPlacementList)
{
ShardPlacement *placement = (ShardPlacement *) lfirst(shardPlacementCell);
char *workerName = placement->nodeName;
uint32 workerPort = placement->nodePort;
StringInfo dropQuery = makeStringInfo();
DeleteShardPlacementRow(placement->placementId);
/* get shard name */
char *qualifiedShardName = ConstructQualifiedShardName(shardInterval);
char storageType = shardInterval->storageType;
if (storageType == SHARD_STORAGE_TABLE)
{
appendStringInfo(dropQuery, DROP_REGULAR_TABLE_COMMAND,
qualifiedShardName);
}
else if (storageType == SHARD_STORAGE_FOREIGN)
{
appendStringInfo(dropQuery, DROP_FOREIGN_TABLE_COMMAND,
qualifiedShardName);
}
/* drop old shard */
SendCommandToWorker(workerName, workerPort, dropQuery->data);
}
/* delete shard row */
DeleteShardRow(oldShardId);
}
}

View File

@ -94,12 +94,12 @@ ActivePrimaryNonCoordinatorNodeCount(void)
/*
* ActivePrimaryNodeCount returns the number of groups with a primary in the cluster.
* ActiveReadableNodeCount returns the number of nodes in the cluster.
*/
uint32
ActivePrimaryNodeCount(void)
ActiveReadableNodeCount(void)
{
List *nodeList = ActivePrimaryNodeList(NoLock);
List *nodeList = ActiveReadableNodeList();
return list_length(nodeList);
}
@ -393,7 +393,7 @@ NodeNamePortCompare(const char *workerLhsName, const char *workerRhsName,
WorkerNode *
GetFirstPrimaryWorkerNode(void)
{
List *workerNodeList = ActivePrimaryNonCoordinatorNodeList(NoLock);
List *workerNodeList = ActivePrimaryNonCoordinatorNodeList(RowShareLock);
WorkerNode *firstWorkerNode = NULL;
WorkerNode *workerNode = NULL;
foreach_ptr(workerNode, workerNodeList)

View File

@ -1065,7 +1065,7 @@ CreateDistributedPlan(uint64 planId, Query *originalQuery, Query *query, ParamLi
/*
* EnsurePartitionTableNotReplicated errors out if the infput relation is
* EnsurePartitionTableNotReplicated errors out if the input relation is
* a partition table and the table has a replication factor greater than
* one.
*

View File

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

View File

@ -57,7 +57,9 @@
#include "nodes/print.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "portability/instr_time.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/dest.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
@ -152,7 +154,11 @@ static void ExplainAnalyzeDestPutTuple(TupleDestination *self, Task *task,
HeapTuple heapTuple, uint64 tupleLibpqSize);
static TupleDesc ExplainAnalyzeDestTupleDescForQuery(TupleDestination *self, int
queryNumber);
static char * WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc);
static char * WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc,
ParamListInfo params);
static char * FetchPlanQueryForExplainAnalyze(const char *queryString,
ParamListInfo params);
static char * ParameterResolutionSubquery(ParamListInfo params);
static List * SplitString(const char *str, char delimiter, int maxLength);
/* Static Explain functions copied from explain.c */
@ -310,6 +316,8 @@ ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es)
es->indent += 3;
}
ExplainOpenGroup("Subplan", NULL, true, es);
if (es->analyze)
{
if (es->timing)
@ -352,9 +360,14 @@ ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es)
}
#endif
ExplainOpenGroup("PlannedStmt", "PlannedStmt", false, es);
ExplainOnePlanCompat(plan, into, es, queryString, params, NULL, &planduration,
(es->buffers ? &bufusage : NULL));
ExplainCloseGroup("PlannedStmt", "PlannedStmt", false, es);
ExplainCloseGroup("Subplan", NULL, true, es);
if (es->format == EXPLAIN_FORMAT_TEXT)
{
es->indent -= 3;
@ -373,7 +386,7 @@ static void
ExplainPropertyBytes(const char *qlabel, int64 bytes, ExplainState *es)
{
Datum textDatum = DirectFunctionCall1(pg_size_pretty, Int64GetDatum(bytes));
ExplainPropertyText(qlabel, text_to_cstring(DatumGetTextP(textDatum)), es);
ExplainPropertyText(qlabel, TextDatumGetCString(textDatum), es);
}
@ -1042,14 +1055,25 @@ worker_save_query_explain_analyze(PG_FUNCTION_ARGS)
int numParams = boundParams ? boundParams->numParams : 0;
Oid *paramTypes = NULL;
const char **paramValues = NULL;
if (boundParams != NULL)
{
ExtractParametersFromParamList(boundParams, &paramTypes, &paramValues, false);
}
List *queryList = pg_analyze_and_rewrite(parseTree, queryString, paramTypes,
numParams, NULL);
/* resolve OIDs of unknown (user-defined) types */
Query *analyzedQuery = parse_analyze_varparams(parseTree, queryString,
&paramTypes, &numParams);
#if PG_VERSION_NUM >= PG_VERSION_14
/* pg_rewrite_query is a wrapper around QueryRewrite with some debugging logic */
List *queryList = pg_rewrite_query(analyzedQuery);
#else
/* pg_rewrite_query is not yet public in PostgreSQL 13 */
List *queryList = QueryRewrite(analyzedQuery);
#endif
if (list_length(queryList) != 1)
{
ereport(ERROR, (errmsg("cannot EXPLAIN ANALYZE a query rewritten "
@ -1377,9 +1401,21 @@ ExplainAnalyzeTaskList(List *originalTaskList,
Task *explainAnalyzeTask = copyObject(originalTask);
const char *queryString = TaskQueryString(explainAnalyzeTask);
char *wrappedQuery = WrapQueryForExplainAnalyze(queryString, tupleDesc);
char *fetchQuery =
"SELECT explain_analyze_output, execution_duration FROM worker_last_saved_explain_analyze()";
ParamListInfo taskParams = params;
/*
* We will not send parameters if they have already been resolved in the query
* string.
*/
if (explainAnalyzeTask->parametersInQueryStringResolved)
{
taskParams = NULL;
}
char *wrappedQuery = WrapQueryForExplainAnalyze(queryString, tupleDesc,
taskParams);
char *fetchQuery = FetchPlanQueryForExplainAnalyze(queryString, taskParams);
SetTaskQueryStringList(explainAnalyzeTask, list_make2(wrappedQuery, fetchQuery));
TupleDestination *originalTaskDest = originalTask->tupleDest ?
@ -1401,7 +1437,8 @@ ExplainAnalyzeTaskList(List *originalTaskList,
* call so we can fetch its explain analyze after its execution.
*/
static char *
WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc)
WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc,
ParamListInfo params)
{
StringInfo columnDef = makeStringInfo();
for (int columnIndex = 0; columnIndex < tupleDesc->natts; columnIndex++)
@ -1453,6 +1490,17 @@ WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc)
* number of columns returned by worker_save_query_explain_analyze.
*/
char *workerSaveQueryFetchCols = (tupleDesc->natts == 0) ? "" : "*";
if (params != NULL)
{
/*
* Add a dummy CTE to ensure all parameters are referenced, such that their
* types can be resolved.
*/
appendStringInfo(wrappedQuery, "WITH unused AS (%s) ",
ParameterResolutionSubquery(params));
}
appendStringInfo(wrappedQuery,
"SELECT %s FROM worker_save_query_explain_analyze(%s, %s) AS (%s)",
workerSaveQueryFetchCols,
@ -1464,6 +1512,60 @@ WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc)
}
/*
* FetchPlanQueryForExplainAnalyze generates a query to fetch the plan saved
* by worker_save_query_explain_analyze from the worker.
*/
static char *
FetchPlanQueryForExplainAnalyze(const char *queryString, ParamListInfo params)
{
StringInfo fetchQuery = makeStringInfo();
if (params != NULL)
{
/*
* Add a dummy CTE to ensure all parameters are referenced, such that their
* types can be resolved.
*/
appendStringInfo(fetchQuery, "WITH unused AS (%s) ",
ParameterResolutionSubquery(params));
}
appendStringInfoString(fetchQuery,
"SELECT explain_analyze_output, execution_duration "
"FROM worker_last_saved_explain_analyze()");
return fetchQuery->data;
}
/*
* ParameterResolutionSubquery generates a subquery that returns all parameters
* in params with explicit casts to their type names. This can be used in cases
* where we use custom type parameters that are not directly referenced.
*/
static char *
ParameterResolutionSubquery(ParamListInfo params)
{
StringInfo paramsQuery = makeStringInfo();
appendStringInfo(paramsQuery, "SELECT");
for (int paramIndex = 0; paramIndex < params->numParams; paramIndex++)
{
ParamExternData *param = &params->params[paramIndex];
char *typeName = format_type_extended(param->ptype, -1,
FORMAT_TYPE_FORCE_QUALIFY);
appendStringInfo(paramsQuery, "%s $%d::%s",
paramIndex > 0 ? "," : "",
paramIndex + 1, typeName);
}
return paramsQuery->data;
}
/*
* SplitString splits the given string by the given delimiter.
*

View File

@ -40,6 +40,7 @@
#include "distributed/multi_physical_planner.h"
#include "distributed/pg_dist_partition.h"
#include "distributed/query_pushdown_planning.h"
#include "distributed/string_utils.h"
#include "distributed/tdigest_extension.h"
#include "distributed/worker_protocol.h"
#include "distributed/version_compat.h"
@ -58,9 +59,6 @@
#include "utils/rel.h"
#include "utils/syscache.h"
#define StartsWith(msg, prefix) \
(strncmp(msg, prefix, strlen(prefix)) == 0)
/* Config variable managed via guc.c */
int LimitClauseRowFetchCount = -1; /* number of rows to fetch from each task */
double CountDistinctErrorRate = 0.0; /* precision of count(distinct) approximate */
@ -3418,7 +3416,7 @@ GetAggregateType(Aggref *aggregateExpression)
* perform these checks if there is some chance it will actually result in a positive
* hit.
*/
if (StartsWith(aggregateProcName, "tdigest"))
if (StringStartsWith(aggregateProcName, "tdigest"))
{
if (aggFunctionId == TDigestExtensionAggTDigest1())
{

View File

@ -75,7 +75,6 @@ static Oid NodeTryGetRteRelid(Node *node);
static bool FullCompositeFieldList(List *compositeFieldList);
static bool HasUnsupportedJoinWalker(Node *node, void *context);
static bool ErrorHintRequired(const char *errorHint, Query *queryTree);
static bool HasTablesample(Query *queryTree);
static bool HasComplexRangeTableType(Query *queryTree);
static bool IsReadIntermediateResultFunction(Node *node);
static bool IsReadIntermediateResultArrayFunction(Node *node);
@ -899,14 +898,6 @@ DeferErrorIfQueryNotSupported(Query *queryTree)
errorHint = filterHint;
}
bool hasTablesample = HasTablesample(queryTree);
if (hasTablesample)
{
preconditionsSatisfied = false;
errorMessage = "could not run distributed query which use TABLESAMPLE";
errorHint = filterHint;
}
bool hasUnsupportedJoin = HasUnsupportedJoinWalker((Node *) queryTree->jointree,
NULL);
if (hasUnsupportedJoin)
@ -960,28 +951,6 @@ DeferErrorIfQueryNotSupported(Query *queryTree)
}
/* HasTablesample returns tree if the query contains tablesample */
static bool
HasTablesample(Query *queryTree)
{
List *rangeTableList = queryTree->rtable;
ListCell *rangeTableEntryCell = NULL;
bool hasTablesample = false;
foreach(rangeTableEntryCell, rangeTableList)
{
RangeTblEntry *rangeTableEntry = lfirst(rangeTableEntryCell);
if (rangeTableEntry->tablesample)
{
hasTablesample = true;
break;
}
}
return hasTablesample;
}
/*
* HasUnsupportedJoinWalker returns tree if the query contains an unsupported
* join type. We currently support inner, left, right, full and anti joins.
@ -1541,6 +1510,7 @@ MultiTableNodeList(List *tableEntryList, List *rangeTableList)
tableNode->alias = rangeTableEntry->alias;
tableNode->referenceNames = rangeTableEntry->eref;
tableNode->includePartitions = GetOriginalInh(rangeTableEntry);
tableNode->tablesample = rangeTableEntry->tablesample;
tableNodeList = lappend(tableNodeList, tableNode);
}

View File

@ -735,6 +735,7 @@ BaseRangeTableList(MultiNode *multiNode)
rangeTableEntry->alias = multiTable->alias;
rangeTableEntry->relid = multiTable->relationId;
rangeTableEntry->inh = multiTable->includePartitions;
rangeTableEntry->tablesample = multiTable->tablesample;
SetRangeTblExtraData(rangeTableEntry, CITUS_RTE_RELATION, NULL, NULL,
list_make1_int(multiTable->rangeTableId),

View File

@ -158,7 +158,7 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId)
AccessShareLock,
missingOk);
if (constraint->contype == CONSTR_PRIMARY && constraint->indexname)
if (constraint->indexname)
{
char **indexName = &(constraint->indexname);
AppendShardIdToName(indexName, shardId);

File diff suppressed because it is too large Load Diff

View File

@ -37,6 +37,7 @@
#include "distributed/connection_management.h"
#include "distributed/cte_inline.h"
#include "distributed/distributed_deadlock_detection.h"
#include "distributed/errormessage.h"
#include "distributed/insert_select_executor.h"
#include "distributed/intermediate_result_pruning.h"
#include "distributed/local_multi_copy.h"
@ -44,6 +45,7 @@
#include "distributed/local_distributed_join_planner.h"
#include "distributed/locally_reserved_shared_connections.h"
#include "distributed/maintenanced.h"
#include "distributed/shard_cleaner.h"
#include "distributed/metadata_utility.h"
#include "distributed/coordinator_protocol.h"
#include "distributed/metadata_cache.h"
@ -52,6 +54,7 @@
#include "distributed/multi_executor.h"
#include "distributed/multi_explain.h"
#include "distributed/multi_join_order.h"
#include "distributed/multi_logical_replication.h"
#include "distributed/multi_logical_optimizer.h"
#include "distributed/distributed_planner.h"
#include "distributed/combine_query_planner.h"
@ -59,6 +62,7 @@
#include "distributed/multi_server_executor.h"
#include "distributed/pg_dist_partition.h"
#include "distributed/placement_connection.h"
#include "distributed/query_stats.h"
#include "distributed/recursive_planning.h"
#include "distributed/reference_table_utils.h"
#include "distributed/relation_access_tracking.h"
@ -74,7 +78,7 @@
#include "distributed/shared_library_init.h"
#include "distributed/statistics_collection.h"
#include "distributed/subplan_execution.h"
#include "distributed/resource_lock.h"
#include "distributed/transaction_management.h"
#include "distributed/transaction_recovery.h"
#include "distributed/worker_log_messages.h"
@ -127,9 +131,9 @@ static bool ErrorIfNotASuitableDeadlockFactor(double *newval, void **extra,
static bool WarnIfDeprecatedExecutorUsed(int *newval, void **extra, GucSource source);
static bool WarnIfReplicationModelIsSet(int *newval, void **extra, GucSource source);
static bool NoticeIfSubqueryPushdownEnabled(bool *newval, void **extra, GucSource source);
static bool HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra,
GucSource source);
static void HideShardsFromAppNamePrefixesAssignHook(const char *newval, void *extra);
static bool ShowShardsForAppNamePrefixesCheckHook(char **newval, void **extra,
GucSource source);
static void ShowShardsForAppNamePrefixesAssignHook(const char *newval, void *extra);
static void ApplicationNameAssignHook(const char *newval, void *extra);
static bool NodeConninfoGucCheckHook(char **newval, void **extra, GucSource source);
static void NodeConninfoGucAssignHook(const char *newval, void *extra);
@ -152,6 +156,12 @@ static const struct config_enum_entry propagate_set_commands_options[] = {
};
static const struct config_enum_entry stat_statements_track_options[] = {
{ "none", STAT_STATEMENTS_TRACK_NONE, false },
{ "all", STAT_STATEMENTS_TRACK_ALL, false },
{ NULL, 0, false }
};
static const struct config_enum_entry task_assignment_policy_options[] = {
{ "greedy", TASK_ASSIGNMENT_GREEDY, false },
{ "first-replica", TASK_ASSIGNMENT_FIRST_REPLICA, false },
@ -601,6 +611,43 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.allow_nested_distributed_execution",
gettext_noop("Enables distributed execution within a task "
"of another distributed execution."),
gettext_noop("Nested distributed execution can happen when Citus "
"pushes down a call to a user-defined function within "
"a distributed query, and the function contains another "
"distributed query. In this scenario, Citus makes no "
"guarantess with regards to correctness and it is therefore "
"disallowed by default. This setting can be used to allow "
"nested distributed execution."),
&AllowNestedDistributedExecution,
false,
PGC_USERSET,
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.allow_unsafe_locks_from_workers",
gettext_noop("Enables acquiring a distributed lock from a worker "
"when the coordinator is not in the metadata"),
gettext_noop("Set to false by default. If set to true, enables "
"acquiring a distributed lock from a worker "
"when the coordinator is not in the metadata. "
"This type of lock is unsafe because the worker will not be "
"able to lock the coordinator; the coordinator will be able to "
"intialize distributed operations on the resources locked "
"by the worker. This can lead to concurrent operations from the "
"coordinator and distributed deadlocks since the coordinator "
"and the workers would not acquire locks across the same nodes "
"in the same order."),
&EnableAcquiringUnsafeLockFromWorkers,
false,
PGC_USERSET,
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.binary_worker_copy_format",
gettext_noop("Use the binary worker copy format."),
@ -765,7 +812,7 @@ RegisterCitusConfigVariables(void)
"workers"),
NULL,
&EnableAlterDatabaseOwner,
false,
true,
PGC_USERSET,
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
@ -818,6 +865,17 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.enable_create_role_propagation",
gettext_noop("Enables propagating CREATE ROLE "
"and DROP ROLE statements to workers"),
NULL,
&EnableCreateRolePropagation,
true,
PGC_USERSET,
GUC_STANDARD,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.enable_create_type_propagation",
gettext_noop("Enables propagating of CREATE TYPE statements to workers"),
@ -1121,24 +1179,6 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomStringVariable(
"citus.hide_shards_from_app_name_prefixes",
gettext_noop("If application_name starts with one of these values, hide shards"),
gettext_noop("Citus places distributed tables and shards in the same schema. "
"That can cause confusion when inspecting the list of tables on "
"a node with shards. This GUC can be used to hide the shards from "
"pg_class for certain applications based on the application_name "
"of the connection. The default is *, which hides shards from all "
"applications. This behaviour can be overridden using the "
"citus.override_table_visibility setting"),
&HideShardsFromAppNamePrefixes,
"*",
PGC_USERSET,
GUC_STANDARD,
HideShardsFromAppNamePrefixesCheckHook,
HideShardsFromAppNamePrefixesAssignHook,
NULL);
DefineCustomIntVariable(
"citus.isolation_test_session_process_id",
NULL,
@ -1283,6 +1323,18 @@ RegisterCitusConfigVariables(void)
GUC_STANDARD,
NULL, NULL, NULL);
DefineCustomIntVariable(
"citus.logical_replication_timeout",
gettext_noop("Sets the timeout to error out when logical replication is used"),
gettext_noop("Citus uses logical replication when it moves/replicates shards. "
"This setting determines when Citus gives up waiting for progress "
"during logical replication and errors out."),
&LogicalReplicationTimeout,
2 * 60 * 60 * 1000, 0, 7 * 24 * 3600 * 1000,
PGC_SIGHUP,
GUC_NO_SHOW_ALL | GUC_UNIT_MS,
NULL, NULL, NULL);
DefineCustomIntVariable(
"citus.max_adaptive_executor_pool_size",
gettext_noop("Sets the maximum number of connections per worker node used by "
@ -1353,6 +1405,17 @@ RegisterCitusConfigVariables(void)
GUC_UNIT_KB | GUC_STANDARD,
NULL, NULL, NULL);
DefineCustomIntVariable(
"citus.max_matview_size_to_auto_recreate",
gettext_noop("Sets the maximum size of materialized views in MB to "
"automatically distribute them."),
NULL,
&MaxMatViewSizeToAutoRecreate,
1024, -1, INT_MAX,
PGC_USERSET,
GUC_UNIT_MB | GUC_STANDARD,
NULL, NULL, NULL);
DefineCustomIntVariable(
"citus.max_rebalancer_logged_ignored_moves",
gettext_noop("Sets the maximum number of ignored moves the rebalance logs"),
@ -1677,6 +1740,41 @@ RegisterCitusConfigVariables(void)
GUC_STANDARD,
NULL, NULL, NULL);
DefineCustomStringVariable(
"citus.show_shards_for_app_name_prefixes",
gettext_noop("If application_name starts with one of these values, show shards"),
gettext_noop("Citus places distributed tables and shards in the same schema. "
"That can cause confusion when inspecting the list of tables on "
"a node with shards. By default the shards are hidden from "
"pg_class. This GUC can be used to show the shards to certain "
"applications based on the application_name of the connection. "
"The default is empty string, which hides shards from all "
"applications. This behaviour can be overridden using the "
"citus.override_table_visibility setting"),
&ShowShardsForAppNamePrefixes,
"",
PGC_USERSET,
GUC_STANDARD,
ShowShardsForAppNamePrefixesCheckHook,
ShowShardsForAppNamePrefixesAssignHook,
NULL);
DefineCustomBoolVariable(
"citus.skip_jsonb_validation_in_copy",
gettext_noop("Skip validation of JSONB columns on the coordinator during COPY "
"into a distributed table"),
gettext_noop("Parsing large JSON objects may incur significant CPU overhead, "
"which can lower COPY throughput. If this GUC is set (the default), "
"JSON parsing is skipped on the coordinator, which means you cannot "
"see the line number in case of malformed JSON, but throughput will "
"be higher. This setting does not apply if the input format is "
"binary."),
&SkipJsonbValidationInCopy,
true,
PGC_USERSET,
0,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.sort_returning",
gettext_noop("Sorts the RETURNING clause to get consistent test output"),
@ -1691,6 +1789,47 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
/*
* It takes about 140 bytes of shared memory to store one row, therefore
* this setting should be used responsibly. setting it to 10M will require
* 1.4GB of shared memory.
*/
DefineCustomIntVariable(
"citus.stat_statements_max",
gettext_noop("Determines maximum number of statements tracked by "
"citus_stat_statements."),
NULL,
&StatStatementsMax,
50000, 1000, 10000000,
PGC_POSTMASTER,
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomIntVariable(
"citus.stat_statements_purge_interval",
gettext_noop("Determines time interval in seconds for "
"citus_stat_statements to purge expired entries."),
NULL,
&StatStatementsPurgeInterval,
10, -1, INT_MAX,
PGC_SIGHUP,
GUC_UNIT_MS | GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomEnumVariable(
"citus.stat_statements_track",
gettext_noop(
"Enables/Disables the stats collection for citus_stat_statements."),
gettext_noop("Enables the stats collection when set to 'all'. "
"Disables when set to 'none'. Disabling can be useful for "
"avoiding extra CPU cycles needed for the calculations."),
&StatStatementsTrack,
STAT_STATEMENTS_TRACK_NONE,
stat_statements_track_options,
PGC_SUSET,
GUC_STANDARD,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.subquery_pushdown",
gettext_noop("Usage of this GUC is highly discouraged, please read the long "
@ -1946,12 +2085,12 @@ WarnIfReplicationModelIsSet(int *newval, void **extra, GucSource source)
/*
* HideShardsFromAppNamePrefixesCheckHook ensures that the
* citus.hide_shards_from_app_name_prefixes holds a valid list of application_name
* ShowShardsForAppNamePrefixesCheckHook ensures that the
* citus.show_shards_for_app_name_prefixes holds a valid list of application_name
* values.
*/
static bool
HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource source)
ShowShardsForAppNamePrefixesCheckHook(char **newval, void **extra, GucSource source)
{
List *prefixList = NIL;
@ -1981,7 +2120,7 @@ HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource so
if (strcmp(prefixAscii, appNamePrefix) != 0)
{
GUC_check_errdetail("prefix %s in citus.hide_shards_from_app_name_prefixes "
GUC_check_errdetail("prefix %s in citus.show_shards_for_app_name_prefixes "
"contains non-ascii characters", appNamePrefix);
return false;
}
@ -1992,12 +2131,12 @@ HideShardsFromAppNamePrefixesCheckHook(char **newval, void **extra, GucSource so
/*
* HideShardsFromAppNamePrefixesAssignHook ensures changes to
* citus.hide_shards_from_app_name_prefixes are reflected in the decision
* ShowShardsForAppNamePrefixesAssignHook ensures changes to
* citus.show_shards_for_app_name_prefixes are reflected in the decision
* whether or not to show shards.
*/
static void
HideShardsFromAppNamePrefixesAssignHook(const char *newval, void *extra)
ShowShardsForAppNamePrefixesAssignHook(const char *newval, void *extra)
{
ResetHideShardsDecision();
}
@ -2011,6 +2150,7 @@ static void
ApplicationNameAssignHook(const char *newval, void *extra)
{
ResetHideShardsDecision();
ResetCitusBackendType();
OldApplicationNameAssignHook(newval, extra);
}
@ -2036,8 +2176,10 @@ NodeConninfoGucCheckHook(char **newval, void **extra, GucSource source)
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
"krbsrvname",
#endif
"sslcert",
"sslcompression",
"sslcrl",
"sslkey",
"sslmode",
"sslrootcert",
"tcp_user_timeout",

View File

@ -0,0 +1,8 @@
#include "udfs/citus_shards_on_worker/11.0-2.sql"
#include "udfs/citus_shard_indexes_on_worker/11.0-2.sql"
#include "udfs/citus_disable_node/11.0-2.sql"
#include "udfs/citus_is_coordinator/11.0-2.sql"
#include "udfs/run_command_on_coordinator/11.0-2.sql"
#include "udfs/start_metadata_sync_to_all_nodes/11.0-2.sql"
#include "udfs/citus_finalize_upgrade_to_citus11/11.0-2.sql"
#include "udfs/citus_finish_citus_upgrade/11.0-2.sql"

View File

@ -0,0 +1 @@
#include "udfs/citus_finalize_upgrade_to_citus11/11.0-3.sql"

View File

@ -1461,8 +1461,17 @@ CREATE FUNCTION pg_catalog.citus_server_id()
COMMENT ON FUNCTION citus_server_id()
IS 'generates a random UUID to be used as server identifier';
-- Insert the latest extension version into pg_dist_node_metadata
-- for new installations.
--
-- While users could technically upgrade to an intermediate version
-- everything in Citus fails until it is upgraded to the latest version,
-- so it seems safe to use the latest.
INSERT INTO pg_dist_node_metadata
VALUES (jsonb_build_object('server_id', citus_server_id()::text));
SELECT jsonb_build_object('server_id', citus_server_id()::text,
'last_upgrade_version', default_version)
FROM pg_available_extensions
WHERE name = 'citus';
-- rebalancer functions
CREATE TYPE citus.shard_transfer_mode AS ENUM (

View File

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

View File

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

View File

@ -0,0 +1 @@
#include "../udfs/citus_finalize_upgrade_to_citus11/11.0-2.sql"

View File

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

View File

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

View File

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

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