Compare commits
345 Commits
Author | SHA1 | Date |
---|---|---|
|
4cd8bb1b67 | |
|
4456913801 | |
|
55a0d1f730 | |
|
5e37fe0c46 | |
|
e8c3179b4d | |
|
92dc7f36fc | |
|
98d95a9b9d | |
|
c7f5e2b975 | |
|
282523549e | |
|
c98341e4ed | |
|
8d2fbca8ef | |
|
088ba75057 | |
|
a18040869a | |
|
a4040ba5da | |
|
d4dd44e715 | |
|
ea7aa6712d | |
|
d2e6cf1de0 | |
|
3d61c4dc71 | |
|
37e23f44b4 | |
|
48d89c9c1b | |
|
bb9d90ecc3 | |
|
0e6127c4f6 | |
|
f084b79a4b | |
|
1dc60e38bb | |
|
a7e686c106 | |
|
88904eda97 | |
|
9bddf57053 | |
|
4b4fa22b64 | |
|
1c09469dd2 | |
|
680b870d45 | |
|
ec13c24558 | |
|
1d947f0734 | |
|
6b2f113947 | |
|
bdd3ff085d | |
|
6b00afac39 | |
|
10f1a50f1f | |
|
52bf7a1d03 | |
|
1d0bdbd749 | |
|
be75c0ec4c | |
|
caceb35eba | |
|
08913e27d7 | |
|
17b4122e84 | |
|
c02d899b6c | |
|
421bc462b2 | |
|
ed40a0ad02 | |
|
e50563fbd8 | |
|
756e8f66e0 | |
|
95da74c47f | |
|
4139370a1d | |
|
87ec3def55 | |
|
ec141f696a | |
|
ccd7ddee36 | |
|
89674d9630 | |
|
2b5dfbbd08 | |
|
7004295065 | |
|
3b1c082791 | |
|
d5618b6b4c | |
|
ef59b659c5 | |
|
85739b34bf | |
|
1bb6c7e95f | |
|
a18f8990be | |
|
7e1f22999b | |
|
0642a4dc08 | |
|
74d945f5ae | |
|
7682d135a4 | |
|
08d94f9eb6 | |
|
8f436e4a48 | |
|
8940665d17 | |
|
1d57a36ecc | |
|
658632642a | |
|
3e96a19606 | |
|
2112aa1860 | |
|
1cf5c190aa | |
|
24585a8c04 | |
|
b7d04038cb | |
|
5e9f8d838c | |
|
202ad077bd | |
|
a383ef6831 | |
|
28b0b0e7a8 | |
|
80c6479408 | |
|
29bd3dc41c | |
|
09e96831b3 | |
|
b22c95933c | |
|
c662e68e44 | |
|
915276ee7f | |
|
3935710c17 | |
|
11f76cb4bb | |
|
35d1160ace | |
|
088731e9db | |
|
c3d21b807a | |
|
592416250c | |
|
beb222ea8d | |
|
f8335c1484 | |
|
1797ab8a4f | |
|
808626ea78 | |
|
6254ad81fc | |
|
1074035446 | |
|
0de7b5a240 | |
|
9615b52863 | |
|
a74bb6280c | |
|
6043fcb263 | |
|
ed71e65333 | |
|
ae104f06a6 | |
|
b46d311e30 | |
|
4c080c48cd | |
|
81bda6fb8e | |
|
9dcd812a40 | |
|
46f89ccf65 | |
|
51c2e63c30 | |
|
9a413e0c32 | |
|
5540096b9a | |
|
c8d9a1bd10 | |
|
7e8bff034f | |
|
41ea21ee0c | |
|
dce54db494 | |
|
6bd3474804 | |
|
d885e1a016 | |
|
26ad52713c | |
|
afcda3feff | |
|
30bf960c5c | |
|
117bd1d04f | |
|
711aec80fa | |
|
829665ebca | |
|
f7c57351a7 | |
|
565c309a1e | |
|
cee0f31ddb | |
|
2d8be01853 | |
|
9a0cc282b7 | |
|
7073f06153 | |
|
8783cae57f | |
|
b6e3f39583 | |
|
a28f75cc77 | |
|
af5fced935 | |
|
7b6a828c74 | |
|
f7bead22d4 | |
|
5ef2cd67ed | |
|
70f84e4aee | |
|
0a6adf4ccc | |
|
ab7c13beb5 | |
|
73411915a4 | |
|
665d72a2f5 | |
|
0355b12c7f | |
|
fe6d198ab2 | |
|
248ff5d52a | |
|
c52f36019f | |
|
f6959715dc | |
|
5bad6c6a1d | |
|
4775715691 | |
|
68d28ecdc0 | |
|
9e1852eac7 | |
|
2a263fe69a | |
|
3c467e6e02 | |
|
accb7d09f7 | |
|
8ac9f0fcee | |
|
c603c3ed74 | |
|
e776a7ebbb | |
|
58fef24142 | |
|
aaaf637a6b | |
|
fa4fc0b372 | |
|
9e71750fcd | |
|
e62ae64d00 | |
|
76f68f47c4 | |
|
d5231c34ab | |
|
f883cfdd77 | |
|
7c8a366ba2 | |
|
06e9c29950 | |
|
493140287a | |
|
ec25b433d4 | |
|
eedb607cd5 | |
|
8c9de08b76 | |
|
2874d7af46 | |
|
0ab42e7a80 | |
|
fcc72d8a23 | |
|
553d5ba15d | |
|
a0151aa31d | |
|
ada3ba2507 | |
|
41e2af8ff5 | |
|
a263ac6f5f | |
|
110b4192b2 | |
|
16604a6601 | |
|
cdf51da458 | |
|
381f31756e | |
|
3586aab17a | |
|
41d99249d9 | |
|
3929a5b2a6 | |
|
fdd658acec | |
|
0acb5f6e86 | |
|
d129064280 | |
|
bf05bf51ec | |
|
8afa2d0386 | |
|
12f56438fc | |
|
f0043b64a1 | |
|
edcdbe67b1 | |
|
d59c93bc50 | |
|
51009d0191 | |
|
f4242685e3 | |
|
cbb90cc4ae | |
|
9ddee5d02a | |
|
3509b7df5a | |
|
211415dd4b | |
|
683e10ab69 | |
|
852bcc5483 | |
|
b3ef1b7e39 | |
|
56e014e64e | |
|
71ccbcf3e2 | |
|
2cbfdbfa46 | |
|
9a0cdbf5af | |
|
74b55d0546 | |
|
15a3adebe8 | |
|
59da0633bb | |
|
c665cb8af3 | |
|
2fae91c5df | |
|
689c6897a4 | |
|
f01c5f2593 | |
|
6869b3ad10 | |
|
9ff8436f14 | |
|
594cb6f274 | |
|
d05174093b | |
|
3ce731d497 | |
|
6f43d5c02f | |
|
5aedec4242 | |
|
f6ea619e27 | |
|
9c243d4477 | |
|
5d673874f7 | |
|
24188959ed | |
|
542212c3d8 | |
|
3de5601bcc | |
|
1d096df7f4 | |
|
8b48d6ab02 | |
|
1cb2e1e4e8 | |
|
3ffb831beb | |
|
863713e9b7 | |
|
11d7c27352 | |
|
9683bef2ec | |
|
72fbea20c4 | |
|
ee11492a0e | |
|
4b295cc857 | |
|
14ecebe47c | |
|
188614512f | |
|
5ec056a172 | |
|
fcfedff8d1 | |
|
6cf6cf37fd | |
|
51e607878b | |
|
21464adfec | |
|
04b374fc01 | |
|
739c6d26df | |
|
00068e07c5 | |
|
9a91136a3d | |
|
8e979f7ac6 | |
|
1d55debb98 | |
|
20dc58cf5d | |
|
968ac74cde | |
|
d940cfa992 | |
|
c3579eef06 | |
|
181b8ab6d5 | |
|
b877d606c7 | |
|
6801a1ed1e | |
|
dbdde111c1 | |
|
47bd9d8917 | |
|
c019acc01b | |
|
0620c8f9a6 | |
|
3b556cb5ed | |
|
cedcc220bf | |
|
c88bf5ff1c | |
|
e14e8667cc | |
|
32b0fc23f5 | |
|
55d500de8d | |
|
5efd3f181a | |
|
0d1f18862b | |
|
c6fbb72c02 | |
|
a960799dfb | |
|
cdef2d5224 | |
|
e4ac3e6d9a | |
|
240313e286 | |
|
57ff762c82 | |
|
c028d929b5 | |
|
d4663212f4 | |
|
0dac63afc0 | |
|
92228b279a | |
|
0dc41ee5a0 | |
|
444e6cb7d6 | |
|
e535f53ce5 | |
|
21646ca1e9 | |
|
5e2439a117 | |
|
85b997a0fb | |
|
f171ec98fc | |
|
b47c8b3fb0 | |
|
0678a2fd89 | |
|
5a48a1602e | |
|
6fed82609c | |
|
9867c5b949 | |
|
184c8fc1ee | |
|
a6e86884f6 | |
|
ea5551689e | |
|
2cf4c04023 | |
|
e3c93c303d | |
|
5903196020 | |
|
c9f2fc892d | |
|
c83c556702 | |
|
2bccb58157 | |
|
0d83ab57de | |
|
20ae42e7fa | |
|
37415ef8f5 | |
|
a76a832553 | |
|
81aa660b31 | |
|
ce58c04304 | |
|
83e3fb817d | |
|
ee8f4bb7e8 | |
|
d0b093c975 | |
|
f9218d9780 | |
|
2bf1472c8e | |
|
10198b18e8 | |
|
1fe16fa746 | |
|
2d1444188c | |
|
db13afaa7b | |
|
71a4633dad | |
|
5eaf6c221e | |
|
788e09a39a | |
|
fb08f9b198 | |
|
e0b0cdbb87 | |
|
e9035f6d32 | |
|
76fdfa3c0f | |
|
6d8725efb0 | |
|
858d99be33 | |
|
7d6c401dd3 | |
|
0dca65c84d | |
|
185ac5e01e | |
|
c323f49e83 | |
|
27ac44eb2a | |
|
b87fbcbf79 | |
|
7fa109c977 | |
|
a9d28ca96f | |
|
111b4c19bc | |
|
f72cd7ffd2 | |
|
01e3c24793 | |
|
f17d31fd94 | |
|
5926ec8bbb | |
|
ab8a3fab74 | |
|
0f28a69f12 | |
|
2c190d0689 | |
|
71e556e090 | |
|
b0e982d0b5 | |
|
4e46708789 | |
|
7c0b289761 | |
|
26dc407f4a |
|
@ -6,9 +6,12 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
# install build tools
|
# install build tools
|
||||||
RUN apt update && apt install -y \
|
RUN apt update && apt install -y \
|
||||||
|
bison \
|
||||||
bzip2 \
|
bzip2 \
|
||||||
cpanminus \
|
cpanminus \
|
||||||
curl \
|
curl \
|
||||||
|
docbook-xml \
|
||||||
|
docbook-xsl \
|
||||||
flex \
|
flex \
|
||||||
gcc \
|
gcc \
|
||||||
git \
|
git \
|
||||||
|
@ -20,6 +23,7 @@ RUN apt update && apt install -y \
|
||||||
libreadline-dev \
|
libreadline-dev \
|
||||||
libselinux1-dev \
|
libselinux1-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
|
libxml2-utils \
|
||||||
libxslt-dev \
|
libxslt-dev \
|
||||||
libzstd-dev \
|
libzstd-dev \
|
||||||
locales \
|
locales \
|
||||||
|
@ -32,6 +36,7 @@ RUN apt update && apt install -y \
|
||||||
sudo \
|
sudo \
|
||||||
uuid-dev \
|
uuid-dev \
|
||||||
valgrind \
|
valgrind \
|
||||||
|
xsltproc \
|
||||||
zlib1g-dev \
|
zlib1g-dev \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
||||||
&& apt install -y \
|
&& apt install -y \
|
||||||
|
@ -67,20 +72,8 @@ ENV PATH="/home/citus/.pgenv/pgsql/bin:${PATH}"
|
||||||
USER citus
|
USER citus
|
||||||
|
|
||||||
# build postgres versions separately for effective parrallelism and caching of already built versions when changing only certain versions
|
# build postgres versions separately for effective parrallelism and caching of already built versions when changing only certain versions
|
||||||
FROM base AS pg14
|
|
||||||
RUN MAKEFLAGS="-j $(nproc)" pgenv build 14.15
|
|
||||||
RUN rm .pgenv/src/*.tar*
|
|
||||||
RUN make -C .pgenv/src/postgresql-*/ clean
|
|
||||||
RUN make -C .pgenv/src/postgresql-*/src/include install
|
|
||||||
|
|
||||||
# create a staging directory with all files we want to copy from our pgenv build
|
|
||||||
# we will copy the contents of the staged folder into the final image at once
|
|
||||||
RUN mkdir .pgenv-staging/
|
|
||||||
RUN cp -r .pgenv/src .pgenv/pgsql-* .pgenv/config .pgenv-staging/
|
|
||||||
RUN rm .pgenv-staging/config/default.conf
|
|
||||||
|
|
||||||
FROM base AS pg15
|
FROM base AS pg15
|
||||||
RUN MAKEFLAGS="-j $(nproc)" pgenv build 15.10
|
RUN MAKEFLAGS="-j $(nproc)" pgenv build 15.13
|
||||||
RUN rm .pgenv/src/*.tar*
|
RUN rm .pgenv/src/*.tar*
|
||||||
RUN make -C .pgenv/src/postgresql-*/ clean
|
RUN make -C .pgenv/src/postgresql-*/ clean
|
||||||
RUN make -C .pgenv/src/postgresql-*/src/include install
|
RUN make -C .pgenv/src/postgresql-*/src/include install
|
||||||
|
@ -92,7 +85,7 @@ RUN cp -r .pgenv/src .pgenv/pgsql-* .pgenv/config .pgenv-staging/
|
||||||
RUN rm .pgenv-staging/config/default.conf
|
RUN rm .pgenv-staging/config/default.conf
|
||||||
|
|
||||||
FROM base AS pg16
|
FROM base AS pg16
|
||||||
RUN MAKEFLAGS="-j $(nproc)" pgenv build 16.6
|
RUN MAKEFLAGS="-j $(nproc)" pgenv build 16.9
|
||||||
RUN rm .pgenv/src/*.tar*
|
RUN rm .pgenv/src/*.tar*
|
||||||
RUN make -C .pgenv/src/postgresql-*/ clean
|
RUN make -C .pgenv/src/postgresql-*/ clean
|
||||||
RUN make -C .pgenv/src/postgresql-*/src/include install
|
RUN make -C .pgenv/src/postgresql-*/src/include install
|
||||||
|
@ -104,7 +97,7 @@ RUN cp -r .pgenv/src .pgenv/pgsql-* .pgenv/config .pgenv-staging/
|
||||||
RUN rm .pgenv-staging/config/default.conf
|
RUN rm .pgenv-staging/config/default.conf
|
||||||
|
|
||||||
FROM base AS pg17
|
FROM base AS pg17
|
||||||
RUN MAKEFLAGS="-j $(nproc)" pgenv build 17.2
|
RUN MAKEFLAGS="-j $(nproc)" pgenv build 17.5
|
||||||
RUN rm .pgenv/src/*.tar*
|
RUN rm .pgenv/src/*.tar*
|
||||||
RUN make -C .pgenv/src/postgresql-*/ clean
|
RUN make -C .pgenv/src/postgresql-*/ clean
|
||||||
RUN make -C .pgenv/src/postgresql-*/src/include install
|
RUN make -C .pgenv/src/postgresql-*/src/include install
|
||||||
|
@ -205,9 +198,9 @@ RUN git clone https://github.com/so-fancy/diff-so-fancy.git \
|
||||||
|
|
||||||
COPY --link --from=uncrustify-builder /uncrustify/usr/ /usr/
|
COPY --link --from=uncrustify-builder /uncrustify/usr/ /usr/
|
||||||
|
|
||||||
COPY --link --from=pg14 /home/citus/.pgenv-staging/ /home/citus/.pgenv/
|
|
||||||
COPY --link --from=pg15 /home/citus/.pgenv-staging/ /home/citus/.pgenv/
|
COPY --link --from=pg15 /home/citus/.pgenv-staging/ /home/citus/.pgenv/
|
||||||
COPY --link --from=pg16 /home/citus/.pgenv-staging/ /home/citus/.pgenv/
|
COPY --link --from=pg16 /home/citus/.pgenv-staging/ /home/citus/.pgenv/
|
||||||
|
COPY --link --from=pg17 /home/citus/.pgenv-staging/ /home/citus/.pgenv/
|
||||||
|
|
||||||
COPY --link --from=pipenv /home/citus/.local/share/virtualenvs/ /home/citus/.local/share/virtualenvs/
|
COPY --link --from=pipenv /home/citus/.local/share/virtualenvs/ /home/citus/.local/share/virtualenvs/
|
||||||
|
|
||||||
|
@ -223,7 +216,7 @@ COPY --chown=citus:citus .psqlrc .
|
||||||
RUN sudo chown --from=root:root citus:citus -R ~
|
RUN sudo chown --from=root:root citus:citus -R ~
|
||||||
|
|
||||||
# sets default pg version
|
# sets default pg version
|
||||||
RUN pgenv switch 17.2
|
RUN pgenv switch 17.5
|
||||||
|
|
||||||
# make connecting to the coordinator easy
|
# make connecting to the coordinator easy
|
||||||
ENV PGPORT=9700
|
ENV PGPORT=9700
|
||||||
|
|
|
@ -329,11 +329,12 @@
|
||||||
},
|
},
|
||||||
"jinja2": {
|
"jinja2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa",
|
"sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d",
|
||||||
"sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"
|
"sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==3.1.3"
|
"version": "==3.1.6"
|
||||||
},
|
},
|
||||||
"kaitaistruct": {
|
"kaitaistruct": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -353,69 +354,70 @@
|
||||||
},
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf",
|
"sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4",
|
||||||
"sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff",
|
"sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30",
|
||||||
"sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f",
|
"sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0",
|
||||||
"sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3",
|
"sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9",
|
||||||
"sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532",
|
"sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396",
|
||||||
"sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f",
|
"sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13",
|
||||||
"sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617",
|
"sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028",
|
||||||
"sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df",
|
"sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca",
|
||||||
"sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4",
|
"sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557",
|
||||||
"sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906",
|
"sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832",
|
||||||
"sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f",
|
"sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0",
|
||||||
"sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4",
|
"sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b",
|
||||||
"sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8",
|
"sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579",
|
||||||
"sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371",
|
"sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a",
|
||||||
"sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2",
|
"sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c",
|
||||||
"sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465",
|
"sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff",
|
||||||
"sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52",
|
"sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c",
|
||||||
"sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6",
|
"sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22",
|
||||||
"sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169",
|
"sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094",
|
||||||
"sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad",
|
"sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb",
|
||||||
"sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2",
|
"sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e",
|
||||||
"sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0",
|
"sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5",
|
||||||
"sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029",
|
"sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a",
|
||||||
"sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f",
|
"sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d",
|
||||||
"sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a",
|
"sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a",
|
||||||
"sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced",
|
"sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b",
|
||||||
"sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5",
|
"sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8",
|
||||||
"sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c",
|
"sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225",
|
||||||
"sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf",
|
"sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c",
|
||||||
"sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9",
|
"sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144",
|
||||||
"sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb",
|
"sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f",
|
||||||
"sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad",
|
"sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87",
|
||||||
"sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3",
|
"sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d",
|
||||||
"sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1",
|
"sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93",
|
||||||
"sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46",
|
"sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf",
|
||||||
"sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc",
|
"sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158",
|
||||||
"sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a",
|
"sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84",
|
||||||
"sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee",
|
"sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb",
|
||||||
"sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900",
|
"sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48",
|
||||||
"sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5",
|
"sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171",
|
||||||
"sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea",
|
"sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c",
|
||||||
"sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f",
|
"sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6",
|
||||||
"sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5",
|
"sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd",
|
||||||
"sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e",
|
"sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d",
|
||||||
"sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a",
|
"sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1",
|
||||||
"sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f",
|
"sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d",
|
||||||
"sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50",
|
"sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca",
|
||||||
"sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a",
|
"sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a",
|
||||||
"sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b",
|
"sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29",
|
||||||
"sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4",
|
"sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe",
|
||||||
"sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff",
|
"sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798",
|
||||||
"sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2",
|
"sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c",
|
||||||
"sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46",
|
"sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8",
|
||||||
"sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b",
|
"sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f",
|
||||||
"sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf",
|
"sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f",
|
||||||
"sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5",
|
"sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a",
|
||||||
"sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5",
|
"sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178",
|
||||||
"sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab",
|
"sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0",
|
||||||
"sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd",
|
"sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79",
|
||||||
"sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"
|
"sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430",
|
||||||
|
"sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.9'",
|
||||||
"version": "==2.1.5"
|
"version": "==3.0.2"
|
||||||
},
|
},
|
||||||
"mitmproxy": {
|
"mitmproxy": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
|
|
|
@ -10,8 +10,13 @@ on:
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
- "release-*"
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened,synchronize]
|
types: [opened, reopened,synchronize]
|
||||||
|
merge_group:
|
||||||
jobs:
|
jobs:
|
||||||
# Since GHA does not interpolate env varibles in matrix context, we need to
|
# Since GHA does not interpolate env varibles in matrix context, we need to
|
||||||
# define them in a separate job and use them in other jobs.
|
# define them in a separate job and use them in other jobs.
|
||||||
|
@ -26,13 +31,12 @@ jobs:
|
||||||
pgupgrade_image_name: "ghcr.io/citusdata/pgupgradetester"
|
pgupgrade_image_name: "ghcr.io/citusdata/pgupgradetester"
|
||||||
style_checker_image_name: "ghcr.io/citusdata/stylechecker"
|
style_checker_image_name: "ghcr.io/citusdata/stylechecker"
|
||||||
style_checker_tools_version: "0.8.18"
|
style_checker_tools_version: "0.8.18"
|
||||||
sql_snapshot_pg_version: "17.2"
|
sql_snapshot_pg_version: "17.5"
|
||||||
image_suffix: "-v889e4c1"
|
image_suffix: "-dev-d28f316"
|
||||||
image_suffix_citus_upgrade: "-dev-2ad1f90"
|
pg15_version: '{ "major": "15", "full": "15.13" }'
|
||||||
pg15_version: '{ "major": "15", "full": "15.10" }'
|
pg16_version: '{ "major": "16", "full": "16.9" }'
|
||||||
pg16_version: '{ "major": "16", "full": "16.6" }'
|
pg17_version: '{ "major": "17", "full": "17.5" }'
|
||||||
pg17_version: '{ "major": "17", "full": "17.2" }'
|
upgrade_pg_versions: "15.13-16.9-17.5"
|
||||||
upgrade_pg_versions: "14.15-15.10-16.6-17.2"
|
|
||||||
steps:
|
steps:
|
||||||
# Since GHA jobs need at least one step we use a noop step here.
|
# Since GHA jobs need at least one step we use a noop step here.
|
||||||
- name: Set up parameters
|
- name: Set up parameters
|
||||||
|
@ -348,7 +352,7 @@ jobs:
|
||||||
name: PG${{ fromJson(needs.params.outputs.pg15_version).major }} - check-citus-upgrade
|
name: PG${{ fromJson(needs.params.outputs.pg15_version).major }} - check-citus-upgrade
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: "${{ needs.params.outputs.citusupgrade_image_name }}:${{ fromJson(needs.params.outputs.pg15_version).full }}${{ needs.params.outputs.image_suffix_citus_upgrade }}"
|
image: "${{ needs.params.outputs.citusupgrade_image_name }}:${{ fromJson(needs.params.outputs.pg15_version).full }}${{ needs.params.outputs.image_suffix }}"
|
||||||
options: --user root
|
options: --user root
|
||||||
needs:
|
needs:
|
||||||
- params
|
- params
|
||||||
|
@ -451,7 +455,7 @@ jobs:
|
||||||
chmod +x run_hammerdb.sh
|
chmod +x run_hammerdb.sh
|
||||||
run_hammerdb.sh citusbot_tpcc_benchmark_rg
|
run_hammerdb.sh citusbot_tpcc_benchmark_rg
|
||||||
prepare_parallelization_matrix_32:
|
prepare_parallelization_matrix_32:
|
||||||
name: Parallel 32
|
name: Prepare parallelization matrix
|
||||||
if: ${{ needs.test-flakyness-pre.outputs.tests != ''}}
|
if: ${{ needs.test-flakyness-pre.outputs.tests != ''}}
|
||||||
needs: test-flakyness-pre
|
needs: test-flakyness-pre
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -477,23 +481,39 @@ jobs:
|
||||||
- name: Detect regression tests need to be ran
|
- name: Detect regression tests need to be ran
|
||||||
id: detect-regression-tests
|
id: detect-regression-tests
|
||||||
run: |-
|
run: |-
|
||||||
detected_changes=$(git diff origin/release-12.1... --name-only --diff-filter=AM | (grep 'src/test/regress/sql/.*\.sql\|src/test/regress/spec/.*\.spec\|src/test/regress/citus_tests/test/test_.*\.py' || true))
|
detected_changes=$(git diff origin/main... --name-only --diff-filter=AM | (grep 'src/test/regress/sql/.*\.sql\|src/test/regress/spec/.*\.spec\|src/test/regress/citus_tests/test/test_.*\.py' || true))
|
||||||
tests=${detected_changes}
|
tests=${detected_changes}
|
||||||
if [ -z "$tests" ]; then
|
|
||||||
echo "No test found."
|
# split the tests to be skipped --today we only skip upgrade tests
|
||||||
|
skipped_tests=""
|
||||||
|
not_skipped_tests=""
|
||||||
|
for test in $tests; do
|
||||||
|
if [[ $test =~ ^src/test/regress/sql/upgrade_ ]]; then
|
||||||
|
skipped_tests="$skipped_tests $test"
|
||||||
else
|
else
|
||||||
echo "Detected tests " $tests
|
not_skipped_tests="$not_skipped_tests $test"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -z "$skipped_tests" ]; then
|
||||||
|
echo "Skipped tests " $skipped_tests
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$not_skipped_tests" ]; then
|
||||||
|
echo "Not detected any tests that flaky test detection should run"
|
||||||
|
else
|
||||||
|
echo "Detected tests " $not_skipped_tests
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo 'tests<<EOF' >> $GITHUB_OUTPUT
|
echo 'tests<<EOF' >> $GITHUB_OUTPUT
|
||||||
echo "$tests" >> "$GITHUB_OUTPUT"
|
echo "$not_skipped_tests" >> "$GITHUB_OUTPUT"
|
||||||
echo 'EOF' >> $GITHUB_OUTPUT
|
echo 'EOF' >> $GITHUB_OUTPUT
|
||||||
test-flakyness:
|
test-flakyness:
|
||||||
if: false
|
if: ${{ needs.test-flakyness-pre.outputs.tests != ''}}
|
||||||
name: Test flakyness
|
name: Test flakyness
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: ${{ needs.params.outputs.fail_test_image_name }}:${{ needs.params.outputs.pg17_version }}${{ needs.params.outputs.image_suffix }}
|
image: ${{ needs.params.outputs.fail_test_image_name }}:${{ fromJson(needs.params.outputs.pg17_version).full }}${{ needs.params.outputs.image_suffix }}
|
||||||
options: --user root
|
options: --user root
|
||||||
env:
|
env:
|
||||||
runs: 8
|
runs: 8
|
||||||
|
@ -516,8 +536,10 @@ jobs:
|
||||||
for test in "${tests_array[@]}"
|
for test in "${tests_array[@]}"
|
||||||
do
|
do
|
||||||
test_name=$(echo "$test" | sed -r "s/.+\/(.+)\..+/\1/")
|
test_name=$(echo "$test" | sed -r "s/.+\/(.+)\..+/\1/")
|
||||||
gosu circleci src/test/regress/citus_tests/run_test.py $test_name --repeat ${{ env.runs }} --use-base-schedule --use-whole-schedule-line
|
gosu circleci src/test/regress/citus_tests/run_test.py $test_name --repeat ${{ env.runs }} --use-whole-schedule-line
|
||||||
done
|
done
|
||||||
shell: bash
|
shell: bash
|
||||||
- uses: "./.github/actions/save_logs_and_results"
|
- uses: "./.github/actions/save_logs_and_results"
|
||||||
if: always()
|
if: always()
|
||||||
|
with:
|
||||||
|
folder: test_flakyness_parallel_${{ matrix.id }}
|
||||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
@ -76,4 +76,4 @@ jobs:
|
||||||
sudo make install-all
|
sudo make install-all
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
name: "Build devcontainer"
|
||||||
|
|
||||||
|
# Since building of containers can be quite time consuming, and take up some storage,
|
||||||
|
# there is no need to finish a build for a tag if new changes are concurrently being made.
|
||||||
|
# This cancels any previous builds for the same tag, and only the latest one will be kept.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- ".devcontainer/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/citusdata/citus-devcontainer
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=sha
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
-
|
||||||
|
name: 'Login to GitHub Container Registry'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{github.actor}}
|
||||||
|
password: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: "{{defaultContext}}:.devcontainer"
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
|
@ -71,9 +71,9 @@ jobs:
|
||||||
- uses: "./.github/actions/setup_extension"
|
- uses: "./.github/actions/setup_extension"
|
||||||
- name: Run minimal tests
|
- name: Run minimal tests
|
||||||
run: |-
|
run: |-
|
||||||
gosu circleci src/test/regress/citus_tests/run_test.py ${{ env.test }} --repeat ${{ env.runs }} --use-base-schedule --use-whole-schedule-line
|
gosu circleci src/test/regress/citus_tests/run_test.py ${{ env.test }} --repeat ${{ env.runs }} --use-whole-schedule-line
|
||||||
shell: bash
|
shell: bash
|
||||||
- uses: "./.github/actions/save_logs_and_results"
|
- uses: "./.github/actions/save_logs_and_results"
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
folder: ${{ matrix.id }}
|
folder: check_flakyness_parallel_${{ matrix.id }}
|
||||||
|
|
|
@ -3,6 +3,7 @@ name: Build tests in packaging images
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened,synchronize]
|
types: [opened, reopened,synchronize]
|
||||||
|
merge_group:
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
|
|
@ -55,3 +55,6 @@ lib*.pc
|
||||||
# style related temporary outputs
|
# style related temporary outputs
|
||||||
*.uncrustify
|
*.uncrustify
|
||||||
.venv
|
.venv
|
||||||
|
|
||||||
|
# added output when modifying check_gucs_are_alphabetically_sorted.sh
|
||||||
|
guc.out
|
||||||
|
|
266
CHANGELOG.md
|
@ -1,3 +1,130 @@
|
||||||
|
### citus v13.1.0 (May 30th, 2025) ###
|
||||||
|
|
||||||
|
* Adds `citus_stat_counters` view that can be used to query
|
||||||
|
stat counters that Citus collects while the feature is enabled, which is
|
||||||
|
controlled by citus.enable_stat_counters. `citus_stat_counters()` can be
|
||||||
|
used to query the stat counters for the provided database oid and
|
||||||
|
`citus_stat_counters_reset()` can be used to reset them for the provided
|
||||||
|
database oid or for the current database if nothing or 0 is provided (#7917)
|
||||||
|
|
||||||
|
* Adds `citus_nodes` view that displays the node name, port role, and "active"
|
||||||
|
for nodes in the cluster (#7968)
|
||||||
|
|
||||||
|
* Adds `citus_is_primary_node()` UDF to determine if the current node is a
|
||||||
|
primary node in the cluster (#7720)
|
||||||
|
|
||||||
|
* Adds support for propagating `GRANT/REVOKE` rights on table columns (#7918)
|
||||||
|
|
||||||
|
* Adds support for propagating `REASSIGN OWNED BY` commands (#7319)
|
||||||
|
|
||||||
|
* Adds support for propagating `CREATE`/`DROP` database from all nodes (#7240,
|
||||||
|
#7253, #7359)
|
||||||
|
|
||||||
|
* Propagates `SECURITY LABEL ON ROLE` statement from any node (#7508)
|
||||||
|
|
||||||
|
* Adds support for issuing role management commands from worker nodes (#7278)
|
||||||
|
|
||||||
|
* Adds support for propagating `ALTER USER RENAME` commands (#7204)
|
||||||
|
|
||||||
|
* Adds support for propagating `ALTER DATABASE <db_name> SET ..` commands
|
||||||
|
(#7181)
|
||||||
|
|
||||||
|
* Adds support for propagating `SECURITY LABEL` on tables and columns (#7956)
|
||||||
|
|
||||||
|
* Adds support for propagating `COMMENT ON <database>/<role>` commands (#7388)
|
||||||
|
|
||||||
|
* Moves some of the internal citus functions from `pg_catalog` to
|
||||||
|
`citus_internal` schema (#7473, #7470, #7466, 7456, 7450)
|
||||||
|
|
||||||
|
* Adjusts `max_prepared_transactions` only when it's set to default on PG >= 16
|
||||||
|
(#7712)
|
||||||
|
|
||||||
|
* Adds skip_qualify_public param to shard_name() UDF to allow qualifying for
|
||||||
|
"public" schema when needed (#8014)
|
||||||
|
|
||||||
|
* Allows `citus_*_size` on indexes on a distributed tables (#7271)
|
||||||
|
|
||||||
|
* Allows `GRANT ADMIN` to now also be `INHERIT` or `SET` in support of PG16
|
||||||
|
|
||||||
|
* Makes sure `worker_copy_table_to_node` errors out with Citus tables (#7662)
|
||||||
|
|
||||||
|
* Adds information to explain output when using
|
||||||
|
`citus.explain_distributed_queries=false` (#7412)
|
||||||
|
|
||||||
|
* Logs username in the failed connection message (#7432)
|
||||||
|
|
||||||
|
* Makes sure to avoid incorrectly pushing-down the outer joins between
|
||||||
|
distributed tables and recurring relations (like reference tables, local
|
||||||
|
tables and `VALUES(..)` etc.) prior to PG 17 (#7937)
|
||||||
|
|
||||||
|
* Prevents incorrectly pushing `nextval()` call down to workers to avoid using
|
||||||
|
incorrect sequence value for some types of `INSERT .. SELECT`s (#7976)
|
||||||
|
|
||||||
|
* Makes sure to prevent `INSERT INTO ... SELECT` queries involving subfield or
|
||||||
|
sublink, to avoid crashes (#7912)
|
||||||
|
|
||||||
|
* Makes sure to take improvement_threshold into the account
|
||||||
|
in `citus_add_rebalance_strategy()` (#7247)
|
||||||
|
|
||||||
|
* Makes sure to disallow creating a replicated distributed
|
||||||
|
table concurrently (#7219)
|
||||||
|
|
||||||
|
* Fixes a bug that causes omitting `CASCADE` clause for the commands sent to
|
||||||
|
workers for `REVOKE` commands on tables (#7958)
|
||||||
|
|
||||||
|
* Fixes an issue detected using address sanitizer (#7948, #7949)
|
||||||
|
|
||||||
|
* Fixes a bug in deparsing of shard query in case of "output-table column" name
|
||||||
|
conflict (#7932)
|
||||||
|
|
||||||
|
* Fixes a crash in columnar custom scan that happens when a columnar table is
|
||||||
|
used in a join (#7703)
|
||||||
|
|
||||||
|
* Fixes `MERGE` command when insert value does not have source distributed
|
||||||
|
column (#7627)
|
||||||
|
|
||||||
|
* Fixes performance issue when using `\d tablename` on a server with many
|
||||||
|
tables (#7577)
|
||||||
|
|
||||||
|
* Fixes performance issue in `GetForeignKeyOids` on systems with many
|
||||||
|
constraints (#7580)
|
||||||
|
|
||||||
|
* Fixes performance issue when distributing a table that depends on an
|
||||||
|
extension (#7574)
|
||||||
|
|
||||||
|
* Fixes performance issue when creating distributed tables if many already
|
||||||
|
exist (#7575)
|
||||||
|
|
||||||
|
* Fixes a crash caused by some form of `ALTER TABLE ADD COLUMN` statements. When
|
||||||
|
adding multiple columns, if one of the `ADD COLUMN` statements contains a
|
||||||
|
`FOREIGN` constraint ommitting the referenced
|
||||||
|
columns in the statement, a `SEGFAULT` occurs (#7522)
|
||||||
|
|
||||||
|
* Fixes assertion failure in maintenance daemon during Citus upgrades (#7537)
|
||||||
|
|
||||||
|
* Fixes segmentation fault when using `CASE WHEN` in `DO` block functions
|
||||||
|
(#7554)
|
||||||
|
|
||||||
|
* Fixes undefined behavior in `master_disable_node` due to argument mismatch
|
||||||
|
(#7492)
|
||||||
|
|
||||||
|
* Fixes incorrect propagating of `GRANTED BY` and `CASCADE/RESTRICT` clauses
|
||||||
|
for `REVOKE` statements (#7451)
|
||||||
|
|
||||||
|
* Fixes the incorrect column count after `ALTER TABLE` (#7379)
|
||||||
|
|
||||||
|
* Fixes timeout when underlying socket is changed for an inter-node connection
|
||||||
|
(#7377)
|
||||||
|
|
||||||
|
* Fixes memory leaks (#7441, #7440)
|
||||||
|
|
||||||
|
* Fixes leaking of memory and memory contexts when tracking foreign keys between
|
||||||
|
Citus tables (#7236)
|
||||||
|
|
||||||
|
* Fixes a potential segfault for background rebalancer (#7694)
|
||||||
|
|
||||||
|
* Fixes potential `NULL` dereference in casual clocks (#7704)
|
||||||
|
|
||||||
### citus v13.0.4 (May 29th, 2025) ###
|
### citus v13.0.4 (May 29th, 2025) ###
|
||||||
|
|
||||||
* Fixes an issue detected using address sanitizer (#7966)
|
* Fixes an issue detected using address sanitizer (#7966)
|
||||||
|
@ -5,6 +132,16 @@
|
||||||
* Error out for queries with outer joins and pseudoconstant quals in versions
|
* Error out for queries with outer joins and pseudoconstant quals in versions
|
||||||
prior to PG 17 (#7937)
|
prior to PG 17 (#7937)
|
||||||
|
|
||||||
|
### citus v12.1.8 (May 29, 2025) ###
|
||||||
|
|
||||||
|
* Fixes a crash in left outer joins that can happen when there is an an
|
||||||
|
aggregate on a column from the inner side of the join (#7904)
|
||||||
|
|
||||||
|
* Fixes an issue detected using address sanitizer (#7965)
|
||||||
|
|
||||||
|
* Fixes a crash when executing a prepared CALL, which is not pure SQL but
|
||||||
|
available with some drivers like npgsql and jpgdbc (#7288)
|
||||||
|
|
||||||
### citus v13.0.3 (March 20th, 2025) ###
|
### citus v13.0.3 (March 20th, 2025) ###
|
||||||
|
|
||||||
* Fixes a version bump issue in 13.0.2
|
* Fixes a version bump issue in 13.0.2
|
||||||
|
@ -34,6 +171,14 @@
|
||||||
* Fixes a bug that breaks router updates on distributed tables
|
* Fixes a bug that breaks router updates on distributed tables
|
||||||
when a reference table is used in the subquery (#7897)
|
when a reference table is used in the subquery (#7897)
|
||||||
|
|
||||||
|
### citus v12.1.7 (Feb 6, 2025) ###
|
||||||
|
|
||||||
|
* Fixes a crash that happens because of unsafe catalog access when re-assigning
|
||||||
|
the global pid after `application_name` changes (#7791)
|
||||||
|
|
||||||
|
* Prevents crashes when another extension skips executing the
|
||||||
|
`ClientAuthentication_hook` of Citus. (#7836)
|
||||||
|
|
||||||
### citus v13.0.1 (February 4th, 2025) ###
|
### citus v13.0.1 (February 4th, 2025) ###
|
||||||
|
|
||||||
* Drops support for PostgreSQL 14 (#7753)
|
* Drops support for PostgreSQL 14 (#7753)
|
||||||
|
@ -76,6 +221,12 @@
|
||||||
* Improves `citus_move_shard_placement()` to fail early if there is a new node
|
* Improves `citus_move_shard_placement()` to fail early if there is a new node
|
||||||
without reference tables yet (#7467)
|
without reference tables yet (#7467)
|
||||||
|
|
||||||
|
### citus v12.1.6 (Nov 14, 2024) ###
|
||||||
|
|
||||||
|
* Propagates `SECURITY LABEL .. ON ROLE` statements (#7304)
|
||||||
|
|
||||||
|
* Fixes crash caused by running queries with window partition (#7718)
|
||||||
|
|
||||||
### citus v12.1.5 (July 17, 2024) ###
|
### citus v12.1.5 (July 17, 2024) ###
|
||||||
|
|
||||||
* Adds support for MERGE commands with single shard distributed target tables
|
* Adds support for MERGE commands with single shard distributed target tables
|
||||||
|
@ -93,9 +244,8 @@
|
||||||
* Allows overwriting host name for all inter-node connections by
|
* Allows overwriting host name for all inter-node connections by
|
||||||
supporting "host" parameter in citus.node_conninfo (#7541)
|
supporting "host" parameter in citus.node_conninfo (#7541)
|
||||||
|
|
||||||
* Changes the order in which the locks are acquired for the target and
|
* Avoids distributed deadlocks by changing the order in which the locks are
|
||||||
reference tables, when a modify request is initiated from a worker
|
acquired for the target and reference tables (#7542)
|
||||||
node that is not the "FirstWorkerNode" (#7542)
|
|
||||||
|
|
||||||
* Fixes a performance issue when distributing a table that depends on an
|
* Fixes a performance issue when distributing a table that depends on an
|
||||||
extension (#7574)
|
extension (#7574)
|
||||||
|
@ -128,10 +278,120 @@
|
||||||
|
|
||||||
* Logs username in the failed connection message (#7432)
|
* Logs username in the failed connection message (#7432)
|
||||||
|
|
||||||
|
### citus v11.0.10 (February 15, 2024) ###
|
||||||
|
|
||||||
|
* Removes pg_send_cancellation and all references (#7135)
|
||||||
|
|
||||||
### citus v12.1.2 (February 12, 2024) ###
|
### citus v12.1.2 (February 12, 2024) ###
|
||||||
|
|
||||||
* Fixes the incorrect column count after ALTER TABLE (#7379)
|
* Fixes the incorrect column count after ALTER TABLE (#7379)
|
||||||
|
|
||||||
|
### citus v12.0.1 (July 11, 2023) ###
|
||||||
|
|
||||||
|
* Fixes incorrect default value assumption for VACUUM(PROCESS_TOAST) #7122)
|
||||||
|
|
||||||
|
* Fixes a bug that causes an unexpected error when adding a column
|
||||||
|
with a NULL constraint (#7093)
|
||||||
|
|
||||||
|
* Fixes a bug that could cause COPY logic to skip data in case of OOM (#7152)
|
||||||
|
|
||||||
|
* Fixes a bug with deleting colocation groups (#6929)
|
||||||
|
|
||||||
|
* Fixes memory and memory contexts leaks in Foreign Constraint Graphs (#7236)
|
||||||
|
|
||||||
|
* Fixes shard size bug with too many shards (#7018)
|
||||||
|
|
||||||
|
* Fixes the incorrect column count after ALTER TABLE (#7379)
|
||||||
|
|
||||||
|
* Improves citus_tables view performance (#7050)
|
||||||
|
|
||||||
|
* Makes sure to disallow creating a replicated distributed table
|
||||||
|
concurrently (#7219)
|
||||||
|
|
||||||
|
* Removes pg_send_cancellation and all references (#7135)
|
||||||
|
|
||||||
|
### citus v11.3.1 (February 12, 2024) ###
|
||||||
|
|
||||||
|
* Disallows MERGE when the query prunes down to zero shards (#6946)
|
||||||
|
|
||||||
|
* Fixes a bug related to non-existent objects in DDL commands (#6984)
|
||||||
|
|
||||||
|
* Fixes a bug that could cause COPY logic to skip data in case of OOM (#7152)
|
||||||
|
|
||||||
|
* Fixes a bug with deleting colocation groups (#6929)
|
||||||
|
|
||||||
|
* Fixes incorrect results on fetching scrollable with hold cursors (#7014)
|
||||||
|
|
||||||
|
* Fixes memory and memory context leaks in Foreign Constraint Graphs (#7236)
|
||||||
|
|
||||||
|
* Fixes replicate reference tables task fail when user is superuser (#6930)
|
||||||
|
|
||||||
|
* Fixes the incorrect column count after ALTER TABLE (#7379)
|
||||||
|
|
||||||
|
* Improves citus_shard_sizes performance (#7050)
|
||||||
|
|
||||||
|
* Makes sure to disallow creating a replicated distributed table
|
||||||
|
concurrently (#7219)
|
||||||
|
|
||||||
|
* Removes pg_send_cancellation and all references (#7135)
|
||||||
|
|
||||||
|
### citus v11.2.2 (February 12, 2024) ###
|
||||||
|
|
||||||
|
* Fixes a bug in background shard rebalancer where the replicate
|
||||||
|
reference tables task fails if the current user is not a superuser (#6930)
|
||||||
|
|
||||||
|
* Fixes a bug related to non-existent objects in DDL commands (#6984)
|
||||||
|
|
||||||
|
* Fixes a bug that could cause COPY logic to skip data in case of OOM (#7152)
|
||||||
|
|
||||||
|
* Fixes a bug with deleting colocation groups (#6929)
|
||||||
|
|
||||||
|
* Fixes incorrect results on fetching scrollable with hold cursors (#7014)
|
||||||
|
|
||||||
|
* Fixes memory and memory context leaks in Foreign Constraint Graphs (#7236)
|
||||||
|
|
||||||
|
* Fixes the incorrect column count after ALTER TABLE (#7379)
|
||||||
|
|
||||||
|
* Improves failure handling of distributed execution (#7090)
|
||||||
|
|
||||||
|
* Makes sure to disallow creating a replicated distributed table
|
||||||
|
concurrently (#7219)
|
||||||
|
|
||||||
|
* Removes pg_send_cancellation (#7135)
|
||||||
|
|
||||||
|
### citus v11.1.7 (February 12, 2024) ###
|
||||||
|
|
||||||
|
* Fixes memory and memory context leaks in Foreign Constraint Graphs (#7236)
|
||||||
|
|
||||||
|
* Fixes a bug related to non-existent objects in DDL commands (#6984)
|
||||||
|
|
||||||
|
* Fixes a bug that could cause COPY logic to skip data in case of OOM (#7152)
|
||||||
|
|
||||||
|
* Fixes a bug with deleting colocation groups (#6929)
|
||||||
|
|
||||||
|
* Fixes incorrect results on fetching scrollable with hold cursors (#7014)
|
||||||
|
|
||||||
|
* Fixes the incorrect column count after ALTER TABLE (#7379)
|
||||||
|
|
||||||
|
* Improves failure handling of distributed execution (#7090)
|
||||||
|
|
||||||
|
* Makes sure to disallow creating a replicated distributed table
|
||||||
|
concurrently (#7219)
|
||||||
|
|
||||||
|
* Removes pg_send_cancellation and all references (#7135)
|
||||||
|
|
||||||
|
### citus v11.0.9 (February 12, 2024) ###
|
||||||
|
|
||||||
|
* Fixes a bug that could cause COPY logic to skip data in case of OOM (#7152)
|
||||||
|
|
||||||
|
* Fixes a bug with deleting colocation groups (#6929)
|
||||||
|
|
||||||
|
* Fixes memory and memory context leaks in Foreign Constraint Graphs (#7236)
|
||||||
|
|
||||||
|
* Fixes the incorrect column count after ALTER TABLE (#7462)
|
||||||
|
|
||||||
|
* Improve failure handling of distributed execution (#7090)
|
||||||
|
|
||||||
### citus v12.1.1 (November 9, 2023) ###
|
### citus v12.1.1 (November 9, 2023) ###
|
||||||
|
|
||||||
* Fixes leaking of memory and memory contexts in Citus foreign key cache
|
* Fixes leaking of memory and memory contexts in Citus foreign key cache
|
||||||
|
|
121
CONTRIBUTING.md
|
@ -11,6 +11,52 @@ sign a Contributor License Agreement (CLA). For an explanation of
|
||||||
why we ask this as well as instructions for how to proceed, see the
|
why we ask this as well as instructions for how to proceed, see the
|
||||||
[Microsoft CLA](https://cla.opensource.microsoft.com/).
|
[Microsoft CLA](https://cla.opensource.microsoft.com/).
|
||||||
|
|
||||||
|
### Devcontainer / Github Codespaces
|
||||||
|
|
||||||
|
The easiest way to start contributing is via our devcontainer. This container works both locally in visual studio code with docker-desktop/docker-for-mac as well as [Github Codespaces](https://github.com/features/codespaces). To open the project in vscode you will need the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). For codespaces you will need to [create a new codespace](https://codespace.new/citusdata/citus).
|
||||||
|
|
||||||
|
With the extension installed you can run the following from the command pallet to get started
|
||||||
|
|
||||||
|
```
|
||||||
|
> Dev Containers: Clone Repository in Container Volume...
|
||||||
|
```
|
||||||
|
|
||||||
|
In the subsequent popup paste the url to the repo and hit enter.
|
||||||
|
|
||||||
|
```
|
||||||
|
https://github.com/citusdata/citus
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create an isolated Workspace in vscode, complete with all tools required to build, test and run the Citus extension. We keep this container up to date with the supported postgres versions as well as the exact versions of tooling we use.
|
||||||
|
|
||||||
|
To quickly start we suggest splitting your terminal once to have two shells. The left one in the `/workspaces/citus`, the second one changed to `/data`. The left terminal will be used to interact with the project, the right one with a testing cluster.
|
||||||
|
|
||||||
|
To get citus installed from source we run `make install -s` in the first terminal. Once installed you can start a Citus cluster in the second terminal via `citus_dev make citus`. The cluster will run in the background, and can be interacted with via `citus_dev`. To get an overview of the available commands.
|
||||||
|
|
||||||
|
With the Citus cluster running you can connect to the coordinator in the first terminal via `psql -p9700`. Because the coordinator is the most common entrypoint the `PGPORT` environment is set accordingly, so a simple `psql` will connect directly to the coordinator.
|
||||||
|
|
||||||
|
### Debugging in the VS code
|
||||||
|
|
||||||
|
1. Start Debugging: Press F5 in VS Code to start debugging. When prompted, you'll need to attach the debugger to the appropriate PostgreSQL process.
|
||||||
|
|
||||||
|
2. Identify the Process: If you're running a psql command, take note of the PID that appears in your psql prompt. For example:
|
||||||
|
```
|
||||||
|
[local] citus@citus:9700 (PID: 5436)=#
|
||||||
|
```
|
||||||
|
This PID (5436 in this case) indicates the process that you should attach the debugger to.
|
||||||
|
If you are uncertain about which process to attach, you can list all running PostgreSQL processes using the following command:
|
||||||
|
```
|
||||||
|
ps aux | grep postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for the process associated with the PID you noted. For example:
|
||||||
|
```
|
||||||
|
citus 5436 0.0 0.0 0 0 ? S 14:00 0:00 postgres: citus citus
|
||||||
|
```
|
||||||
|
4. Attach the Debugger: Once you've identified the correct PID, select that process when prompted in VS Code to attach the debugger. You should now be able to debug the PostgreSQL session tied to the psql command.
|
||||||
|
|
||||||
|
5. Set Breakpoints and Debug: With the debugger attached, you can set breakpoints within the code. This allows you to step through the code execution, inspect variables, and fully debug the PostgreSQL instance running in your container.
|
||||||
|
|
||||||
### Getting and building
|
### Getting and building
|
||||||
|
|
||||||
[PostgreSQL documentation](https://www.postgresql.org/support/versioning/) has a
|
[PostgreSQL documentation](https://www.postgresql.org/support/versioning/) has a
|
||||||
|
@ -41,6 +87,8 @@ that are missing in earlier minor versions.
|
||||||
|
|
||||||
cd citus
|
cd citus
|
||||||
./configure
|
./configure
|
||||||
|
# If you have already installed the project, you need to clean it first
|
||||||
|
make clean
|
||||||
make
|
make
|
||||||
make install
|
make install
|
||||||
# Optionally, you might instead want to use `make install-all`
|
# Optionally, you might instead want to use `make install-all`
|
||||||
|
@ -79,6 +127,8 @@ that are missing in earlier minor versions.
|
||||||
git clone https://github.com/citusdata/citus.git
|
git clone https://github.com/citusdata/citus.git
|
||||||
cd citus
|
cd citus
|
||||||
./configure
|
./configure
|
||||||
|
# If you have already installed the project previously, you need to clean it first
|
||||||
|
make clean
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
# Optionally, you might instead want to use `sudo make install-all`
|
# Optionally, you might instead want to use `sudo make install-all`
|
||||||
|
@ -129,6 +179,8 @@ that are missing in earlier minor versions.
|
||||||
git clone https://github.com/citusdata/citus.git
|
git clone https://github.com/citusdata/citus.git
|
||||||
cd citus
|
cd citus
|
||||||
PG_CONFIG=/usr/pgsql-14/bin/pg_config ./configure
|
PG_CONFIG=/usr/pgsql-14/bin/pg_config ./configure
|
||||||
|
# If you have already installed the project previously, you need to clean it first
|
||||||
|
make clean
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
# Optionally, you might instead want to use `sudo make install-all`
|
# Optionally, you might instead want to use `sudo make install-all`
|
||||||
|
@ -145,43 +197,7 @@ that are missing in earlier minor versions.
|
||||||
|
|
||||||
### Following our coding conventions
|
### Following our coding conventions
|
||||||
|
|
||||||
CircleCI will automatically reject any PRs which do not follow our coding
|
Our coding conventions are documented in [STYLEGUIDE.md](STYLEGUIDE.md).
|
||||||
conventions. The easiest way to ensure your PR adheres to those conventions is
|
|
||||||
to use the [citus_indent](https://github.com/citusdata/tools/tree/develop/uncrustify)
|
|
||||||
tool. This tool uses `uncrustify` under the hood.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Uncrustify changes the way it formats code every release a bit. To make sure
|
|
||||||
# everyone formats consistently we use version 0.68.1:
|
|
||||||
curl -L https://github.com/uncrustify/uncrustify/archive/uncrustify-0.68.1.tar.gz | tar xz
|
|
||||||
cd uncrustify-uncrustify-0.68.1/
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
make -j5
|
|
||||||
sudo make install
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
git clone https://github.com/citusdata/tools.git
|
|
||||||
cd tools
|
|
||||||
make uncrustify/.install
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you've done that, you can run the `make reindent` command from the top
|
|
||||||
directory to recursively check and correct the style of any source files in the
|
|
||||||
current directory. Under the hood, `make reindent` will run `citus_indent` and
|
|
||||||
some other style corrections for you.
|
|
||||||
|
|
||||||
You can also run the following in the directory of this repository to
|
|
||||||
automatically format all the files that you have changed before committing:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat > .git/hooks/pre-commit << __EOF__
|
|
||||||
#!/bin/bash
|
|
||||||
citus_indent --check --diff || { citus_indent --diff; exit 1; }
|
|
||||||
__EOF__
|
|
||||||
chmod +x .git/hooks/pre-commit
|
|
||||||
```
|
|
||||||
|
|
||||||
### Making SQL changes
|
### Making SQL changes
|
||||||
|
|
||||||
|
@ -234,3 +250,34 @@ Any other SQL you can put directly in the main sql file, e.g.
|
||||||
### Running tests
|
### Running tests
|
||||||
|
|
||||||
See [`src/test/regress/README.md`](https://github.com/citusdata/citus/blob/master/src/test/regress/README.md)
|
See [`src/test/regress/README.md`](https://github.com/citusdata/citus/blob/master/src/test/regress/README.md)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
User-facing documentation is published on [docs.citusdata.com](https://docs.citusdata.com/). When adding a new feature, function, or setting, you can open a pull request or issue against the [Citus docs repo](https://github.com/citusdata/citus_docs/).
|
||||||
|
|
||||||
|
Detailed descriptions of the implementation for Citus developers are provided in the [Citus Technical Documentation](src/backend/distributed/README.md). It is currently a single file for ease of searching. Please update the documentation if you make any changes that affect the design or add major new features.
|
||||||
|
|
||||||
|
# Making a pull request ready for reviews
|
||||||
|
|
||||||
|
Asking for help and asking for reviews are two different things. When you're asking for help, you're asking for someone to help you with something that you're not expected to know.
|
||||||
|
|
||||||
|
But when you're asking for a review, you're asking for someone to review your work and provide feedback. So, when you're asking for a review, you're expected to make sure that:
|
||||||
|
|
||||||
|
* Your changes don't perform **unnecessary line addition / deletions / style changes on unrelated files / lines**.
|
||||||
|
|
||||||
|
* All CI jobs are **passing**, including **style checks** and **flaky test detection jobs**. Note that if you're an external contributor, you don't have to wait CI jobs to run (and finish) because they don't get automatically triggered for external contributors.
|
||||||
|
|
||||||
|
* Your PR has necessary amount of **tests** and that they're passing.
|
||||||
|
|
||||||
|
* You separated as much as possible work into **separate PRs**, e.g., a prerequisite bugfix, a refactoring etc..
|
||||||
|
|
||||||
|
* Your PR doesn't introduce a typo or something that you can easily fix yourself.
|
||||||
|
|
||||||
|
* After all CI jobs pass, code-coverage measurement job (CodeCov as of today) then kicks in. That's why it's important to make the **tests passing** first. At that point, you're expected to check **CodeCov annotations** that can be seen in the **Files Changed** tab and expected to make sure that it doesn't complain about any lines that are not covered. For example, it's ok if CodeCov complains about an `ereport()` call that you put for an "unexpected-but-better-than-crashing" case, but it's not ok if it complains about an uncovered `if` branch that you added.
|
||||||
|
|
||||||
|
* And finally, perform a **self-review** to make sure that:
|
||||||
|
* Code and code-comments reflects the idea **without requiring an extra explanation** via a chat message / email / PR comment.
|
||||||
|
This is important because we don't expect developers to reach out to author / read about the whole discussion in the PR to understand the idea behind a commit merged into `main` branch.
|
||||||
|
* PR description is clear enough.
|
||||||
|
* If-and-only-if you're **introducing a user facing change / bugfix**, your PR has a line that starts with `DESCRIPTION: <Present simple tense word that starts with a capital letter, e.g., Adds support for / Fixes / Disallows>`.
|
||||||
|
* **Commit messages** are clear enough if the commits are doing logically different things.
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Devcontainer
|
||||||
|
|
||||||
|
## Coredumps
|
||||||
|
When postgres/citus crashes, there is the option to create a coredump. This is useful for debugging the issue. Coredumps are enabled in the devcontainer by default. However, not all environments are configured correctly out of the box. The most important configuration that is not standardized is the `core_pattern`. The configuration can be verified from the container, however, you cannot change this setting from inside the container as the filesystem containing this setting is in read only mode while inside the container.
|
||||||
|
|
||||||
|
To verify if corefiles are written run the following command in a terminal. This shows the filename pattern with which the corefile will be written.
|
||||||
|
```bash
|
||||||
|
cat /proc/sys/kernel/core_pattern
|
||||||
|
```
|
||||||
|
|
||||||
|
This should be configured with a relative path or simply a simple filename, such as `core`. When your environment shows an absolute path you will need to change this setting. How to change this setting depends highly on the underlying system as the setting needs to be changed on the kernel of the host running the container.
|
||||||
|
|
||||||
|
You can put any pattern in `/proc/sys/kernel/core_pattern` as you see fit. eg. You can add the PID to the core pattern in one of two ways;
|
||||||
|
- You either include `%p` in the core_pattern. This gets substituted with the PID of the crashing process.
|
||||||
|
- Alternatively you could set `/proc/sys/kernel/core_uses_pid` to `1` in the same way as you set `core_pattern`. This will append the PID to the corefile if `%p` is not explicitly contained in the core_pattern.
|
||||||
|
|
||||||
|
When a coredump is written you can use the debug/launch configuration `Open core file` which is preconfigured in the devcontainer. This will open a fileprompt that lists all coredumps that are found in your workspace. When you want to debug coredumps from `citus_dev` that are run in your `/data` directory, you can add the data directory to your workspace. In the command pallet of vscode you can run `>Workspace: Add Folder to Workspace...` and select the `/data` directory. This will allow you to open the coredumps from the `/data` directory in the `Open core file` debug configuration.
|
||||||
|
|
||||||
|
### Windows (docker desktop)
|
||||||
|
When running in docker desktop on windows you will most likely need to change this setting. The linux guest in WSL2 that runs your container is the `docker-desktop` environment. The easiest way to get onto the host, where you can change this setting, is to open a powershell window and verify you have the docker-desktop environment listed.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
wsl --list
|
||||||
|
```
|
||||||
|
|
||||||
|
Among others this should list both `docker-desktop` and `docker-desktop-data`. You can then open a shell in the `docker-desktop` environment.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
wsl -d docker-desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside this shell you can verify that you have the right environment by running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /proc/sys/kernel/core_pattern
|
||||||
|
```
|
||||||
|
|
||||||
|
This should show the same configuration as the one you see inside the devcontainer. You can then change the setting by running the following command.
|
||||||
|
This will change the setting for the current session. If you want to make the change permanent you will need to add this to a startup script.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "core" > /proc/sys/kernel/core_pattern
|
||||||
|
```
|
3
Makefile
|
@ -61,6 +61,7 @@ check-style:
|
||||||
|
|
||||||
# depend on install-all so that downgrade scripts are installed as well
|
# depend on install-all so that downgrade scripts are installed as well
|
||||||
check: all install-all
|
check: all install-all
|
||||||
$(MAKE) -C src/test/regress check-full
|
# explicetely does not use $(MAKE) to avoid parallelism
|
||||||
|
make -C src/test/regress check
|
||||||
|
|
||||||
.PHONY: all check clean install install-downgrades install-all
|
.PHONY: all check clean install install-downgrades install-all
|
||||||
|
|
22
README.md
|
@ -1,10 +1,10 @@
|
||||||
| **<br/>The Citus database is 100% open source.<br/><img width=1000/><br/>Learn what's new in the [Citus 12.0 release blog](https://www.citusdata.com/blog/2023/07/18/citus-12-schema-based-sharding-comes-to-postgres/) and the [Citus Updates page](https://www.citusdata.com/updates/).<br/><br/>**|
|
| **<br/>The Citus database is 100% open source.<br/><img width=1000/><br/>Learn what's new in the [Citus 13.0 release blog](https://www.citusdata.com/blog/2025/02/06/distribute-postgresql-17-with-citus-13/) and the [Citus Updates page](https://www.citusdata.com/updates/).<br/><br/>**|
|
||||||
|---|
|
|---|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[](https://docs.citusdata.com/)
|
[](https://docs.citusdata.com/)
|
||||||
[](https://stackoverflow.com/questions/tagged/citus)
|
[](https://stackoverflow.com/questions/tagged/citus)
|
||||||
|
@ -31,7 +31,7 @@ You can use these Citus superpowers to make your Postgres database scale-out rea
|
||||||
|
|
||||||
Our [SIGMOD '21](https://2021.sigmod.org/) paper [Citus: Distributed PostgreSQL for Data-Intensive Applications](https://doi.org/10.1145/3448016.3457551) gives a more detailed look into what Citus is, how it works, and why it works that way.
|
Our [SIGMOD '21](https://2021.sigmod.org/) paper [Citus: Distributed PostgreSQL for Data-Intensive Applications](https://doi.org/10.1145/3448016.3457551) gives a more detailed look into what Citus is, how it works, and why it works that way.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Since Citus is an extension to Postgres, you can use Citus with the latest Postgres versions. And Citus works seamlessly with the PostgreSQL tools and extensions you are already familiar with.
|
Since Citus is an extension to Postgres, you can use Citus with the latest Postgres versions. And Citus works seamlessly with the PostgreSQL tools and extensions you are already familiar with.
|
||||||
|
|
||||||
|
@ -95,14 +95,14 @@ Install packages on Ubuntu / Debian:
|
||||||
```bash
|
```bash
|
||||||
curl https://install.citusdata.com/community/deb.sh > add-citus-repo.sh
|
curl https://install.citusdata.com/community/deb.sh > add-citus-repo.sh
|
||||||
sudo bash add-citus-repo.sh
|
sudo bash add-citus-repo.sh
|
||||||
sudo apt-get -y install postgresql-15-citus-12.0
|
sudo apt-get -y install postgresql-17-citus-13.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Install packages on CentOS / Red Hat:
|
Install packages on Red Hat:
|
||||||
```bash
|
```bash
|
||||||
curl https://install.citusdata.com/community/rpm.sh > add-citus-repo.sh
|
curl https://install.citusdata.com/community/rpm.sh > add-citus-repo.sh
|
||||||
sudo bash add-citus-repo.sh
|
sudo bash add-citus-repo.sh
|
||||||
sudo yum install -y citus120_15
|
sudo yum install -y citus130_17
|
||||||
```
|
```
|
||||||
|
|
||||||
To add Citus to your local PostgreSQL database, add the following to `postgresql.conf`:
|
To add Citus to your local PostgreSQL database, add the following to `postgresql.conf`:
|
||||||
|
@ -423,12 +423,14 @@ A Citus database cluster grows from a single PostgreSQL node into a cluster by a
|
||||||
|
|
||||||
Data in distributed tables is stored in “shards”, which are actually just regular PostgreSQL tables on the worker nodes. When querying a distributed table on the coordinator node, Citus will send regular SQL queries to the worker nodes. That way, all the usual PostgreSQL optimizations and extensions can automatically be used with Citus.
|
Data in distributed tables is stored in “shards”, which are actually just regular PostgreSQL tables on the worker nodes. When querying a distributed table on the coordinator node, Citus will send regular SQL queries to the worker nodes. That way, all the usual PostgreSQL optimizations and extensions can automatically be used with Citus.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
When you send a query in which all (co-located) distributed tables have the same filter on the distribution column, Citus will automatically detect that and send the whole query to the worker node that stores the data. That way, arbitrarily complex queries are supported with minimal routing overhead, which is especially useful for scaling transactional workloads. If queries do not have a specific filter, each shard is queried in parallel, which is especially useful in analytical workloads. The Citus distributed executor is adaptive and is designed to handle both query types at the same time on the same system under high concurrency, which enables large-scale mixed workloads.
|
When you send a query in which all (co-located) distributed tables have the same filter on the distribution column, Citus will automatically detect that and send the whole query to the worker node that stores the data. That way, arbitrarily complex queries are supported with minimal routing overhead, which is especially useful for scaling transactional workloads. If queries do not have a specific filter, each shard is queried in parallel, which is especially useful in analytical workloads. The Citus distributed executor is adaptive and is designed to handle both query types at the same time on the same system under high concurrency, which enables large-scale mixed workloads.
|
||||||
|
|
||||||
The schema and metadata of distributed tables and reference tables are automatically synchronized to all the nodes in the cluster. That way, you can connect to any node to run distributed queries. Schema changes and cluster administration still need to go through the coordinator.
|
The schema and metadata of distributed tables and reference tables are automatically synchronized to all the nodes in the cluster. That way, you can connect to any node to run distributed queries. Schema changes and cluster administration still need to go through the coordinator.
|
||||||
|
|
||||||
|
Detailed descriptions of the implementation for Citus developers are provided in the [Citus Technical Documentation](src/backend/distributed/README.md).
|
||||||
|
|
||||||
## When to use Citus
|
## When to use Citus
|
||||||
|
|
||||||
Citus is uniquely capable of scaling both analytical and transactional workloads with up to petabytes of data. Use cases in which Citus is commonly used:
|
Citus is uniquely capable of scaling both analytical and transactional workloads with up to petabytes of data. Use cases in which Citus is commonly used:
|
||||||
|
@ -438,21 +440,21 @@ Citus is uniquely capable of scaling both analytical and transactional workloads
|
||||||
|
|
||||||
The advanced parallel, distributed query engine in Citus combined with PostgreSQL features such as [array types](https://www.postgresql.org/docs/current/arrays.html), [JSONB](https://www.postgresql.org/docs/current/datatype-json.html), [lateral joins](https://heap.io/blog/engineering/postgresqls-powerful-new-join-type-lateral), and extensions like [HyperLogLog](https://github.com/citusdata/postgresql-hll) and [TopN](https://github.com/citusdata/postgresql-topn) allow you to build responsive analytics dashboards no matter how many customers or how much data you have.
|
The advanced parallel, distributed query engine in Citus combined with PostgreSQL features such as [array types](https://www.postgresql.org/docs/current/arrays.html), [JSONB](https://www.postgresql.org/docs/current/datatype-json.html), [lateral joins](https://heap.io/blog/engineering/postgresqls-powerful-new-join-type-lateral), and extensions like [HyperLogLog](https://github.com/citusdata/postgresql-hll) and [TopN](https://github.com/citusdata/postgresql-topn) allow you to build responsive analytics dashboards no matter how many customers or how much data you have.
|
||||||
|
|
||||||
Example real-time analytics users: [Algolia](https://www.citusdata.com/customers/algolia), [Heap](https://www.citusdata.com/customers/heap)
|
Example real-time analytics users: [Algolia](https://www.citusdata.com/customers/algolia)
|
||||||
|
|
||||||
- **[Time series data](http://docs.citusdata.com/en/stable/use_cases/timeseries.html)**:
|
- **[Time series data](http://docs.citusdata.com/en/stable/use_cases/timeseries.html)**:
|
||||||
Citus enables you to process and analyze very large amounts of time series data. The biggest Citus clusters store well over a petabyte of time series data and ingest terabytes per day.
|
Citus enables you to process and analyze very large amounts of time series data. The biggest Citus clusters store well over a petabyte of time series data and ingest terabytes per day.
|
||||||
|
|
||||||
Citus integrates seamlessly with [Postgres table partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) and has [built-in functions for partitioning by time](https://www.citusdata.com/blog/2021/10/22/how-to-scale-postgres-for-time-series-data-with-citus/), which can speed up queries and writes on time series tables. You can take advantage of Citus’s parallel, distributed query engine for fast analytical queries, and use the built-in *columnar storage* to compress old partitions.
|
Citus integrates seamlessly with [Postgres table partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html) and has [built-in functions for partitioning by time](https://www.citusdata.com/blog/2021/10/22/how-to-scale-postgres-for-time-series-data-with-citus/), which can speed up queries and writes on time series tables. You can take advantage of Citus’s parallel, distributed query engine for fast analytical queries, and use the built-in *columnar storage* to compress old partitions.
|
||||||
|
|
||||||
Example users: [MixRank](https://www.citusdata.com/customers/mixrank), [Windows team](https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/architecting-petabyte-scale-analytics-by-scaling-out-postgres-on/ba-p/969685)
|
Example users: [MixRank](https://www.citusdata.com/customers/mixrank)
|
||||||
|
|
||||||
- **[Software-as-a-service (SaaS) applications](http://docs.citusdata.com/en/stable/use_cases/multi_tenant.html)**:
|
- **[Software-as-a-service (SaaS) applications](http://docs.citusdata.com/en/stable/use_cases/multi_tenant.html)**:
|
||||||
SaaS and other multi-tenant applications need to be able to scale their database as the number of tenants/customers grows. Citus enables you to transparently shard a complex data model by the tenant dimension, so your database can grow along with your business.
|
SaaS and other multi-tenant applications need to be able to scale their database as the number of tenants/customers grows. Citus enables you to transparently shard a complex data model by the tenant dimension, so your database can grow along with your business.
|
||||||
|
|
||||||
By distributing tables along a tenant ID column and co-locating data for the same tenant, Citus can horizontally scale complex (tenant-scoped) queries, transactions, and foreign key graphs. Reference tables and distributed DDL commands make database management a breeze compared to manual sharding. On top of that, you have a built-in distributed query engine for doing cross-tenant analytics inside the database.
|
By distributing tables along a tenant ID column and co-locating data for the same tenant, Citus can horizontally scale complex (tenant-scoped) queries, transactions, and foreign key graphs. Reference tables and distributed DDL commands make database management a breeze compared to manual sharding. On top of that, you have a built-in distributed query engine for doing cross-tenant analytics inside the database.
|
||||||
|
|
||||||
Example multi-tenant SaaS users: [Copper](https://www.citusdata.com/customers/copper), [Salesloft](https://fivetran.com/case-studies/replicating-sharded-databases-a-case-study-of-salesloft-citus-data-and-fivetran), [ConvertFlow](https://www.citusdata.com/customers/convertflow)
|
Example multi-tenant SaaS users: [Salesloft](https://fivetran.com/case-studies/replicating-sharded-databases-a-case-study-of-salesloft-citus-data-and-fivetran), [ConvertFlow](https://www.citusdata.com/customers/convertflow)
|
||||||
|
|
||||||
- **[Microservices](https://docs.citusdata.com/en/stable/get_started/tutorial_microservices.html)**: Citus supports schema based sharding, which allows distributing regular database schemas across many machines. This sharding methodology fits nicely with typical Microservices architecture, where storage is fully owned by the service hence can’t share the same schema definition with other tenants. Citus allows distributing horizontally scalable state across services, solving one of the [main problems](https://stackoverflow.blog/2020/11/23/the-macro-problem-with-microservices/) of microservices.
|
- **[Microservices](https://docs.citusdata.com/en/stable/get_started/tutorial_microservices.html)**: Citus supports schema based sharding, which allows distributing regular database schemas across many machines. This sharding methodology fits nicely with typical Microservices architecture, where storage is fully owned by the service hence can’t share the same schema definition with other tenants. Citus allows distributing horizontally scalable state across services, solving one of the [main problems](https://stackoverflow.blog/2020/11/23/the-macro-problem-with-microservices/) of microservices.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
# Coding style
|
||||||
|
|
||||||
|
The existing code-style in our code-base is not super consistent. There are multiple reasons for that. One big reason is because our code-base is relatively old and our standards have changed over time. The second big reason is that our style-guide is different from style-guide of Postgres and some code is copied from Postgres source code and is slightly modified. The below rules are for new code. If you're changing existing code that uses a different style, use your best judgement to decide if you use the rules here or if you match the existing style.
|
||||||
|
|
||||||
|
## Using citus_indent
|
||||||
|
|
||||||
|
CI pipeline will automatically reject any PRs which do not follow our coding
|
||||||
|
conventions. The easiest way to ensure your PR adheres to those conventions is
|
||||||
|
to use the [citus_indent](https://github.com/citusdata/tools/tree/develop/uncrustify)
|
||||||
|
tool. This tool uses `uncrustify` under the hood.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Uncrustify changes the way it formats code every release a bit. To make sure
|
||||||
|
# everyone formats consistently we use version 0.68.1:
|
||||||
|
curl -L https://github.com/uncrustify/uncrustify/archive/uncrustify-0.68.1.tar.gz | tar xz
|
||||||
|
cd uncrustify-uncrustify-0.68.1/
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make -j5
|
||||||
|
sudo make install
|
||||||
|
cd ../..
|
||||||
|
|
||||||
|
git clone https://github.com/citusdata/tools.git
|
||||||
|
cd tools
|
||||||
|
make uncrustify/.install
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you've done that, you can run the `make reindent` command from the top
|
||||||
|
directory to recursively check and correct the style of any source files in the
|
||||||
|
current directory. Under the hood, `make reindent` will run `citus_indent` and
|
||||||
|
some other style corrections for you.
|
||||||
|
|
||||||
|
You can also run the following in the directory of this repository to
|
||||||
|
automatically format all the files that you have changed before committing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > .git/hooks/pre-commit << __EOF__
|
||||||
|
#!/bin/bash
|
||||||
|
citus_indent --check --diff || { citus_indent --diff; exit 1; }
|
||||||
|
__EOF__
|
||||||
|
chmod +x .git/hooks/pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other rules we follow that citus_indent does not enforce
|
||||||
|
|
||||||
|
* We almost always use **CamelCase**, when naming functions, variables etc., **not snake_case**.
|
||||||
|
|
||||||
|
* We also have the habits of using a **lowerCamelCase** for some variables named from their type or from their function name, as shown in the examples:
|
||||||
|
|
||||||
|
```c
|
||||||
|
bool IsCitusExtensionLoaded = false;
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
IsAlterTableRenameStmt(RenameStmt *renameStmt)
|
||||||
|
{
|
||||||
|
AlterTableCmd *alterTableCommand = NULL;
|
||||||
|
..
|
||||||
|
..
|
||||||
|
|
||||||
|
bool isAlterTableRenameStmt = false;
|
||||||
|
..
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* We **start functions with a comment**:
|
||||||
|
|
||||||
|
```c
|
||||||
|
/*
|
||||||
|
* MyNiceFunction <something in present simple tense, e.g., processes / returns / checks / takes X as input / does Y> ..
|
||||||
|
* <some more nice words> ..
|
||||||
|
* <some more nice words> ..
|
||||||
|
*/
|
||||||
|
<static?> <return type>
|
||||||
|
MyNiceFunction(..)
|
||||||
|
{
|
||||||
|
..
|
||||||
|
..
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `#includes` needs to be sorted based on below ordering and then alphabetically and we should not include what we don't need in a file:
|
||||||
|
|
||||||
|
* System includes (eg. #include<...>)
|
||||||
|
* Postgres.h (eg. #include "postgres.h")
|
||||||
|
* Toplevel imports from postgres, not contained in a directory (eg. #include "miscadmin.h")
|
||||||
|
* General postgres includes (eg . #include "nodes/...")
|
||||||
|
* Toplevel citus includes, not contained in a directory (eg. #include "citus_verion.h")
|
||||||
|
* Columnar includes (eg. #include "columnar/...")
|
||||||
|
* Distributed includes (eg. #include "distributed/...")
|
||||||
|
|
||||||
|
* Comments:
|
||||||
|
```c
|
||||||
|
/* single line comments start with a lower-case */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We start multi-line comments with a capital letter
|
||||||
|
* and keep adding a star to the beginning of each line
|
||||||
|
* until we close the comment with a star and a slash.
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
* Order of function implementations and their declarations in a file:
|
||||||
|
|
||||||
|
We define static functions after the functions that call them. For example:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include<..>
|
||||||
|
#include<..>
|
||||||
|
..
|
||||||
|
..
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
..
|
||||||
|
..
|
||||||
|
} MyNiceStruct;
|
||||||
|
..
|
||||||
|
..
|
||||||
|
PG_FUNCTION_INFO_V1(my_nice_udf1);
|
||||||
|
PG_FUNCTION_INFO_V1(my_nice_udf2);
|
||||||
|
..
|
||||||
|
..
|
||||||
|
// .. somewhere on top of the file …
|
||||||
|
static void MyNiceStaticlyDeclaredFunction1(…);
|
||||||
|
static void MyNiceStaticlyDeclaredFunction2(…);
|
||||||
|
..
|
||||||
|
..
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
MyNiceFunctionExternedViaHeaderFile(..)
|
||||||
|
{
|
||||||
|
..
|
||||||
|
..
|
||||||
|
MyNiceStaticlyDeclaredFunction1(..);
|
||||||
|
..
|
||||||
|
..
|
||||||
|
MyNiceStaticlyDeclaredFunction2(..);
|
||||||
|
..
|
||||||
|
}
|
||||||
|
|
||||||
|
..
|
||||||
|
..
|
||||||
|
|
||||||
|
// we define this first because it's called by MyNiceFunctionExternedViaHeaderFile()
|
||||||
|
// before MyNiceStaticlyDeclaredFunction2()
|
||||||
|
static void
|
||||||
|
MyNiceStaticlyDeclaredFunction1(…)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
..
|
||||||
|
..
|
||||||
|
|
||||||
|
// then we define this
|
||||||
|
static void
|
||||||
|
MyNiceStaticlyDeclaredFunction2(…)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
|
@ -4,7 +4,22 @@ set -euo pipefail
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source ci/ci_helpers.sh
|
source ci/ci_helpers.sh
|
||||||
|
|
||||||
# extract citus gucs in the form of "citus.X"
|
# Find the line that exactly matches "RegisterCitusConfigVariables(void)" in
|
||||||
grep -o -E "(\.*\"citus\.\w+\")," src/backend/distributed/shared_library_init.c > gucs.out
|
# shared_library_init.c. grep command returns something like
|
||||||
sort -c gucs.out
|
# "934:RegisterCitusConfigVariables(void)" and we extract the line number
|
||||||
|
# with cut.
|
||||||
|
RegisterCitusConfigVariables_begin_linenumber=$(grep -n "^RegisterCitusConfigVariables(void)$" src/backend/distributed/shared_library_init.c | cut -d: -f1)
|
||||||
|
|
||||||
|
# Consider the lines starting from $RegisterCitusConfigVariables_begin_linenumber,
|
||||||
|
# grep the first line that starts with "}" and extract the line number with cut
|
||||||
|
# as in the previous step.
|
||||||
|
RegisterCitusConfigVariables_length=$(tail -n +$RegisterCitusConfigVariables_begin_linenumber src/backend/distributed/shared_library_init.c | grep -n -m 1 "^}$" | cut -d: -f1)
|
||||||
|
|
||||||
|
# extract the function definition of RegisterCitusConfigVariables into a temp file
|
||||||
|
tail -n +$RegisterCitusConfigVariables_begin_linenumber src/backend/distributed/shared_library_init.c | head -n $(($RegisterCitusConfigVariables_length)) > RegisterCitusConfigVariables_func_def.out
|
||||||
|
|
||||||
|
# extract citus gucs in the form of <tab><tab>"citus.X"
|
||||||
|
grep -P "^[\t][\t]\"citus\.[a-zA-Z_0-9]+\"" RegisterCitusConfigVariables_func_def.out > gucs.out
|
||||||
|
LC_COLLATE=C sort -c gucs.out
|
||||||
rm gucs.out
|
rm gucs.out
|
||||||
|
rm RegisterCitusConfigVariables_func_def.out
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
# Guess values for system-dependent variables and create Makefiles.
|
# Guess values for system-dependent variables and create Makefiles.
|
||||||
# Generated by GNU Autoconf 2.69 for Citus 13.0.4.
|
# Generated by GNU Autoconf 2.69 for Citus 13.2devel.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
||||||
|
@ -579,8 +579,8 @@ MAKEFLAGS=
|
||||||
# Identity of this package.
|
# Identity of this package.
|
||||||
PACKAGE_NAME='Citus'
|
PACKAGE_NAME='Citus'
|
||||||
PACKAGE_TARNAME='citus'
|
PACKAGE_TARNAME='citus'
|
||||||
PACKAGE_VERSION='13.0.4'
|
PACKAGE_VERSION='13.2devel'
|
||||||
PACKAGE_STRING='Citus 13.0.4'
|
PACKAGE_STRING='Citus 13.2devel'
|
||||||
PACKAGE_BUGREPORT=''
|
PACKAGE_BUGREPORT=''
|
||||||
PACKAGE_URL=''
|
PACKAGE_URL=''
|
||||||
|
|
||||||
|
@ -1262,7 +1262,7 @@ if test "$ac_init_help" = "long"; then
|
||||||
# Omit some internal or obsolete options to make the list less imposing.
|
# 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.
|
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||||
cat <<_ACEOF
|
cat <<_ACEOF
|
||||||
\`configure' configures Citus 13.0.4 to adapt to many kinds of systems.
|
\`configure' configures Citus 13.2devel to adapt to many kinds of systems.
|
||||||
|
|
||||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||||
|
|
||||||
|
@ -1324,7 +1324,7 @@ fi
|
||||||
|
|
||||||
if test -n "$ac_init_help"; then
|
if test -n "$ac_init_help"; then
|
||||||
case $ac_init_help in
|
case $ac_init_help in
|
||||||
short | recursive ) echo "Configuration of Citus 13.0.4:";;
|
short | recursive ) echo "Configuration of Citus 13.2devel:";;
|
||||||
esac
|
esac
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
|
|
||||||
|
@ -1429,7 +1429,7 @@ fi
|
||||||
test -n "$ac_init_help" && exit $ac_status
|
test -n "$ac_init_help" && exit $ac_status
|
||||||
if $ac_init_version; then
|
if $ac_init_version; then
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
Citus configure 13.0.4
|
Citus configure 13.2devel
|
||||||
generated by GNU Autoconf 2.69
|
generated by GNU Autoconf 2.69
|
||||||
|
|
||||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||||
|
@ -1912,7 +1912,7 @@ cat >config.log <<_ACEOF
|
||||||
This file contains any messages produced by compilers while
|
This file contains any messages produced by compilers while
|
||||||
running configure, to aid debugging if configure makes a mistake.
|
running configure, to aid debugging if configure makes a mistake.
|
||||||
|
|
||||||
It was created by Citus $as_me 13.0.4, which was
|
It was created by Citus $as_me 13.2devel, which was
|
||||||
generated by GNU Autoconf 2.69. Invocation command line was
|
generated by GNU Autoconf 2.69. Invocation command line was
|
||||||
|
|
||||||
$ $0 $@
|
$ $0 $@
|
||||||
|
@ -5393,7 +5393,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||||
# report actual input values of CONFIG_FILES etc. instead of their
|
# report actual input values of CONFIG_FILES etc. instead of their
|
||||||
# values after options handling.
|
# values after options handling.
|
||||||
ac_log="
|
ac_log="
|
||||||
This file was extended by Citus $as_me 13.0.4, which was
|
This file was extended by Citus $as_me 13.2devel, which was
|
||||||
generated by GNU Autoconf 2.69. Invocation command line was
|
generated by GNU Autoconf 2.69. Invocation command line was
|
||||||
|
|
||||||
CONFIG_FILES = $CONFIG_FILES
|
CONFIG_FILES = $CONFIG_FILES
|
||||||
|
@ -5455,7 +5455,7 @@ _ACEOF
|
||||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||||
ac_cs_version="\\
|
ac_cs_version="\\
|
||||||
Citus config.status 13.0.4
|
Citus config.status 13.2devel
|
||||||
configured by $0, generated by GNU Autoconf 2.69,
|
configured by $0, generated by GNU Autoconf 2.69,
|
||||||
with options \\"\$ac_cs_config\\"
|
with options \\"\$ac_cs_config\\"
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
# everyone needing autoconf installed, the resulting files are checked
|
# everyone needing autoconf installed, the resulting files are checked
|
||||||
# into the SCM.
|
# into the SCM.
|
||||||
|
|
||||||
AC_INIT([Citus], [13.0.4])
|
AC_INIT([Citus], [13.2devel])
|
||||||
AC_COPYRIGHT([Copyright (c) Citus Data, Inc.])
|
AC_COPYRIGHT([Copyright (c) Citus Data, Inc.])
|
||||||
|
|
||||||
# we'll need sed and awk for some of the version commands
|
# we'll need sed and awk for some of the version commands
|
||||||
|
|
133
gucs.out
|
@ -1,133 +0,0 @@
|
||||||
"citus.all_modifications_commutative",
|
|
||||||
"citus.allow_modifications_from_workers_to_replicated_tables",
|
|
||||||
"citus.allow_nested_distributed_execution",
|
|
||||||
"citus.allow_unsafe_constraints",
|
|
||||||
"citus.allow_unsafe_locks_from_workers",
|
|
||||||
"citus.background_task_queue_interval",
|
|
||||||
"citus.check_available_space_before_move",
|
|
||||||
"citus.cluster_name",
|
|
||||||
"citus.coordinator_aggregation_strategy",
|
|
||||||
"citus.copy_switchover_threshold",
|
|
||||||
"citus.count_distinct_error_rate",
|
|
||||||
"citus.cpu_priority",
|
|
||||||
"citus.cpu_priority_for_logical_replication_senders",
|
|
||||||
"citus.create_object_propagation",
|
|
||||||
"citus.defer_drop_after_shard_move",
|
|
||||||
"citus.defer_drop_after_shard_split",
|
|
||||||
"citus.defer_shard_delete_interval",
|
|
||||||
"citus.desired_percent_disk_available_after_move",
|
|
||||||
"citus.distributed_deadlock_detection_factor",
|
|
||||||
"citus.enable_alter_database_owner",
|
|
||||||
"citus.enable_alter_role_propagation",
|
|
||||||
"citus.enable_alter_role_set_propagation",
|
|
||||||
"citus.enable_binary_protocol",
|
|
||||||
"citus.enable_change_data_capture",
|
|
||||||
"citus.enable_cluster_clock",
|
|
||||||
"citus.enable_cost_based_connection_establishment",
|
|
||||||
"citus.enable_create_role_propagation",
|
|
||||||
"citus.enable_create_type_propagation",
|
|
||||||
"citus.enable_ddl_propagation",
|
|
||||||
"citus.enable_deadlock_prevention",
|
|
||||||
"citus.enable_fast_path_router_planner",
|
|
||||||
"citus.enable_local_execution",
|
|
||||||
"citus.enable_local_reference_table_foreign_keys",
|
|
||||||
"citus.enable_manual_changes_to_shards",
|
|
||||||
"citus.enable_manual_metadata_changes_for_user",
|
|
||||||
"citus.enable_metadata_sync",
|
|
||||||
"citus.enable_non_colocated_router_query_pushdown",
|
|
||||||
"citus.enable_repartition_joins",
|
|
||||||
"citus.enable_repartitioned_insert_select",
|
|
||||||
"citus.enable_router_execution",
|
|
||||||
"citus.enable_schema_based_sharding",
|
|
||||||
"citus.enable_single_hash_repartition_joins",
|
|
||||||
"citus.enable_statistics_collection",
|
|
||||||
"citus.enable_unique_job_ids",
|
|
||||||
"citus.enable_unsafe_triggers",
|
|
||||||
"citus.enable_unsupported_feature_messages",
|
|
||||||
"citus.enable_version_checks",
|
|
||||||
"citus.enforce_foreign_key_restrictions",
|
|
||||||
"citus.enforce_object_restrictions_for_local_objects",
|
|
||||||
"citus.executor_slow_start_interval",
|
|
||||||
"citus.explain_all_tasks",
|
|
||||||
"citus.explain_analyze_sort_method",
|
|
||||||
"citus.explain_distributed_queries",
|
|
||||||
"citus.force_max_query_parallelization",
|
|
||||||
"citus.function_opens_transaction_block",
|
|
||||||
"citus.grep_remote_commands",
|
|
||||||
"citus.hide_citus_dependent_objects",
|
|
||||||
"citus.hide_shards_from_app_name_prefixes",
|
|
||||||
"citus.isolation_test_session_process_id",
|
|
||||||
"citus.isolation_test_session_remote_process_id",
|
|
||||||
"citus.limit_clause_row_fetch_count",
|
|
||||||
"citus.local_copy_flush_threshold",
|
|
||||||
"citus.local_hostname",
|
|
||||||
"citus.local_shared_pool_size",
|
|
||||||
"citus.local_table_join_policy",
|
|
||||||
"citus.log_distributed_deadlock_detection",
|
|
||||||
"citus.log_intermediate_results",
|
|
||||||
"citus.log_local_commands",
|
|
||||||
"citus.log_multi_join_order",
|
|
||||||
"citus.log_remote_commands",
|
|
||||||
"citus.logical_replication_timeout",
|
|
||||||
"citus.main_db",
|
|
||||||
"citus.max_adaptive_executor_pool_size",
|
|
||||||
"citus.max_background_task_executors",
|
|
||||||
"citus.max_background_task_executors_per_node",
|
|
||||||
"citus.max_cached_connection_lifetime",
|
|
||||||
"citus.max_cached_conns_per_worker",
|
|
||||||
"citus.max_client_connections",
|
|
||||||
"citus.max_high_priority_background_processes",
|
|
||||||
"citus.max_intermediate_result_size",
|
|
||||||
"citus.max_matview_size_to_auto_recreate",
|
|
||||||
"citus.max_rebalancer_logged_ignored_moves",
|
|
||||||
"citus.max_shared_pool_size",
|
|
||||||
"citus.max_worker_nodes_tracked",
|
|
||||||
"citus.metadata_sync_interval",
|
|
||||||
"citus.metadata_sync_mode",
|
|
||||||
"citus.metadata_sync_retry_interval",
|
|
||||||
"citus.mitmfifo",
|
|
||||||
"citus.multi_shard_modify_mode",
|
|
||||||
"citus.multi_task_query_log_level",
|
|
||||||
"citus.next_cleanup_record_id",
|
|
||||||
"citus.next_operation_id",
|
|
||||||
"citus.next_placement_id",
|
|
||||||
"citus.next_shard_id",
|
|
||||||
"citus.node_connection_timeout",
|
|
||||||
"citus.node_conninfo",
|
|
||||||
"citus.override_table_visibility",
|
|
||||||
"citus.prevent_incomplete_connection_establishment",
|
|
||||||
"citus.propagate_session_settings_for_loopback_connection",
|
|
||||||
"citus.propagate_set_commands",
|
|
||||||
"citus.rebalancer_by_disk_size_base_cost",
|
|
||||||
"citus.recover_2pc_interval",
|
|
||||||
"citus.remote_copy_flush_threshold",
|
|
||||||
"citus.remote_task_check_interval",
|
|
||||||
"citus.repartition_join_bucket_count_per_node",
|
|
||||||
"citus.replicate_reference_tables_on_activate",
|
|
||||||
"citus.replication_model",
|
|
||||||
"citus.running_under_citus_test_suite",
|
|
||||||
"citus.select_opens_transaction_block",
|
|
||||||
"citus.shard_count",
|
|
||||||
"citus.shard_replication_factor",
|
|
||||||
"citus.show_shards_for_app_name_prefixes",
|
|
||||||
"citus.skip_advisory_lock_permission_checks",
|
|
||||||
"citus.skip_constraint_validation",
|
|
||||||
"citus.skip_jsonb_validation_in_copy",
|
|
||||||
"citus.sort_returning",
|
|
||||||
"citus.stat_statements_max",
|
|
||||||
"citus.stat_statements_purge_interval",
|
|
||||||
"citus.stat_statements_track",
|
|
||||||
"citus.stat_tenants_limit",
|
|
||||||
"citus.stat_tenants_log_level",
|
|
||||||
"citus.stat_tenants_period",
|
|
||||||
"citus.stat_tenants_track",
|
|
||||||
"citus.stat_tenants_untracked_sample_rate",
|
|
||||||
"citus.subquery_pushdown",
|
|
||||||
"citus.task_assignment_policy",
|
|
||||||
"citus.task_executor_type",
|
|
||||||
"citus.use_citus_managed_tables",
|
|
||||||
"citus.use_secondary_nodes",
|
|
||||||
"citus.values_materialization_threshold",
|
|
||||||
"citus.version",
|
|
||||||
"citus.worker_min_messages",
|
|
||||||
"citus.writable_standby_coordinator",
|
|
After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 168 KiB |
|
@ -1,6 +1,6 @@
|
||||||
# Columnar extension
|
# Columnar extension
|
||||||
comment = 'Citus Columnar extension'
|
comment = 'Citus Columnar extension'
|
||||||
default_version = '11.3-1'
|
default_version = '12.2-1'
|
||||||
module_pathname = '$libdir/citus_columnar'
|
module_pathname = '$libdir/citus_columnar'
|
||||||
relocatable = false
|
relocatable = false
|
||||||
schema = pg_catalog
|
schema = pg_catalog
|
||||||
|
|
|
@ -32,8 +32,6 @@
|
||||||
#include "optimizer/paths.h"
|
#include "optimizer/paths.h"
|
||||||
#include "optimizer/plancat.h"
|
#include "optimizer/plancat.h"
|
||||||
#include "optimizer/restrictinfo.h"
|
#include "optimizer/restrictinfo.h"
|
||||||
|
|
||||||
#include "citus_version.h"
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
#include "parser/parse_relation.h"
|
#include "parser/parse_relation.h"
|
||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
|
@ -45,6 +43,8 @@
|
||||||
#include "utils/selfuncs.h"
|
#include "utils/selfuncs.h"
|
||||||
#include "utils/spccache.h"
|
#include "utils/spccache.h"
|
||||||
|
|
||||||
|
#include "citus_version.h"
|
||||||
|
|
||||||
#include "columnar/columnar.h"
|
#include "columnar/columnar.h"
|
||||||
#include "columnar/columnar_customscan.h"
|
#include "columnar/columnar_customscan.h"
|
||||||
#include "columnar/columnar_metadata.h"
|
#include "columnar/columnar_metadata.h"
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "port.h"
|
||||||
#include "safe_lib.h"
|
#include "safe_lib.h"
|
||||||
|
|
||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
|
@ -42,19 +43,6 @@
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
|
|
||||||
#include "citus_version.h"
|
|
||||||
|
|
||||||
#include "columnar/columnar.h"
|
|
||||||
#include "columnar/columnar_storage.h"
|
|
||||||
#include "columnar/columnar_version_compat.h"
|
|
||||||
|
|
||||||
#include "distributed/listutils.h"
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
|
||||||
#include "parser/parse_relation.h"
|
|
||||||
#endif
|
|
||||||
#include "port.h"
|
|
||||||
|
|
||||||
#include "storage/fd.h"
|
#include "storage/fd.h"
|
||||||
#include "storage/lmgr.h"
|
#include "storage/lmgr.h"
|
||||||
#include "storage/procarray.h"
|
#include "storage/procarray.h"
|
||||||
|
@ -64,7 +52,18 @@
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
|
|
||||||
|
#include "citus_version.h"
|
||||||
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
|
#include "columnar/columnar.h"
|
||||||
|
#include "columnar/columnar_storage.h"
|
||||||
|
#include "columnar/columnar_version_compat.h"
|
||||||
|
|
||||||
|
#include "distributed/listutils.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
#include "parser/parse_relation.h"
|
||||||
#include "storage/relfilelocator.h"
|
#include "storage/relfilelocator.h"
|
||||||
#include "utils/relfilenumbermap.h"
|
#include "utils/relfilenumbermap.h"
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -29,6 +29,12 @@
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
|
|
||||||
#include "pg_version_compat.h"
|
#include "pg_version_compat.h"
|
||||||
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
|
#include "columnar/columnar.h"
|
||||||
|
#include "columnar/columnar_storage.h"
|
||||||
|
#include "columnar/columnar_version_compat.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
#include "storage/relfilelocator.h"
|
#include "storage/relfilelocator.h"
|
||||||
#include "utils/relfilenumbermap.h"
|
#include "utils/relfilenumbermap.h"
|
||||||
|
@ -36,10 +42,6 @@
|
||||||
#include "utils/relfilenodemap.h"
|
#include "utils/relfilenodemap.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "columnar/columnar.h"
|
|
||||||
#include "columnar/columnar_storage.h"
|
|
||||||
#include "columnar/columnar_version_compat.h"
|
|
||||||
|
|
||||||
struct ColumnarWriteState
|
struct ColumnarWriteState
|
||||||
{
|
{
|
||||||
TupleDesc tupleDescriptor;
|
TupleDesc tupleDescriptor;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
-- citus_columnar--11.3-1--12.2-1
|
|
@ -0,0 +1 @@
|
||||||
|
-- citus_columnar--12.2-1--11.3-1
|
|
@ -18,7 +18,7 @@ generated_downgrade_sql_files += $(patsubst %,$(citus_abs_srcdir)/build/sql/%,$(
|
||||||
DATA_built = $(generated_sql_files)
|
DATA_built = $(generated_sql_files)
|
||||||
|
|
||||||
# directories with source files
|
# directories with source files
|
||||||
SUBDIRS = . commands connection ddl deparser executor metadata operations planner progress relay safeclib shardsplit test transaction utils worker clock
|
SUBDIRS = . commands connection ddl deparser executor metadata operations planner progress relay safeclib shardsplit stats test transaction utils worker clock
|
||||||
# enterprise modules
|
# enterprise modules
|
||||||
SUBDIRS += replication
|
SUBDIRS += replication
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Citus extension
|
# Citus extension
|
||||||
comment = 'Citus distributed database'
|
comment = 'Citus distributed database'
|
||||||
default_version = '13.0-1'
|
default_version = '13.2-1'
|
||||||
module_pathname = '$libdir/citus'
|
module_pathname = '$libdir/citus'
|
||||||
relocatable = false
|
relocatable = false
|
||||||
schema = pg_catalog
|
schema = pg_catalog
|
||||||
|
|
|
@ -386,7 +386,7 @@ AdjustClocksToTransactionHighest(List *nodeConnectionList,
|
||||||
|
|
||||||
/* Set the clock value on participating worker nodes */
|
/* Set the clock value on participating worker nodes */
|
||||||
appendStringInfo(queryToSend,
|
appendStringInfo(queryToSend,
|
||||||
"SELECT pg_catalog.citus_internal_adjust_local_clock_to_remote"
|
"SELECT citus_internal.adjust_local_clock_to_remote"
|
||||||
"('(%lu, %u)'::pg_catalog.cluster_clock);",
|
"('(%lu, %u)'::pg_catalog.cluster_clock);",
|
||||||
transactionClockValue->logical, transactionClockValue->counter);
|
transactionClockValue->logical, transactionClockValue->counter);
|
||||||
|
|
||||||
|
@ -420,6 +420,11 @@ PrepareAndSetTransactionClock(void)
|
||||||
MultiConnection *connection = dlist_container(MultiConnection, transactionNode,
|
MultiConnection *connection = dlist_container(MultiConnection, transactionNode,
|
||||||
iter.cur);
|
iter.cur);
|
||||||
WorkerNode *workerNode = FindWorkerNode(connection->hostname, connection->port);
|
WorkerNode *workerNode = FindWorkerNode(connection->hostname, connection->port);
|
||||||
|
if (!workerNode)
|
||||||
|
{
|
||||||
|
ereport(WARNING, errmsg("Worker node is missing"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Skip the node if we already in the list */
|
/* Skip the node if we already in the list */
|
||||||
if (list_member_int(nodeList, workerNode->groupId))
|
if (list_member_int(nodeList, workerNode->groupId))
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* comment.c
|
||||||
|
* Commands to interact with the comments for all database
|
||||||
|
* object types.
|
||||||
|
*
|
||||||
|
* Copyright (c) Citus Data, Inc.
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/genam.h"
|
||||||
|
#include "access/htup_details.h"
|
||||||
|
#include "access/table.h"
|
||||||
|
#include "catalog/pg_shdescription.h"
|
||||||
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/fmgroids.h"
|
||||||
|
#include "utils/rel.h"
|
||||||
|
|
||||||
|
#include "distributed/comment.h"
|
||||||
|
|
||||||
|
static char * GetCommentForObject(Oid classOid, Oid objectOid);
|
||||||
|
|
||||||
|
|
||||||
|
List *
|
||||||
|
GetCommentPropagationCommands(Oid classOid, Oid objOoid, char *objectName, ObjectType
|
||||||
|
objectType)
|
||||||
|
{
|
||||||
|
List *commands = NIL;
|
||||||
|
|
||||||
|
StringInfo commentStmt = makeStringInfo();
|
||||||
|
|
||||||
|
/* Get the comment for the database */
|
||||||
|
char *comment = GetCommentForObject(classOid, objOoid);
|
||||||
|
char const *commentObjectType = ObjectTypeNames[objectType];
|
||||||
|
|
||||||
|
/* Create the SQL command to propagate the comment to other nodes */
|
||||||
|
if (comment != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfo(commentStmt, "COMMENT ON %s %s IS %s;", commentObjectType,
|
||||||
|
quote_identifier(objectName),
|
||||||
|
quote_literal_cstr(comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Add the command to the list */
|
||||||
|
if (commentStmt->len > 0)
|
||||||
|
{
|
||||||
|
commands = list_make1(commentStmt->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *
|
||||||
|
GetCommentForObject(Oid classOid, Oid objectOid)
|
||||||
|
{
|
||||||
|
HeapTuple tuple;
|
||||||
|
char *comment = NULL;
|
||||||
|
|
||||||
|
/* Open pg_shdescription catalog */
|
||||||
|
Relation shdescRelation = table_open(SharedDescriptionRelationId, AccessShareLock);
|
||||||
|
|
||||||
|
/* Scan the table */
|
||||||
|
ScanKeyData scanKey[2];
|
||||||
|
|
||||||
|
ScanKeyInit(&scanKey[0],
|
||||||
|
Anum_pg_shdescription_objoid,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(objectOid));
|
||||||
|
ScanKeyInit(&scanKey[1],
|
||||||
|
Anum_pg_shdescription_classoid,
|
||||||
|
BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(classOid));
|
||||||
|
bool indexOk = true;
|
||||||
|
int scanKeyCount = 2;
|
||||||
|
SysScanDesc scan = systable_beginscan(shdescRelation, SharedDescriptionObjIndexId,
|
||||||
|
indexOk, NULL, scanKeyCount,
|
||||||
|
scanKey);
|
||||||
|
if ((tuple = systable_getnext(scan)) != NULL)
|
||||||
|
{
|
||||||
|
bool isNull = false;
|
||||||
|
|
||||||
|
TupleDesc tupdesc = RelationGetDescr(shdescRelation);
|
||||||
|
|
||||||
|
Datum descDatum = heap_getattr(tuple, Anum_pg_shdescription_description, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
|
||||||
|
|
||||||
|
/* Add the command to the list */
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
comment = TextDatumGetCString(descDatum);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
comment = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End the scan and close the catalog */
|
||||||
|
systable_endscan(scan);
|
||||||
|
table_close(shdescRelation, AccessShareLock);
|
||||||
|
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CommentObjectAddress resolves the ObjectAddress for the object
|
||||||
|
* on which the comment is placed. Optionally errors if the object does not
|
||||||
|
* exist based on the missing_ok flag passed in by the caller.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
CommentObjectAddress(Node *node, bool missing_ok, bool isPostprocess)
|
||||||
|
{
|
||||||
|
CommentStmt *stmt = castNode(CommentStmt, node);
|
||||||
|
Relation relation;
|
||||||
|
|
||||||
|
ObjectAddress objectAddress = get_object_address(stmt->objtype, stmt->object,
|
||||||
|
&relation, AccessExclusiveLock,
|
||||||
|
missing_ok);
|
||||||
|
|
||||||
|
ObjectAddress *objectAddressCopy = palloc0(sizeof(ObjectAddress));
|
||||||
|
*objectAddressCopy = objectAddress;
|
||||||
|
return list_make1(objectAddressCopy);
|
||||||
|
}
|
|
@ -2727,11 +2727,15 @@ CopyFromLocalTableIntoDistTable(Oid localTableId, Oid distributedTableId)
|
||||||
ExprContext *econtext = GetPerTupleExprContext(estate);
|
ExprContext *econtext = GetPerTupleExprContext(estate);
|
||||||
econtext->ecxt_scantuple = slot;
|
econtext->ecxt_scantuple = slot;
|
||||||
const bool nonPublishableData = false;
|
const bool nonPublishableData = false;
|
||||||
|
|
||||||
|
/* we don't track query counters when distributing a table */
|
||||||
|
const bool trackQueryCounters = false;
|
||||||
DestReceiver *copyDest =
|
DestReceiver *copyDest =
|
||||||
(DestReceiver *) CreateCitusCopyDestReceiver(distributedTableId,
|
(DestReceiver *) CreateCitusCopyDestReceiver(distributedTableId,
|
||||||
columnNameList,
|
columnNameList,
|
||||||
partitionColumnIndex,
|
partitionColumnIndex,
|
||||||
estate, NULL, nonPublishableData);
|
estate, NULL, nonPublishableData,
|
||||||
|
trackQueryCounters);
|
||||||
|
|
||||||
/* initialise state for writing to shards, we'll open connections on demand */
|
/* initialise state for writing to shards, we'll open connections on demand */
|
||||||
copyDest->rStartup(copyDest, 0, sourceTupleDescriptor);
|
copyDest->rStartup(copyDest, 0, sourceTupleDescriptor);
|
||||||
|
|
|
@ -13,35 +13,97 @@
|
||||||
|
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
|
||||||
|
#include "access/genam.h"
|
||||||
|
#include "access/heapam.h"
|
||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
|
#include "access/table.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/objectaddress.h"
|
#include "catalog/objectaddress.h"
|
||||||
|
#include "catalog/pg_collation.h"
|
||||||
#include "catalog/pg_database.h"
|
#include "catalog/pg_database.h"
|
||||||
|
#include "catalog/pg_database_d.h"
|
||||||
|
#include "catalog/pg_tablespace.h"
|
||||||
#include "commands/dbcommands.h"
|
#include "commands/dbcommands.h"
|
||||||
|
#include "commands/defrem.h"
|
||||||
#include "nodes/parsenodes.h"
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/fmgroids.h"
|
||||||
|
#include "utils/lsyscache.h"
|
||||||
|
#include "utils/rel.h"
|
||||||
|
#include "utils/relcache.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
#include "distributed/adaptive_executor.h"
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
|
#include "distributed/commands/serialize_distributed_ddls.h"
|
||||||
#include "distributed/commands/utility_hook.h"
|
#include "distributed/commands/utility_hook.h"
|
||||||
|
#include "distributed/comment.h"
|
||||||
|
#include "distributed/deparse_shard_query.h"
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
|
#include "distributed/local_executor.h"
|
||||||
#include "distributed/metadata/distobject.h"
|
#include "distributed/metadata/distobject.h"
|
||||||
#include "distributed/metadata_sync.h"
|
#include "distributed/metadata_sync.h"
|
||||||
#include "distributed/metadata_utility.h"
|
#include "distributed/metadata_utility.h"
|
||||||
#include "distributed/multi_executor.h"
|
#include "distributed/multi_executor.h"
|
||||||
#include "distributed/relation_access_tracking.h"
|
#include "distributed/relation_access_tracking.h"
|
||||||
|
#include "distributed/shard_cleaner.h"
|
||||||
|
#include "distributed/worker_protocol.h"
|
||||||
#include "distributed/worker_transaction.h"
|
#include "distributed/worker_transaction.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used to save original name of the database before it is replaced with a
|
||||||
|
* temporary name for failure handling purposes in PreprocessCreateDatabaseStmt().
|
||||||
|
*/
|
||||||
|
static char *CreateDatabaseCommandOriginalDbName = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The format string used when creating a temporary databases for failure
|
||||||
|
* handling purposes.
|
||||||
|
*
|
||||||
|
* The fields are as follows to ensure using a unique name for each temporary
|
||||||
|
* database:
|
||||||
|
* - operationId: The operation id returned by RegisterOperationNeedingCleanup().
|
||||||
|
* - groupId: The group id of the worker node where CREATE DATABASE command
|
||||||
|
* is issued from.
|
||||||
|
*/
|
||||||
|
#define TEMP_DATABASE_NAME_FMT "citus_temp_database_%lu_%d"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DatabaseCollationInfo is used to store collation related information of a database.
|
||||||
|
*/
|
||||||
|
typedef struct DatabaseCollationInfo
|
||||||
|
{
|
||||||
|
char *datcollate;
|
||||||
|
char *datctype;
|
||||||
|
char *daticulocale;
|
||||||
|
char *datcollversion;
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
char *daticurules;
|
||||||
|
#endif
|
||||||
|
} DatabaseCollationInfo;
|
||||||
|
|
||||||
|
static char * GenerateCreateDatabaseStatementFromPgDatabase(Form_pg_database
|
||||||
|
databaseForm);
|
||||||
|
static DatabaseCollationInfo GetDatabaseCollation(Oid dbOid);
|
||||||
static AlterOwnerStmt * RecreateAlterDatabaseOwnerStmt(Oid databaseOid);
|
static AlterOwnerStmt * RecreateAlterDatabaseOwnerStmt(Oid databaseOid);
|
||||||
static Oid get_database_owner(Oid db_oid);
|
static char * GetLocaleProviderString(char datlocprovider);
|
||||||
|
static char * GetTablespaceName(Oid tablespaceOid);
|
||||||
static ObjectAddress * GetDatabaseAddressFromDatabaseName(char *databaseName,
|
static ObjectAddress * GetDatabaseAddressFromDatabaseName(char *databaseName,
|
||||||
bool missingOk);
|
bool missingOk);
|
||||||
List * PreprocessGrantOnDatabaseStmt(Node *node, const char *queryString,
|
|
||||||
ProcessUtilityContext processUtilityContext);
|
static List * FilterDistributedDatabases(List *databases);
|
||||||
|
static Oid get_database_owner(Oid dbId);
|
||||||
|
|
||||||
|
|
||||||
/* controlled via GUC */
|
/* controlled via GUC */
|
||||||
|
bool EnableCreateDatabasePropagation = false;
|
||||||
bool EnableAlterDatabaseOwner = true;
|
bool EnableAlterDatabaseOwner = true;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AlterDatabaseOwnerObjectAddress returns the ObjectAddress of the database that is the
|
* AlterDatabaseOwnerObjectAddress returns the ObjectAddress of the database that is the
|
||||||
* object of the AlterOwnerStmt. Errors if missing_ok is false.
|
* object of the AlterOwnerStmt. Errors if missing_ok is false.
|
||||||
|
@ -98,13 +160,13 @@ RecreateAlterDatabaseOwnerStmt(Oid databaseOid)
|
||||||
* get_database_owner returns the Oid of the role owning the database
|
* get_database_owner returns the Oid of the role owning the database
|
||||||
*/
|
*/
|
||||||
static Oid
|
static Oid
|
||||||
get_database_owner(Oid db_oid)
|
get_database_owner(Oid dbId)
|
||||||
{
|
{
|
||||||
HeapTuple tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(db_oid));
|
HeapTuple tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbId));
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE),
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||||
errmsg("database with OID %u does not exist", db_oid)));
|
errmsg("database with OID %u does not exist", dbId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Oid dba = ((Form_pg_database) GETSTRUCT(tuple))->datdba;
|
Oid dba = ((Form_pg_database) GETSTRUCT(tuple))->datdba;
|
||||||
|
@ -134,15 +196,152 @@ PreprocessGrantOnDatabaseStmt(Node *node, const char *queryString,
|
||||||
GrantStmt *stmt = castNode(GrantStmt, node);
|
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||||
Assert(stmt->objtype == OBJECT_DATABASE);
|
Assert(stmt->objtype == OBJECT_DATABASE);
|
||||||
|
|
||||||
List *databaseList = stmt->objects;
|
List *distributedDatabases = FilterDistributedDatabases(stmt->objects);
|
||||||
|
|
||||||
if (list_length(databaseList) == 0)
|
if (list_length(distributedDatabases) == 0)
|
||||||
{
|
{
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsureCoordinator();
|
||||||
|
|
||||||
|
List *originalObjects = stmt->objects;
|
||||||
|
|
||||||
|
stmt->objects = distributedDatabases;
|
||||||
|
|
||||||
|
char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
|
|
||||||
|
stmt->objects = originalObjects;
|
||||||
|
|
||||||
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) sql,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FilterDistributedDatabases filters the database list and returns the distributed ones,
|
||||||
|
* as a list.
|
||||||
|
*/
|
||||||
|
static List *
|
||||||
|
FilterDistributedDatabases(List *databases)
|
||||||
|
{
|
||||||
|
List *distributedDatabases = NIL;
|
||||||
|
String *databaseName = NULL;
|
||||||
|
foreach_declared_ptr(databaseName, databases)
|
||||||
|
{
|
||||||
|
bool missingOk = true;
|
||||||
|
ObjectAddress *dbAddress =
|
||||||
|
GetDatabaseAddressFromDatabaseName(strVal(databaseName), missingOk);
|
||||||
|
if (IsAnyObjectDistributed(list_make1(dbAddress)))
|
||||||
|
{
|
||||||
|
distributedDatabases = lappend(distributedDatabases, databaseName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return distributedDatabases;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IsSetTablespaceStatement returns true if the statement is a SET TABLESPACE statement,
|
||||||
|
* false otherwise.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
IsSetTablespaceStatement(AlterDatabaseStmt *stmt)
|
||||||
|
{
|
||||||
|
DefElem *def = NULL;
|
||||||
|
foreach_declared_ptr(def, stmt->options)
|
||||||
|
{
|
||||||
|
if (strcmp(def->defname, "tablespace") == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PreprocessAlterDatabaseStmt is executed before the statement is applied to the local
|
||||||
|
* postgres instance.
|
||||||
|
*
|
||||||
|
* In this stage we can prepare the commands that need to be run on all workers to grant
|
||||||
|
* on databases.
|
||||||
|
*
|
||||||
|
* We also serialize database commands globally by acquiring a Citus specific advisory
|
||||||
|
* lock based on OCLASS_DATABASE on the first primary worker node.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PreprocessAlterDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext)
|
||||||
|
{
|
||||||
|
bool missingOk = false;
|
||||||
|
AlterDatabaseStmt *stmt = castNode(AlterDatabaseStmt, node);
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
||||||
|
missingOk);
|
||||||
|
|
||||||
|
if (!ShouldPropagate() || !IsAnyObjectDistributed(list_make1(dbAddress)))
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
SerializeDistributedDDLsOnObjectClassObject(OCLASS_DATABASE, stmt->dbname);
|
||||||
|
|
||||||
|
char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
|
|
||||||
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
sql,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
if (IsSetTablespaceStatement(stmt))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Set tablespace does not work inside a transaction.Therefore, we need to use
|
||||||
|
* NontransactionalNodeDDLTask to run the command on the workers outside
|
||||||
|
* the transaction block.
|
||||||
|
*/
|
||||||
|
bool warnForPartialFailure = true;
|
||||||
|
return NontransactionalNodeDDLTaskList(NON_COORDINATOR_NODES, commands,
|
||||||
|
warnForPartialFailure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PreprocessAlterDatabaseRefreshCollStmt is executed before the statement is applied to
|
||||||
|
* the local postgres instance.
|
||||||
|
*
|
||||||
|
* In this stage we can prepare the commands that need to be run on all workers to grant
|
||||||
|
* on databases.
|
||||||
|
*
|
||||||
|
* We also serialize database commands globally by acquiring a Citus specific advisory
|
||||||
|
* lock based on OCLASS_DATABASE on the first primary worker node.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PreprocessAlterDatabaseRefreshCollStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext)
|
||||||
|
{
|
||||||
|
bool missingOk = true;
|
||||||
|
AlterDatabaseRefreshCollStmt *stmt = castNode(AlterDatabaseRefreshCollStmt, node);
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
||||||
|
missingOk);
|
||||||
|
|
||||||
|
if (!ShouldPropagate() || !IsAnyObjectDistributed(list_make1(dbAddress)))
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
SerializeDistributedDDLsOnObjectClassObject(OCLASS_DATABASE, stmt->dbname);
|
||||||
|
|
||||||
char *sql = DeparseTreeNode((Node *) stmt);
|
char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
|
|
||||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
@ -154,20 +353,60 @@ PreprocessGrantOnDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PreprocessAlterDatabaseStmt is executed before the statement is applied to the local
|
* PreprocessAlterDatabaseRenameStmt is executed before the statement is applied to
|
||||||
* postgres instance.
|
* the local postgres instance.
|
||||||
*
|
*
|
||||||
* In this stage we can prepare the commands that need to be run on all workers to grant
|
* We also serialize database commands globally by acquiring a Citus specific advisory
|
||||||
* on databases.
|
* lock based on OCLASS_DATABASE on the first primary worker node.
|
||||||
|
*
|
||||||
|
* We acquire this lock here instead of PostprocessAlterDatabaseRenameStmt because the
|
||||||
|
* command renames the database and SerializeDistributedDDLsOnObjectClass resolves the
|
||||||
|
* object on workers based on database name. For this reason, we need to acquire the lock
|
||||||
|
* before the command is applied to the local postgres instance.
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
PreprocessAlterDatabaseStmt(Node *node, const char *queryString,
|
PreprocessAlterDatabaseRenameStmt(Node *node, const char *queryString,
|
||||||
ProcessUtilityContext processUtilityContext)
|
ProcessUtilityContext processUtilityContext)
|
||||||
{
|
{
|
||||||
AlterDatabaseStmt *stmt = castNode(AlterDatabaseStmt, node);
|
bool missingOk = true;
|
||||||
bool missingOk = false;
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||||
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->subname,
|
||||||
missingOk);
|
missingOk);
|
||||||
|
|
||||||
|
if (!ShouldPropagate() || !IsAnyObjectDistributed(list_make1(dbAddress)))
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Different than other ALTER DATABASE commands, we first acquire a lock
|
||||||
|
* by providing InvalidOid because we want ALTER TABLE .. RENAME TO ..
|
||||||
|
* commands to block not only with ALTER DATABASE operations but also
|
||||||
|
* with CREATE DATABASE operations because they might cause name conflicts
|
||||||
|
* and that could also cause deadlocks too.
|
||||||
|
*/
|
||||||
|
SerializeDistributedDDLsOnObjectClass(OCLASS_DATABASE);
|
||||||
|
SerializeDistributedDDLsOnObjectClassObject(OCLASS_DATABASE, stmt->subname);
|
||||||
|
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostprocessAlterDatabaseRenameStmt is executed after the statement is applied to the local
|
||||||
|
* postgres instance. In this stage we prepare ALTER DATABASE RENAME statement to be run on
|
||||||
|
* all workers.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PostprocessAlterDatabaseRenameStmt(Node *node, const char *queryString)
|
||||||
|
{
|
||||||
|
bool missingOk = false;
|
||||||
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->newname,
|
||||||
|
missingOk);
|
||||||
|
|
||||||
if (!ShouldPropagate() || !IsAnyObjectDistributed(list_make1(dbAddress)))
|
if (!ShouldPropagate() || !IsAnyObjectDistributed(list_make1(dbAddress)))
|
||||||
{
|
{
|
||||||
return NIL;
|
return NIL;
|
||||||
|
@ -191,19 +430,26 @@ PreprocessAlterDatabaseStmt(Node *node, const char *queryString,
|
||||||
*
|
*
|
||||||
* In this stage we can prepare the commands that need to be run on all workers to grant
|
* In this stage we can prepare the commands that need to be run on all workers to grant
|
||||||
* on databases.
|
* on databases.
|
||||||
|
*
|
||||||
|
* We also serialize database commands globally by acquiring a Citus specific advisory
|
||||||
|
* lock based on OCLASS_DATABASE on the first primary worker node.
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
PreprocessAlterDatabaseRefreshCollStmt(Node *node, const char *queryString,
|
PreprocessAlterDatabaseSetStmt(Node *node, const char *queryString,
|
||||||
ProcessUtilityContext processUtilityContext)
|
ProcessUtilityContext processUtilityContext)
|
||||||
{
|
{
|
||||||
if (!ShouldPropagate())
|
AlterDatabaseSetStmt *stmt = castNode(AlterDatabaseSetStmt, node);
|
||||||
|
|
||||||
|
bool missingOk = true;
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
||||||
|
missingOk);
|
||||||
|
if (!ShouldPropagate() || !IsAnyObjectDistributed(list_make1(dbAddress)))
|
||||||
{
|
{
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
AlterDatabaseRefreshCollStmt *stmt = castNode(AlterDatabaseRefreshCollStmt, node);
|
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsureCoordinator();
|
||||||
|
SerializeDistributedDDLsOnObjectClassObject(OCLASS_DATABASE, stmt->dbname);
|
||||||
|
|
||||||
char *sql = DeparseTreeNode((Node *) stmt);
|
char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
|
|
||||||
|
@ -215,6 +461,296 @@ PreprocessAlterDatabaseRefreshCollStmt(Node *node, const char *queryString,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PreprocessCreateDatabaseStmt is executed before the statement is applied to the local
|
||||||
|
* Postgres instance.
|
||||||
|
*
|
||||||
|
* In this stage, we perform validations that we want to ensure before delegating to
|
||||||
|
* previous utility hooks because it might not be convenient to throw an error in an
|
||||||
|
* implicit transaction that creates a database. Also in this stage, we save the original
|
||||||
|
* database name and replace dbname field with a temporary name for failure handling
|
||||||
|
* purposes. We let Postgres create the database with the temporary name, insert a cleanup
|
||||||
|
* record for the temporary database name on all nodes and let PostprocessCreateDatabaseStmt()
|
||||||
|
* to return the distributed DDL job that both creates the database with the temporary name
|
||||||
|
* and then renames it back to its original name.
|
||||||
|
*
|
||||||
|
* We also serialize database commands globally by acquiring a Citus specific advisory
|
||||||
|
* lock based on OCLASS_DATABASE on the first primary worker node.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PreprocessCreateDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext)
|
||||||
|
{
|
||||||
|
if (!EnableCreateDatabasePropagation || !ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinatorIsInMetadata();
|
||||||
|
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
EnsureSupportedCreateDatabaseCommand(stmt);
|
||||||
|
|
||||||
|
SerializeDistributedDDLsOnObjectClass(OCLASS_DATABASE);
|
||||||
|
|
||||||
|
OperationId operationId = RegisterOperationNeedingCleanup();
|
||||||
|
|
||||||
|
char *tempDatabaseName = psprintf(TEMP_DATABASE_NAME_FMT,
|
||||||
|
operationId, GetLocalGroupId());
|
||||||
|
|
||||||
|
List *remoteNodes = TargetWorkerSetNodeList(ALL_SHARD_NODES, RowShareLock);
|
||||||
|
WorkerNode *remoteNode = NULL;
|
||||||
|
foreach_declared_ptr(remoteNode, remoteNodes)
|
||||||
|
{
|
||||||
|
InsertCleanupRecordOutsideTransaction(
|
||||||
|
CLEANUP_OBJECT_DATABASE,
|
||||||
|
pstrdup(quote_identifier(tempDatabaseName)),
|
||||||
|
remoteNode->groupId,
|
||||||
|
CLEANUP_ON_FAILURE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDatabaseCommandOriginalDbName = stmt->dbname;
|
||||||
|
stmt->dbname = tempDatabaseName;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete cleanup records in the same transaction so that if the current
|
||||||
|
* transactions fails for some reason, then the cleanup records won't be
|
||||||
|
* deleted. In the happy path, we will delete the cleanup records without
|
||||||
|
* deferring them to the background worker.
|
||||||
|
*/
|
||||||
|
FinalizeOperationNeedingCleanupOnSuccess("create database");
|
||||||
|
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostprocessCreateDatabaseStmt is executed after the statement is applied to the local
|
||||||
|
* postgres instance.
|
||||||
|
*
|
||||||
|
* In this stage, we first rename the temporary database back to its original name for
|
||||||
|
* local node and then return a list of distributed DDL jobs to create the database with
|
||||||
|
* the temporary name and then to rename it back to its original name. That way, if CREATE
|
||||||
|
* DATABASE fails on any of the nodes, the temporary database will be cleaned up by the
|
||||||
|
* cleanup records that we inserted in PreprocessCreateDatabaseStmt() and in case of a
|
||||||
|
* failure, we won't leak any databases called as the name that user intended to use for
|
||||||
|
* the database.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PostprocessCreateDatabaseStmt(Node *node, const char *queryString)
|
||||||
|
{
|
||||||
|
if (!EnableCreateDatabasePropagation || !ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given that CREATE DATABASE doesn't support "IF NOT EXISTS" and we're
|
||||||
|
* in the post-process, database must exist, hence missingOk = false.
|
||||||
|
*/
|
||||||
|
bool missingOk = false;
|
||||||
|
bool isPostProcess = true;
|
||||||
|
List *addresses = GetObjectAddressListFromParseTree(node, missingOk,
|
||||||
|
isPostProcess);
|
||||||
|
EnsureAllObjectDependenciesExistOnAllNodes(addresses);
|
||||||
|
|
||||||
|
char *createDatabaseCommand = DeparseTreeNode(node);
|
||||||
|
|
||||||
|
List *createDatabaseCommands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) createDatabaseCommand,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since the CREATE DATABASE statements cannot be executed in a transaction
|
||||||
|
* block, we need to use NontransactionalNodeDDLTaskList() to send the CREATE
|
||||||
|
* DATABASE statement to the workers.
|
||||||
|
*/
|
||||||
|
bool warnForPartialFailure = false;
|
||||||
|
List *createDatabaseDDLJobList =
|
||||||
|
NontransactionalNodeDDLTaskList(REMOTE_NODES, createDatabaseCommands,
|
||||||
|
warnForPartialFailure);
|
||||||
|
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
|
||||||
|
char *renameDatabaseCommand =
|
||||||
|
psprintf("ALTER DATABASE %s RENAME TO %s",
|
||||||
|
quote_identifier(stmt->dbname),
|
||||||
|
quote_identifier(CreateDatabaseCommandOriginalDbName));
|
||||||
|
|
||||||
|
List *renameDatabaseCommands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
renameDatabaseCommand,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We use NodeDDLTaskList() to send the RENAME DATABASE statement to the
|
||||||
|
* workers because we want to execute it in a coordinated transaction.
|
||||||
|
*/
|
||||||
|
List *renameDatabaseDDLJobList =
|
||||||
|
NodeDDLTaskList(REMOTE_NODES, renameDatabaseCommands);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Temporarily disable citus.enable_ddl_propagation before issuing
|
||||||
|
* rename command locally because we don't want to execute it on remote
|
||||||
|
* nodes yet. We will execute it on remote nodes by returning it as a
|
||||||
|
* distributed DDL job.
|
||||||
|
*
|
||||||
|
* The reason why we don't want to execute it on remote nodes yet is that
|
||||||
|
* the database is not created on remote nodes yet.
|
||||||
|
*/
|
||||||
|
int saveNestLevel = NewGUCNestLevel();
|
||||||
|
set_config_option("citus.enable_ddl_propagation", "off",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
|
||||||
|
ExecuteUtilityCommand(renameDatabaseCommand);
|
||||||
|
|
||||||
|
AtEOXact_GUC(true, saveNestLevel);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Restore the original database name because MarkObjectDistributed()
|
||||||
|
* resolves oid of the object based on the database name and is called
|
||||||
|
* after executing the distributed DDL job that renames temporary database.
|
||||||
|
*/
|
||||||
|
stmt->dbname = CreateDatabaseCommandOriginalDbName;
|
||||||
|
|
||||||
|
return list_concat(createDatabaseDDLJobList, renameDatabaseDDLJobList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PreprocessDropDatabaseStmt 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 drop the database.
|
||||||
|
*
|
||||||
|
* We also serialize database commands globally by acquiring a Citus specific advisory
|
||||||
|
* lock based on OCLASS_DATABASE on the first primary worker node.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PreprocessDropDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext)
|
||||||
|
{
|
||||||
|
if (!EnableCreateDatabasePropagation || !ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
|
DropdbStmt *stmt = (DropdbStmt *) node;
|
||||||
|
|
||||||
|
bool isPostProcess = false;
|
||||||
|
List *addresses = GetObjectAddressListFromParseTree(node, stmt->missing_ok,
|
||||||
|
isPostProcess);
|
||||||
|
|
||||||
|
if (list_length(addresses) != 1)
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("unexpected number of objects found when "
|
||||||
|
"executing DROP DATABASE command")));
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectAddress *address = (ObjectAddress *) linitial(addresses);
|
||||||
|
if (address->objectId == InvalidOid || !IsAnyObjectDistributed(list_make1(address)))
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializeDistributedDDLsOnObjectClassObject(OCLASS_DATABASE, stmt->dbname);
|
||||||
|
|
||||||
|
char *dropDatabaseCommand = DeparseTreeNode(node);
|
||||||
|
|
||||||
|
List *dropDatabaseCommands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) dropDatabaseCommand,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Due to same reason stated in PostprocessCreateDatabaseStmt(), we need to
|
||||||
|
* use NontransactionalNodeDDLTaskList() to send the DROP DATABASE statement
|
||||||
|
* to the workers.
|
||||||
|
*/
|
||||||
|
bool warnForPartialFailure = true;
|
||||||
|
List *dropDatabaseDDLJobList =
|
||||||
|
NontransactionalNodeDDLTaskList(REMOTE_NODES, dropDatabaseCommands,
|
||||||
|
warnForPartialFailure);
|
||||||
|
return dropDatabaseDDLJobList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DropDatabaseStmtObjectAddress gets the ObjectAddress of the database that is the
|
||||||
|
* object of the DropdbStmt.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
DropDatabaseStmtObjectAddress(Node *node, bool missingOk, bool isPostprocess)
|
||||||
|
{
|
||||||
|
DropdbStmt *stmt = castNode(DropdbStmt, node);
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
||||||
|
missingOk);
|
||||||
|
return list_make1(dbAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateDatabaseStmtObjectAddress gets the ObjectAddress of the database that is the
|
||||||
|
* object of the CreatedbStmt.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
CreateDatabaseStmtObjectAddress(Node *node, bool missingOk, bool isPostprocess)
|
||||||
|
{
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
||||||
|
missingOk);
|
||||||
|
return list_make1(dbAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EnsureSupportedCreateDatabaseCommand validates the options provided for the CREATE
|
||||||
|
* DATABASE command.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* stmt: A CreatedbStmt struct representing a CREATE DATABASE command.
|
||||||
|
* The options field is a list of DefElem structs, each representing an option.
|
||||||
|
*
|
||||||
|
* Currently, this function checks for the following:
|
||||||
|
* - The "oid" option is not supported.
|
||||||
|
* - The "template" option is only supported with the value "template1".
|
||||||
|
* - The "strategy" option is only supported with the value "wal_log".
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
EnsureSupportedCreateDatabaseCommand(CreatedbStmt *stmt)
|
||||||
|
{
|
||||||
|
DefElem *option = NULL;
|
||||||
|
foreach_declared_ptr(option, stmt->options)
|
||||||
|
{
|
||||||
|
if (strcmp(option->defname, "oid") == 0)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
errmsg("CREATE DATABASE option \"%s\" is not supported",
|
||||||
|
option->defname));
|
||||||
|
}
|
||||||
|
|
||||||
|
char *optionValue = defGetString(option);
|
||||||
|
|
||||||
|
if (strcmp(option->defname, "template") == 0 &&
|
||||||
|
strcmp(optionValue, "template1") != 0)
|
||||||
|
{
|
||||||
|
ereport(ERROR, errmsg("Only template1 is supported as template "
|
||||||
|
"parameter for CREATE DATABASE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(option->defname, "strategy") == 0 &&
|
||||||
|
strcmp(optionValue, "wal_log") != 0)
|
||||||
|
{
|
||||||
|
ereport(ERROR, errmsg("Only wal_log is supported as strategy "
|
||||||
|
"parameter for CREATE DATABASE"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GetDatabaseAddressFromDatabaseName gets the database name and returns the ObjectAddress
|
* GetDatabaseAddressFromDatabaseName gets the database name and returns the ObjectAddress
|
||||||
* of the database.
|
* of the database.
|
||||||
|
@ -227,3 +763,238 @@ GetDatabaseAddressFromDatabaseName(char *databaseName, bool missingOk)
|
||||||
ObjectAddressSet(*dbObjectAddress, DatabaseRelationId, databaseOid);
|
ObjectAddressSet(*dbObjectAddress, DatabaseRelationId, databaseOid);
|
||||||
return dbObjectAddress;
|
return dbObjectAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetTablespaceName gets the tablespace oid and returns the tablespace name.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
GetTablespaceName(Oid tablespaceOid)
|
||||||
|
{
|
||||||
|
HeapTuple tuple = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(tablespaceOid));
|
||||||
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Form_pg_tablespace tablespaceForm = (Form_pg_tablespace) GETSTRUCT(tuple);
|
||||||
|
char *tablespaceName = pstrdup(NameStr(tablespaceForm->spcname));
|
||||||
|
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
|
return tablespaceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetDatabaseMetadataSyncCommands returns a list of sql statements
|
||||||
|
* for the given database id. The list contains the database ddl command,
|
||||||
|
* grant commands and comment propagation commands.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
GetDatabaseMetadataSyncCommands(Oid dbOid)
|
||||||
|
{
|
||||||
|
char *databaseName = get_database_name(dbOid);
|
||||||
|
char *databaseDDLCommand = CreateDatabaseDDLCommand(dbOid);
|
||||||
|
|
||||||
|
List *ddlCommands = list_make1(databaseDDLCommand);
|
||||||
|
|
||||||
|
List *grantDDLCommands = GrantOnDatabaseDDLCommands(dbOid);
|
||||||
|
List *commentDDLCommands = GetCommentPropagationCommands(DatabaseRelationId, dbOid,
|
||||||
|
databaseName,
|
||||||
|
OBJECT_DATABASE);
|
||||||
|
|
||||||
|
ddlCommands = list_concat(ddlCommands, grantDDLCommands);
|
||||||
|
ddlCommands = list_concat(ddlCommands, commentDDLCommands);
|
||||||
|
|
||||||
|
return ddlCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetDatabaseCollation gets oid of a database and returns all the collation related information
|
||||||
|
* We need this method since collation related info in Form_pg_database is not accessible.
|
||||||
|
*/
|
||||||
|
static DatabaseCollationInfo
|
||||||
|
GetDatabaseCollation(Oid dbOid)
|
||||||
|
{
|
||||||
|
DatabaseCollationInfo info;
|
||||||
|
memset(&info, 0, sizeof(DatabaseCollationInfo));
|
||||||
|
|
||||||
|
Relation rel = table_open(DatabaseRelationId, AccessShareLock);
|
||||||
|
HeapTuple tup = get_catalog_object_by_oid(rel, Anum_pg_database_oid, dbOid);
|
||||||
|
if (!HeapTupleIsValid(tup))
|
||||||
|
{
|
||||||
|
elog(ERROR, "cache lookup failed for database %u", dbOid);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNull = false;
|
||||||
|
|
||||||
|
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||||
|
|
||||||
|
Datum collationDatum = heap_getattr(tup, Anum_pg_database_datcollate, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
info.datcollate = TextDatumGetCString(collationDatum);
|
||||||
|
|
||||||
|
Datum ctypeDatum = heap_getattr(tup, Anum_pg_database_datctype, tupdesc, &isNull);
|
||||||
|
info.datctype = TextDatumGetCString(ctypeDatum);
|
||||||
|
|
||||||
|
Datum icuLocaleDatum = heap_getattr(tup, Anum_pg_database_datlocale, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
info.daticulocale = TextDatumGetCString(icuLocaleDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum collverDatum = heap_getattr(tup, Anum_pg_database_datcollversion, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
info.datcollversion = TextDatumGetCString(collverDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
Datum icurulesDatum = heap_getattr(tup, Anum_pg_database_daticurules, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
info.daticurules = TextDatumGetCString(icurulesDatum);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
table_close(rel, AccessShareLock);
|
||||||
|
heap_freetuple(tup);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetLocaleProviderString gets the datlocprovider stored in pg_database
|
||||||
|
* and returns the string representation of the datlocprovider
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
GetLocaleProviderString(char datlocprovider)
|
||||||
|
{
|
||||||
|
switch (datlocprovider)
|
||||||
|
{
|
||||||
|
case 'c':
|
||||||
|
{
|
||||||
|
return "libc";
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
{
|
||||||
|
return "icu";
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("unexpected datlocprovider value: %c",
|
||||||
|
datlocprovider)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GenerateCreateDatabaseStatementFromPgDatabase gets the pg_database tuple and returns the
|
||||||
|
* CREATE DATABASE statement that can be used to create given database.
|
||||||
|
*
|
||||||
|
* Note that this doesn't deparse OID of the database and this is not a
|
||||||
|
* problem as we anyway don't allow specifying custom OIDs for databases
|
||||||
|
* when creating them.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
GenerateCreateDatabaseStatementFromPgDatabase(Form_pg_database databaseForm)
|
||||||
|
{
|
||||||
|
DatabaseCollationInfo collInfo = GetDatabaseCollation(databaseForm->oid);
|
||||||
|
|
||||||
|
StringInfoData str;
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
appendStringInfo(&str, "CREATE DATABASE %s",
|
||||||
|
quote_identifier(NameStr(databaseForm->datname)));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " CONNECTION LIMIT %d", databaseForm->datconnlimit);
|
||||||
|
|
||||||
|
appendStringInfo(&str, " ALLOW_CONNECTIONS = %s",
|
||||||
|
quote_literal_cstr(databaseForm->datallowconn ? "true" : "false"));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " IS_TEMPLATE = %s",
|
||||||
|
quote_literal_cstr(databaseForm->datistemplate ? "true" : "false"));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " LC_COLLATE = %s",
|
||||||
|
quote_literal_cstr(collInfo.datcollate));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " LC_CTYPE = %s", quote_literal_cstr(collInfo.datctype));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " OWNER = %s",
|
||||||
|
quote_identifier(GetUserNameFromId(databaseForm->datdba, false)));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " TABLESPACE = %s",
|
||||||
|
quote_identifier(GetTablespaceName(databaseForm->dattablespace)));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " ENCODING = %s",
|
||||||
|
quote_literal_cstr(pg_encoding_to_char(databaseForm->encoding)));
|
||||||
|
|
||||||
|
if (collInfo.datcollversion != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, " COLLATION_VERSION = %s",
|
||||||
|
quote_identifier(collInfo.datcollversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collInfo.daticulocale != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, " ICU_LOCALE = %s", quote_identifier(
|
||||||
|
collInfo.daticulocale));
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(&str, " LOCALE_PROVIDER = %s",
|
||||||
|
quote_identifier(GetLocaleProviderString(
|
||||||
|
databaseForm->datlocprovider)));
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
if (collInfo.daticurules != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, " ICU_RULES = %s", quote_identifier(
|
||||||
|
collInfo.daticurules));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateDatabaseDDLCommand returns a CREATE DATABASE command to create given
|
||||||
|
* database
|
||||||
|
*
|
||||||
|
* Command is wrapped by citus_internal_database_command() UDF
|
||||||
|
* to avoid from transaction block restrictions that apply to database commands.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
CreateDatabaseDDLCommand(Oid dbId)
|
||||||
|
{
|
||||||
|
HeapTuple tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbId));
|
||||||
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||||
|
errmsg("database with OID %u does not exist", dbId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Form_pg_database databaseForm = (Form_pg_database) GETSTRUCT(tuple);
|
||||||
|
|
||||||
|
char *createStmt = GenerateCreateDatabaseStatementFromPgDatabase(databaseForm);
|
||||||
|
|
||||||
|
StringInfo outerDbStmt = makeStringInfo();
|
||||||
|
|
||||||
|
/* Generate the CREATE DATABASE statement */
|
||||||
|
appendStringInfo(outerDbStmt,
|
||||||
|
"SELECT citus_internal.database_command(%s)",
|
||||||
|
quote_literal_cstr(createStmt));
|
||||||
|
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
|
return outerDbStmt->data;
|
||||||
|
}
|
||||||
|
|
|
@ -31,53 +31,146 @@
|
||||||
#include "distributed/worker_manager.h"
|
#include "distributed/worker_manager.h"
|
||||||
#include "distributed/worker_transaction.h"
|
#include "distributed/worker_transaction.h"
|
||||||
|
|
||||||
|
typedef enum RequiredObjectSet
|
||||||
|
{
|
||||||
|
REQUIRE_ONLY_DEPENDENCIES = 1,
|
||||||
|
REQUIRE_OBJECT_AND_DEPENDENCIES = 2,
|
||||||
|
} RequiredObjectSet;
|
||||||
|
|
||||||
|
|
||||||
static void EnsureDependenciesCanBeDistributed(const ObjectAddress *relationAddress);
|
static void EnsureDependenciesCanBeDistributed(const ObjectAddress *relationAddress);
|
||||||
static void ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress);
|
static void ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress);
|
||||||
static int ObjectAddressComparator(const void *a, const void *b);
|
static int ObjectAddressComparator(const void *a, const void *b);
|
||||||
static void EnsureDependenciesExistOnAllNodes(const ObjectAddress *target);
|
static void EnsureDependenciesExistOnAllNodes(const ObjectAddress *target);
|
||||||
|
static void EnsureRequiredObjectSetExistOnAllNodes(const ObjectAddress *target,
|
||||||
|
RequiredObjectSet requiredObjectSet);
|
||||||
static List * GetDependencyCreateDDLCommands(const ObjectAddress *dependency);
|
static List * GetDependencyCreateDDLCommands(const ObjectAddress *dependency);
|
||||||
static bool ShouldPropagateObject(const ObjectAddress *address);
|
static bool ShouldPropagateObject(const ObjectAddress *address);
|
||||||
static char * DropTableIfExistsCommand(Oid relationId);
|
static char * DropTableIfExistsCommand(Oid relationId);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EnsureDependenciesExistOnAllNodes finds all the dependencies that we support and makes
|
* EnsureObjectAndDependenciesExistOnAllNodes is a wrapper around
|
||||||
* sure these are available on all workers. If not available they will be created on the
|
* EnsureRequiredObjectSetExistOnAllNodes to ensure the "object itself" (together
|
||||||
* workers via a separate session that will be committed directly so that the objects are
|
* with its dependencies) is available on all nodes.
|
||||||
* visible to potentially multiple sessions creating the shards.
|
*
|
||||||
|
* Different than EnsureDependenciesExistOnAllNodes, we return early if the
|
||||||
|
* target object is distributed already.
|
||||||
|
*
|
||||||
|
* The reason why we don't do the same in EnsureDependenciesExistOnAllNodes
|
||||||
|
* is that it's is used when altering an object too and hence the target object
|
||||||
|
* may instantly have a dependency that needs to be propagated now. For example,
|
||||||
|
* when "GRANT non_dist_role TO dist_role" is executed, we need to propagate
|
||||||
|
* "non_dist_role" to all nodes before propagating the "GRANT" command itself.
|
||||||
|
* For this reason, we call EnsureDependenciesExistOnAllNodes for "dist_role"
|
||||||
|
* and it would automatically discover that "non_dist_role" is a dependency of
|
||||||
|
* "dist_role" and propagate it beforehand.
|
||||||
|
*
|
||||||
|
* However, when we're requested to create the target object itself (and
|
||||||
|
* implicitly its dependencies), we're sure that we're not altering the target
|
||||||
|
* object itself, hence we can return early if the target object is already
|
||||||
|
* distributed. This is the case, for example, when
|
||||||
|
* "REASSIGN OWNED BY dist_role TO non_dist_role" is executed. In that case,
|
||||||
|
* "non_dist_role" is not a dependency of "dist_role" but we want to distribute
|
||||||
|
* "non_dist_role" beforehand and we call this function for "non_dist_role",
|
||||||
|
* not for "dist_role".
|
||||||
|
*
|
||||||
|
* See EnsureRequiredObjectExistOnAllNodes to learn more about how this
|
||||||
|
* function deals with an object created within the same transaction.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
EnsureObjectAndDependenciesExistOnAllNodes(const ObjectAddress *target)
|
||||||
|
{
|
||||||
|
if (IsAnyObjectDistributed(list_make1((ObjectAddress *) target)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EnsureRequiredObjectSetExistOnAllNodes(target, REQUIRE_OBJECT_AND_DEPENDENCIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EnsureDependenciesExistOnAllNodes is a wrapper around
|
||||||
|
* EnsureRequiredObjectSetExistOnAllNodes to ensure "all dependencies" of given
|
||||||
|
* object --but not the object itself-- are available on all nodes.
|
||||||
|
*
|
||||||
|
* See EnsureRequiredObjectSetExistOnAllNodes to learn more about how this
|
||||||
|
* function deals with an object created within the same transaction.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
|
||||||
|
{
|
||||||
|
EnsureRequiredObjectSetExistOnAllNodes(target, REQUIRE_ONLY_DEPENDENCIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EnsureRequiredObjectSetExistOnAllNodes finds all the dependencies that we support and makes
|
||||||
|
* sure these are available on all nodes if required object set is REQUIRE_ONLY_DEPENDENCIES.
|
||||||
|
* Otherwise, i.e., if required object set is REQUIRE_OBJECT_AND_DEPENDENCIES, then this
|
||||||
|
* function creates the object itself on all nodes too. This function ensures that each
|
||||||
|
* of the dependencies are supported by Citus but doesn't check the same for the target
|
||||||
|
* object itself (when REQUIRE_OBJECT_AND_DEPENDENCIES) is provided because we assume that
|
||||||
|
* callers don't call this function for an unsupported function at all.
|
||||||
|
*
|
||||||
|
* If not available, they will be created on the nodes via a separate session that will be
|
||||||
|
* committed directly so that the objects are visible to potentially multiple sessions creating
|
||||||
|
* the shards.
|
||||||
*
|
*
|
||||||
* Note; only the actual objects are created via a separate session, the records to
|
* Note; only the actual objects are created via a separate session, the records to
|
||||||
* pg_dist_object are created in this session. As a side effect the objects could be
|
* pg_dist_object are created in this session. As a side effect the objects could be
|
||||||
* created on the workers without a catalog entry. Updates to the objects on the coordinator
|
* created on the nodes without a catalog entry. Updates to the objects on local node
|
||||||
* are not propagated to the workers until the record is visible on the coordinator.
|
* are not propagated to the remote nodes until the record is visible on local node.
|
||||||
*
|
*
|
||||||
* This is solved by creating the dependencies in an idempotent manner, either via
|
* This is solved by creating the dependencies in an idempotent manner, either via
|
||||||
* postgres native CREATE IF NOT EXISTS, or citus helper functions.
|
* postgres native CREATE IF NOT EXISTS, or citus helper functions.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
|
EnsureRequiredObjectSetExistOnAllNodes(const ObjectAddress *target,
|
||||||
|
RequiredObjectSet requiredObjectSet)
|
||||||
{
|
{
|
||||||
List *dependenciesWithCommands = NIL;
|
Assert(requiredObjectSet == REQUIRE_ONLY_DEPENDENCIES ||
|
||||||
|
requiredObjectSet == REQUIRE_OBJECT_AND_DEPENDENCIES);
|
||||||
|
|
||||||
|
|
||||||
|
List *objectsWithCommands = NIL;
|
||||||
List *ddlCommands = NULL;
|
List *ddlCommands = NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there is any unsupported dependency or circular dependency exists, Citus can
|
* If there is any unsupported dependency or circular dependency exists, Citus can
|
||||||
* not ensure dependencies will exist on all nodes.
|
* not ensure dependencies will exist on all nodes.
|
||||||
|
*
|
||||||
|
* Note that we don't check whether "target" is distributable (in case
|
||||||
|
* REQUIRE_OBJECT_AND_DEPENDENCIES is provided) because we expect callers
|
||||||
|
* to not even call this function if Citus doesn't know how to propagate
|
||||||
|
* "target" object itself.
|
||||||
*/
|
*/
|
||||||
EnsureDependenciesCanBeDistributed(target);
|
EnsureDependenciesCanBeDistributed(target);
|
||||||
|
|
||||||
/* collect all dependencies in creation order and get their ddl commands */
|
/* collect all dependencies in creation order and get their ddl commands */
|
||||||
List *dependencies = GetDependenciesForObject(target);
|
List *objectsToBeCreated = GetDependenciesForObject(target);
|
||||||
ObjectAddress *dependency = NULL;
|
|
||||||
foreach_declared_ptr(dependency, dependencies)
|
/*
|
||||||
|
* Append the target object to make sure that it's created after its
|
||||||
|
* dependencies are created, if requested.
|
||||||
|
*/
|
||||||
|
if (requiredObjectSet == REQUIRE_OBJECT_AND_DEPENDENCIES)
|
||||||
{
|
{
|
||||||
List *dependencyCommands = GetDependencyCreateDDLCommands(dependency);
|
ObjectAddress *targetCopy = palloc(sizeof(ObjectAddress));
|
||||||
|
*targetCopy = *target;
|
||||||
|
|
||||||
|
objectsToBeCreated = lappend(objectsToBeCreated, targetCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectAddress *object = NULL;
|
||||||
|
foreach_declared_ptr(object, objectsToBeCreated)
|
||||||
|
{
|
||||||
|
List *dependencyCommands = GetDependencyCreateDDLCommands(object);
|
||||||
ddlCommands = list_concat(ddlCommands, dependencyCommands);
|
ddlCommands = list_concat(ddlCommands, dependencyCommands);
|
||||||
|
|
||||||
/* create a new list with dependencies that actually created commands */
|
/* create a new list with objects that actually created commands */
|
||||||
if (list_length(dependencyCommands) > 0)
|
if (list_length(dependencyCommands) > 0)
|
||||||
{
|
{
|
||||||
dependenciesWithCommands = lappend(dependenciesWithCommands, dependency);
|
objectsWithCommands = lappend(objectsWithCommands, object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (list_length(ddlCommands) <= 0)
|
if (list_length(ddlCommands) <= 0)
|
||||||
|
@ -97,29 +190,31 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
|
||||||
* either get it now, or get it in citus_add_node after this transaction finishes and
|
* either get it now, or get it in citus_add_node after this transaction finishes and
|
||||||
* the pg_dist_object record becomes visible.
|
* the pg_dist_object record becomes visible.
|
||||||
*/
|
*/
|
||||||
List *workerNodeList = ActivePrimaryNonCoordinatorNodeList(RowShareLock);
|
List *remoteNodeList = ActivePrimaryRemoteNodeList(RowShareLock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lock dependent objects explicitly to make sure same DDL command won't be sent
|
* Lock objects to be created explicitly to make sure same DDL command won't be sent
|
||||||
* multiple times from parallel sessions.
|
* multiple times from parallel sessions.
|
||||||
*
|
*
|
||||||
* Sort dependencies that will be created on workers to not to have any deadlock
|
* Sort the objects that will be created on workers to not to have any deadlock
|
||||||
* issue if different sessions are creating different objects.
|
* issue if different sessions are creating different objects.
|
||||||
*/
|
*/
|
||||||
List *addressSortedDependencies = SortList(dependenciesWithCommands,
|
List *addressSortedDependencies = SortList(objectsWithCommands,
|
||||||
ObjectAddressComparator);
|
ObjectAddressComparator);
|
||||||
foreach_declared_ptr(dependency, addressSortedDependencies)
|
foreach_declared_ptr(object, addressSortedDependencies)
|
||||||
{
|
{
|
||||||
LockDatabaseObject(dependency->classId, dependency->objectId,
|
LockDatabaseObject(object->classId, object->objectId,
|
||||||
dependency->objectSubId, ExclusiveLock);
|
object->objectSubId, ExclusiveLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We need to propagate dependencies via the current user's metadata connection if
|
* We need to propagate objects via the current user's metadata connection if
|
||||||
* any dependency for the target is created in the current transaction. Our assumption
|
* any of the objects that we're interested in are created in the current transaction.
|
||||||
* is that if we rely on a dependency created in the current transaction, then the
|
* Our assumption is that if we rely on an object created in the current transaction,
|
||||||
* current user, most probably, has permissions to create the target object as well.
|
* then the current user, most probably, has permissions to create the target object
|
||||||
|
* as well.
|
||||||
|
*
|
||||||
* Note that, user still may not be able to create the target due to no permissions
|
* Note that, user still may not be able to create the target due to no permissions
|
||||||
* for any of its dependencies. But this is ok since it should be rare.
|
* for any of its dependencies. But this is ok since it should be rare.
|
||||||
*
|
*
|
||||||
|
@ -127,14 +222,25 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
|
||||||
* have visibility issues since propagated dependencies would be invisible to
|
* have visibility issues since propagated dependencies would be invisible to
|
||||||
* the separate connection until we locally commit.
|
* the separate connection until we locally commit.
|
||||||
*/
|
*/
|
||||||
if (HasAnyDependencyInPropagatedObjects(target))
|
List *createdObjectList = GetAllSupportedDependenciesForObject(target);
|
||||||
|
|
||||||
|
/* consider target as well if we're requested to create it too */
|
||||||
|
if (requiredObjectSet == REQUIRE_OBJECT_AND_DEPENDENCIES)
|
||||||
{
|
{
|
||||||
SendCommandListToWorkersWithMetadata(ddlCommands);
|
ObjectAddress *targetCopy = palloc(sizeof(ObjectAddress));
|
||||||
|
*targetCopy = *target;
|
||||||
|
|
||||||
|
createdObjectList = lappend(createdObjectList, targetCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasAnyObjectInPropagatedObjects(createdObjectList))
|
||||||
|
{
|
||||||
|
SendCommandListToRemoteNodesWithMetadata(ddlCommands);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WorkerNode *workerNode = NULL;
|
WorkerNode *workerNode = NULL;
|
||||||
foreach_declared_ptr(workerNode, workerNodeList)
|
foreach_declared_ptr(workerNode, remoteNodeList)
|
||||||
{
|
{
|
||||||
const char *nodeName = workerNode->workerName;
|
const char *nodeName = workerNode->workerName;
|
||||||
uint32 nodePort = workerNode->workerPort;
|
uint32 nodePort = workerNode->workerPort;
|
||||||
|
@ -146,11 +252,11 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We do this after creating the objects on the workers, we make sure
|
* We do this after creating the objects on remote nodes, we make sure
|
||||||
* that objects have been created on worker nodes before marking them
|
* that objects have been created on remote nodes before marking them
|
||||||
* distributed, so MarkObjectDistributed wouldn't fail.
|
* distributed, so MarkObjectDistributed wouldn't fail.
|
||||||
*/
|
*/
|
||||||
foreach_declared_ptr(dependency, dependenciesWithCommands)
|
foreach_declared_ptr(object, objectsWithCommands)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* pg_dist_object entries must be propagated with the super user, since
|
* pg_dist_object entries must be propagated with the super user, since
|
||||||
|
@ -160,7 +266,7 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
|
||||||
* Only dependent object's metadata should be propagated with super user.
|
* Only dependent object's metadata should be propagated with super user.
|
||||||
* Metadata of the table itself must be propagated with the current user.
|
* Metadata of the table itself must be propagated with the current user.
|
||||||
*/
|
*/
|
||||||
MarkObjectDistributedViaSuperUser(dependency);
|
MarkObjectDistributedViaSuperUser(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,16 +565,29 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
|
||||||
|
|
||||||
case OCLASS_DATABASE:
|
case OCLASS_DATABASE:
|
||||||
{
|
{
|
||||||
List *databaseDDLCommands = NIL;
|
/*
|
||||||
|
* For the database where Citus is installed, only propagate the ownership of the
|
||||||
/* only propagate the ownership of the database when the feature is on */
|
* database, only when the feature is on.
|
||||||
if (EnableAlterDatabaseOwner)
|
*
|
||||||
|
* This is because this database must exist on all nodes already so we shouldn't
|
||||||
|
* need to "CREATE" it on other nodes. However, we still need to correctly reflect
|
||||||
|
* its owner on other nodes too.
|
||||||
|
*/
|
||||||
|
if (dependency->objectId == MyDatabaseId && EnableAlterDatabaseOwner)
|
||||||
{
|
{
|
||||||
List *ownerDDLCommands = DatabaseOwnerDDLCommands(dependency);
|
return DatabaseOwnerDDLCommands(dependency);
|
||||||
databaseDDLCommands = list_concat(databaseDDLCommands, ownerDDLCommands);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return databaseDDLCommands;
|
/*
|
||||||
|
* For the other databases, create the database on all nodes, only when the feature
|
||||||
|
* is on.
|
||||||
|
*/
|
||||||
|
if (dependency->objectId != MyDatabaseId && EnableCreateDatabasePropagation)
|
||||||
|
{
|
||||||
|
return GetDatabaseMetadataSyncCommands(dependency->objectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
case OCLASS_PROC:
|
case OCLASS_PROC:
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
#include "distributed/commands/utility_hook.h"
|
#include "distributed/commands/utility_hook.h"
|
||||||
|
#include "distributed/comment.h"
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
|
|
||||||
|
@ -151,6 +152,17 @@ static DistributeObjectOps Any_AlterRole = {
|
||||||
.address = AlterRoleStmtObjectAddress,
|
.address = AlterRoleStmtObjectAddress,
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Any_AlterRoleRename = {
|
||||||
|
.deparse = DeparseRenameRoleStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessAlterRoleRenameStmt,
|
||||||
|
.postprocess = NULL,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = RenameRoleStmtObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
|
||||||
static DistributeObjectOps Any_AlterRoleSet = {
|
static DistributeObjectOps Any_AlterRoleSet = {
|
||||||
.deparse = DeparseAlterRoleSetStmt,
|
.deparse = DeparseAlterRoleSetStmt,
|
||||||
.qualify = QualifyAlterRoleSetStmt,
|
.qualify = QualifyAlterRoleSetStmt,
|
||||||
|
@ -264,6 +276,17 @@ static DistributeObjectOps Any_CreateRole = {
|
||||||
.address = CreateRoleStmtObjectAddress,
|
.address = CreateRoleStmtObjectAddress,
|
||||||
.markDistributed = true,
|
.markDistributed = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Any_ReassignOwned = {
|
||||||
|
.deparse = DeparseReassignOwnedStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = NULL,
|
||||||
|
.postprocess = PostprocessReassignOwnedStmt,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = NULL,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
|
||||||
static DistributeObjectOps Any_DropOwned = {
|
static DistributeObjectOps Any_DropOwned = {
|
||||||
.deparse = DeparseDropOwnedStmt,
|
.deparse = DeparseDropOwnedStmt,
|
||||||
.qualify = NULL,
|
.qualify = NULL,
|
||||||
|
@ -282,6 +305,17 @@ static DistributeObjectOps Any_DropRole = {
|
||||||
.address = NULL,
|
.address = NULL,
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Role_Comment = {
|
||||||
|
.deparse = DeparseCommentStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||||
|
.postprocess = NULL,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = CommentObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
static DistributeObjectOps Any_CreateForeignServer = {
|
static DistributeObjectOps Any_CreateForeignServer = {
|
||||||
.deparse = DeparseCreateForeignServerStmt,
|
.deparse = DeparseCreateForeignServerStmt,
|
||||||
.qualify = NULL,
|
.qualify = NULL,
|
||||||
|
@ -365,10 +399,37 @@ static DistributeObjectOps Any_Rename = {
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
static DistributeObjectOps Any_SecLabel = {
|
static DistributeObjectOps Any_SecLabel = {
|
||||||
.deparse = DeparseSecLabelStmt,
|
.deparse = NULL,
|
||||||
.qualify = NULL,
|
.qualify = NULL,
|
||||||
.preprocess = NULL,
|
.preprocess = NULL,
|
||||||
.postprocess = PostprocessSecLabelStmt,
|
.postprocess = PostprocessAnySecLabelStmt,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = SecLabelStmtObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
static DistributeObjectOps Role_SecLabel = {
|
||||||
|
.deparse = DeparseRoleSecLabelStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = NULL,
|
||||||
|
.postprocess = PostprocessRoleSecLabelStmt,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = SecLabelStmtObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
static DistributeObjectOps Table_SecLabel = {
|
||||||
|
.deparse = DeparseTableSecLabelStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = NULL,
|
||||||
|
.postprocess = PostprocessTableOrColumnSecLabelStmt,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = SecLabelStmtObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
static DistributeObjectOps Column_SecLabel = {
|
||||||
|
.deparse = DeparseColumnSecLabelStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = NULL,
|
||||||
|
.postprocess = PostprocessTableOrColumnSecLabelStmt,
|
||||||
.operationType = DIST_OPS_ALTER,
|
.operationType = DIST_OPS_ALTER,
|
||||||
.address = SecLabelStmtObjectAddress,
|
.address = SecLabelStmtObjectAddress,
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
|
@ -465,6 +526,28 @@ static DistributeObjectOps Database_Alter = {
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Database_Create = {
|
||||||
|
.deparse = DeparseCreateDatabaseStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessCreateDatabaseStmt,
|
||||||
|
.postprocess = PostprocessCreateDatabaseStmt,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_CREATE,
|
||||||
|
.address = CreateDatabaseStmtObjectAddress,
|
||||||
|
.markDistributed = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Database_Drop = {
|
||||||
|
.deparse = DeparseDropDatabaseStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessDropDatabaseStmt,
|
||||||
|
.postprocess = NULL,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_DROP,
|
||||||
|
.address = DropDatabaseStmtObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
|
||||||
static DistributeObjectOps Database_RefreshColl = {
|
static DistributeObjectOps Database_RefreshColl = {
|
||||||
.deparse = DeparseAlterDatabaseRefreshCollStmt,
|
.deparse = DeparseAlterDatabaseRefreshCollStmt,
|
||||||
.qualify = NULL,
|
.qualify = NULL,
|
||||||
|
@ -476,6 +559,39 @@ static DistributeObjectOps Database_RefreshColl = {
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Database_Set = {
|
||||||
|
.deparse = DeparseAlterDatabaseSetStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessAlterDatabaseSetStmt,
|
||||||
|
.postprocess = NULL,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = NULL,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Database_Comment = {
|
||||||
|
.deparse = DeparseCommentStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||||
|
.postprocess = NULL,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = CommentObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Database_Rename = {
|
||||||
|
.deparse = DeparseAlterDatabaseRenameStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessAlterDatabaseRenameStmt,
|
||||||
|
.postprocess = PostprocessAlterDatabaseRenameStmt,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_ALTER,
|
||||||
|
.address = NULL,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
|
||||||
static DistributeObjectOps Domain_Alter = {
|
static DistributeObjectOps Domain_Alter = {
|
||||||
.deparse = DeparseAlterDomainStmt,
|
.deparse = DeparseAlterDomainStmt,
|
||||||
.qualify = QualifyAlterDomainStmt,
|
.qualify = QualifyAlterDomainStmt,
|
||||||
|
@ -902,13 +1018,18 @@ static DistributeObjectOps TextSearchConfig_AlterOwner = {
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
static DistributeObjectOps TextSearchConfig_Comment = {
|
static DistributeObjectOps TextSearchConfig_Comment = {
|
||||||
.deparse = DeparseTextSearchConfigurationCommentStmt,
|
.deparse = DeparseCommentStmt,
|
||||||
|
|
||||||
|
/* TODO: When adding new comment types we should create an abstracted
|
||||||
|
* qualify function, just like we have an abstract deparse
|
||||||
|
* and adress function
|
||||||
|
*/
|
||||||
.qualify = QualifyTextSearchConfigurationCommentStmt,
|
.qualify = QualifyTextSearchConfigurationCommentStmt,
|
||||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||||
.postprocess = NULL,
|
.postprocess = NULL,
|
||||||
.objectType = OBJECT_TSCONFIGURATION,
|
.objectType = OBJECT_TSCONFIGURATION,
|
||||||
.operationType = DIST_OPS_ALTER,
|
.operationType = DIST_OPS_ALTER,
|
||||||
.address = TextSearchConfigurationCommentObjectAddress,
|
.address = CommentObjectAddress,
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
static DistributeObjectOps TextSearchConfig_Define = {
|
static DistributeObjectOps TextSearchConfig_Define = {
|
||||||
|
@ -971,13 +1092,13 @@ static DistributeObjectOps TextSearchDict_AlterOwner = {
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
static DistributeObjectOps TextSearchDict_Comment = {
|
static DistributeObjectOps TextSearchDict_Comment = {
|
||||||
.deparse = DeparseTextSearchDictionaryCommentStmt,
|
.deparse = DeparseCommentStmt,
|
||||||
.qualify = QualifyTextSearchDictionaryCommentStmt,
|
.qualify = QualifyTextSearchDictionaryCommentStmt,
|
||||||
.preprocess = PreprocessAlterDistributedObjectStmt,
|
.preprocess = PreprocessAlterDistributedObjectStmt,
|
||||||
.postprocess = NULL,
|
.postprocess = NULL,
|
||||||
.objectType = OBJECT_TSDICTIONARY,
|
.objectType = OBJECT_TSDICTIONARY,
|
||||||
.operationType = DIST_OPS_ALTER,
|
.operationType = DIST_OPS_ALTER,
|
||||||
.address = TextSearchDictCommentObjectAddress,
|
.address = CommentObjectAddress,
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
static DistributeObjectOps TextSearchDict_Define = {
|
static DistributeObjectOps TextSearchDict_Define = {
|
||||||
|
@ -1317,11 +1438,27 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Database_Alter;
|
return &Database_Alter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case T_CreatedbStmt:
|
||||||
|
{
|
||||||
|
return &Database_Create;
|
||||||
|
}
|
||||||
|
|
||||||
|
case T_DropdbStmt:
|
||||||
|
{
|
||||||
|
return &Database_Drop;
|
||||||
|
}
|
||||||
|
|
||||||
case T_AlterDatabaseRefreshCollStmt:
|
case T_AlterDatabaseRefreshCollStmt:
|
||||||
{
|
{
|
||||||
return &Database_RefreshColl;
|
return &Database_RefreshColl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case T_AlterDatabaseSetStmt:
|
||||||
|
{
|
||||||
|
return &Database_Set;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
case T_AlterDomainStmt:
|
case T_AlterDomainStmt:
|
||||||
{
|
{
|
||||||
return &Domain_Alter;
|
return &Domain_Alter;
|
||||||
|
@ -1689,6 +1826,16 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &TextSearchDict_Comment;
|
return &TextSearchDict_Comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OBJECT_DATABASE:
|
||||||
|
{
|
||||||
|
return &Database_Comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OBJECT_ROLE:
|
||||||
|
{
|
||||||
|
return &Role_Comment;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
return &NoDistributeOps;
|
return &NoDistributeOps;
|
||||||
|
@ -1798,6 +1945,11 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Any_DropOwned;
|
return &Any_DropOwned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case T_ReassignOwnedStmt:
|
||||||
|
{
|
||||||
|
return &Any_ReassignOwned;
|
||||||
|
}
|
||||||
|
|
||||||
case T_DropStmt:
|
case T_DropStmt:
|
||||||
{
|
{
|
||||||
DropStmt *stmt = castNode(DropStmt, node);
|
DropStmt *stmt = castNode(DropStmt, node);
|
||||||
|
@ -1994,8 +2146,28 @@ GetDistributeObjectOps(Node *node)
|
||||||
|
|
||||||
case T_SecLabelStmt:
|
case T_SecLabelStmt:
|
||||||
{
|
{
|
||||||
|
SecLabelStmt *stmt = castNode(SecLabelStmt, node);
|
||||||
|
switch (stmt->objtype)
|
||||||
|
{
|
||||||
|
case OBJECT_ROLE:
|
||||||
|
{
|
||||||
|
return &Role_SecLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OBJECT_TABLE:
|
||||||
|
{
|
||||||
|
return &Table_SecLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OBJECT_COLUMN:
|
||||||
|
{
|
||||||
|
return &Column_SecLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
return &Any_SecLabel;
|
return &Any_SecLabel;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case T_RenameStmt:
|
case T_RenameStmt:
|
||||||
{
|
{
|
||||||
|
@ -2017,6 +2189,11 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Collation_Rename;
|
return &Collation_Rename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OBJECT_DATABASE:
|
||||||
|
{
|
||||||
|
return &Database_Rename;
|
||||||
|
}
|
||||||
|
|
||||||
case OBJECT_DOMAIN:
|
case OBJECT_DOMAIN:
|
||||||
{
|
{
|
||||||
return &Domain_Rename;
|
return &Domain_Rename;
|
||||||
|
@ -2047,6 +2224,11 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Publication_Rename;
|
return &Publication_Rename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OBJECT_ROLE:
|
||||||
|
{
|
||||||
|
return &Any_AlterRoleRename;
|
||||||
|
}
|
||||||
|
|
||||||
case OBJECT_ROUTINE:
|
case OBJECT_ROUTINE:
|
||||||
{
|
{
|
||||||
return &Routine_Rename;
|
return &Routine_Rename;
|
||||||
|
|
|
@ -885,6 +885,7 @@ UpdateFunctionDistributionInfo(const ObjectAddress *distAddress,
|
||||||
|
|
||||||
char *workerPgDistObjectUpdateCommand =
|
char *workerPgDistObjectUpdateCommand =
|
||||||
MarkObjectsDistributedCreateCommand(objectAddressList,
|
MarkObjectsDistributedCreateCommand(objectAddressList,
|
||||||
|
NIL,
|
||||||
distArgumentIndexList,
|
distArgumentIndexList,
|
||||||
colocationIdList,
|
colocationIdList,
|
||||||
forceDelegationList);
|
forceDelegationList);
|
||||||
|
@ -980,7 +981,6 @@ GetAggregateDDLCommand(const RegProcedure funcOid, bool useCreateOrReplace)
|
||||||
char *argmodes = NULL;
|
char *argmodes = NULL;
|
||||||
int insertorderbyat = -1;
|
int insertorderbyat = -1;
|
||||||
int argsprinted = 0;
|
int argsprinted = 0;
|
||||||
int inputargno = 0;
|
|
||||||
|
|
||||||
HeapTuple proctup = SearchSysCache1(PROCOID, funcOid);
|
HeapTuple proctup = SearchSysCache1(PROCOID, funcOid);
|
||||||
if (!HeapTupleIsValid(proctup))
|
if (!HeapTupleIsValid(proctup))
|
||||||
|
@ -1060,7 +1060,6 @@ GetAggregateDDLCommand(const RegProcedure funcOid, bool useCreateOrReplace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputargno++; /* this is a 1-based counter */
|
|
||||||
if (argsprinted == insertorderbyat)
|
if (argsprinted == insertorderbyat)
|
||||||
{
|
{
|
||||||
appendStringInfoString(&buf, " ORDER BY ");
|
appendStringInfoString(&buf, " ORDER BY ");
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "distributed/citus_ruleutils.h"
|
#include "distributed/citus_ruleutils.h"
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
#include "distributed/commands/utility_hook.h"
|
#include "distributed/commands/utility_hook.h"
|
||||||
|
#include "distributed/deparser.h"
|
||||||
#include "distributed/metadata/distobject.h"
|
#include "distributed/metadata/distobject.h"
|
||||||
#include "distributed/metadata_cache.h"
|
#include "distributed/metadata_cache.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
|
@ -32,7 +33,6 @@ static List * CollectGrantTableIdList(GrantStmt *grantStmt);
|
||||||
* needed during the worker node portion of DDL execution before returning the
|
* 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.
|
* DDLJobs in a List. If no distributed table is involved, this returns NIL.
|
||||||
*
|
*
|
||||||
* NB: So far column level privileges are not supported.
|
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
PreprocessGrantStmt(Node *node, const char *queryString,
|
PreprocessGrantStmt(Node *node, const char *queryString,
|
||||||
|
@ -70,9 +70,12 @@ PreprocessGrantStmt(Node *node, const char *queryString,
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
|
||||||
/* deparse the privileges */
|
/* deparse the privileges */
|
||||||
if (grantStmt->privileges == NIL)
|
if (grantStmt->privileges == NIL)
|
||||||
{
|
{
|
||||||
|
/* this is used for table level only */
|
||||||
appendStringInfo(&privsString, "ALL");
|
appendStringInfo(&privsString, "ALL");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -88,18 +91,44 @@ PreprocessGrantStmt(Node *node, const char *queryString,
|
||||||
{
|
{
|
||||||
appendStringInfoString(&privsString, ", ");
|
appendStringInfoString(&privsString, ", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (priv->priv_name)
|
||||||
|
{
|
||||||
|
appendStringInfo(&privsString, "%s", priv->priv_name);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* ALL can only be set alone.
|
||||||
|
* And ALL is not added as a keyword in priv_name by parser, but
|
||||||
|
* because there are column(s) defined, a grantStmt->privileges is
|
||||||
|
* defined. So we need to handle this special case here (see if
|
||||||
|
* condition above).
|
||||||
|
*/
|
||||||
|
else if (isFirst)
|
||||||
|
{
|
||||||
|
/* this is used for column level only */
|
||||||
|
appendStringInfo(&privsString, "ALL");
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Instead of relying only on the syntax check done by Postgres and
|
||||||
|
* adding an assert here, add a default ERROR if ALL is not first
|
||||||
|
* and no priv_name is defined.
|
||||||
|
*/
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
|
||||||
|
errmsg("Cannot parse GRANT/REVOKE privileges")));
|
||||||
|
}
|
||||||
|
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
|
|
||||||
if (priv->cols != NIL)
|
if (priv->cols != NIL)
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
StringInfoData colsString;
|
||||||
errmsg("grant/revoke on column list is currently "
|
initStringInfo(&colsString);
|
||||||
"unsupported")));
|
|
||||||
|
AppendColumnNameList(&colsString, priv->cols);
|
||||||
|
appendStringInfo(&privsString, "%s", colsString.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert(priv->priv_name != NULL);
|
|
||||||
|
|
||||||
appendStringInfo(&privsString, "%s", priv->priv_name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +182,15 @@ PreprocessGrantStmt(Node *node, const char *queryString,
|
||||||
appendStringInfo(&ddlString, "REVOKE %s%s ON %s FROM %s",
|
appendStringInfo(&ddlString, "REVOKE %s%s ON %s FROM %s",
|
||||||
grantOption, privsString.data, targetString.data,
|
grantOption, privsString.data, targetString.data,
|
||||||
granteesString.data);
|
granteesString.data);
|
||||||
|
|
||||||
|
if (grantStmt->behavior == DROP_CASCADE)
|
||||||
|
{
|
||||||
|
appendStringInfoString(&ddlString, " CASCADE");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfoString(&ddlString, " RESTRICT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
|
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "miscadmin.h"
|
||||||
|
|
||||||
#include "access/genam.h"
|
#include "access/genam.h"
|
||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
|
@ -17,13 +19,6 @@
|
||||||
#include "catalog/index.h"
|
#include "catalog/index.h"
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/pg_class.h"
|
#include "catalog/pg_class.h"
|
||||||
|
|
||||||
#include "pg_version_constants.h"
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
|
||||||
#include "catalog/pg_namespace.h"
|
|
||||||
#endif
|
|
||||||
#include "miscadmin.h"
|
|
||||||
|
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
#include "commands/tablecmds.h"
|
#include "commands/tablecmds.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
|
@ -36,6 +31,8 @@
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
#include "distributed/citus_ruleutils.h"
|
#include "distributed/citus_ruleutils.h"
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
#include "distributed/commands/utility_hook.h"
|
#include "distributed/commands/utility_hook.h"
|
||||||
|
@ -56,6 +53,10 @@
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_manager.h"
|
#include "distributed/worker_manager.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
#include "catalog/pg_namespace.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* Local functions forward declarations for helper functions */
|
/* Local functions forward declarations for helper functions */
|
||||||
static void ErrorIfCreateIndexHasTooManyColumns(IndexStmt *createIndexStatement);
|
static void ErrorIfCreateIndexHasTooManyColumns(IndexStmt *createIndexStatement);
|
||||||
|
@ -183,6 +184,8 @@ PreprocessIndexStmt(Node *node, const char *createIndexCommand,
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
|
||||||
if (createIndexStatement->idxname == NULL)
|
if (createIndexStatement->idxname == NULL)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -490,6 +493,7 @@ GenerateCreateIndexDDLJob(IndexStmt *createIndexStatement, const char *createInd
|
||||||
ddlJob->startNewTransaction = createIndexStatement->concurrent;
|
ddlJob->startNewTransaction = createIndexStatement->concurrent;
|
||||||
ddlJob->metadataSyncCommand = createIndexCommand;
|
ddlJob->metadataSyncCommand = createIndexCommand;
|
||||||
ddlJob->taskList = CreateIndexTaskList(createIndexStatement);
|
ddlJob->taskList = CreateIndexTaskList(createIndexStatement);
|
||||||
|
ddlJob->warnForPartialFailure = true;
|
||||||
|
|
||||||
return ddlJob;
|
return ddlJob;
|
||||||
}
|
}
|
||||||
|
@ -649,6 +653,7 @@ PreprocessReindexStmt(Node *node, const char *reindexCommand,
|
||||||
"concurrently");
|
"concurrently");
|
||||||
ddlJob->metadataSyncCommand = reindexCommand;
|
ddlJob->metadataSyncCommand = reindexCommand;
|
||||||
ddlJob->taskList = CreateReindexTaskList(relationId, reindexStatement);
|
ddlJob->taskList = CreateReindexTaskList(relationId, reindexStatement);
|
||||||
|
ddlJob->warnForPartialFailure = true;
|
||||||
|
|
||||||
ddlJobs = list_make1(ddlJob);
|
ddlJobs = list_make1(ddlJob);
|
||||||
}
|
}
|
||||||
|
@ -777,6 +782,7 @@ PreprocessDropIndexStmt(Node *node, const char *dropIndexCommand,
|
||||||
ddlJob->metadataSyncCommand = dropIndexCommand;
|
ddlJob->metadataSyncCommand = dropIndexCommand;
|
||||||
ddlJob->taskList = DropIndexTaskList(distributedRelationId, distributedIndexId,
|
ddlJob->taskList = DropIndexTaskList(distributedRelationId, distributedIndexId,
|
||||||
dropIndexStatement);
|
dropIndexStatement);
|
||||||
|
ddlJob->warnForPartialFailure = true;
|
||||||
|
|
||||||
ddlJobs = list_make1(ddlJob);
|
ddlJobs = list_make1(ddlJob);
|
||||||
}
|
}
|
||||||
|
@ -941,7 +947,7 @@ CreateIndexTaskList(IndexStmt *indexStmt)
|
||||||
task->dependentTaskList = NULL;
|
task->dependentTaskList = NULL;
|
||||||
task->anchorShardId = shardId;
|
task->anchorShardId = shardId;
|
||||||
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
||||||
task->cannotBeExecutedInTransction = indexStmt->concurrent;
|
task->cannotBeExecutedInTransaction = indexStmt->concurrent;
|
||||||
|
|
||||||
taskList = lappend(taskList, task);
|
taskList = lappend(taskList, task);
|
||||||
|
|
||||||
|
@ -986,7 +992,7 @@ CreateReindexTaskList(Oid relationId, ReindexStmt *reindexStmt)
|
||||||
task->dependentTaskList = NULL;
|
task->dependentTaskList = NULL;
|
||||||
task->anchorShardId = shardId;
|
task->anchorShardId = shardId;
|
||||||
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
||||||
task->cannotBeExecutedInTransction =
|
task->cannotBeExecutedInTransaction =
|
||||||
IsReindexWithParam_compat(reindexStmt, "concurrently");
|
IsReindexWithParam_compat(reindexStmt, "concurrently");
|
||||||
|
|
||||||
taskList = lappend(taskList, task);
|
taskList = lappend(taskList, task);
|
||||||
|
@ -1324,7 +1330,7 @@ DropIndexTaskList(Oid relationId, Oid indexId, DropStmt *dropStmt)
|
||||||
task->dependentTaskList = NULL;
|
task->dependentTaskList = NULL;
|
||||||
task->anchorShardId = shardId;
|
task->anchorShardId = shardId;
|
||||||
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
||||||
task->cannotBeExecutedInTransction = dropStmt->concurrent;
|
task->cannotBeExecutedInTransaction = dropStmt->concurrent;
|
||||||
|
|
||||||
taskList = lappend(taskList, task);
|
taskList = lappend(taskList, task);
|
||||||
|
|
||||||
|
|
|
@ -64,28 +64,6 @@
|
||||||
#include "commands/copy.h"
|
#include "commands/copy.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
#include "commands/progress.h"
|
#include "commands/progress.h"
|
||||||
|
|
||||||
#include "pg_version_constants.h"
|
|
||||||
|
|
||||||
#include "distributed/citus_safe_lib.h"
|
|
||||||
#include "distributed/commands/multi_copy.h"
|
|
||||||
#include "distributed/commands/utility_hook.h"
|
|
||||||
#include "distributed/coordinator_protocol.h"
|
|
||||||
#include "distributed/intermediate_results.h"
|
|
||||||
#include "distributed/listutils.h"
|
|
||||||
#include "distributed/local_executor.h"
|
|
||||||
#include "distributed/locally_reserved_shared_connections.h"
|
|
||||||
#include "distributed/log_utils.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"
|
|
||||||
#include "distributed/placement_connection.h"
|
|
||||||
#include "distributed/relation_access_tracking.h"
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
|
||||||
#include "distributed/relation_utils.h"
|
|
||||||
#endif
|
|
||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
#include "foreign/foreign.h"
|
#include "foreign/foreign.h"
|
||||||
#include "libpq/libpq.h"
|
#include "libpq/libpq.h"
|
||||||
|
@ -102,18 +80,41 @@
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
|
#include "distributed/citus_safe_lib.h"
|
||||||
|
#include "distributed/commands/multi_copy.h"
|
||||||
|
#include "distributed/commands/utility_hook.h"
|
||||||
|
#include "distributed/coordinator_protocol.h"
|
||||||
#include "distributed/hash_helpers.h"
|
#include "distributed/hash_helpers.h"
|
||||||
|
#include "distributed/intermediate_results.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
|
#include "distributed/local_executor.h"
|
||||||
#include "distributed/local_multi_copy.h"
|
#include "distributed/local_multi_copy.h"
|
||||||
|
#include "distributed/locally_reserved_shared_connections.h"
|
||||||
|
#include "distributed/log_utils.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"
|
||||||
|
#include "distributed/placement_connection.h"
|
||||||
|
#include "distributed/relation_access_tracking.h"
|
||||||
#include "distributed/remote_commands.h"
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/remote_transaction.h"
|
#include "distributed/remote_transaction.h"
|
||||||
#include "distributed/replication_origin_session_utils.h"
|
#include "distributed/replication_origin_session_utils.h"
|
||||||
#include "distributed/resource_lock.h"
|
#include "distributed/resource_lock.h"
|
||||||
#include "distributed/shard_pruning.h"
|
#include "distributed/shard_pruning.h"
|
||||||
#include "distributed/shared_connection_stats.h"
|
#include "distributed/shared_connection_stats.h"
|
||||||
|
#include "distributed/stats/stat_counters.h"
|
||||||
#include "distributed/transmit.h"
|
#include "distributed/transmit.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_protocol.h"
|
#include "distributed/worker_protocol.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
#include "distributed/relation_utils.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* constant used in binary protocol */
|
/* constant used in binary protocol */
|
||||||
static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
|
static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
|
||||||
|
@ -499,10 +500,14 @@ CopyToExistingShards(CopyStmt *copyStatement, QueryCompletion *completionTag)
|
||||||
|
|
||||||
/* set up the destination for the COPY */
|
/* set up the destination for the COPY */
|
||||||
const bool publishableData = true;
|
const bool publishableData = true;
|
||||||
|
|
||||||
|
/* we want to track query counters for "COPY (to) distributed-table .." commands */
|
||||||
|
const bool trackQueryCounters = true;
|
||||||
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(tableId, columnNameList,
|
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(tableId, columnNameList,
|
||||||
partitionColumnIndex,
|
partitionColumnIndex,
|
||||||
executorState, NULL,
|
executorState, NULL,
|
||||||
publishableData);
|
publishableData,
|
||||||
|
trackQueryCounters);
|
||||||
|
|
||||||
/* if the user specified an explicit append-to_shard option, write to it */
|
/* if the user specified an explicit append-to_shard option, write to it */
|
||||||
uint64 appendShardId = ProcessAppendToShardOption(tableId, copyStatement);
|
uint64 appendShardId = ProcessAppendToShardOption(tableId, copyStatement);
|
||||||
|
@ -1877,11 +1882,15 @@ CopyFlushOutput(CopyOutState cstate, char *start, char *pointer)
|
||||||
* of intermediate results that are co-located with the actual table.
|
* of intermediate results that are co-located with the actual table.
|
||||||
* The names of the intermediate results with be of the form:
|
* The names of the intermediate results with be of the form:
|
||||||
* intermediateResultIdPrefix_<shardid>
|
* intermediateResultIdPrefix_<shardid>
|
||||||
|
*
|
||||||
|
* If trackQueryCounters is true, the COPY will increment the query stat
|
||||||
|
* counters as needed at the end of the COPY.
|
||||||
*/
|
*/
|
||||||
CitusCopyDestReceiver *
|
CitusCopyDestReceiver *
|
||||||
CreateCitusCopyDestReceiver(Oid tableId, List *columnNameList, int partitionColumnIndex,
|
CreateCitusCopyDestReceiver(Oid tableId, List *columnNameList, int partitionColumnIndex,
|
||||||
EState *executorState,
|
EState *executorState,
|
||||||
char *intermediateResultIdPrefix, bool isPublishable)
|
char *intermediateResultIdPrefix, bool isPublishable,
|
||||||
|
bool trackQueryCounters)
|
||||||
{
|
{
|
||||||
CitusCopyDestReceiver *copyDest = (CitusCopyDestReceiver *) palloc0(
|
CitusCopyDestReceiver *copyDest = (CitusCopyDestReceiver *) palloc0(
|
||||||
sizeof(CitusCopyDestReceiver));
|
sizeof(CitusCopyDestReceiver));
|
||||||
|
@ -1901,6 +1910,7 @@ CreateCitusCopyDestReceiver(Oid tableId, List *columnNameList, int partitionColu
|
||||||
copyDest->colocatedIntermediateResultIdPrefix = intermediateResultIdPrefix;
|
copyDest->colocatedIntermediateResultIdPrefix = intermediateResultIdPrefix;
|
||||||
copyDest->memoryContext = CurrentMemoryContext;
|
copyDest->memoryContext = CurrentMemoryContext;
|
||||||
copyDest->isPublishable = isPublishable;
|
copyDest->isPublishable = isPublishable;
|
||||||
|
copyDest->trackQueryCounters = trackQueryCounters;
|
||||||
|
|
||||||
return copyDest;
|
return copyDest;
|
||||||
}
|
}
|
||||||
|
@ -2587,8 +2597,9 @@ ShardIdForTuple(CitusCopyDestReceiver *copyDest, Datum *columnValues, bool *colu
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CitusCopyDestReceiverShutdown implements the rShutdown interface of
|
* CitusCopyDestReceiverShutdown implements the rShutdown interface of
|
||||||
* CitusCopyDestReceiver. It ends the COPY on all the open connections and closes
|
* CitusCopyDestReceiver. It ends the COPY on all the open connections, closes
|
||||||
* the relation.
|
* the relation and increments the query stat counters based on the shards
|
||||||
|
* copied into if requested.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
CitusCopyDestReceiverShutdown(DestReceiver *destReceiver)
|
CitusCopyDestReceiverShutdown(DestReceiver *destReceiver)
|
||||||
|
@ -2599,6 +2610,26 @@ CitusCopyDestReceiverShutdown(DestReceiver *destReceiver)
|
||||||
ListCell *connectionStateCell = NULL;
|
ListCell *connectionStateCell = NULL;
|
||||||
Relation distributedRelation = copyDest->distributedRelation;
|
Relation distributedRelation = copyDest->distributedRelation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Increment the query stat counters based on the shards copied into
|
||||||
|
* if requested.
|
||||||
|
*/
|
||||||
|
if (copyDest->trackQueryCounters)
|
||||||
|
{
|
||||||
|
int copiedShardCount =
|
||||||
|
copyDest->shardStateHash ?
|
||||||
|
hash_get_num_entries(copyDest->shardStateHash) :
|
||||||
|
0;
|
||||||
|
if (copiedShardCount <= 1)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List *connectionStateList = ConnectionStateList(connectionStateHash);
|
List *connectionStateList = ConnectionStateList(connectionStateHash);
|
||||||
|
|
||||||
FinishLocalColocatedIntermediateFiles(copyDest);
|
FinishLocalColocatedIntermediateFiles(copyDest);
|
||||||
|
@ -2665,7 +2696,6 @@ CreateLocalColocatedIntermediateFile(CitusCopyDestReceiver *copyDest,
|
||||||
CreateIntermediateResultsDirectory();
|
CreateIntermediateResultsDirectory();
|
||||||
|
|
||||||
const int fileFlags = (O_CREAT | O_RDWR | O_TRUNC);
|
const int fileFlags = (O_CREAT | O_RDWR | O_TRUNC);
|
||||||
const int fileMode = (S_IRUSR | S_IWUSR);
|
|
||||||
|
|
||||||
StringInfo filePath = makeStringInfo();
|
StringInfo filePath = makeStringInfo();
|
||||||
appendStringInfo(filePath, "%s_%ld", copyDest->colocatedIntermediateResultIdPrefix,
|
appendStringInfo(filePath, "%s_%ld", copyDest->colocatedIntermediateResultIdPrefix,
|
||||||
|
@ -2673,7 +2703,7 @@ CreateLocalColocatedIntermediateFile(CitusCopyDestReceiver *copyDest,
|
||||||
|
|
||||||
const char *fileName = QueryResultFileName(filePath->data);
|
const char *fileName = QueryResultFileName(filePath->data);
|
||||||
shardState->fileDest =
|
shardState->fileDest =
|
||||||
FileCompatFromFileStart(FileOpenForTransmit(fileName, fileFlags, fileMode));
|
FileCompatFromFileStart(FileOpenForTransmit(fileName, fileFlags));
|
||||||
|
|
||||||
CopyOutState localFileCopyOutState = shardState->copyOutState;
|
CopyOutState localFileCopyOutState = shardState->copyOutState;
|
||||||
bool isBinaryCopy = localFileCopyOutState->binary;
|
bool isBinaryCopy = localFileCopyOutState->binary;
|
||||||
|
@ -3142,6 +3172,15 @@ CitusCopyTo(CopyStmt *copyStatement, QueryCompletion *completionTag)
|
||||||
|
|
||||||
SendCopyEnd(copyOutState);
|
SendCopyEnd(copyOutState);
|
||||||
|
|
||||||
|
if (list_length(shardIntervalList) <= 1)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
|
||||||
table_close(distributedRelation, AccessShareLock);
|
table_close(distributedRelation, AccessShareLock);
|
||||||
|
|
||||||
if (completionTag != NULL)
|
if (completionTag != NULL)
|
||||||
|
|
|
@ -0,0 +1,351 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* non_main_db_distribute_object_ops.c
|
||||||
|
*
|
||||||
|
* Routines to support node-wide object management commands from non-main
|
||||||
|
* databases.
|
||||||
|
*
|
||||||
|
* RunPreprocessNonMainDBCommand and RunPostprocessNonMainDBCommand are
|
||||||
|
* the entrypoints for this module. These functions are called from
|
||||||
|
* utility_hook.c to support some of the node-wide object management
|
||||||
|
* commands from non-main databases.
|
||||||
|
*
|
||||||
|
* To add support for a new command type, one needs to define a new
|
||||||
|
* NonMainDbDistributeObjectOps object within OperationArray. Also, if
|
||||||
|
* the command requires marking or unmarking some objects as distributed,
|
||||||
|
* the necessary operations can be implemented in
|
||||||
|
* RunPreprocessNonMainDBCommand and RunPostprocessNonMainDBCommand.
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "access/xact.h"
|
||||||
|
#include "catalog/pg_authid_d.h"
|
||||||
|
#include "nodes/nodes.h"
|
||||||
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
|
||||||
|
#include "distributed/commands.h"
|
||||||
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
|
#include "distributed/metadata_cache.h"
|
||||||
|
#include "distributed/remote_transaction.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define EXECUTE_COMMAND_ON_REMOTE_NODES_AS_USER \
|
||||||
|
"SELECT citus_internal.execute_command_on_remote_nodes_as_user(%s, %s)"
|
||||||
|
#define START_MANAGEMENT_TRANSACTION \
|
||||||
|
"SELECT citus_internal.start_management_transaction('%lu')"
|
||||||
|
#define MARK_OBJECT_DISTRIBUTED \
|
||||||
|
"SELECT citus_internal.mark_object_distributed(%d, %s, %d, %s)"
|
||||||
|
#define UNMARK_OBJECT_DISTRIBUTED \
|
||||||
|
"SELECT pg_catalog.citus_unmark_object_distributed(%d, %d, %d, %s)"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NonMainDbDistributeObjectOps contains the necessary callbacks / flags to
|
||||||
|
* support node-wide object management commands from non-main databases.
|
||||||
|
*
|
||||||
|
* cannotBeExecutedInTransaction:
|
||||||
|
* Indicates whether the statement cannot be executed in a transaction. If
|
||||||
|
* this is set to true, the statement will be executed directly on the main
|
||||||
|
* database because there are no transactional visibility issues for such
|
||||||
|
* commands.
|
||||||
|
*
|
||||||
|
* checkSupportedObjectType:
|
||||||
|
* Callback function that checks whether type of the object referred to by
|
||||||
|
* given statement is supported. Can be NULL if not applicable for the
|
||||||
|
* statement type.
|
||||||
|
*/
|
||||||
|
typedef struct NonMainDbDistributeObjectOps
|
||||||
|
{
|
||||||
|
bool cannotBeExecutedInTransaction;
|
||||||
|
bool (*checkSupportedObjectType)(Node *parsetree);
|
||||||
|
} NonMainDbDistributeObjectOps;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* checkSupportedObjectType callbacks for OperationArray.
|
||||||
|
*/
|
||||||
|
static bool CreateDbStmtCheckSupportedObjectType(Node *node);
|
||||||
|
static bool DropDbStmtCheckSupportedObjectType(Node *node);
|
||||||
|
static bool GrantStmtCheckSupportedObjectType(Node *node);
|
||||||
|
static bool SecLabelStmtCheckSupportedObjectType(Node *node);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OperationArray that holds NonMainDbDistributeObjectOps for different command types.
|
||||||
|
*/
|
||||||
|
static const NonMainDbDistributeObjectOps *const OperationArray[] = {
|
||||||
|
[T_CreateRoleStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = false,
|
||||||
|
.checkSupportedObjectType = NULL
|
||||||
|
},
|
||||||
|
[T_DropRoleStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = false,
|
||||||
|
.checkSupportedObjectType = NULL
|
||||||
|
},
|
||||||
|
[T_AlterRoleStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = false,
|
||||||
|
.checkSupportedObjectType = NULL
|
||||||
|
},
|
||||||
|
[T_GrantRoleStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = false,
|
||||||
|
.checkSupportedObjectType = NULL
|
||||||
|
},
|
||||||
|
[T_CreatedbStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = true,
|
||||||
|
.checkSupportedObjectType = CreateDbStmtCheckSupportedObjectType
|
||||||
|
},
|
||||||
|
[T_DropdbStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = true,
|
||||||
|
.checkSupportedObjectType = DropDbStmtCheckSupportedObjectType
|
||||||
|
},
|
||||||
|
[T_GrantStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = false,
|
||||||
|
.checkSupportedObjectType = GrantStmtCheckSupportedObjectType
|
||||||
|
},
|
||||||
|
[T_SecLabelStmt] = &(NonMainDbDistributeObjectOps) {
|
||||||
|
.cannotBeExecutedInTransaction = false,
|
||||||
|
.checkSupportedObjectType = SecLabelStmtCheckSupportedObjectType
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* other static function declarations */
|
||||||
|
const NonMainDbDistributeObjectOps * GetNonMainDbDistributeObjectOps(Node *parsetree);
|
||||||
|
static void CreateRoleStmtMarkDistGloballyOnMainDbs(CreateRoleStmt *createRoleStmt);
|
||||||
|
static void DropRoleStmtUnmarkDistOnLocalMainDb(DropRoleStmt *dropRoleStmt);
|
||||||
|
static void MarkObjectDistributedGloballyOnMainDbs(Oid catalogRelId, Oid objectId,
|
||||||
|
char *objectName);
|
||||||
|
static void UnmarkObjectDistributedOnLocalMainDb(uint16 catalogRelId, Oid objectId);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RunPreprocessNonMainDBCommand runs the necessary commands for a query, in main
|
||||||
|
* database before query is run on the local node with PrevProcessUtility.
|
||||||
|
*
|
||||||
|
* Returns true if previous utility hook needs to be skipped after completing
|
||||||
|
* preprocess phase.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
RunPreprocessNonMainDBCommand(Node *parsetree)
|
||||||
|
{
|
||||||
|
if (IsMainDB)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonMainDbDistributeObjectOps *ops = GetNonMainDbDistributeObjectOps(parsetree);
|
||||||
|
if (!ops)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *queryString = DeparseTreeNode(parsetree);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the commands that cannot be executed in a transaction, there are no
|
||||||
|
* transactional visibility issues. We directly route them to main database
|
||||||
|
* so that we only have to consider one code-path for such commands.
|
||||||
|
*/
|
||||||
|
if (ops->cannotBeExecutedInTransaction)
|
||||||
|
{
|
||||||
|
IsMainDBCommandInXact = false;
|
||||||
|
RunCitusMainDBQuery((char *) queryString);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsMainDBCommandInXact = true;
|
||||||
|
|
||||||
|
StringInfo mainDBQuery = makeStringInfo();
|
||||||
|
appendStringInfo(mainDBQuery,
|
||||||
|
START_MANAGEMENT_TRANSACTION,
|
||||||
|
GetCurrentFullTransactionId().value);
|
||||||
|
RunCitusMainDBQuery(mainDBQuery->data);
|
||||||
|
|
||||||
|
mainDBQuery = makeStringInfo();
|
||||||
|
appendStringInfo(mainDBQuery,
|
||||||
|
EXECUTE_COMMAND_ON_REMOTE_NODES_AS_USER,
|
||||||
|
quote_literal_cstr(queryString),
|
||||||
|
quote_literal_cstr(CurrentUserName()));
|
||||||
|
RunCitusMainDBQuery(mainDBQuery->data);
|
||||||
|
|
||||||
|
if (IsA(parsetree, DropRoleStmt))
|
||||||
|
{
|
||||||
|
DropRoleStmtUnmarkDistOnLocalMainDb((DropRoleStmt *) parsetree);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RunPostprocessNonMainDBCommand runs the necessary commands for a query, in main
|
||||||
|
* database after query is run on the local node with PrevProcessUtility.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
RunPostprocessNonMainDBCommand(Node *parsetree)
|
||||||
|
{
|
||||||
|
if (IsMainDB || !GetNonMainDbDistributeObjectOps(parsetree))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsA(parsetree, CreateRoleStmt))
|
||||||
|
{
|
||||||
|
CreateRoleStmtMarkDistGloballyOnMainDbs((CreateRoleStmt *) parsetree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetNonMainDbDistributeObjectOps returns the NonMainDbDistributeObjectOps for given
|
||||||
|
* command if it's node-wide object management command that's supported from non-main
|
||||||
|
* databases.
|
||||||
|
*/
|
||||||
|
const NonMainDbDistributeObjectOps *
|
||||||
|
GetNonMainDbDistributeObjectOps(Node *parsetree)
|
||||||
|
{
|
||||||
|
NodeTag tag = nodeTag(parsetree);
|
||||||
|
if (tag >= lengthof(OperationArray))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NonMainDbDistributeObjectOps *ops = OperationArray[tag];
|
||||||
|
|
||||||
|
if (ops == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ops->checkSupportedObjectType ||
|
||||||
|
ops->checkSupportedObjectType(parsetree))
|
||||||
|
{
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateRoleStmtMarkDistGloballyOnMainDbs marks the role as
|
||||||
|
* distributed on all main databases globally.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
CreateRoleStmtMarkDistGloballyOnMainDbs(CreateRoleStmt *createRoleStmt)
|
||||||
|
{
|
||||||
|
/* object must exist as we've just created it */
|
||||||
|
bool missingOk = false;
|
||||||
|
Oid roleId = get_role_oid(createRoleStmt->role, missingOk);
|
||||||
|
|
||||||
|
MarkObjectDistributedGloballyOnMainDbs(AuthIdRelationId, roleId,
|
||||||
|
createRoleStmt->role);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DropRoleStmtUnmarkDistOnLocalMainDb unmarks the roles as
|
||||||
|
* distributed on the local main database.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
DropRoleStmtUnmarkDistOnLocalMainDb(DropRoleStmt *dropRoleStmt)
|
||||||
|
{
|
||||||
|
RoleSpec *roleSpec = NULL;
|
||||||
|
foreach_declared_ptr(roleSpec, dropRoleStmt->roles)
|
||||||
|
{
|
||||||
|
Oid roleOid = get_role_oid(roleSpec->rolename,
|
||||||
|
dropRoleStmt->missing_ok);
|
||||||
|
if (roleOid == InvalidOid)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnmarkObjectDistributedOnLocalMainDb(AuthIdRelationId, roleOid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MarkObjectDistributedGloballyOnMainDbs marks an object as
|
||||||
|
* distributed on all main databases globally.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
MarkObjectDistributedGloballyOnMainDbs(Oid catalogRelId, Oid objectId, char *objectName)
|
||||||
|
{
|
||||||
|
StringInfo mainDBQuery = makeStringInfo();
|
||||||
|
appendStringInfo(mainDBQuery,
|
||||||
|
MARK_OBJECT_DISTRIBUTED,
|
||||||
|
catalogRelId,
|
||||||
|
quote_literal_cstr(objectName),
|
||||||
|
objectId,
|
||||||
|
quote_literal_cstr(CurrentUserName()));
|
||||||
|
RunCitusMainDBQuery(mainDBQuery->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UnmarkObjectDistributedOnLocalMainDb unmarks an object as
|
||||||
|
* distributed on the local main database.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
UnmarkObjectDistributedOnLocalMainDb(uint16 catalogRelId, Oid objectId)
|
||||||
|
{
|
||||||
|
const int subObjectId = 0;
|
||||||
|
const char *checkObjectExistence = "false";
|
||||||
|
|
||||||
|
StringInfo query = makeStringInfo();
|
||||||
|
appendStringInfo(query,
|
||||||
|
UNMARK_OBJECT_DISTRIBUTED,
|
||||||
|
catalogRelId, objectId,
|
||||||
|
subObjectId, checkObjectExistence);
|
||||||
|
RunCitusMainDBQuery(query->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* checkSupportedObjectTypes callbacks for OperationArray lie below.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
CreateDbStmtCheckSupportedObjectType(Node *node)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We don't try to send the query to the main database if the CREATE
|
||||||
|
* DATABASE command is for the main database itself, this is a very
|
||||||
|
* rare case but it's exercised by our test suite.
|
||||||
|
*/
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
return strcmp(stmt->dbname, MainDb) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
DropDbStmtCheckSupportedObjectType(Node *node)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We don't try to send the query to the main database if the DROP
|
||||||
|
* DATABASE command is for the main database itself, this is a very
|
||||||
|
* rare case but it's exercised by our test suite.
|
||||||
|
*/
|
||||||
|
DropdbStmt *stmt = castNode(DropdbStmt, node);
|
||||||
|
return strcmp(stmt->dbname, MainDb) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
GrantStmtCheckSupportedObjectType(Node *node)
|
||||||
|
{
|
||||||
|
GrantStmt *stmt = castNode(GrantStmt, node);
|
||||||
|
return stmt->objtype == OBJECT_DATABASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
SecLabelStmtCheckSupportedObjectType(Node *node)
|
||||||
|
{
|
||||||
|
SecLabelStmt *stmt = castNode(SecLabelStmt, node);
|
||||||
|
return stmt->objtype == OBJECT_ROLE;
|
||||||
|
}
|
|
@ -48,6 +48,9 @@
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_transaction.h"
|
#include "distributed/worker_transaction.h"
|
||||||
|
|
||||||
|
|
||||||
|
static ObjectAddress * GetNewRoleAddress(ReassignOwnedStmt *stmt);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PreprocessDropOwnedStmt finds the distributed role out of the ones
|
* PreprocessDropOwnedStmt finds the distributed role out of the ones
|
||||||
* being dropped and unmarks them distributed and creates the drop statements
|
* being dropped and unmarks them distributed and creates the drop statements
|
||||||
|
@ -89,3 +92,81 @@ PreprocessDropOwnedStmt(Node *node, const char *queryString,
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostprocessReassignOwnedStmt takes a Node pointer representing a REASSIGN
|
||||||
|
* OWNED statement and performs any necessary post-processing after the statement
|
||||||
|
* has been executed locally.
|
||||||
|
*
|
||||||
|
* We filter out local roles in OWNED BY clause before deparsing the command,
|
||||||
|
* meaning that we skip reassigning what is owned by local roles. However,
|
||||||
|
* if the role specified in TO clause is local, we automatically distribute
|
||||||
|
* it before deparsing the command.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PostprocessReassignOwnedStmt(Node *node, const char *queryString)
|
||||||
|
{
|
||||||
|
ReassignOwnedStmt *stmt = castNode(ReassignOwnedStmt, node);
|
||||||
|
List *allReassignRoles = stmt->roles;
|
||||||
|
|
||||||
|
List *distributedReassignRoles = FilterDistributedRoles(allReassignRoles);
|
||||||
|
|
||||||
|
if (list_length(distributedReassignRoles) <= 0)
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
|
||||||
|
stmt->roles = distributedReassignRoles;
|
||||||
|
char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
|
stmt->roles = allReassignRoles;
|
||||||
|
|
||||||
|
ObjectAddress *newRoleAddress = GetNewRoleAddress(stmt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We temporarily enable create / alter role propagation to properly
|
||||||
|
* propagate the role specified in TO clause.
|
||||||
|
*/
|
||||||
|
int saveNestLevel = NewGUCNestLevel();
|
||||||
|
set_config_option("citus.enable_create_role_propagation", "on",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
set_config_option("citus.enable_alter_role_propagation", "on",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
|
||||||
|
set_config_option("citus.enable_alter_role_set_propagation", "on",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
|
||||||
|
EnsureObjectAndDependenciesExistOnAllNodes(newRoleAddress);
|
||||||
|
|
||||||
|
/* rollback GUCs to the state before this session */
|
||||||
|
AtEOXact_GUC(true, saveNestLevel);
|
||||||
|
|
||||||
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
sql,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetNewRoleAddress returns the ObjectAddress of the new role
|
||||||
|
*/
|
||||||
|
static ObjectAddress *
|
||||||
|
GetNewRoleAddress(ReassignOwnedStmt *stmt)
|
||||||
|
{
|
||||||
|
Oid roleOid = get_role_oid(stmt->newrole->rolename, false);
|
||||||
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
||||||
|
ObjectAddressSet(*address, AuthIdRelationId, roleOid);
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
|
@ -173,7 +173,6 @@ BuildCreatePublicationStmt(Oid publicationId)
|
||||||
PUBLICATION_PART_ROOT :
|
PUBLICATION_PART_ROOT :
|
||||||
PUBLICATION_PART_LEAF);
|
PUBLICATION_PART_LEAF);
|
||||||
Oid relationId = InvalidOid;
|
Oid relationId = InvalidOid;
|
||||||
int citusTableCount PG_USED_FOR_ASSERTS_ONLY = 0;
|
|
||||||
|
|
||||||
/* mainly for consistent ordering in test output */
|
/* mainly for consistent ordering in test output */
|
||||||
relationIds = SortList(relationIds, CompareOids);
|
relationIds = SortList(relationIds, CompareOids);
|
||||||
|
@ -187,11 +186,6 @@ BuildCreatePublicationStmt(Oid publicationId)
|
||||||
BuildPublicationRelationObjSpec(relationId, publicationId, tableOnly);
|
BuildPublicationRelationObjSpec(relationId, publicationId, tableOnly);
|
||||||
|
|
||||||
createPubStmt->pubobjects = lappend(createPubStmt->pubobjects, publicationObject);
|
createPubStmt->pubobjects = lappend(createPubStmt->pubobjects, publicationObject);
|
||||||
|
|
||||||
if (IsCitusTable(relationId))
|
|
||||||
{
|
|
||||||
citusTableCount++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* WITH (publish_via_partition_root = true) option */
|
/* WITH (publish_via_partition_root = true) option */
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include "distributed/citus_safe_lib.h"
|
#include "distributed/citus_safe_lib.h"
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
#include "distributed/commands/utility_hook.h"
|
#include "distributed/commands/utility_hook.h"
|
||||||
|
#include "distributed/comment.h"
|
||||||
#include "distributed/coordinator_protocol.h"
|
#include "distributed/coordinator_protocol.h"
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
#include "distributed/listutils.h"
|
#include "distributed/listutils.h"
|
||||||
|
@ -82,7 +83,6 @@ static const char * WrapQueryInAlterRoleIfExistsCall(const char *query, RoleSpec
|
||||||
static VariableSetStmt * MakeVariableSetStmt(const char *config);
|
static VariableSetStmt * MakeVariableSetStmt(const char *config);
|
||||||
static int ConfigGenericNameCompare(const void *lhs, const void *rhs);
|
static int ConfigGenericNameCompare(const void *lhs, const void *rhs);
|
||||||
static List * RoleSpecToObjectAddress(RoleSpec *role, bool missing_ok);
|
static List * RoleSpecToObjectAddress(RoleSpec *role, bool missing_ok);
|
||||||
static bool IsGrantRoleWithInheritOrSetOption(GrantRoleStmt *stmt);
|
|
||||||
|
|
||||||
/* controlled via GUC */
|
/* controlled via GUC */
|
||||||
bool EnableCreateRolePropagation = true;
|
bool EnableCreateRolePropagation = true;
|
||||||
|
@ -160,7 +160,7 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
AlterRoleStmt *stmt = castNode(AlterRoleStmt, node);
|
AlterRoleStmt *stmt = castNode(AlterRoleStmt, node);
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ PostprocessAlterRoleStmt(Node *node, const char *queryString)
|
||||||
(void *) CreateAlterRoleIfExistsCommand(stmt),
|
(void *) CreateAlterRoleIfExistsCommand(stmt),
|
||||||
ENABLE_DDL_PROPAGATION);
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
return NodeDDLTaskList(REMOTE_NODES, commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ PreprocessAlterRoleSetStmt(Node *node, const char *queryString,
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
QualifyTreeNode((Node *) stmt);
|
QualifyTreeNode((Node *) stmt);
|
||||||
const char *sql = DeparseTreeNode((Node *) stmt);
|
const char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
|
@ -244,7 +244,7 @@ PreprocessAlterRoleSetStmt(Node *node, const char *queryString,
|
||||||
(void *) sql,
|
(void *) sql,
|
||||||
ENABLE_DDL_PROPAGATION);
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commandList);
|
return NodeDDLTaskList(REMOTE_NODES, commandList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -584,6 +584,17 @@ GenerateCreateOrAlterRoleCommand(Oid roleOid)
|
||||||
{
|
{
|
||||||
completeRoleList = lappend(completeRoleList, DeparseTreeNode(stmt));
|
completeRoleList = lappend(completeRoleList, DeparseTreeNode(stmt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* append COMMENT ON ROLE commands for this specific user
|
||||||
|
* When we propagate user creation, we also want to make sure that we propagate
|
||||||
|
* all the comments it has been given. For this, we check pg_shdescription
|
||||||
|
* for the ROLE entry corresponding to roleOid, and generate the relevant
|
||||||
|
* Comment stmts to be run in the new node.
|
||||||
|
*/
|
||||||
|
List *commentStmts = GetCommentPropagationCommands(AuthIdRelationId, roleOid,
|
||||||
|
rolename, OBJECT_ROLE);
|
||||||
|
completeRoleList = list_concat(completeRoleList, commentStmts);
|
||||||
}
|
}
|
||||||
|
|
||||||
return completeRoleList;
|
return completeRoleList;
|
||||||
|
@ -876,6 +887,14 @@ GenerateGrantRoleStmtsOfRole(Oid roleid)
|
||||||
{
|
{
|
||||||
Form_pg_auth_members membership = (Form_pg_auth_members) GETSTRUCT(tuple);
|
Form_pg_auth_members membership = (Form_pg_auth_members) GETSTRUCT(tuple);
|
||||||
|
|
||||||
|
ObjectAddress *roleAddress = palloc0(sizeof(ObjectAddress));
|
||||||
|
ObjectAddressSet(*roleAddress, AuthIdRelationId, membership->grantor);
|
||||||
|
if (!IsAnyObjectDistributed(list_make1(roleAddress)))
|
||||||
|
{
|
||||||
|
/* we only need to propagate the grant if the grantor is distributed */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
GrantRoleStmt *grantRoleStmt = makeNode(GrantRoleStmt);
|
GrantRoleStmt *grantRoleStmt = makeNode(GrantRoleStmt);
|
||||||
grantRoleStmt->is_grant = true;
|
grantRoleStmt->is_grant = true;
|
||||||
|
|
||||||
|
@ -891,13 +910,38 @@ GenerateGrantRoleStmtsOfRole(Oid roleid)
|
||||||
granteeRole->rolename = GetUserNameFromId(membership->member, true);
|
granteeRole->rolename = GetUserNameFromId(membership->member, true);
|
||||||
grantRoleStmt->grantee_roles = list_make1(granteeRole);
|
grantRoleStmt->grantee_roles = list_make1(granteeRole);
|
||||||
|
|
||||||
grantRoleStmt->grantor = NULL;
|
RoleSpec *grantorRole = makeNode(RoleSpec);
|
||||||
|
grantorRole->roletype = ROLESPEC_CSTRING;
|
||||||
|
grantorRole->location = -1;
|
||||||
|
grantorRole->rolename = GetUserNameFromId(membership->grantor, false);
|
||||||
|
grantRoleStmt->grantor = grantorRole;
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
|
||||||
|
/* inherit option is always included */
|
||||||
|
DefElem *inherit_opt;
|
||||||
|
if (membership->inherit_option)
|
||||||
|
{
|
||||||
|
inherit_opt = makeDefElem("inherit", (Node *) makeBoolean(true), -1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inherit_opt = makeDefElem("inherit", (Node *) makeBoolean(false), -1);
|
||||||
|
}
|
||||||
|
grantRoleStmt->opt = list_make1(inherit_opt);
|
||||||
|
|
||||||
|
/* admin option is false by default, only include true case */
|
||||||
if (membership->admin_option)
|
if (membership->admin_option)
|
||||||
{
|
{
|
||||||
DefElem *opt = makeDefElem("admin", (Node *) makeBoolean(true), -1);
|
DefElem *admin_opt = makeDefElem("admin", (Node *) makeBoolean(true), -1);
|
||||||
grantRoleStmt->opt = list_make1(opt);
|
grantRoleStmt->opt = lappend(grantRoleStmt->opt, admin_opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set option is true by default, only include false case */
|
||||||
|
if (!membership->set_option)
|
||||||
|
{
|
||||||
|
DefElem *set_opt = makeDefElem("set", (Node *) makeBoolean(false), -1);
|
||||||
|
grantRoleStmt->opt = lappend(grantRoleStmt->opt, set_opt);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
grantRoleStmt->admin_opt = membership->admin_option;
|
grantRoleStmt->admin_opt = membership->admin_option;
|
||||||
|
@ -976,7 +1020,8 @@ PreprocessCreateRoleStmt(Node *node, const char *queryString,
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
EnsureSequentialModeForRoleDDL();
|
EnsureSequentialModeForRoleDDL();
|
||||||
|
|
||||||
LockRelationOid(DistNodeRelationId(), RowShareLock);
|
LockRelationOid(DistNodeRelationId(), RowShareLock);
|
||||||
|
@ -1011,7 +1056,7 @@ PreprocessCreateRoleStmt(Node *node, const char *queryString,
|
||||||
|
|
||||||
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
|
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
return NodeDDLTaskList(REMOTE_NODES, commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1097,7 +1142,8 @@ PreprocessDropRoleStmt(Node *node, const char *queryString,
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
EnsureSequentialModeForRoleDDL();
|
EnsureSequentialModeForRoleDDL();
|
||||||
|
|
||||||
|
|
||||||
|
@ -1109,7 +1155,7 @@ PreprocessDropRoleStmt(Node *node, const char *queryString,
|
||||||
sql,
|
sql,
|
||||||
ENABLE_DDL_PROPAGATION);
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
return NodeDDLTaskList(REMOTE_NODES, commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1186,7 +1232,7 @@ PreprocessGrantRoleStmt(Node *node, const char *queryString,
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
|
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
|
||||||
List *allGranteeRoles = stmt->grantee_roles;
|
List *allGranteeRoles = stmt->grantee_roles;
|
||||||
|
@ -1198,25 +1244,6 @@ PreprocessGrantRoleStmt(Node *node, const char *queryString,
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsGrantRoleWithInheritOrSetOption(stmt))
|
|
||||||
{
|
|
||||||
if (EnableUnsupportedFeatureMessages)
|
|
||||||
{
|
|
||||||
ereport(NOTICE, (errmsg("not propagating GRANT/REVOKE commands with specified"
|
|
||||||
" INHERIT/SET options to worker nodes"),
|
|
||||||
errhint(
|
|
||||||
"Connect to worker nodes directly to manually run the same"
|
|
||||||
" GRANT/REVOKE command after disabling DDL propagation.")));
|
|
||||||
}
|
|
||||||
return NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Postgres don't seem to use the grantor. Even dropping the grantor doesn't
|
|
||||||
* seem to affect the membership. If this changes, we might need to add grantors
|
|
||||||
* to the dependency resolution too. For now we just don't propagate it.
|
|
||||||
*/
|
|
||||||
stmt->grantor = NULL;
|
|
||||||
stmt->grantee_roles = distributedGranteeRoles;
|
stmt->grantee_roles = distributedGranteeRoles;
|
||||||
char *sql = DeparseTreeNode((Node *) stmt);
|
char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
stmt->grantee_roles = allGranteeRoles;
|
stmt->grantee_roles = allGranteeRoles;
|
||||||
|
@ -1226,7 +1253,7 @@ PreprocessGrantRoleStmt(Node *node, const char *queryString,
|
||||||
sql,
|
sql,
|
||||||
ENABLE_DDL_PROPAGATION);
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
return NodeDDLTaskList(REMOTE_NODES, commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1237,11 +1264,13 @@ PreprocessGrantRoleStmt(Node *node, const char *queryString,
|
||||||
List *
|
List *
|
||||||
PostprocessGrantRoleStmt(Node *node, const char *queryString)
|
PostprocessGrantRoleStmt(Node *node, const char *queryString)
|
||||||
{
|
{
|
||||||
if (!EnableCreateRolePropagation || !IsCoordinator() || !ShouldPropagate())
|
if (!EnableCreateRolePropagation || !ShouldPropagate())
|
||||||
{
|
{
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
|
GrantRoleStmt *stmt = castNode(GrantRoleStmt, node);
|
||||||
|
|
||||||
RoleSpec *role = NULL;
|
RoleSpec *role = NULL;
|
||||||
|
@ -1260,27 +1289,6 @@ PostprocessGrantRoleStmt(Node *node, const char *queryString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* IsGrantRoleWithInheritOrSetOption returns true if the given
|
|
||||||
* GrantRoleStmt has inherit or set option specified in its options
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
IsGrantRoleWithInheritOrSetOption(GrantRoleStmt *stmt)
|
|
||||||
{
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
|
||||||
DefElem *opt = NULL;
|
|
||||||
foreach_declared_ptr(opt, stmt->opt)
|
|
||||||
{
|
|
||||||
if (strcmp(opt->defname, "inherit") == 0 || strcmp(opt->defname, "set") == 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ConfigGenericNameCompare compares two config_generic structs based on their
|
* ConfigGenericNameCompare compares two config_generic structs based on their
|
||||||
* name fields. If the name fields contain the same strings two structs are
|
* name fields. If the name fields contain the same strings two structs are
|
||||||
|
@ -1362,3 +1370,54 @@ EnsureSequentialModeForRoleDDL(void)
|
||||||
"use only one connection for all future commands")));
|
"use only one connection for all future commands")));
|
||||||
SetLocalMultiShardModifyModeToSequential();
|
SetLocalMultiShardModifyModeToSequential();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PreprocessAlterDatabaseSetStmt is executed before the statement is applied to the local
|
||||||
|
* postgres instance.
|
||||||
|
*
|
||||||
|
* In this stage we can prepare the commands that need to be run on all workers to grant
|
||||||
|
* on databases.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PreprocessAlterRoleRenameStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext)
|
||||||
|
{
|
||||||
|
if (!ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EnableAlterRolePropagation)
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||||
|
Assert(stmt->renameType == OBJECT_ROLE);
|
||||||
|
|
||||||
|
|
||||||
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
|
char *sql = DeparseTreeNode((Node *) stmt);
|
||||||
|
|
||||||
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) sql,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
return NodeDDLTaskList(REMOTE_NODES, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List *
|
||||||
|
RenameRoleStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess)
|
||||||
|
{
|
||||||
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||||
|
Assert(stmt->renameType == OBJECT_ROLE);
|
||||||
|
|
||||||
|
Oid roleOid = get_role_oid(stmt->subname, missing_ok);
|
||||||
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
||||||
|
ObjectAddressSet(*address, AuthIdRelationId, roleOid);
|
||||||
|
|
||||||
|
return list_make1(address);
|
||||||
|
}
|
||||||
|
|
|
@ -15,21 +15,20 @@
|
||||||
#include "distributed/commands/utility_hook.h"
|
#include "distributed/commands/utility_hook.h"
|
||||||
#include "distributed/coordinator_protocol.h"
|
#include "distributed/coordinator_protocol.h"
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
#include "distributed/log_utils.h"
|
#include "distributed/log_utils.h"
|
||||||
#include "distributed/metadata/distobject.h"
|
#include "distributed/metadata/distobject.h"
|
||||||
#include "distributed/metadata_sync.h"
|
#include "distributed/metadata_sync.h"
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PostprocessSecLabelStmt prepares the commands that need to be run on all workers to assign
|
* PostprocessRoleSecLabelStmt prepares the commands that need to be run on all workers to assign
|
||||||
* security labels on distributed objects, currently supporting just Role objects.
|
* security labels on distributed roles. It also ensures that all object dependencies exist on all
|
||||||
* It also ensures that all object dependencies exist on all
|
* nodes for the role in the SecLabelStmt.
|
||||||
* nodes for the object in the SecLabelStmt.
|
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
PostprocessSecLabelStmt(Node *node, const char *queryString)
|
PostprocessRoleSecLabelStmt(Node *node, const char *queryString)
|
||||||
{
|
{
|
||||||
if (!ShouldPropagate())
|
if (!EnableAlterRolePropagation || !ShouldPropagate())
|
||||||
{
|
{
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +41,77 @@ PostprocessSecLabelStmt(Node *node, const char *queryString)
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secLabelStmt->objtype != OBJECT_ROLE)
|
EnsurePropagationToCoordinator();
|
||||||
|
EnsureAllObjectDependenciesExistOnAllNodes(objectAddresses);
|
||||||
|
|
||||||
|
const char *secLabelCommands = DeparseTreeNode((Node *) secLabelStmt);
|
||||||
|
List *commandList = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) secLabelCommands,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
return NodeDDLTaskList(REMOTE_NODES, commandList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostprocessTableOrColumnSecLabelStmt prepares the commands that need to be run on all
|
||||||
|
* workers to assign security labels on distributed tables or the columns of a distributed
|
||||||
|
* table. It also ensures that all object dependencies exist on all nodes for the table in
|
||||||
|
* the SecLabelStmt.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PostprocessTableOrColumnSecLabelStmt(Node *node, const char *queryString)
|
||||||
|
{
|
||||||
|
if (!EnableAlterRolePropagation || !ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SecLabelStmt *secLabelStmt = castNode(SecLabelStmt, node);
|
||||||
|
|
||||||
|
List *objectAddresses = GetObjectAddressListFromParseTree(node, false, true);
|
||||||
|
if (!IsAnyParentObjectDistributed(objectAddresses))
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsurePropagationToCoordinator();
|
||||||
|
EnsureAllObjectDependenciesExistOnAllNodes(objectAddresses);
|
||||||
|
|
||||||
|
const char *secLabelCommands = DeparseTreeNode((Node *) secLabelStmt);
|
||||||
|
List *commandList = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) secLabelCommands,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
List *DDLJobs = NodeDDLTaskList(REMOTE_NODES, commandList);
|
||||||
|
ListCell *lc = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The label is for a table or a column, so we need to set the targetObjectAddress
|
||||||
|
* of the DDLJob to the relationId of the table. This is needed to ensure that
|
||||||
|
* the search path is correctly set for the remote security label command; it
|
||||||
|
* needs to be able to resolve the table that the label is being defined on.
|
||||||
|
*/
|
||||||
|
Assert(list_length(objectAddresses) == 1);
|
||||||
|
ObjectAddress *target = linitial(objectAddresses);
|
||||||
|
Oid relationId = target->objectId;
|
||||||
|
Assert(relationId != InvalidOid);
|
||||||
|
|
||||||
|
foreach(lc, DDLJobs)
|
||||||
|
{
|
||||||
|
DDLJob *ddlJob = (DDLJob *) lfirst(lc);
|
||||||
|
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DDLJobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostprocessAnySecLabelStmt is used for any other object types
|
||||||
|
* that are not supported by Citus. It issues a notice to the client
|
||||||
|
* if appropriate. Is effectively a nop.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PostprocessAnySecLabelStmt(Node *node, const char *queryString)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* If we are not in the coordinator, we don't want to interrupt the security
|
* If we are not in the coordinator, we don't want to interrupt the security
|
||||||
|
@ -52,30 +121,13 @@ PostprocessSecLabelStmt(Node *node, const char *queryString)
|
||||||
if (EnableUnsupportedFeatureMessages && IsCoordinator())
|
if (EnableUnsupportedFeatureMessages && IsCoordinator())
|
||||||
{
|
{
|
||||||
ereport(NOTICE, (errmsg("not propagating SECURITY LABEL commands whose "
|
ereport(NOTICE, (errmsg("not propagating SECURITY LABEL commands whose "
|
||||||
"object type is not role"),
|
"object type is not role or table or column"),
|
||||||
errhint("Connect to worker nodes directly to manually "
|
errhint("Connect to worker nodes directly to manually "
|
||||||
"run the same SECURITY LABEL command.")));
|
"run the same SECURITY LABEL command.")));
|
||||||
}
|
}
|
||||||
return NIL;
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EnableCreateRolePropagation)
|
|
||||||
{
|
|
||||||
return NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureCoordinator();
|
|
||||||
EnsureAllObjectDependenciesExistOnAllNodes(objectAddresses);
|
|
||||||
|
|
||||||
const char *sql = DeparseTreeNode((Node *) secLabelStmt);
|
|
||||||
|
|
||||||
List *commandList = list_make3(DISABLE_DDL_PROPAGATION,
|
|
||||||
(void *) sql,
|
|
||||||
ENABLE_DDL_PROPAGATION);
|
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commandList);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SecLabelStmtObjectAddress returns the object address of the object on
|
* SecLabelStmtObjectAddress returns the object address of the object on
|
||||||
|
|
|
@ -0,0 +1,275 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* serialize_distributed_ddls.c
|
||||||
|
*
|
||||||
|
* This file contains functions for serializing distributed DDLs.
|
||||||
|
*
|
||||||
|
* If you're adding support for serializing a new DDL, you should
|
||||||
|
* extend the following functions to support the new object class:
|
||||||
|
* AcquireCitusAdvisoryObjectClassLockGetOid()
|
||||||
|
* AcquireCitusAdvisoryObjectClassLockCheckPrivileges()
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "miscadmin.h"
|
||||||
|
|
||||||
|
#include "catalog/dependency.h"
|
||||||
|
#include "catalog/pg_database_d.h"
|
||||||
|
#include "commands/dbcommands.h"
|
||||||
|
#include "storage/lock.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
|
||||||
|
#include "pg_version_compat.h"
|
||||||
|
|
||||||
|
#include "distributed/adaptive_executor.h"
|
||||||
|
#include "distributed/argutils.h"
|
||||||
|
#include "distributed/commands/serialize_distributed_ddls.h"
|
||||||
|
#include "distributed/deparse_shard_query.h"
|
||||||
|
#include "distributed/resource_lock.h"
|
||||||
|
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(citus_internal_acquire_citus_advisory_object_class_lock);
|
||||||
|
|
||||||
|
|
||||||
|
static void SerializeDistributedDDLsOnObjectClassInternal(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName);
|
||||||
|
static char * AcquireCitusAdvisoryObjectClassLockCommand(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName);
|
||||||
|
static void AcquireCitusAdvisoryObjectClassLock(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName);
|
||||||
|
static Oid AcquireCitusAdvisoryObjectClassLockGetOid(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName);
|
||||||
|
static void AcquireCitusAdvisoryObjectClassLockCheckPrivileges(ObjectClass objectClass,
|
||||||
|
Oid oid);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* citus_internal_acquire_citus_advisory_object_class_lock is an internal UDF
|
||||||
|
* to call AcquireCitusAdvisoryObjectClassLock().
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
citus_internal_acquire_citus_advisory_object_class_lock(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
CheckCitusVersion(ERROR);
|
||||||
|
|
||||||
|
PG_ENSURE_ARGNOTNULL(0, "object_class");
|
||||||
|
ObjectClass objectClass = PG_GETARG_INT32(0);
|
||||||
|
|
||||||
|
char *qualifiedObjectName = PG_ARGISNULL(1) ? NULL : PG_GETARG_CSTRING(1);
|
||||||
|
|
||||||
|
AcquireCitusAdvisoryObjectClassLock(objectClass, qualifiedObjectName);
|
||||||
|
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SerializeDistributedDDLsOnObjectClass is a wrapper around
|
||||||
|
* SerializeDistributedDDLsOnObjectClassInternal to acquire the lock on given
|
||||||
|
* object class itself, see the comment in header file for more details about
|
||||||
|
* the difference between this function and
|
||||||
|
* SerializeDistributedDDLsOnObjectClassObject().
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SerializeDistributedDDLsOnObjectClass(ObjectClass objectClass)
|
||||||
|
{
|
||||||
|
SerializeDistributedDDLsOnObjectClassInternal(objectClass, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SerializeDistributedDDLsOnObjectClassObject is a wrapper around
|
||||||
|
* SerializeDistributedDDLsOnObjectClassInternal to acquire the lock on given
|
||||||
|
* object that belongs to given object class, see the comment in header file
|
||||||
|
* for more details about the difference between this function and
|
||||||
|
* SerializeDistributedDDLsOnObjectClass().
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SerializeDistributedDDLsOnObjectClassObject(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName)
|
||||||
|
{
|
||||||
|
if (qualifiedObjectName == NULL)
|
||||||
|
{
|
||||||
|
elog(ERROR, "qualified object name cannot be NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializeDistributedDDLsOnObjectClassInternal(objectClass, qualifiedObjectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SerializeDistributedDDLsOnObjectClassInternal serializes distributed DDLs
|
||||||
|
* that target given object class by acquiring a Citus specific advisory lock
|
||||||
|
* on the first primary worker node if there are any workers in the cluster.
|
||||||
|
*
|
||||||
|
* The lock is acquired via a coordinated transaction. For this reason,
|
||||||
|
* it automatically gets released when (maybe implicit) transaction on
|
||||||
|
* current server commits or rolls back.
|
||||||
|
*
|
||||||
|
* If qualifiedObjectName is provided to be non-null, then the oid of the
|
||||||
|
* object is first resolved on the first primary worker node and then the
|
||||||
|
* lock is acquired on that oid. If qualifiedObjectName is null, then the
|
||||||
|
* lock is acquired on the object class itself.
|
||||||
|
*
|
||||||
|
* Note that those two lock types don't conflict with each other and are
|
||||||
|
* acquired for different purposes. The lock on the object class
|
||||||
|
* (qualifiedObjectName = NULL) is used to serialize DDLs that target the
|
||||||
|
* object class itself, e.g., when creating a new object of that class, and
|
||||||
|
* the latter is used to serialize DDLs that target a specific object of
|
||||||
|
* that class, e.g., when altering an object.
|
||||||
|
*
|
||||||
|
* In some cases, we may want to acquire both locks at the same time. For
|
||||||
|
* example, when renaming a database, we want to acquire both lock types
|
||||||
|
* because while the object class lock is used to ensure that another session
|
||||||
|
* doesn't create a new database with the same name, the object lock is used
|
||||||
|
* to ensure that another session doesn't alter the same database.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
SerializeDistributedDDLsOnObjectClassInternal(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName)
|
||||||
|
{
|
||||||
|
WorkerNode *firstWorkerNode = GetFirstPrimaryWorkerNode();
|
||||||
|
if (firstWorkerNode == NULL)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If there are no worker nodes in the cluster, then we don't need
|
||||||
|
* to acquire the lock at all; and we cannot indeed.
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Indeed we would already ensure permission checks in remote node
|
||||||
|
* --via AcquireCitusAdvisoryObjectClassLock()-- but we first do so on
|
||||||
|
* the local node to avoid from reporting confusing error messages.
|
||||||
|
*/
|
||||||
|
Oid oid = AcquireCitusAdvisoryObjectClassLockGetOid(objectClass, qualifiedObjectName);
|
||||||
|
AcquireCitusAdvisoryObjectClassLockCheckPrivileges(objectClass, oid);
|
||||||
|
|
||||||
|
Task *task = CitusMakeNode(Task);
|
||||||
|
task->taskType = DDL_TASK;
|
||||||
|
|
||||||
|
char *command = AcquireCitusAdvisoryObjectClassLockCommand(objectClass,
|
||||||
|
qualifiedObjectName);
|
||||||
|
SetTaskQueryString(task, command);
|
||||||
|
|
||||||
|
ShardPlacement *targetPlacement = CitusMakeNode(ShardPlacement);
|
||||||
|
SetPlacementNodeMetadata(targetPlacement, firstWorkerNode);
|
||||||
|
task->taskPlacementList = list_make1(targetPlacement);
|
||||||
|
|
||||||
|
/* need to be in a transaction to acquire a lock that's bound to transactions */
|
||||||
|
UseCoordinatedTransaction();
|
||||||
|
|
||||||
|
bool localExecutionSupported = true;
|
||||||
|
ExecuteUtilityTaskList(list_make1(task), localExecutionSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AcquireCitusAdvisoryObjectClassLockCommand returns a command to call
|
||||||
|
* citus_internal.acquire_citus_advisory_object_class_lock().
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
AcquireCitusAdvisoryObjectClassLockCommand(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName)
|
||||||
|
{
|
||||||
|
/* safe to cast to int as it's an enum */
|
||||||
|
int objectClassInt = (int) objectClass;
|
||||||
|
|
||||||
|
char *quotedObjectName =
|
||||||
|
!qualifiedObjectName ? "NULL" :
|
||||||
|
quote_literal_cstr(qualifiedObjectName);
|
||||||
|
|
||||||
|
StringInfo command = makeStringInfo();
|
||||||
|
appendStringInfo(command,
|
||||||
|
"SELECT citus_internal.acquire_citus_advisory_object_class_lock(%d, %s)",
|
||||||
|
objectClassInt, quotedObjectName);
|
||||||
|
|
||||||
|
return command->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AcquireCitusAdvisoryObjectClassLock acquires a Citus specific advisory
|
||||||
|
* ExclusiveLock based on given object class.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
AcquireCitusAdvisoryObjectClassLock(ObjectClass objectClass, char *qualifiedObjectName)
|
||||||
|
{
|
||||||
|
Oid oid = AcquireCitusAdvisoryObjectClassLockGetOid(objectClass, qualifiedObjectName);
|
||||||
|
|
||||||
|
AcquireCitusAdvisoryObjectClassLockCheckPrivileges(objectClass, oid);
|
||||||
|
|
||||||
|
LOCKTAG locktag;
|
||||||
|
SET_LOCKTAG_GLOBAL_DDL_SERIALIZATION(locktag, objectClass, oid);
|
||||||
|
|
||||||
|
LOCKMODE lockmode = ExclusiveLock;
|
||||||
|
bool sessionLock = false;
|
||||||
|
bool dontWait = false;
|
||||||
|
LockAcquire(&locktag, lockmode, sessionLock, dontWait);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AcquireCitusAdvisoryObjectClassLockGetOid returns the oid of given object
|
||||||
|
* that belongs to given object class. If qualifiedObjectName is NULL, then
|
||||||
|
* it returns InvalidOid.
|
||||||
|
*/
|
||||||
|
static Oid
|
||||||
|
AcquireCitusAdvisoryObjectClassLockGetOid(ObjectClass objectClass,
|
||||||
|
char *qualifiedObjectName)
|
||||||
|
{
|
||||||
|
if (qualifiedObjectName == NULL)
|
||||||
|
{
|
||||||
|
return InvalidOid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool missingOk = false;
|
||||||
|
|
||||||
|
switch (objectClass)
|
||||||
|
{
|
||||||
|
case OCLASS_DATABASE:
|
||||||
|
{
|
||||||
|
return get_database_oid(qualifiedObjectName, missingOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
elog(ERROR, "unsupported object class: %d", objectClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AcquireCitusAdvisoryObjectClassLockCheckPrivileges is used to perform privilege checks
|
||||||
|
* before acquiring the Citus specific advisory lock on given object class and oid.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
AcquireCitusAdvisoryObjectClassLockCheckPrivileges(ObjectClass objectClass, Oid oid)
|
||||||
|
{
|
||||||
|
switch (objectClass)
|
||||||
|
{
|
||||||
|
case OCLASS_DATABASE:
|
||||||
|
{
|
||||||
|
if (OidIsValid(oid) && !object_ownercheck(DatabaseRelationId, oid,
|
||||||
|
GetUserId()))
|
||||||
|
{
|
||||||
|
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
|
||||||
|
get_database_name(oid));
|
||||||
|
}
|
||||||
|
else if (!OidIsValid(oid) && !have_createdb_privilege())
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||||
|
errmsg("permission denied to create / rename database")));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
elog(ERROR, "unsupported object class: %d", objectClass);
|
||||||
|
}
|
||||||
|
}
|
|
@ -790,45 +790,6 @@ AlterTextSearchDictionarySchemaStmtObjectAddress(Node *node, bool missing_ok, bo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TextSearchConfigurationCommentObjectAddress resolves the ObjectAddress for the TEXT
|
|
||||||
* SEARCH CONFIGURATION on which the comment is placed. Optionally errors if the
|
|
||||||
* configuration does not exist based on the missing_ok flag passed in by the caller.
|
|
||||||
*/
|
|
||||||
List *
|
|
||||||
TextSearchConfigurationCommentObjectAddress(Node *node, bool missing_ok, bool
|
|
||||||
isPostprocess)
|
|
||||||
{
|
|
||||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
|
||||||
Assert(stmt->objtype == OBJECT_TSCONFIGURATION);
|
|
||||||
|
|
||||||
Oid objid = get_ts_config_oid(castNode(List, stmt->object), missing_ok);
|
|
||||||
|
|
||||||
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
||||||
ObjectAddressSet(*address, TSConfigRelationId, objid);
|
|
||||||
return list_make1(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TextSearchDictCommentObjectAddress resolves the ObjectAddress for the TEXT SEARCH
|
|
||||||
* DICTIONARY on which the comment is placed. Optionally errors if the dictionary does not
|
|
||||||
* exist based on the missing_ok flag passed in by the caller.
|
|
||||||
*/
|
|
||||||
List *
|
|
||||||
TextSearchDictCommentObjectAddress(Node *node, bool missing_ok, bool isPostprocess)
|
|
||||||
{
|
|
||||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
|
||||||
Assert(stmt->objtype == OBJECT_TSDICTIONARY);
|
|
||||||
|
|
||||||
Oid objid = get_ts_dict_oid(castNode(List, stmt->object), missing_ok);
|
|
||||||
|
|
||||||
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
||||||
ObjectAddressSet(*address, TSDictionaryRelationId, objid);
|
|
||||||
return list_make1(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AlterTextSearchConfigurationOwnerObjectAddress resolves the ObjectAddress for the TEXT
|
* AlterTextSearchConfigurationOwnerObjectAddress resolves the ObjectAddress for the TEXT
|
||||||
* SEARCH CONFIGURATION for which the owner is changed. Optionally errors if the
|
* SEARCH CONFIGURATION for which the owner is changed. Optionally errors if the
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
#include "catalog/catalog.h"
|
#include "catalog/catalog.h"
|
||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
|
#include "catalog/pg_authid.h"
|
||||||
|
#include "catalog/pg_database.h"
|
||||||
#include "commands/dbcommands.h"
|
#include "commands/dbcommands.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
#include "commands/extension.h"
|
#include "commands/extension.h"
|
||||||
|
@ -43,6 +45,7 @@
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "nodes/parsenodes.h"
|
#include "nodes/parsenodes.h"
|
||||||
#include "nodes/pg_list.h"
|
#include "nodes/pg_list.h"
|
||||||
|
#include "postmaster/postmaster.h"
|
||||||
#include "tcop/utility.h"
|
#include "tcop/utility.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/fmgroids.h"
|
#include "utils/fmgroids.h"
|
||||||
|
@ -76,6 +79,7 @@
|
||||||
#include "distributed/multi_partitioning_utils.h"
|
#include "distributed/multi_partitioning_utils.h"
|
||||||
#include "distributed/multi_physical_planner.h"
|
#include "distributed/multi_physical_planner.h"
|
||||||
#include "distributed/reference_table_utils.h"
|
#include "distributed/reference_table_utils.h"
|
||||||
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/resource_lock.h"
|
#include "distributed/resource_lock.h"
|
||||||
#include "distributed/string_utils.h"
|
#include "distributed/string_utils.h"
|
||||||
#include "distributed/transaction_management.h"
|
#include "distributed/transaction_management.h"
|
||||||
|
@ -83,7 +87,6 @@
|
||||||
#include "distributed/worker_shard_visibility.h"
|
#include "distributed/worker_shard_visibility.h"
|
||||||
#include "distributed/worker_transaction.h"
|
#include "distributed/worker_transaction.h"
|
||||||
|
|
||||||
|
|
||||||
bool EnableDDLPropagation = true; /* ddl propagation is enabled */
|
bool EnableDDLPropagation = true; /* ddl propagation is enabled */
|
||||||
int CreateObjectPropagationMode = CREATE_OBJECT_PROPAGATION_IMMEDIATE;
|
int CreateObjectPropagationMode = CREATE_OBJECT_PROPAGATION_IMMEDIATE;
|
||||||
PropSetCmdBehavior PropagateSetCommands = PROPSETCMD_NONE; /* SET prop off */
|
PropSetCmdBehavior PropagateSetCommands = PROPSETCMD_NONE; /* SET prop off */
|
||||||
|
@ -97,7 +100,7 @@ int UtilityHookLevel = 0;
|
||||||
|
|
||||||
|
|
||||||
/* Local functions forward declarations for helper functions */
|
/* Local functions forward declarations for helper functions */
|
||||||
static void ProcessUtilityInternal(PlannedStmt *pstmt,
|
static void citus_ProcessUtilityInternal(PlannedStmt *pstmt,
|
||||||
const char *queryString,
|
const char *queryString,
|
||||||
ProcessUtilityContext context,
|
ProcessUtilityContext context,
|
||||||
ParamListInfo params,
|
ParamListInfo params,
|
||||||
|
@ -112,6 +115,7 @@ static void DecrementUtilityHookCountersIfNecessary(Node *parsetree);
|
||||||
static bool IsDropSchemaOrDB(Node *parsetree);
|
static bool IsDropSchemaOrDB(Node *parsetree);
|
||||||
static bool ShouldCheckUndistributeCitusLocalTables(void);
|
static bool ShouldCheckUndistributeCitusLocalTables(void);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ProcessUtilityParseTree is a convenience method to create a PlannedStmt out of
|
* ProcessUtilityParseTree is a convenience method to create a PlannedStmt out of
|
||||||
* pieces of a utility statement before invoking ProcessUtility.
|
* pieces of a utility statement before invoking ProcessUtility.
|
||||||
|
@ -132,7 +136,7 @@ ProcessUtilityParseTree(Node *node, const char *queryString, ProcessUtilityConte
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* multi_ProcessUtility is the main entry hook for implementing Citus-specific
|
* citus_ProcessUtility is the main entry hook for implementing Citus-specific
|
||||||
* utility behavior. Its primary responsibilities are intercepting COPY and DDL
|
* utility behavior. Its primary responsibilities are intercepting COPY and DDL
|
||||||
* commands and augmenting the coordinator's command with corresponding tasks
|
* commands and augmenting the coordinator's command with corresponding tasks
|
||||||
* to be run on worker nodes, after suitably ensuring said commands' options
|
* to be run on worker nodes, after suitably ensuring said commands' options
|
||||||
|
@ -141,7 +145,7 @@ ProcessUtilityParseTree(Node *node, const char *queryString, ProcessUtilityConte
|
||||||
* TRUNCATE and VACUUM are also supported.
|
* TRUNCATE and VACUUM are also supported.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
multi_ProcessUtility(PlannedStmt *pstmt,
|
citus_ProcessUtility(PlannedStmt *pstmt,
|
||||||
const char *queryString,
|
const char *queryString,
|
||||||
bool readOnlyTree,
|
bool readOnlyTree,
|
||||||
ProcessUtilityContext context,
|
ProcessUtilityContext context,
|
||||||
|
@ -241,6 +245,17 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CitusHasBeenLoaded())
|
if (!CitusHasBeenLoaded())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Process the command via RunPreprocessNonMainDBCommand and
|
||||||
|
* RunPostprocessNonMainDBCommand hooks if we're in a non-main database
|
||||||
|
* and if the command is a node-wide object management command that we
|
||||||
|
* support from non-main databases.
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool shouldSkipPrevUtilityHook = RunPreprocessNonMainDBCommand(parsetree);
|
||||||
|
|
||||||
|
if (!shouldSkipPrevUtilityHook)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Ensure that utility commands do not behave any differently until CREATE
|
* Ensure that utility commands do not behave any differently until CREATE
|
||||||
|
@ -248,6 +263,9 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
||||||
*/
|
*/
|
||||||
PrevProcessUtility(pstmt, queryString, false, context,
|
PrevProcessUtility(pstmt, queryString, false, context,
|
||||||
params, queryEnv, dest, completionTag);
|
params, queryEnv, dest, completionTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
RunPostprocessNonMainDBCommand(parsetree);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -331,7 +349,7 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
||||||
|
|
||||||
PG_TRY();
|
PG_TRY();
|
||||||
{
|
{
|
||||||
ProcessUtilityInternal(pstmt, queryString, context, params, queryEnv, dest,
|
citus_ProcessUtilityInternal(pstmt, queryString, context, params, queryEnv, dest,
|
||||||
completionTag);
|
completionTag);
|
||||||
|
|
||||||
if (UtilityHookLevel == 1)
|
if (UtilityHookLevel == 1)
|
||||||
|
@ -406,7 +424,7 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ProcessUtilityInternal is a helper function for multi_ProcessUtility where majority
|
* citus_ProcessUtilityInternal is a helper function for citus_ProcessUtility where majority
|
||||||
* of the Citus specific utility statements are handled here. The distinction between
|
* of the Citus specific utility statements are handled here. The distinction between
|
||||||
* both functions is that Citus_ProcessUtility does not handle CALL and DO statements.
|
* both functions is that Citus_ProcessUtility does not handle CALL and DO statements.
|
||||||
* The reason for the distinction is implemented to be able to find the "top-level" DDL
|
* The reason for the distinction is implemented to be able to find the "top-level" DDL
|
||||||
|
@ -414,7 +432,7 @@ multi_ProcessUtility(PlannedStmt *pstmt,
|
||||||
* this goal.
|
* this goal.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
ProcessUtilityInternal(PlannedStmt *pstmt,
|
citus_ProcessUtilityInternal(PlannedStmt *pstmt,
|
||||||
const char *queryString,
|
const char *queryString,
|
||||||
ProcessUtilityContext context,
|
ProcessUtilityContext context,
|
||||||
ParamListInfo params,
|
ParamListInfo params,
|
||||||
|
@ -696,25 +714,32 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* inform the user about potential caveats */
|
/* inform the user about potential caveats */
|
||||||
if (IsA(parsetree, CreatedbStmt))
|
if (IsA(parsetree, CreatedbStmt) && !EnableCreateDatabasePropagation)
|
||||||
{
|
{
|
||||||
if (EnableUnsupportedFeatureMessages)
|
if (EnableUnsupportedFeatureMessages)
|
||||||
{
|
{
|
||||||
ereport(NOTICE, (errmsg("Citus partially supports CREATE DATABASE for "
|
ereport(NOTICE, (errmsg("Citus partially supports CREATE DATABASE for "
|
||||||
"distributed databases"),
|
"distributed databases"),
|
||||||
errdetail("Citus does not propagate CREATE DATABASE "
|
errdetail("Citus does not propagate CREATE DATABASE "
|
||||||
"command to workers"),
|
"command to other nodes"),
|
||||||
errhint("You can manually create a database and its "
|
errhint("You can manually create a database and its "
|
||||||
"extensions on workers.")));
|
"extensions on other nodes.")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (IsA(parsetree, CreateRoleStmt) && !EnableCreateRolePropagation)
|
else if (IsA(parsetree, CreateRoleStmt) && !EnableCreateRolePropagation)
|
||||||
{
|
{
|
||||||
ereport(NOTICE, (errmsg("not propagating CREATE ROLE/USER commands to worker"
|
ereport(NOTICE, (errmsg("not propagating CREATE ROLE/USER commands to other"
|
||||||
" nodes"),
|
" nodes"),
|
||||||
errhint("Connect to worker nodes directly to manually create all"
|
errhint("Connect to other nodes directly to manually create all"
|
||||||
" necessary users and roles.")));
|
" necessary users and roles.")));
|
||||||
}
|
}
|
||||||
|
else if (IsA(parsetree, SecLabelStmt) && !EnableAlterRolePropagation)
|
||||||
|
{
|
||||||
|
ereport(NOTICE, (errmsg("not propagating SECURITY LABEL commands to other"
|
||||||
|
" nodes"),
|
||||||
|
errhint("Connect to other nodes directly to manually assign"
|
||||||
|
" necessary labels.")));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure that on DROP EXTENSION we terminate the background daemon
|
* Make sure that on DROP EXTENSION we terminate the background daemon
|
||||||
|
@ -726,22 +751,13 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure that dropping the role deletes the pg_dist_object entries. There is a
|
* Make sure that dropping node-wide objects deletes the pg_dist_object
|
||||||
* separate logic for roles, since roles are not included as dropped objects in the
|
* entries. There is a separate logic for node-wide objects (such as role
|
||||||
* drop event trigger. To handle it both on worker and coordinator nodes, it is not
|
* and databases), since they are not included as dropped objects in the
|
||||||
* implemented as a part of process functions but here.
|
* 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))
|
UnmarkNodeWideObjectsDistributed(parsetree);
|
||||||
{
|
|
||||||
DropRoleStmt *stmt = castNode(DropRoleStmt, parsetree);
|
|
||||||
List *allDropRoles = stmt->roles;
|
|
||||||
|
|
||||||
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
|
|
||||||
if (list_length(distributedDropRoles) > 0)
|
|
||||||
{
|
|
||||||
UnmarkRolesDistributed(distributedDropRoles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pstmt->utilityStmt = parsetree;
|
pstmt->utilityStmt = parsetree;
|
||||||
|
|
||||||
|
@ -819,19 +835,6 @@ ProcessUtilityInternal(PlannedStmt *pstmt,
|
||||||
ddlJobs = processJobs;
|
ddlJobs = processJobs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsA(parsetree, RenameStmt) && ((RenameStmt *) parsetree)->renameType ==
|
|
||||||
OBJECT_ROLE && EnableAlterRolePropagation)
|
|
||||||
{
|
|
||||||
if (EnableUnsupportedFeatureMessages)
|
|
||||||
{
|
|
||||||
ereport(NOTICE, (errmsg(
|
|
||||||
"not propagating ALTER ROLE ... RENAME TO commands "
|
|
||||||
"to worker nodes"),
|
|
||||||
errhint("Connect to worker nodes directly to manually "
|
|
||||||
"rename the role")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsA(parsetree, CreateStmt))
|
if (IsA(parsetree, CreateStmt))
|
||||||
|
@ -1121,16 +1124,17 @@ IsDropSchemaOrDB(Node *parsetree)
|
||||||
* each shard placement and COMMIT/ROLLBACK is handled by
|
* each shard placement and COMMIT/ROLLBACK is handled by
|
||||||
* CoordinatedTransactionCallback function.
|
* CoordinatedTransactionCallback function.
|
||||||
*
|
*
|
||||||
* The function errors out if the node is not the coordinator or if the DDL is on
|
* The function errors out if the DDL is on a partitioned table which has replication
|
||||||
* a partitioned table which has replication factor > 1.
|
* factor > 1, or if the the coordinator is not added into metadata and we're on a
|
||||||
*
|
* worker node because we want to make sure that distributed DDL jobs are executed
|
||||||
|
* on the coordinator node too. See EnsurePropagationToCoordinator() for more details.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
ExecuteDistributedDDLJob(DDLJob *ddlJob)
|
ExecuteDistributedDDLJob(DDLJob *ddlJob)
|
||||||
{
|
{
|
||||||
bool shouldSyncMetadata = false;
|
bool shouldSyncMetadata = false;
|
||||||
|
|
||||||
EnsureCoordinator();
|
EnsurePropagationToCoordinator();
|
||||||
|
|
||||||
ObjectAddress targetObjectAddress = ddlJob->targetObjectAddress;
|
ObjectAddress targetObjectAddress = ddlJob->targetObjectAddress;
|
||||||
|
|
||||||
|
@ -1154,23 +1158,24 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
|
||||||
{
|
{
|
||||||
if (shouldSyncMetadata)
|
if (shouldSyncMetadata)
|
||||||
{
|
{
|
||||||
SendCommandToWorkersWithMetadata(DISABLE_DDL_PROPAGATION);
|
SendCommandToRemoteNodesWithMetadata(DISABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
char *currentSearchPath = CurrentSearchPath();
|
char *currentSearchPath = CurrentSearchPath();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Given that we're relaying the query to the worker nodes directly,
|
* Given that we're relaying the query to the remote nodes directly,
|
||||||
* we should set the search path exactly the same when necessary.
|
* we should set the search path exactly the same when necessary.
|
||||||
*/
|
*/
|
||||||
if (currentSearchPath != NULL)
|
if (currentSearchPath != NULL)
|
||||||
{
|
{
|
||||||
SendCommandToWorkersWithMetadata(
|
SendCommandToRemoteNodesWithMetadata(
|
||||||
psprintf("SET LOCAL search_path TO %s;", currentSearchPath));
|
psprintf("SET LOCAL search_path TO %s;", currentSearchPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ddlJob->metadataSyncCommand != NULL)
|
if (ddlJob->metadataSyncCommand != NULL)
|
||||||
{
|
{
|
||||||
SendCommandToWorkersWithMetadata((char *) ddlJob->metadataSyncCommand);
|
SendCommandToRemoteNodesWithMetadata(
|
||||||
|
(char *) ddlJob->metadataSyncCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1249,7 +1254,7 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
|
||||||
char *currentSearchPath = CurrentSearchPath();
|
char *currentSearchPath = CurrentSearchPath();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Given that we're relaying the query to the worker nodes directly,
|
* Given that we're relaying the query to the remote nodes directly,
|
||||||
* we should set the search path exactly the same when necessary.
|
* we should set the search path exactly the same when necessary.
|
||||||
*/
|
*/
|
||||||
if (currentSearchPath != NULL)
|
if (currentSearchPath != NULL)
|
||||||
|
@ -1261,7 +1266,7 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
|
||||||
|
|
||||||
commandList = lappend(commandList, (char *) ddlJob->metadataSyncCommand);
|
commandList = lappend(commandList, (char *) ddlJob->metadataSyncCommand);
|
||||||
|
|
||||||
SendBareCommandListToMetadataWorkers(commandList);
|
SendBareCommandListToRemoteMetadataNodes(commandList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PG_CATCH();
|
PG_CATCH();
|
||||||
|
@ -1284,15 +1289,18 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
|
||||||
errhint("Use DROP INDEX CONCURRENTLY IF EXISTS to remove the "
|
errhint("Use DROP INDEX CONCURRENTLY IF EXISTS to remove the "
|
||||||
"invalid index, then retry the original command.")));
|
"invalid index, then retry the original command.")));
|
||||||
}
|
}
|
||||||
else
|
else if (ddlJob->warnForPartialFailure)
|
||||||
{
|
{
|
||||||
ereport(WARNING,
|
ereport(WARNING,
|
||||||
(errmsg(
|
(errmsg(
|
||||||
"CONCURRENTLY-enabled index commands can fail partially, "
|
"Commands that are not transaction-safe may result in "
|
||||||
"leaving behind an INVALID index.\n Use DROP INDEX "
|
"partial failure, potentially leading to an inconsistent "
|
||||||
"CONCURRENTLY IF EXISTS to remove the invalid index.")));
|
"state.\nIf the problematic command is a CREATE operation, "
|
||||||
PG_RE_THROW();
|
"consider using the 'IF EXISTS' syntax to drop the object,"
|
||||||
|
"\nif applicable, and then re-attempt the original command.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PG_RE_THROW();
|
||||||
}
|
}
|
||||||
PG_END_TRY();
|
PG_END_TRY();
|
||||||
}
|
}
|
||||||
|
@ -1401,7 +1409,7 @@ PostStandardProcessUtility(Node *parsetree)
|
||||||
* on the local table first. However, in order to decide whether the
|
* on the local table first. However, in order to decide whether the
|
||||||
* command leads to an invalidation, we need to check before the command
|
* command leads to an invalidation, we need to check before the command
|
||||||
* is being executed since we read pg_constraint table. Thus, we maintain a
|
* is being executed since we read pg_constraint table. Thus, we maintain a
|
||||||
* local flag and do the invalidation after multi_ProcessUtility,
|
* local flag and do the invalidation after citus_ProcessUtility,
|
||||||
* before ExecuteDistributedDDLJob().
|
* before ExecuteDistributedDDLJob().
|
||||||
*/
|
*/
|
||||||
InvalidateForeignKeyGraphForDDL();
|
InvalidateForeignKeyGraphForDDL();
|
||||||
|
@ -1504,6 +1512,33 @@ DDLTaskList(Oid relationId, const char *commandString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NontransactionalNodeDDLTaskList builds a list of tasks to execute a DDL command on a
|
||||||
|
* given target set of nodes with cannotBeExecutedInTransaction is set to make sure
|
||||||
|
* that task list is executed outside a transaction block.
|
||||||
|
*
|
||||||
|
* Also sets warnForPartialFailure for the returned DDLJobs.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
NontransactionalNodeDDLTaskList(TargetWorkerSet targets, List *commands,
|
||||||
|
bool warnForPartialFailure)
|
||||||
|
{
|
||||||
|
List *ddlJobs = NodeDDLTaskList(targets, commands);
|
||||||
|
DDLJob *ddlJob = NULL;
|
||||||
|
foreach_declared_ptr(ddlJob, ddlJobs)
|
||||||
|
{
|
||||||
|
Task *task = NULL;
|
||||||
|
foreach_declared_ptr(task, ddlJob->taskList)
|
||||||
|
{
|
||||||
|
task->cannotBeExecutedInTransaction = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ddlJob->warnForPartialFailure = warnForPartialFailure;
|
||||||
|
}
|
||||||
|
return ddlJobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NodeDDLTaskList builds a list of tasks to execute a DDL command on a
|
* NodeDDLTaskList builds a list of tasks to execute a DDL command on a
|
||||||
* given target set of nodes.
|
* given target set of nodes.
|
||||||
|
|
|
@ -185,7 +185,6 @@ ExecuteVacuumOnDistributedTables(VacuumStmt *vacuumStmt, List *relationIdList,
|
||||||
CitusVacuumParams vacuumParams)
|
CitusVacuumParams vacuumParams)
|
||||||
{
|
{
|
||||||
int relationIndex = 0;
|
int relationIndex = 0;
|
||||||
int executedVacuumCount = 0;
|
|
||||||
|
|
||||||
Oid relationId = InvalidOid;
|
Oid relationId = InvalidOid;
|
||||||
foreach_declared_oid(relationId, relationIdList)
|
foreach_declared_oid(relationId, relationIdList)
|
||||||
|
@ -198,7 +197,6 @@ ExecuteVacuumOnDistributedTables(VacuumStmt *vacuumStmt, List *relationIdList,
|
||||||
/* local execution is not implemented for VACUUM commands */
|
/* local execution is not implemented for VACUUM commands */
|
||||||
bool localExecutionSupported = false;
|
bool localExecutionSupported = false;
|
||||||
ExecuteUtilityTaskList(taskList, localExecutionSupported);
|
ExecuteUtilityTaskList(taskList, localExecutionSupported);
|
||||||
executedVacuumCount++;
|
|
||||||
}
|
}
|
||||||
relationIndex++;
|
relationIndex++;
|
||||||
}
|
}
|
||||||
|
@ -280,7 +278,7 @@ VacuumTaskList(Oid relationId, CitusVacuumParams vacuumParams, List *vacuumColum
|
||||||
task->replicationModel = REPLICATION_MODEL_INVALID;
|
task->replicationModel = REPLICATION_MODEL_INVALID;
|
||||||
task->anchorShardId = shardId;
|
task->anchorShardId = shardId;
|
||||||
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
task->taskPlacementList = ActiveShardPlacementList(shardId);
|
||||||
task->cannotBeExecutedInTransction = ((vacuumParams.options) & VACOPT_VACUUM);
|
task->cannotBeExecutedInTransaction = ((vacuumParams.options) & VACOPT_VACUUM);
|
||||||
|
|
||||||
taskList = lappend(taskList, task);
|
taskList = lappend(taskList, task);
|
||||||
}
|
}
|
||||||
|
@ -720,7 +718,7 @@ ExecuteUnqualifiedVacuumTasks(VacuumStmt *vacuumStmt, CitusVacuumParams vacuumPa
|
||||||
SetTaskQueryStringList(task, unqualifiedVacuumCommands);
|
SetTaskQueryStringList(task, unqualifiedVacuumCommands);
|
||||||
task->dependentTaskList = NULL;
|
task->dependentTaskList = NULL;
|
||||||
task->replicationModel = REPLICATION_MODEL_INVALID;
|
task->replicationModel = REPLICATION_MODEL_INVALID;
|
||||||
task->cannotBeExecutedInTransction = ((vacuumParams.options) & VACOPT_VACUUM);
|
task->cannotBeExecutedInTransaction = ((vacuumParams.options) & VACOPT_VACUUM);
|
||||||
|
|
||||||
|
|
||||||
bool hasPeerWorker = false;
|
bool hasPeerWorker = false;
|
||||||
|
|
|
@ -444,11 +444,13 @@ GetConnParam(const char *keyword)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GetEffectiveConnKey checks whether there is any pooler configuration for the
|
* GetEffectiveConnKey checks whether there is any pooler configuration for the
|
||||||
* provided key (host/port combination). The one case where this logic is not
|
* provided key (host/port combination). If a corresponding row is found in the
|
||||||
* applied is for loopback connections originating within the task tracker. If
|
* poolinfo table, a modified (effective) key is returned with the node, port,
|
||||||
* a corresponding row is found in the poolinfo table, a modified (effective)
|
* and dbname overridden, as applicable, otherwise, the original key is returned
|
||||||
* key is returned with the node, port, and dbname overridden, as applicable,
|
* unmodified.
|
||||||
* otherwise, the original key is returned unmodified.
|
*
|
||||||
|
* In the case of Citus non-main databases we just return the key, since we
|
||||||
|
* would not have access to tables with worker information.
|
||||||
*/
|
*/
|
||||||
ConnectionHashKey *
|
ConnectionHashKey *
|
||||||
GetEffectiveConnKey(ConnectionHashKey *key)
|
GetEffectiveConnKey(ConnectionHashKey *key)
|
||||||
|
@ -458,12 +460,22 @@ GetEffectiveConnKey(ConnectionHashKey *key)
|
||||||
if (!IsTransactionState())
|
if (!IsTransactionState())
|
||||||
{
|
{
|
||||||
/* we're in the task tracker, so should only see loopback */
|
/* we're in the task tracker, so should only see loopback */
|
||||||
Assert(strncmp(LOCAL_HOST_NAME, key->hostname, MAX_NODE_LENGTH) == 0 &&
|
Assert(strncmp(LocalHostName, key->hostname, MAX_NODE_LENGTH) == 0 &&
|
||||||
PostPortNumber == key->port);
|
PostPortNumber == key->port);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CitusHasBeenLoaded())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This happens when we connect to main database over localhost
|
||||||
|
* from some non Citus database.
|
||||||
|
*/
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
WorkerNode *worker = FindWorkerNode(key->hostname, key->port);
|
WorkerNode *worker = FindWorkerNode(key->hostname, key->port);
|
||||||
|
|
||||||
if (worker == NULL)
|
if (worker == NULL)
|
||||||
{
|
{
|
||||||
/* this can be hit when the key references an unknown node */
|
/* this can be hit when the key references an unknown node */
|
||||||
|
@ -524,9 +536,23 @@ char *
|
||||||
GetAuthinfo(char *hostname, int32 port, char *user)
|
GetAuthinfo(char *hostname, int32 port, char *user)
|
||||||
{
|
{
|
||||||
char *authinfo = NULL;
|
char *authinfo = NULL;
|
||||||
bool isLoopback = (strncmp(LOCAL_HOST_NAME, hostname, MAX_NODE_LENGTH) == 0 &&
|
bool isLoopback = (strncmp(LocalHostName, hostname, MAX_NODE_LENGTH) == 0 &&
|
||||||
PostPortNumber == port);
|
PostPortNumber == port);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Citus will not be loaded when we run a global DDL command from a
|
||||||
|
* Citus non-main database.
|
||||||
|
*/
|
||||||
|
if (!CitusHasBeenLoaded())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We don't expect non-main databases to connect to a node other than
|
||||||
|
* the local one.
|
||||||
|
*/
|
||||||
|
Assert(isLoopback);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
if (IsTransactionState())
|
if (IsTransactionState())
|
||||||
{
|
{
|
||||||
int64 nodeId = WILDCARD_NODE_ID;
|
int64 nodeId = WILDCARD_NODE_ID;
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include "distributed/remote_commands.h"
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/run_from_same_connection.h"
|
#include "distributed/run_from_same_connection.h"
|
||||||
#include "distributed/shared_connection_stats.h"
|
#include "distributed/shared_connection_stats.h"
|
||||||
|
#include "distributed/stats/stat_counters.h"
|
||||||
#include "distributed/time_constants.h"
|
#include "distributed/time_constants.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_log_messages.h"
|
#include "distributed/worker_log_messages.h"
|
||||||
|
@ -354,6 +355,18 @@ StartNodeUserDatabaseConnection(uint32 flags, const char *hostname, int32 port,
|
||||||
MultiConnection *connection = FindAvailableConnection(entry->connections, flags);
|
MultiConnection *connection = FindAvailableConnection(entry->connections, flags);
|
||||||
if (connection)
|
if (connection)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* Increment the connection stat counter for the connections that are
|
||||||
|
* reused only if the connection is in a good state. Here we don't
|
||||||
|
* bother shutting down the connection or such if it is not in a good
|
||||||
|
* state but we mostly want to avoid incrementing the connection stat
|
||||||
|
* counter for a connection that the caller cannot really use.
|
||||||
|
*/
|
||||||
|
if (PQstatus(connection->pgConn) == CONNECTION_OK)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_REUSED);
|
||||||
|
}
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,6 +408,12 @@ StartNodeUserDatabaseConnection(uint32 flags, const char *hostname, int32 port,
|
||||||
dlist_delete(&connection->connectionNode);
|
dlist_delete(&connection->connectionNode);
|
||||||
pfree(connection);
|
pfree(connection);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here we don't increment the connection stat counter for the optional
|
||||||
|
* connections that we gave up establishing due to connection throttling
|
||||||
|
* because the callers who request optional connections know how to
|
||||||
|
* survive without them.
|
||||||
|
*/
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -982,6 +1001,14 @@ FinishConnectionListEstablishment(List *multiConnectionList)
|
||||||
{
|
{
|
||||||
waitCount++;
|
waitCount++;
|
||||||
}
|
}
|
||||||
|
else if (connectionState->phase == MULTI_CONNECTION_PHASE_ERROR)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Here we count the connections establishments that failed and that
|
||||||
|
* we won't wait anymore.
|
||||||
|
*/
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_FAILED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* prepare space for socket events */
|
/* prepare space for socket events */
|
||||||
|
@ -1026,6 +1053,11 @@ FinishConnectionListEstablishment(List *multiConnectionList)
|
||||||
|
|
||||||
if (event->events & WL_POSTMASTER_DEATH)
|
if (event->events & WL_POSTMASTER_DEATH)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* Here we don't increment the connection stat counter for the
|
||||||
|
* optional failed connections because this is not a connection
|
||||||
|
* failure, but a postmaster death in the local node.
|
||||||
|
*/
|
||||||
ereport(ERROR, (errmsg("postmaster was shut down, exiting")));
|
ereport(ERROR, (errmsg("postmaster was shut down, exiting")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1042,6 +1074,12 @@ FinishConnectionListEstablishment(List *multiConnectionList)
|
||||||
* reset the memory context
|
* reset the memory context
|
||||||
*/
|
*/
|
||||||
MemoryContextDelete(MemoryContextSwitchTo(oldContext));
|
MemoryContextDelete(MemoryContextSwitchTo(oldContext));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similarly, we don't increment the connection stat counter for the
|
||||||
|
* failed connections here because this is not a connection failure
|
||||||
|
* but a cancellation request is received.
|
||||||
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1072,6 +1110,7 @@ FinishConnectionListEstablishment(List *multiConnectionList)
|
||||||
eventMask, NULL);
|
eventMask, NULL);
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_FAILED);
|
||||||
ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE),
|
ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE),
|
||||||
errmsg("connection establishment for node %s:%d "
|
errmsg("connection establishment for node %s:%d "
|
||||||
"failed", connection->hostname,
|
"failed", connection->hostname,
|
||||||
|
@ -1088,7 +1127,15 @@ FinishConnectionListEstablishment(List *multiConnectionList)
|
||||||
*/
|
*/
|
||||||
if (connectionState->phase == MULTI_CONNECTION_PHASE_CONNECTED)
|
if (connectionState->phase == MULTI_CONNECTION_PHASE_CONNECTED)
|
||||||
{
|
{
|
||||||
MarkConnectionConnected(connectionState->connection);
|
/*
|
||||||
|
* Since WaitEventSetFromMultiConnectionStates() only adds the
|
||||||
|
* connections that we haven't completed the connection
|
||||||
|
* establishment yet, here we always have a new connection.
|
||||||
|
* In other words, at this point, we surely know that we're
|
||||||
|
* not dealing with a cached connection.
|
||||||
|
*/
|
||||||
|
bool newConnection = true;
|
||||||
|
MarkConnectionConnected(connectionState->connection, newConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1172,6 +1219,8 @@ CloseNotReadyMultiConnectionStates(List *connectionStates)
|
||||||
|
|
||||||
/* close connection, otherwise we take up resource on the other side */
|
/* close connection, otherwise we take up resource on the other side */
|
||||||
CitusPQFinish(connection);
|
CitusPQFinish(connection);
|
||||||
|
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1584,7 +1633,7 @@ RemoteTransactionIdle(MultiConnection *connection)
|
||||||
* establishment time when necessary.
|
* establishment time when necessary.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
MarkConnectionConnected(MultiConnection *connection)
|
MarkConnectionConnected(MultiConnection *connection, bool newConnection)
|
||||||
{
|
{
|
||||||
connection->connectionState = MULTI_CONNECTION_CONNECTED;
|
connection->connectionState = MULTI_CONNECTION_CONNECTED;
|
||||||
|
|
||||||
|
@ -1592,6 +1641,11 @@ MarkConnectionConnected(MultiConnection *connection)
|
||||||
{
|
{
|
||||||
INSTR_TIME_SET_CURRENT(connection->connectionEstablishmentEnd);
|
INSTR_TIME_SET_CURRENT(connection->connectionEstablishmentEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newConnection)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_SUCCEEDED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -303,8 +303,8 @@ EnsureConnectionPossibilityForRemotePrimaryNodes(void)
|
||||||
* seem to cause any problems as none of the placements that we are
|
* seem to cause any problems as none of the placements that we are
|
||||||
* going to access would be on the new node.
|
* going to access would be on the new node.
|
||||||
*/
|
*/
|
||||||
List *primaryNodeList = ActivePrimaryRemoteNodeList(NoLock);
|
List *remoteNodeList = ActivePrimaryRemoteNodeList(NoLock);
|
||||||
EnsureConnectionPossibilityForNodeList(primaryNodeList);
|
EnsureConnectionPossibilityForNodeList(remoteNodeList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* citus_deparseutils.c
|
||||||
|
*
|
||||||
|
* This file contains common functions used for deparsing PostgreSQL
|
||||||
|
* statements to their equivalent SQL representation.
|
||||||
|
*
|
||||||
|
* Copyright (c) Citus Data, Inc.
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "commands/defrem.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/elog.h"
|
||||||
|
#include "utils/rel.h"
|
||||||
|
#include "utils/relcache.h"
|
||||||
|
#include "utils/syscache.h"
|
||||||
|
#include "utils/typcache.h"
|
||||||
|
|
||||||
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
|
#include "distributed/deparser.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefElemOptionToStatement converts a DefElem option to a SQL statement and
|
||||||
|
* appends it to the given StringInfo buffer.
|
||||||
|
*
|
||||||
|
* @param buf The StringInfo buffer to append the SQL statement to.
|
||||||
|
* @param option The DefElem option to convert to a SQL statement.
|
||||||
|
* @param optionFormats The option format specification to use for the conversion.
|
||||||
|
* @param optionFormatsLen The number of option formats in the opt_formats array.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
DefElemOptionToStatement(StringInfo buf, DefElem *option,
|
||||||
|
const DefElemOptionFormat *optionFormats,
|
||||||
|
int optionFormatsLen)
|
||||||
|
{
|
||||||
|
const char *name = option->defname;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < optionFormatsLen; i++)
|
||||||
|
{
|
||||||
|
if (strcmp(name, optionFormats[i].name) == 0)
|
||||||
|
{
|
||||||
|
switch (optionFormats[i].type)
|
||||||
|
{
|
||||||
|
case OPTION_FORMAT_STRING:
|
||||||
|
{
|
||||||
|
char *value = defGetString(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, quote_identifier(
|
||||||
|
value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_FORMAT_INTEGER:
|
||||||
|
{
|
||||||
|
int32 value = defGetInt32(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_FORMAT_BOOLEAN:
|
||||||
|
{
|
||||||
|
bool value = defGetBoolean(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, value ? "true" :
|
||||||
|
"false");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_FORMAT_LITERAL_CSTR:
|
||||||
|
{
|
||||||
|
char *value = defGetString(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, quote_literal_cstr(
|
||||||
|
value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
elog(ERROR, "unrecognized option type: %d", optionFormats[i].type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,7 +74,7 @@ AppendGrantRestrictAndCascade(StringInfo buf, GrantStmt *stmt)
|
||||||
void
|
void
|
||||||
AppendGrantedByInGrantForRoleSpec(StringInfo buf, RoleSpec *grantor, bool isGrant)
|
AppendGrantedByInGrantForRoleSpec(StringInfo buf, RoleSpec *grantor, bool isGrant)
|
||||||
{
|
{
|
||||||
if (isGrant && grantor)
|
if (grantor)
|
||||||
{
|
{
|
||||||
appendStringInfo(buf, " GRANTED BY %s", RoleSpecString(grantor, true));
|
appendStringInfo(buf, " GRANTED BY %s", RoleSpecString(grantor, true));
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,8 @@ static void AppendStorageParametersToString(StringInfo stringBuffer,
|
||||||
static const char * convert_aclright_to_string(int aclright);
|
static const char * convert_aclright_to_string(int aclright);
|
||||||
static void simple_quote_literal(StringInfo buf, const char *val);
|
static void simple_quote_literal(StringInfo buf, const char *val);
|
||||||
static void AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer);
|
static void AddVacuumParams(ReindexStmt *reindexStmt, StringInfo buffer);
|
||||||
|
static void process_acl_items(Acl *acl, const char *relationName,
|
||||||
|
const char *attributeName, List **defs);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1110,9 +1112,8 @@ pg_get_indexclusterdef_string(Oid indexRelationId)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* pg_get_table_grants returns a list of sql statements which recreate the
|
* pg_get_table_grants returns a list of sql statements which recreate the
|
||||||
* permissions for a specific table.
|
* permissions for a specific table, including attributes privileges.
|
||||||
*
|
*
|
||||||
* This function is modeled after aclexplode(), don't change too heavily.
|
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
pg_get_table_grants(Oid relationId)
|
pg_get_table_grants(Oid relationId)
|
||||||
|
@ -1136,6 +1137,8 @@ pg_get_table_grants(Oid relationId)
|
||||||
errmsg("relation with OID %u does not exist",
|
errmsg("relation with OID %u does not exist",
|
||||||
relationId)));
|
relationId)));
|
||||||
}
|
}
|
||||||
|
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(classTuple);
|
||||||
|
AttrNumber nattrs = classForm->relnatts;
|
||||||
|
|
||||||
Datum aclDatum = SysCacheGetAttr(RELOID, classTuple, Anum_pg_class_relacl,
|
Datum aclDatum = SysCacheGetAttr(RELOID, classTuple, Anum_pg_class_relacl,
|
||||||
&isNull);
|
&isNull);
|
||||||
|
@ -1163,17 +1166,79 @@ pg_get_table_grants(Oid relationId)
|
||||||
/* iterate through the acl datastructure, emit GRANTs */
|
/* iterate through the acl datastructure, emit GRANTs */
|
||||||
|
|
||||||
Acl *acl = DatumGetAclP(aclDatum);
|
Acl *acl = DatumGetAclP(aclDatum);
|
||||||
AclItem *aidat = ACL_DAT(acl);
|
|
||||||
|
|
||||||
int offtype = -1;
|
process_acl_items(acl, relationName, NULL, &defs);
|
||||||
|
|
||||||
|
/* if we have a detoasted copy, free it */
|
||||||
|
if ((Pointer) acl != DatumGetPointer(aclDatum))
|
||||||
|
pfree(acl);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetStringInfo(&buffer);
|
||||||
|
|
||||||
|
/* lookup all attribute level grants */
|
||||||
|
for (AttrNumber attNum = 1; attNum <= nattrs; attNum++)
|
||||||
|
{
|
||||||
|
HeapTuple attTuple = SearchSysCache2(ATTNUM, ObjectIdGetDatum(relationId),
|
||||||
|
Int16GetDatum(attNum));
|
||||||
|
if (!HeapTupleIsValid(attTuple))
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
|
errmsg("attribute with OID %u does not exist",
|
||||||
|
attNum)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Form_pg_attribute thisAttribute = (Form_pg_attribute) GETSTRUCT(attTuple);
|
||||||
|
|
||||||
|
/* ignore dropped columns */
|
||||||
|
if (thisAttribute->attisdropped)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(attTuple);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum aclAttDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
/* iterate through the acl datastructure, emit GRANTs */
|
||||||
|
Acl *acl = DatumGetAclP(aclAttDatum);
|
||||||
|
|
||||||
|
process_acl_items(acl, relationName, NameStr(thisAttribute->attname), &defs);
|
||||||
|
|
||||||
|
/* if we have a detoasted copy, free it */
|
||||||
|
if ((Pointer) acl != DatumGetPointer(aclAttDatum))
|
||||||
|
pfree(acl);
|
||||||
|
}
|
||||||
|
ReleaseSysCache(attTuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_close(relation, NoLock);
|
||||||
|
return defs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function to process ACL items.
|
||||||
|
* If attributeName is NULL, the function emits table-level GRANT commands;
|
||||||
|
* otherwise it emits column-level GRANT commands.
|
||||||
|
* This function was modeled after aclexplode(), previously in pg_get_table_grants().
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
process_acl_items(Acl *acl, const char *relationName, const char *attributeName,
|
||||||
|
List **defs)
|
||||||
|
{
|
||||||
|
AclItem *aidat = ACL_DAT(acl);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
int offtype = -1;
|
||||||
|
StringInfoData buffer;
|
||||||
|
|
||||||
|
initStringInfo(&buffer);
|
||||||
|
|
||||||
while (i < ACL_NUM(acl))
|
while (i < ACL_NUM(acl))
|
||||||
{
|
{
|
||||||
AclItem *aidata = NULL;
|
|
||||||
AclMode priv_bit = 0;
|
|
||||||
|
|
||||||
offtype++;
|
offtype++;
|
||||||
|
|
||||||
if (offtype == N_ACL_RIGHTS)
|
if (offtype == N_ACL_RIGHTS)
|
||||||
{
|
{
|
||||||
offtype = 0;
|
offtype = 0;
|
||||||
|
@ -1184,8 +1249,8 @@ pg_get_table_grants(Oid relationId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aidata = &aidat[i];
|
AclItem *aidata = &aidat[i];
|
||||||
priv_bit = 1 << offtype;
|
AclMode priv_bit = 1 << offtype;
|
||||||
|
|
||||||
if (ACLITEM_GET_PRIVS(*aidata) & priv_bit)
|
if (ACLITEM_GET_PRIVS(*aidata) & priv_bit)
|
||||||
{
|
{
|
||||||
|
@ -1194,20 +1259,7 @@ pg_get_table_grants(Oid relationId)
|
||||||
|
|
||||||
if (aidata->ai_grantee != 0)
|
if (aidata->ai_grantee != 0)
|
||||||
{
|
{
|
||||||
|
roleName = quote_identifier(GetUserNameFromId(aidata->ai_grantee, false));
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -1219,26 +1271,29 @@ pg_get_table_grants(Oid relationId)
|
||||||
withGrant = " WITH GRANT OPTION";
|
withGrant = " WITH GRANT OPTION";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attributeName)
|
||||||
|
{
|
||||||
|
appendStringInfo(&buffer, "GRANT %s(%s) ON %s TO %s%s",
|
||||||
|
convert_aclright_to_string(priv_bit),
|
||||||
|
quote_identifier(attributeName),
|
||||||
|
relationName,
|
||||||
|
roleName,
|
||||||
|
withGrant);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
appendStringInfo(&buffer, "GRANT %s ON %s TO %s%s",
|
appendStringInfo(&buffer, "GRANT %s ON %s TO %s%s",
|
||||||
convert_aclright_to_string(priv_bit),
|
convert_aclright_to_string(priv_bit),
|
||||||
relationName,
|
relationName,
|
||||||
roleName,
|
roleName,
|
||||||
withGrant);
|
withGrant);
|
||||||
|
}
|
||||||
defs = lappend(defs, pstrdup(buffer.data));
|
*defs = lappend(*defs, pstrdup(buffer.data));
|
||||||
|
|
||||||
resetStringInfo(&buffer);
|
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
|
* generate_qualified_relation_name computes the schema-qualified name to display for a
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "catalog/namespace.h"
|
||||||
|
#include "commands/defrem.h"
|
||||||
|
#include "lib/stringinfo.h"
|
||||||
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "nodes/print.h"
|
||||||
|
#include "parser/parse_type.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
|
||||||
|
#include "pg_version_compat.h"
|
||||||
|
|
||||||
|
#include "distributed/citus_ruleutils.h"
|
||||||
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/log_utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
void AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AppendVarSetValueDb deparses a VariableSetStmt with VAR_SET_VALUE kind.
|
||||||
|
* It takes from flatten_set_variable_args in postgres's utils/misc/guc.c,
|
||||||
|
* however flatten_set_variable_args does not apply correct quoting.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
|
||||||
|
{
|
||||||
|
ListCell *varArgCell = NULL;
|
||||||
|
ListCell *firstCell = list_head(setStmt->args);
|
||||||
|
|
||||||
|
Assert(setStmt->kind == VAR_SET_VALUE);
|
||||||
|
|
||||||
|
foreach(varArgCell, setStmt->args)
|
||||||
|
{
|
||||||
|
Node *varArgNode = lfirst(varArgCell);
|
||||||
|
A_Const *varArgConst = NULL;
|
||||||
|
TypeName *typeName = NULL;
|
||||||
|
|
||||||
|
if (IsA(varArgNode, A_Const))
|
||||||
|
{
|
||||||
|
varArgConst = (A_Const *) varArgNode;
|
||||||
|
}
|
||||||
|
else if (IsA(varArgNode, TypeCast))
|
||||||
|
{
|
||||||
|
TypeCast *varArgTypeCast = (TypeCast *) varArgNode;
|
||||||
|
|
||||||
|
varArgConst = castNode(A_Const, varArgTypeCast->arg);
|
||||||
|
typeName = varArgTypeCast->typeName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elog(ERROR, "unrecognized node type: %d", varArgNode->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* don't know how to start SET until we inspect first arg */
|
||||||
|
if (varArgCell != firstCell)
|
||||||
|
{
|
||||||
|
appendStringInfoChar(buf, ',');
|
||||||
|
}
|
||||||
|
else if (typeName != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfoString(buf, " SET TIME ZONE");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " SET %s =", quote_identifier(setStmt->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Node *value = (Node *) &varArgConst->val;
|
||||||
|
switch (value->type)
|
||||||
|
{
|
||||||
|
case T_Integer:
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " %d", intVal(value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case T_Float:
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " %s", nodeToString(value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case T_String:
|
||||||
|
{
|
||||||
|
if (typeName != NULL)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Must be a ConstInterval argument for TIME ZONE. Coerce
|
||||||
|
* to interval and back to normalize the value and account
|
||||||
|
* for any typmod.
|
||||||
|
*/
|
||||||
|
Oid typoid = InvalidOid;
|
||||||
|
int32 typmod = -1;
|
||||||
|
|
||||||
|
typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod);
|
||||||
|
Assert(typoid == INTERVALOID);
|
||||||
|
|
||||||
|
Datum interval =
|
||||||
|
DirectFunctionCall3(interval_in,
|
||||||
|
CStringGetDatum(strVal(value)),
|
||||||
|
ObjectIdGetDatum(InvalidOid),
|
||||||
|
Int32GetDatum(typmod));
|
||||||
|
|
||||||
|
char *intervalout =
|
||||||
|
DatumGetCString(DirectFunctionCall1(interval_out,
|
||||||
|
interval));
|
||||||
|
appendStringInfo(buf, " INTERVAL '%s'", intervalout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " %s", quote_literal_cstr(strVal(value)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
elog(ERROR, "Unexpected Value type in VAR_SET_VALUE arguments.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AppendVariableSetDb appends a string representing the VariableSetStmt to a buffer
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
AppendVariableSet(StringInfo buf, VariableSetStmt *setStmt)
|
||||||
|
{
|
||||||
|
switch (setStmt->kind)
|
||||||
|
{
|
||||||
|
case VAR_SET_VALUE:
|
||||||
|
{
|
||||||
|
AppendVarSetValue(buf, setStmt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case VAR_SET_CURRENT:
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " SET %s FROM CURRENT", quote_identifier(
|
||||||
|
setStmt->name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case VAR_SET_DEFAULT:
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " SET %s TO DEFAULT", quote_identifier(setStmt->name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case VAR_RESET:
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " RESET %s", quote_identifier(setStmt->name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case VAR_RESET_ALL:
|
||||||
|
{
|
||||||
|
appendStringInfoString(buf, " RESET ALL");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* VAR_SET_MULTI is a special case for SET TRANSACTION that should not occur here */
|
||||||
|
case VAR_SET_MULTI:
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("Unable to deparse SET statement")));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* deparse_coment_stmts.c
|
||||||
|
*
|
||||||
|
* All routines to deparse comment statements.
|
||||||
|
*
|
||||||
|
* Copyright (c), Citus Data, Inc.
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "catalog/namespace.h"
|
||||||
|
#include "commands/defrem.h"
|
||||||
|
#include "lib/stringinfo.h"
|
||||||
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "parser/parse_type.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/elog.h"
|
||||||
|
|
||||||
|
#include "pg_version_compat.h"
|
||||||
|
|
||||||
|
#include "distributed/citus_ruleutils.h"
|
||||||
|
#include "distributed/commands.h"
|
||||||
|
#include "distributed/comment.h"
|
||||||
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
|
#include "distributed/log_utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
const char *ObjectTypeNames[] =
|
||||||
|
{
|
||||||
|
[OBJECT_DATABASE] = "DATABASE",
|
||||||
|
[OBJECT_ROLE] = "ROLE",
|
||||||
|
[OBJECT_TSCONFIGURATION] = "TEXT SEARCH CONFIGURATION",
|
||||||
|
[OBJECT_TSDICTIONARY] = "TEXT SEARCH DICTIONARY",
|
||||||
|
|
||||||
|
/* When support for propagating comments to new objects is introduced, an entry for each
|
||||||
|
* statement type should be added to this list. The first element in each entry is the 'object_type' keyword
|
||||||
|
* that will be included in the 'COMMENT ON <object_type> ..' statement (i.e. DATABASE,). The second element is the type of
|
||||||
|
* stmt->object, which represents the name of the propagated object.
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseCommentStmt(Node *node)
|
||||||
|
{
|
||||||
|
CommentStmt *stmt = castNode(CommentStmt, node);
|
||||||
|
StringInfoData str = { 0 };
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
const char *objectName = NULL;
|
||||||
|
if (IsA(stmt->object, String))
|
||||||
|
{
|
||||||
|
objectName = quote_identifier(strVal(stmt->object));
|
||||||
|
}
|
||||||
|
else if (IsA(stmt->object, List))
|
||||||
|
{
|
||||||
|
objectName = NameListToQuotedString(castNode(List, stmt->object));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
||||||
|
errmsg("unknown object type")));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *objectType = ObjectTypeNames[stmt->objtype];
|
||||||
|
|
||||||
|
char *comment = stmt->comment != NULL ? quote_literal_cstr(stmt->comment) : "NULL";
|
||||||
|
|
||||||
|
|
||||||
|
appendStringInfo(&str, "COMMENT ON %s %s IS %s;", objectType, objectName, comment);
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
|
@ -15,16 +15,53 @@
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
#include "nodes/parsenodes.h"
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "parser/parse_type.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
|
||||||
#include "pg_version_compat.h"
|
#include "pg_version_compat.h"
|
||||||
|
|
||||||
#include "distributed/citus_ruleutils.h"
|
#include "distributed/citus_ruleutils.h"
|
||||||
|
#include "distributed/commands.h"
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
#include "distributed/log_utils.h"
|
#include "distributed/log_utils.h"
|
||||||
|
|
||||||
|
|
||||||
static void AppendAlterDatabaseOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
static void AppendAlterDatabaseOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
||||||
|
static void AppendAlterDatabaseSetStmt(StringInfo buf, AlterDatabaseSetStmt *stmt);
|
||||||
static void AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt);
|
static void AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt);
|
||||||
|
static void AppendCreateDatabaseStmt(StringInfo buf, CreatedbStmt *stmt);
|
||||||
|
static void AppendDropDatabaseStmt(StringInfo buf, DropdbStmt *stmt);
|
||||||
|
static void AppendGrantOnDatabaseStmt(StringInfo buf, GrantStmt *stmt);
|
||||||
|
static void AppendBasicAlterDatabaseOptions(StringInfo buf, AlterDatabaseStmt *stmt);
|
||||||
|
static void AppendGrantDatabases(StringInfo buf, GrantStmt *stmt);
|
||||||
|
static void AppendAlterDatabaseSetTablespace(StringInfo buf, DefElem *def, char *dbname);
|
||||||
|
|
||||||
|
const DefElemOptionFormat createDatabaseOptionFormats[] = {
|
||||||
|
{ "owner", " OWNER %s", OPTION_FORMAT_STRING },
|
||||||
|
{ "template", " TEMPLATE %s", OPTION_FORMAT_STRING },
|
||||||
|
{ "encoding", " ENCODING %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "strategy", " STRATEGY %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "locale", " LOCALE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "lc_collate", " LC_COLLATE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "lc_ctype", " LC_CTYPE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "icu_locale", " ICU_LOCALE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "icu_rules", " ICU_RULES %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "locale_provider", " LOCALE_PROVIDER %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "collation_version", " COLLATION_VERSION %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "tablespace", " TABLESPACE %s", OPTION_FORMAT_STRING },
|
||||||
|
{ "allow_connections", " ALLOW_CONNECTIONS %s", OPTION_FORMAT_BOOLEAN },
|
||||||
|
{ "connection_limit", " CONNECTION LIMIT %d", OPTION_FORMAT_INTEGER },
|
||||||
|
{ "is_template", " IS_TEMPLATE %s", OPTION_FORMAT_BOOLEAN }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const DefElemOptionFormat alterDatabaseOptionFormats[] = {
|
||||||
|
{ "is_template", " IS_TEMPLATE %s", OPTION_FORMAT_BOOLEAN },
|
||||||
|
{ "allow_connections", " ALLOW_CONNECTIONS %s", OPTION_FORMAT_BOOLEAN },
|
||||||
|
{ "connection_limit", " CONNECTION LIMIT %d", OPTION_FORMAT_INTEGER },
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
char *
|
char *
|
||||||
DeparseAlterDatabaseOwnerStmt(Node *node)
|
DeparseAlterDatabaseOwnerStmt(Node *node)
|
||||||
|
@ -85,48 +122,63 @@ AppendGrantOnDatabaseStmt(StringInfo buf, GrantStmt *stmt)
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
AppendDefElemConnLimit(StringInfo buf, DefElem *def)
|
AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt)
|
||||||
{
|
{
|
||||||
appendStringInfo(buf, " CONNECTION LIMIT %ld", (long int) defGetNumeric(def));
|
if (list_length(stmt->options) == 0)
|
||||||
|
{
|
||||||
|
elog(ERROR, "got unexpected number of options for ALTER DATABASE");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt->options)
|
||||||
|
{
|
||||||
|
DefElem *firstOption = linitial(stmt->options);
|
||||||
|
if (strcmp(firstOption->defname, "tablespace") == 0)
|
||||||
|
{
|
||||||
|
AppendAlterDatabaseSetTablespace(buf, firstOption, stmt->dbname);
|
||||||
|
|
||||||
|
/* SET tablespace cannot be combined with other options */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
appendStringInfo(buf, "ALTER DATABASE %s WITH",
|
||||||
|
quote_identifier(stmt->dbname));
|
||||||
|
|
||||||
|
AppendBasicAlterDatabaseOptions(buf, stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(buf, ";");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt)
|
AppendAlterDatabaseSetTablespace(StringInfo buf, DefElem *def, char *dbname)
|
||||||
{
|
{
|
||||||
appendStringInfo(buf, "ALTER DATABASE %s ", quote_identifier(stmt->dbname));
|
appendStringInfo(buf,
|
||||||
|
"ALTER DATABASE %s SET TABLESPACE %s",
|
||||||
if (stmt->options)
|
quote_identifier(dbname), quote_identifier(defGetString(def)));
|
||||||
{
|
|
||||||
ListCell *cell = NULL;
|
|
||||||
appendStringInfo(buf, "WITH ");
|
|
||||||
foreach(cell, stmt->options)
|
|
||||||
{
|
|
||||||
DefElem *def = castNode(DefElem, lfirst(cell));
|
|
||||||
if (strcmp(def->defname, "is_template") == 0)
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, "IS_TEMPLATE %s",
|
|
||||||
quote_literal_cstr(strVal(def->arg)));
|
|
||||||
}
|
|
||||||
else if (strcmp(def->defname, "connection_limit") == 0)
|
|
||||||
{
|
|
||||||
AppendDefElemConnLimit(buf, def);
|
|
||||||
}
|
|
||||||
else if (strcmp(def->defname, "allow_connections") == 0)
|
|
||||||
{
|
|
||||||
ereport(ERROR,
|
|
||||||
errmsg("ALLOW_CONNECTIONS is not supported"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ereport(ERROR,
|
|
||||||
errmsg("unrecognized ALTER DATABASE option: %s",
|
|
||||||
def->defname));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
appendStringInfo(buf, ";");
|
|
||||||
|
/*
|
||||||
|
* AppendBasicAlterDatabaseOptions appends basic ALTER DATABASE options to a string buffer.
|
||||||
|
* Basic options are those that can be appended to the ALTER DATABASE statement
|
||||||
|
* after the "WITH" keyword.(i.e. ALLOW_CONNECTIONS, CONNECTION LIMIT, IS_TEMPLATE)
|
||||||
|
* For example, the tablespace option is not a basic option since it is defined via SET keyword.
|
||||||
|
*
|
||||||
|
* This function takes a string buffer and an AlterDatabaseStmt as input.
|
||||||
|
* It appends the basic options to the string buffer.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
AppendBasicAlterDatabaseOptions(StringInfo buf, AlterDatabaseStmt *stmt)
|
||||||
|
{
|
||||||
|
DefElem *def = NULL;
|
||||||
|
foreach_declared_ptr(def, stmt->options)
|
||||||
|
{
|
||||||
|
DefElemOptionToStatement(buf, def, alterDatabaseOptionFormats, lengthof(
|
||||||
|
alterDatabaseOptionFormats));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -173,3 +225,133 @@ DeparseAlterDatabaseRefreshCollStmt(Node *node)
|
||||||
|
|
||||||
return str.data;
|
return str.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
AppendAlterDatabaseSetStmt(StringInfo buf, AlterDatabaseSetStmt *stmt)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "ALTER DATABASE %s", quote_identifier(stmt->dbname));
|
||||||
|
|
||||||
|
VariableSetStmt *varSetStmt = castNode(VariableSetStmt, stmt->setstmt);
|
||||||
|
|
||||||
|
AppendVariableSet(buf, varSetStmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseAlterDatabaseRenameStmt(Node *node)
|
||||||
|
{
|
||||||
|
RenameStmt *stmt = (RenameStmt *) node;
|
||||||
|
|
||||||
|
StringInfoData str;
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
appendStringInfo(&str, "ALTER DATABASE %s RENAME TO %s",
|
||||||
|
quote_identifier(stmt->subname),
|
||||||
|
quote_identifier(stmt->newname));
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseAlterDatabaseSetStmt(Node *node)
|
||||||
|
{
|
||||||
|
AlterDatabaseSetStmt *stmt = castNode(AlterDatabaseSetStmt, node);
|
||||||
|
|
||||||
|
StringInfoData str = { 0 };
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
AppendAlterDatabaseSetStmt(&str, stmt);
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
AppendCreateDatabaseStmt(StringInfo buf, CreatedbStmt *stmt)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Make sure that we don't try to deparse something that this
|
||||||
|
* function doesn't expect.
|
||||||
|
*
|
||||||
|
* This is also useful to throw an error for unsupported CREATE
|
||||||
|
* DATABASE options when the command is issued from non-main dbs
|
||||||
|
* because we use the same function to deparse CREATE DATABASE
|
||||||
|
* commands there too.
|
||||||
|
*/
|
||||||
|
EnsureSupportedCreateDatabaseCommand(stmt);
|
||||||
|
|
||||||
|
appendStringInfo(buf,
|
||||||
|
"CREATE DATABASE %s",
|
||||||
|
quote_identifier(stmt->dbname));
|
||||||
|
|
||||||
|
DefElem *option = NULL;
|
||||||
|
foreach_declared_ptr(option, stmt->options)
|
||||||
|
{
|
||||||
|
DefElemOptionToStatement(buf, option, createDatabaseOptionFormats,
|
||||||
|
lengthof(createDatabaseOptionFormats));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseCreateDatabaseStmt(Node *node)
|
||||||
|
{
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
StringInfoData str = { 0 };
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
AppendCreateDatabaseStmt(&str, stmt);
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
AppendDropDatabaseStmt(StringInfo buf, DropdbStmt *stmt)
|
||||||
|
{
|
||||||
|
char *ifExistsStatement = stmt->missing_ok ? "IF EXISTS" : "";
|
||||||
|
appendStringInfo(buf,
|
||||||
|
"DROP DATABASE %s %s",
|
||||||
|
ifExistsStatement,
|
||||||
|
quote_identifier(stmt->dbname));
|
||||||
|
|
||||||
|
if (list_length(stmt->options) > 1)
|
||||||
|
{
|
||||||
|
/* FORCE is the only option that can be provided for this command */
|
||||||
|
elog(ERROR, "got unexpected number of options for DROP DATABASE");
|
||||||
|
}
|
||||||
|
else if (list_length(stmt->options) == 1)
|
||||||
|
{
|
||||||
|
DefElem *option = linitial(stmt->options);
|
||||||
|
appendStringInfo(buf, " WITH ( ");
|
||||||
|
|
||||||
|
if (strcmp(option->defname, "force") == 0)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "FORCE");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* FORCE is the only option that can be provided for this command */
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("unrecognized DROP DATABASE option \"%s\"",
|
||||||
|
option->defname)));
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(buf, " )");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseDropDatabaseStmt(Node *node)
|
||||||
|
{
|
||||||
|
DropdbStmt *stmt = castNode(DropdbStmt, node);
|
||||||
|
StringInfoData str = { 0 };
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
AppendDropDatabaseStmt(&str, stmt);
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
|
@ -62,7 +62,6 @@ static void AppendDefElemRows(StringInfo buf, DefElem *def);
|
||||||
static void AppendDefElemSet(StringInfo buf, DefElem *def);
|
static void AppendDefElemSet(StringInfo buf, DefElem *def);
|
||||||
static void AppendDefElemSupport(StringInfo buf, DefElem *def);
|
static void AppendDefElemSupport(StringInfo buf, DefElem *def);
|
||||||
|
|
||||||
static void AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt);
|
|
||||||
static void AppendRenameFunctionStmt(StringInfo buf, RenameStmt *stmt);
|
static void AppendRenameFunctionStmt(StringInfo buf, RenameStmt *stmt);
|
||||||
static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt);
|
static void AppendAlterFunctionSchemaStmt(StringInfo buf, AlterObjectSchemaStmt *stmt);
|
||||||
static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
static void AppendAlterFunctionOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
||||||
|
@ -301,164 +300,6 @@ AppendDefElemSupport(StringInfo buf, DefElem *def)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* AppendVariableSet appends a string representing the VariableSetStmt to a buffer
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
AppendVariableSet(StringInfo buf, VariableSetStmt *setStmt)
|
|
||||||
{
|
|
||||||
switch (setStmt->kind)
|
|
||||||
{
|
|
||||||
case VAR_SET_VALUE:
|
|
||||||
{
|
|
||||||
AppendVarSetValue(buf, setStmt);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case VAR_SET_CURRENT:
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " SET %s FROM CURRENT", quote_identifier(
|
|
||||||
setStmt->name));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case VAR_SET_DEFAULT:
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " SET %s TO DEFAULT", quote_identifier(setStmt->name));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case VAR_RESET:
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " RESET %s", quote_identifier(setStmt->name));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case VAR_RESET_ALL:
|
|
||||||
{
|
|
||||||
appendStringInfoString(buf, " RESET ALL");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* VAR_SET_MULTI is a special case for SET TRANSACTION that should not occur here */
|
|
||||||
case VAR_SET_MULTI:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
ereport(ERROR, (errmsg("Unable to deparse SET statement")));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* AppendVarSetValue deparses a VariableSetStmt with VAR_SET_VALUE kind.
|
|
||||||
* It takes from flatten_set_variable_args in postgres's utils/misc/guc.c,
|
|
||||||
* however flatten_set_variable_args does not apply correct quoting.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
AppendVarSetValue(StringInfo buf, VariableSetStmt *setStmt)
|
|
||||||
{
|
|
||||||
ListCell *varArgCell = NULL;
|
|
||||||
ListCell *firstCell = list_head(setStmt->args);
|
|
||||||
|
|
||||||
Assert(setStmt->kind == VAR_SET_VALUE);
|
|
||||||
|
|
||||||
foreach(varArgCell, setStmt->args)
|
|
||||||
{
|
|
||||||
Node *varArgNode = lfirst(varArgCell);
|
|
||||||
A_Const *varArgConst = NULL;
|
|
||||||
TypeName *typeName = NULL;
|
|
||||||
|
|
||||||
if (IsA(varArgNode, A_Const))
|
|
||||||
{
|
|
||||||
varArgConst = (A_Const *) varArgNode;
|
|
||||||
}
|
|
||||||
else if (IsA(varArgNode, TypeCast))
|
|
||||||
{
|
|
||||||
TypeCast *varArgTypeCast = (TypeCast *) varArgNode;
|
|
||||||
|
|
||||||
varArgConst = castNode(A_Const, varArgTypeCast->arg);
|
|
||||||
typeName = varArgTypeCast->typeName;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
elog(ERROR, "unrecognized node type: %d", varArgNode->type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* don't know how to start SET until we inspect first arg */
|
|
||||||
if (varArgCell != firstCell)
|
|
||||||
{
|
|
||||||
appendStringInfoChar(buf, ',');
|
|
||||||
}
|
|
||||||
else if (typeName != NULL)
|
|
||||||
{
|
|
||||||
appendStringInfoString(buf, " SET TIME ZONE");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " SET %s =", quote_identifier(setStmt->name));
|
|
||||||
}
|
|
||||||
|
|
||||||
Node *value = (Node *) &varArgConst->val;
|
|
||||||
switch (value->type)
|
|
||||||
{
|
|
||||||
case T_Integer:
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " %d", intVal(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case T_Float:
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " %s", strVal(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case T_String:
|
|
||||||
{
|
|
||||||
if (typeName != NULL)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Must be a ConstInterval argument for TIME ZONE. Coerce
|
|
||||||
* to interval and back to normalize the value and account
|
|
||||||
* for any typmod.
|
|
||||||
*/
|
|
||||||
Oid typoid = InvalidOid;
|
|
||||||
int32 typmod = -1;
|
|
||||||
|
|
||||||
typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod);
|
|
||||||
Assert(typoid == INTERVALOID);
|
|
||||||
|
|
||||||
Datum interval =
|
|
||||||
DirectFunctionCall3(interval_in,
|
|
||||||
CStringGetDatum(strVal(value)),
|
|
||||||
ObjectIdGetDatum(InvalidOid),
|
|
||||||
Int32GetDatum(typmod));
|
|
||||||
|
|
||||||
char *intervalout =
|
|
||||||
DatumGetCString(DirectFunctionCall1(interval_out,
|
|
||||||
interval));
|
|
||||||
appendStringInfo(buf, " INTERVAL '%s'", intervalout);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " %s", quote_literal_cstr(strVal(
|
|
||||||
value)));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
elog(ERROR, "Unexpected Value type in VAR_SET_VALUE arguments.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DeparseRenameFunctionStmt builds and returns a string representing the RenameStmt
|
* DeparseRenameFunctionStmt builds and returns a string representing the RenameStmt
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -71,7 +71,7 @@ AppendRoleList(StringInfo buf, List *roleList)
|
||||||
{
|
{
|
||||||
Node *roleNode = (Node *) lfirst(cell);
|
Node *roleNode = (Node *) lfirst(cell);
|
||||||
Assert(IsA(roleNode, RoleSpec) || IsA(roleNode, AccessPriv));
|
Assert(IsA(roleNode, RoleSpec) || IsA(roleNode, AccessPriv));
|
||||||
char const *rolename = NULL;
|
const char *rolename = NULL;
|
||||||
if (IsA(roleNode, RoleSpec))
|
if (IsA(roleNode, RoleSpec))
|
||||||
{
|
{
|
||||||
rolename = RoleSpecString((RoleSpec *) roleNode, true);
|
rolename = RoleSpecString((RoleSpec *) roleNode, true);
|
||||||
|
@ -83,3 +83,27 @@ AppendRoleList(StringInfo buf, List *roleList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
AppendReassignOwnedStmt(StringInfo buf, ReassignOwnedStmt *stmt)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "REASSIGN OWNED BY ");
|
||||||
|
|
||||||
|
AppendRoleList(buf, stmt->roles);
|
||||||
|
const char *newRoleName = RoleSpecString(stmt->newrole, true);
|
||||||
|
appendStringInfo(buf, " TO %s", newRoleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseReassignOwnedStmt(Node *node)
|
||||||
|
{
|
||||||
|
ReassignOwnedStmt *stmt = castNode(ReassignOwnedStmt, node);
|
||||||
|
StringInfoData buf = { 0 };
|
||||||
|
initStringInfo(&buf);
|
||||||
|
|
||||||
|
AppendReassignOwnedStmt(&buf, stmt);
|
||||||
|
|
||||||
|
return buf.data;
|
||||||
|
}
|
||||||
|
|
|
@ -202,17 +202,51 @@ DeparseCreateRoleStmt(Node *node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
AppendSysIdStatement(StringInfo buf, ListCell *optionCell)
|
||||||
|
{
|
||||||
|
DefElem *option = (DefElem *) lfirst(optionCell);
|
||||||
|
if (strcmp(option->defname, "sysid") == 0)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, " SYSID %d", intVal(option->arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AppendCreateRoleStmt generates the string representation of the
|
* AppendInlinePriviliges generates the string representation for the inline
|
||||||
* CreateRoleStmt and appends it to the buffer.
|
* privileges of the role in create statement and appends it to the buffer.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt)
|
AppendInlinePriviliges(StringInfo buf, ListCell *optionCell)
|
||||||
{
|
{
|
||||||
ListCell *optionCell = NULL;
|
DefElem *option = (DefElem *) lfirst(optionCell);
|
||||||
|
|
||||||
appendStringInfo(buf, "CREATE ");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AppendStatementType generates the string representation for the statement
|
||||||
|
* type (role, user or group) in alter/create statement and appends it to the buffer.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
AppendStatementType(StringInfo buf, CreateRoleStmt *stmt)
|
||||||
|
{
|
||||||
switch (stmt->stmt_type)
|
switch (stmt->stmt_type)
|
||||||
{
|
{
|
||||||
case ROLESTMT_ROLE:
|
case ROLESTMT_ROLE:
|
||||||
|
@ -233,34 +267,29 @@ AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 ");
|
||||||
|
|
||||||
|
AppendStatementType(buf, stmt);
|
||||||
|
|
||||||
appendStringInfo(buf, "%s", quote_identifier(stmt->role));
|
appendStringInfo(buf, "%s", quote_identifier(stmt->role));
|
||||||
|
|
||||||
foreach(optionCell, stmt->options)
|
foreach(optionCell, stmt->options)
|
||||||
{
|
{
|
||||||
AppendRoleOption(buf, optionCell);
|
AppendRoleOption(buf, optionCell);
|
||||||
|
AppendInlinePriviliges(buf, optionCell);
|
||||||
DefElem *option = (DefElem *) lfirst(optionCell);
|
AppendSysIdStatement(buf, optionCell);
|
||||||
|
|
||||||
if (strcmp(option->defname, "sysid") == 0)
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, " SYSID %d", intVal(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,6 +356,22 @@ AppendRoleList(StringInfo buf, List *roleList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseRenameRoleStmt(Node *node)
|
||||||
|
{
|
||||||
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||||
|
StringInfoData str = { 0 };
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
Assert(stmt->renameType == OBJECT_ROLE);
|
||||||
|
|
||||||
|
appendStringInfo(&str, "ALTER ROLE %s RENAME TO %s;",
|
||||||
|
quote_identifier(stmt->subname), quote_identifier(stmt->newname));
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DeparseGrantRoleStmt builds and returns a string representing of the
|
* DeparseGrantRoleStmt builds and returns a string representing of the
|
||||||
* GrantRoleStmt for application on a remote server.
|
* GrantRoleStmt for application on a remote server.
|
||||||
|
@ -366,6 +411,16 @@ AppendRevokeAdminOptionFor(StringInfo buf, GrantRoleStmt *stmt)
|
||||||
appendStringInfo(buf, "ADMIN OPTION FOR ");
|
appendStringInfo(buf, "ADMIN OPTION FOR ");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(opt->defname, "inherit") == 0)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "INHERIT OPTION FOR ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (strcmp(opt->defname, "set") == 0)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "SET OPTION FOR ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -383,16 +438,29 @@ AppendGrantWithAdminOption(StringInfo buf, GrantRoleStmt *stmt)
|
||||||
if (stmt->is_grant)
|
if (stmt->is_grant)
|
||||||
{
|
{
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
int opt_count = 0;
|
||||||
DefElem *opt = NULL;
|
DefElem *opt = NULL;
|
||||||
foreach_declared_ptr(opt, stmt->opt)
|
foreach_declared_ptr(opt, stmt->opt)
|
||||||
{
|
{
|
||||||
bool admin_option = false;
|
|
||||||
char *optval = defGetString(opt);
|
char *optval = defGetString(opt);
|
||||||
if (strcmp(opt->defname, "admin") == 0 &&
|
bool option_value = false;
|
||||||
parse_bool(optval, &admin_option) && admin_option)
|
if (parse_bool(optval, &option_value))
|
||||||
{
|
{
|
||||||
appendStringInfo(buf, " WITH ADMIN OPTION");
|
opt_count++;
|
||||||
break;
|
char *prefix = opt_count > 1 ? "," : " WITH";
|
||||||
|
if (strcmp(opt->defname, "inherit") == 0)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "%s INHERIT %s", prefix, option_value ? "TRUE" :
|
||||||
|
"FALSE");
|
||||||
|
}
|
||||||
|
else if (strcmp(opt->defname, "admin") == 0 && option_value)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "%s ADMIN OPTION", prefix);
|
||||||
|
}
|
||||||
|
else if (strcmp(opt->defname, "set") == 0 && !option_value)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "%s SET FALSE", prefix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -420,7 +488,6 @@ AppendGrantRoleStmt(StringInfo buf, GrantRoleStmt *stmt)
|
||||||
AppendGrantWithAdminOption(buf, stmt);
|
AppendGrantWithAdminOption(buf, stmt);
|
||||||
AppendGrantedByInGrantForRoleSpec(buf, stmt->grantor, stmt->is_grant);
|
AppendGrantedByInGrantForRoleSpec(buf, stmt->grantor, stmt->is_grant);
|
||||||
AppendGrantRestrictAndCascadeForRoleSpec(buf, stmt->behavior, stmt->is_grant);
|
AppendGrantRestrictAndCascadeForRoleSpec(buf, stmt->behavior, stmt->is_grant);
|
||||||
AppendGrantedByInGrantForRoleSpec(buf, stmt->grantor, stmt->is_grant);
|
|
||||||
appendStringInfo(buf, ";");
|
appendStringInfo(buf, ";");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,37 +10,16 @@
|
||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "catalog/namespace.h"
|
||||||
#include "nodes/parsenodes.h"
|
#include "nodes/parsenodes.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
|
|
||||||
static void AppendSecLabelStmt(StringInfo buf, SecLabelStmt *stmt);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* DeparseSecLabelStmt builds and returns a string representing of the
|
|
||||||
* SecLabelStmt for application on a remote server.
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
DeparseSecLabelStmt(Node *node)
|
|
||||||
{
|
|
||||||
SecLabelStmt *secLabelStmt = castNode(SecLabelStmt, node);
|
|
||||||
StringInfoData buf = { 0 };
|
|
||||||
initStringInfo(&buf);
|
|
||||||
|
|
||||||
AppendSecLabelStmt(&buf, secLabelStmt);
|
|
||||||
|
|
||||||
return buf.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* AppendSecLabelStmt generates the string representation of the
|
|
||||||
* SecLabelStmt and appends it to the buffer.
|
|
||||||
*/
|
|
||||||
static void
|
static void
|
||||||
AppendSecLabelStmt(StringInfo buf, SecLabelStmt *stmt)
|
BeginSecLabel(StringInfo buf, SecLabelStmt *stmt)
|
||||||
{
|
{
|
||||||
|
initStringInfo(buf);
|
||||||
appendStringInfoString(buf, "SECURITY LABEL ");
|
appendStringInfoString(buf, "SECURITY LABEL ");
|
||||||
|
|
||||||
if (stmt->provider != NULL)
|
if (stmt->provider != NULL)
|
||||||
|
@ -49,31 +28,84 @@ AppendSecLabelStmt(StringInfo buf, SecLabelStmt *stmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
appendStringInfoString(buf, "ON ");
|
appendStringInfoString(buf, "ON ");
|
||||||
|
|
||||||
switch (stmt->objtype)
|
|
||||||
{
|
|
||||||
case OBJECT_ROLE:
|
|
||||||
{
|
|
||||||
appendStringInfo(buf, "ROLE %s ", quote_identifier(strVal(stmt->object)));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* normally, we shouldn't reach this */
|
|
||||||
default:
|
static void
|
||||||
|
EndSecLabel(StringInfo buf, SecLabelStmt *stmt)
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errmsg("unsupported security label statement for"
|
appendStringInfo(buf, "IS %s", (stmt->label != NULL) ?
|
||||||
" deparsing")));
|
quote_literal_cstr(stmt->label) : "NULL");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
appendStringInfoString(buf, "IS ");
|
|
||||||
|
|
||||||
if (stmt->label != NULL)
|
/*
|
||||||
|
* DeparseRoleSecLabelStmt builds and returns a string representation of the
|
||||||
|
* SecLabelStmt for application on a remote server. The SecLabelStmt is for
|
||||||
|
* a role object.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
DeparseRoleSecLabelStmt(Node *node)
|
||||||
{
|
{
|
||||||
appendStringInfo(buf, "%s", quote_literal_cstr(stmt->label));
|
SecLabelStmt *secLabelStmt = castNode(SecLabelStmt, node);
|
||||||
|
char *role_name = strVal(secLabelStmt->object);
|
||||||
|
StringInfoData buf = { 0 };
|
||||||
|
|
||||||
|
BeginSecLabel(&buf, secLabelStmt);
|
||||||
|
appendStringInfo(&buf, "ROLE %s ", quote_identifier(role_name));
|
||||||
|
EndSecLabel(&buf, secLabelStmt);
|
||||||
|
|
||||||
|
return buf.data;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DeparseTableSecLabelStmt builds and returns a string representation of the
|
||||||
|
* SecLabelStmt for application on a remote server. The SecLabelStmt is for a
|
||||||
|
* table.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
DeparseTableSecLabelStmt(Node *node)
|
||||||
{
|
{
|
||||||
appendStringInfoString(buf, "NULL");
|
SecLabelStmt *secLabelStmt = castNode(SecLabelStmt, node);
|
||||||
|
List *names = (List *) secLabelStmt->object;
|
||||||
|
StringInfoData buf = { 0 };
|
||||||
|
|
||||||
|
BeginSecLabel(&buf, secLabelStmt);
|
||||||
|
appendStringInfo(&buf, "TABLE %s", quote_identifier(strVal(linitial(names))));
|
||||||
|
if (list_length(names) > 1)
|
||||||
|
{
|
||||||
|
appendStringInfo(&buf, ".%s", quote_identifier(strVal(lsecond(names))));
|
||||||
}
|
}
|
||||||
|
appendStringInfoString(&buf, " ");
|
||||||
|
EndSecLabel(&buf, secLabelStmt);
|
||||||
|
|
||||||
|
return buf.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DeparseColumnSecLabelStmt builds and returns a string representation of the
|
||||||
|
* SecLabelStmt for application on a remote server. The SecLabelStmt is for a
|
||||||
|
* column of a distributed table.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
DeparseColumnSecLabelStmt(Node *node)
|
||||||
|
{
|
||||||
|
SecLabelStmt *secLabelStmt = castNode(SecLabelStmt, node);
|
||||||
|
List *names = (List *) secLabelStmt->object;
|
||||||
|
StringInfoData buf = { 0 };
|
||||||
|
|
||||||
|
BeginSecLabel(&buf, secLabelStmt);
|
||||||
|
appendStringInfo(&buf, "COLUMN %s.%s",
|
||||||
|
quote_identifier(strVal(linitial(names))),
|
||||||
|
quote_identifier(strVal(lsecond(names))));
|
||||||
|
if (list_length(names) > 2)
|
||||||
|
{
|
||||||
|
appendStringInfo(&buf, ".%s", quote_identifier(strVal(lthird(names))));
|
||||||
|
}
|
||||||
|
appendStringInfoString(&buf, " ");
|
||||||
|
EndSecLabel(&buf, secLabelStmt);
|
||||||
|
|
||||||
|
return buf.data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,68 +395,6 @@ DeparseAlterTextSearchDictionarySchemaStmt(Node *node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* DeparseTextSearchConfigurationCommentStmt returns the sql statement representing
|
|
||||||
* COMMENT ON TEXT SEARCH CONFIGURATION ... IS ...
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
DeparseTextSearchConfigurationCommentStmt(Node *node)
|
|
||||||
{
|
|
||||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
|
||||||
Assert(stmt->objtype == OBJECT_TSCONFIGURATION);
|
|
||||||
|
|
||||||
StringInfoData buf = { 0 };
|
|
||||||
initStringInfo(&buf);
|
|
||||||
|
|
||||||
appendStringInfo(&buf, "COMMENT ON TEXT SEARCH CONFIGURATION %s IS ",
|
|
||||||
NameListToQuotedString(castNode(List, stmt->object)));
|
|
||||||
|
|
||||||
if (stmt->comment == NULL)
|
|
||||||
{
|
|
||||||
appendStringInfoString(&buf, "NULL");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
appendStringInfoString(&buf, quote_literal_cstr(stmt->comment));
|
|
||||||
}
|
|
||||||
|
|
||||||
appendStringInfoString(&buf, ";");
|
|
||||||
|
|
||||||
return buf.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* DeparseTextSearchDictionaryCommentStmt returns the sql statement representing
|
|
||||||
* COMMENT ON TEXT SEARCH DICTIONARY ... IS ...
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
DeparseTextSearchDictionaryCommentStmt(Node *node)
|
|
||||||
{
|
|
||||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
|
||||||
Assert(stmt->objtype == OBJECT_TSDICTIONARY);
|
|
||||||
|
|
||||||
StringInfoData buf = { 0 };
|
|
||||||
initStringInfo(&buf);
|
|
||||||
|
|
||||||
appendStringInfo(&buf, "COMMENT ON TEXT SEARCH DICTIONARY %s IS ",
|
|
||||||
NameListToQuotedString(castNode(List, stmt->object)));
|
|
||||||
|
|
||||||
if (stmt->comment == NULL)
|
|
||||||
{
|
|
||||||
appendStringInfoString(&buf, "NULL");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
appendStringInfoString(&buf, quote_literal_cstr(stmt->comment));
|
|
||||||
}
|
|
||||||
|
|
||||||
appendStringInfoString(&buf, ";");
|
|
||||||
|
|
||||||
return buf.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AppendStringInfoTokentypeList specializes in adding a comma separated list of
|
* AppendStringInfoTokentypeList specializes in adding a comma separated list of
|
||||||
* token_tyoe's to TEXT SEARCH CONFIGURATION commands
|
* token_tyoe's to TEXT SEARCH CONFIGURATION commands
|
||||||
|
|
|
@ -67,7 +67,6 @@
|
||||||
#include "parser/parse_node.h"
|
#include "parser/parse_node.h"
|
||||||
#include "parser/parse_agg.h"
|
#include "parser/parse_agg.h"
|
||||||
#include "parser/parse_func.h"
|
#include "parser/parse_func.h"
|
||||||
#include "parser/parse_node.h"
|
|
||||||
#include "parser/parse_oper.h"
|
#include "parser/parse_oper.h"
|
||||||
#include "parser/parse_relation.h"
|
#include "parser/parse_relation.h"
|
||||||
#include "parser/parser.h"
|
#include "parser/parser.h"
|
||||||
|
@ -123,16 +122,18 @@ typedef struct
|
||||||
{
|
{
|
||||||
StringInfo buf; /* output buffer to append to */
|
StringInfo buf; /* output buffer to append to */
|
||||||
List *namespaces; /* List of deparse_namespace nodes */
|
List *namespaces; /* List of deparse_namespace nodes */
|
||||||
|
TupleDesc resultDesc; /* if top level of a view, the view's tupdesc */
|
||||||
|
List *targetList; /* Current query level's SELECT targetlist */
|
||||||
List *windowClause; /* Current query level's WINDOW clause */
|
List *windowClause; /* Current query level's WINDOW clause */
|
||||||
List *windowTList; /* targetlist for resolving WINDOW clause */
|
|
||||||
int prettyFlags; /* enabling of pretty-print functions */
|
int prettyFlags; /* enabling of pretty-print functions */
|
||||||
int wrapColumn; /* max line length, or -1 for no limit */
|
int wrapColumn; /* max line length, or -1 for no limit */
|
||||||
int indentLevel; /* current indent level for prettyprint */
|
int indentLevel; /* current indent level for prettyprint */
|
||||||
bool varprefix; /* true to print prefixes on Vars */
|
bool varprefix; /* true to print prefixes on Vars */
|
||||||
Oid distrelid; /* the distributed table being modified, if valid */
|
Oid distrelid; /* the distributed table being modified, if valid */
|
||||||
int64 shardid; /* a distributed table's shardid, if positive */
|
int64 shardid; /* a distributed table's shardid, if positive */
|
||||||
ParseExprKind special_exprkind; /* set only for exprkinds needing special
|
bool colNamesVisible; /* do we care about output column names? */
|
||||||
* handling */
|
bool inGroupBy; /* deparsing GROUP BY clause? */
|
||||||
|
bool varInOrderBy; /* deparsing simple Var in ORDER BY? */
|
||||||
Bitmapset *appendparents; /* if not null, map child Vars of these relids
|
Bitmapset *appendparents; /* if not null, map child Vars of these relids
|
||||||
* back to the parent rel */
|
* back to the parent rel */
|
||||||
} deparse_context;
|
} deparse_context;
|
||||||
|
@ -364,27 +365,19 @@ static void get_query_def_extended(Query *query, StringInfo buf,
|
||||||
int startIndent);
|
int startIndent);
|
||||||
static void get_values_def(List *values_lists, deparse_context *context);
|
static void get_values_def(List *values_lists, deparse_context *context);
|
||||||
static void get_with_clause(Query *query, deparse_context *context);
|
static void get_with_clause(Query *query, deparse_context *context);
|
||||||
static void get_select_query_def(Query *query, deparse_context *context,
|
static void get_select_query_def(Query *query, deparse_context *context);
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
static void get_insert_query_def(Query *query, deparse_context *context);
|
||||||
static void get_insert_query_def(Query *query, deparse_context *context,
|
static void get_update_query_def(Query *query, deparse_context *context);
|
||||||
bool colNamesVisible);
|
|
||||||
static void get_update_query_def(Query *query, deparse_context *context,
|
|
||||||
bool colNamesVisible);
|
|
||||||
static void get_merge_query_def(Query *query, deparse_context *context);
|
|
||||||
|
|
||||||
static void get_update_query_targetlist_def(Query *query, List *targetList,
|
static void get_update_query_targetlist_def(Query *query, List *targetList,
|
||||||
deparse_context *context,
|
deparse_context *context,
|
||||||
RangeTblEntry *rte);
|
RangeTblEntry *rte);
|
||||||
static void get_delete_query_def(Query *query, deparse_context *context,
|
static void get_delete_query_def(Query *query, deparse_context *context);
|
||||||
bool colNamesVisible);
|
static void get_merge_query_def(Query *query, deparse_context *context);
|
||||||
static void get_utility_query_def(Query *query, deparse_context *context);
|
static void get_utility_query_def(Query *query, deparse_context *context);
|
||||||
static void get_basic_select_query(Query *query, deparse_context *context,
|
static void get_basic_select_query(Query *query, deparse_context *context);
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
static void get_target_list(List *targetList, deparse_context *context);
|
||||||
static void get_target_list(List *targetList, deparse_context *context,
|
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
|
||||||
static void get_setop_query(Node *setOp, Query *query,
|
static void get_setop_query(Node *setOp, Query *query,
|
||||||
deparse_context *context,
|
deparse_context *context);
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
|
||||||
static Node *get_rule_sortgroupclause(Index ref, List *tlist,
|
static Node *get_rule_sortgroupclause(Index ref, List *tlist,
|
||||||
bool force_colno,
|
bool force_colno,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
|
@ -462,7 +455,7 @@ static char *generate_fragment_name(char *schemaName, char *tableName);
|
||||||
static char *generate_function_name(Oid funcid, int nargs,
|
static char *generate_function_name(Oid funcid, int nargs,
|
||||||
List *argnames, Oid *argtypes,
|
List *argnames, Oid *argtypes,
|
||||||
bool has_variadic, bool *use_variadic_p,
|
bool has_variadic, bool *use_variadic_p,
|
||||||
ParseExprKind special_exprkind);
|
bool inGroupBy);
|
||||||
static List *get_insert_column_names_list(List *targetList, StringInfo buf, deparse_context *context, RangeTblEntry *rte);
|
static List *get_insert_column_names_list(List *targetList, StringInfo buf, deparse_context *context, RangeTblEntry *rte);
|
||||||
|
|
||||||
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
|
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
|
||||||
|
@ -636,13 +629,16 @@ pg_get_rule_expr(Node *expression)
|
||||||
|
|
||||||
context.buf = buffer;
|
context.buf = buffer;
|
||||||
context.namespaces = NIL;
|
context.namespaces = NIL;
|
||||||
|
context.resultDesc = NULL;
|
||||||
|
context.targetList = NIL;
|
||||||
context.windowClause = NIL;
|
context.windowClause = NIL;
|
||||||
context.windowTList = NIL;
|
|
||||||
context.varprefix = false;
|
context.varprefix = false;
|
||||||
context.prettyFlags = 0;
|
context.prettyFlags = 0;
|
||||||
context.wrapColumn = WRAP_COLUMN_DEFAULT;
|
context.wrapColumn = WRAP_COLUMN_DEFAULT;
|
||||||
context.indentLevel = 0;
|
context.indentLevel = 0;
|
||||||
context.special_exprkind = EXPR_KIND_NONE;
|
context.colNamesVisible = true;
|
||||||
|
context.inGroupBy = false;
|
||||||
|
context.varInOrderBy = false;
|
||||||
context.distrelid = InvalidOid;
|
context.distrelid = InvalidOid;
|
||||||
context.shardid = INVALID_SHARD_ID;
|
context.shardid = INVALID_SHARD_ID;
|
||||||
|
|
||||||
|
@ -2066,14 +2062,17 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
||||||
|
|
||||||
context.buf = buf;
|
context.buf = buf;
|
||||||
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
||||||
|
context.resultDesc = NULL;
|
||||||
|
context.targetList = NIL;
|
||||||
context.windowClause = NIL;
|
context.windowClause = NIL;
|
||||||
context.windowTList = NIL;
|
|
||||||
context.varprefix = (parentnamespace != NIL ||
|
context.varprefix = (parentnamespace != NIL ||
|
||||||
list_length(query->rtable) != 1);
|
list_length(query->rtable) != 1);
|
||||||
context.prettyFlags = prettyFlags;
|
context.prettyFlags = prettyFlags;
|
||||||
context.wrapColumn = wrapColumn;
|
context.wrapColumn = wrapColumn;
|
||||||
context.indentLevel = startIndent;
|
context.indentLevel = startIndent;
|
||||||
context.special_exprkind = EXPR_KIND_NONE;
|
context.colNamesVisible = true;
|
||||||
|
context.inGroupBy = false;
|
||||||
|
context.varInOrderBy = false;
|
||||||
context.appendparents = NULL;
|
context.appendparents = NULL;
|
||||||
context.distrelid = distrelid;
|
context.distrelid = distrelid;
|
||||||
context.shardid = shardid;
|
context.shardid = shardid;
|
||||||
|
@ -2083,19 +2082,21 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
||||||
switch (query->commandType)
|
switch (query->commandType)
|
||||||
{
|
{
|
||||||
case CMD_SELECT:
|
case CMD_SELECT:
|
||||||
get_select_query_def(query, &context, resultDesc, colNamesVisible);
|
/* We set context.resultDesc only if it's a SELECT */
|
||||||
|
context.resultDesc = resultDesc;
|
||||||
|
get_select_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
get_update_query_def(query, &context, colNamesVisible);
|
get_update_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
get_insert_query_def(query, &context, colNamesVisible);
|
get_insert_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
get_delete_query_def(query, &context, colNamesVisible);
|
get_delete_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_MERGE:
|
case CMD_MERGE:
|
||||||
|
@ -2307,23 +2308,18 @@ get_with_clause(Query *query, deparse_context *context)
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_select_query_def(Query *query, deparse_context *context,
|
get_select_query_def(Query *query, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
List *save_windowclause;
|
|
||||||
List *save_windowtlist;
|
|
||||||
bool force_colno;
|
bool force_colno;
|
||||||
ListCell *l;
|
ListCell *l;
|
||||||
|
|
||||||
/* Insert the WITH clause if given */
|
/* Insert the WITH clause if given */
|
||||||
get_with_clause(query, context);
|
get_with_clause(query, context);
|
||||||
|
|
||||||
/* Set up context for possible window functions */
|
/* Subroutines may need to consult the SELECT targetlist and windowClause */
|
||||||
save_windowclause = context->windowClause;
|
context->targetList = query->targetList;
|
||||||
context->windowClause = query->windowClause;
|
context->windowClause = query->windowClause;
|
||||||
save_windowtlist = context->windowTList;
|
|
||||||
context->windowTList = query->targetList;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the Query node has a setOperations tree, then it's the top level of
|
* If the Query node has a setOperations tree, then it's the top level of
|
||||||
|
@ -2332,14 +2328,13 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||||
*/
|
*/
|
||||||
if (query->setOperations)
|
if (query->setOperations)
|
||||||
{
|
{
|
||||||
get_setop_query(query->setOperations, query, context, resultDesc,
|
get_setop_query(query->setOperations, query, context);
|
||||||
colNamesVisible);
|
|
||||||
/* ORDER BY clauses must be simple in this case */
|
/* ORDER BY clauses must be simple in this case */
|
||||||
force_colno = true;
|
force_colno = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
get_basic_select_query(query, context, resultDesc, colNamesVisible);
|
get_basic_select_query(query, context);
|
||||||
force_colno = false;
|
force_colno = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2429,9 +2424,6 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||||
appendStringInfoString(buf, " SKIP LOCKED");
|
appendStringInfoString(buf, " SKIP LOCKED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context->windowClause = save_windowclause;
|
|
||||||
context->windowTList = save_windowtlist;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2506,8 +2498,7 @@ get_simple_values_rte(Query *query, TupleDesc resultDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
get_basic_select_query(Query *query, deparse_context *context,
|
get_basic_select_query(Query *query, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *values_rte;
|
RangeTblEntry *values_rte;
|
||||||
|
@ -2525,7 +2516,7 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
* VALUES part. This reverses what transformValuesClause() did at parse
|
* VALUES part. This reverses what transformValuesClause() did at parse
|
||||||
* time.
|
* time.
|
||||||
*/
|
*/
|
||||||
values_rte = get_simple_values_rte(query, resultDesc);
|
values_rte = get_simple_values_rte(query, context->resultDesc);
|
||||||
if (values_rte)
|
if (values_rte)
|
||||||
{
|
{
|
||||||
get_values_def(values_rte->values_lists, context);
|
get_values_def(values_rte->values_lists, context);
|
||||||
|
@ -2563,7 +2554,7 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Then we tell what to select (the targetlist) */
|
/* Then we tell what to select (the targetlist) */
|
||||||
get_target_list(query->targetList, context, resultDesc, colNamesVisible);
|
get_target_list(query->targetList, context);
|
||||||
|
|
||||||
/* Add the FROM clause if needed */
|
/* Add the FROM clause if needed */
|
||||||
get_from_clause(query, " FROM ", context);
|
get_from_clause(query, " FROM ", context);
|
||||||
|
@ -2579,15 +2570,15 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
/* Add the GROUP BY clause if given */
|
/* Add the GROUP BY clause if given */
|
||||||
if (query->groupClause != NULL || query->groupingSets != NULL)
|
if (query->groupClause != NULL || query->groupingSets != NULL)
|
||||||
{
|
{
|
||||||
ParseExprKind save_exprkind;
|
bool save_ingroupby;
|
||||||
|
|
||||||
appendContextKeyword(context, " GROUP BY ",
|
appendContextKeyword(context, " GROUP BY ",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
if (query->groupDistinct)
|
if (query->groupDistinct)
|
||||||
appendStringInfoString(buf, "DISTINCT ");
|
appendStringInfoString(buf, "DISTINCT ");
|
||||||
|
|
||||||
save_exprkind = context->special_exprkind;
|
save_ingroupby = context->inGroupBy;
|
||||||
context->special_exprkind = EXPR_KIND_GROUP_BY;
|
context->inGroupBy = true;
|
||||||
|
|
||||||
if (query->groupingSets == NIL)
|
if (query->groupingSets == NIL)
|
||||||
{
|
{
|
||||||
|
@ -2615,7 +2606,7 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context->special_exprkind = save_exprkind;
|
context->inGroupBy = save_ingroupby;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add the HAVING clause if given */
|
/* Add the HAVING clause if given */
|
||||||
|
@ -2634,14 +2625,11 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
/* ----------
|
/* ----------
|
||||||
* get_target_list - Parse back a SELECT target list
|
* get_target_list - Parse back a SELECT target list
|
||||||
*
|
*
|
||||||
* This is also used for RETURNING lists in INSERT/UPDATE/DELETE.
|
* This is also used for RETURNING lists in INSERT/UPDATE/DELETE/MERGE.
|
||||||
*
|
|
||||||
* resultDesc and colNamesVisible are as for get_query_def()
|
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_target_list(List *targetList, deparse_context *context,
|
get_target_list(List *targetList, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
StringInfoData targetbuf;
|
StringInfoData targetbuf;
|
||||||
|
@ -2698,7 +2686,7 @@ get_target_list(List *targetList, deparse_context *context,
|
||||||
* assigned column name explicitly. Otherwise, show it only if
|
* assigned column name explicitly. Otherwise, show it only if
|
||||||
* it's not FigureColname's fallback.
|
* it's not FigureColname's fallback.
|
||||||
*/
|
*/
|
||||||
attname = colNamesVisible ? NULL : "?column?";
|
attname = context->colNamesVisible ? NULL : "?column?";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2707,8 +2695,9 @@ get_target_list(List *targetList, deparse_context *context,
|
||||||
* effects of any column RENAME that's been done on the view).
|
* effects of any column RENAME that's been done on the view).
|
||||||
* Otherwise, just use what we can find in the TLE.
|
* Otherwise, just use what we can find in the TLE.
|
||||||
*/
|
*/
|
||||||
if (resultDesc && colno <= resultDesc->natts)
|
if (context->resultDesc && colno <= context->resultDesc->natts)
|
||||||
colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
|
colname = NameStr(TupleDescAttr(context->resultDesc,
|
||||||
|
colno - 1)->attname);
|
||||||
else
|
else
|
||||||
colname = tle->resname;
|
colname = tle->resname;
|
||||||
|
|
||||||
|
@ -2776,8 +2765,7 @@ get_target_list(List *targetList, deparse_context *context,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
get_setop_query(Node *setOp, Query *query, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
bool need_paren;
|
bool need_paren;
|
||||||
|
@ -2802,8 +2790,8 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
subquery->limitCount);
|
subquery->limitCount);
|
||||||
if (need_paren)
|
if (need_paren)
|
||||||
appendStringInfoChar(buf, '(');
|
appendStringInfoChar(buf, '(');
|
||||||
get_query_def(subquery, buf, context->namespaces, resultDesc,
|
get_query_def(subquery, buf, context->namespaces,
|
||||||
colNamesVisible,
|
context->resultDesc, context->colNamesVisible,
|
||||||
context->prettyFlags, context->wrapColumn,
|
context->prettyFlags, context->wrapColumn,
|
||||||
context->indentLevel);
|
context->indentLevel);
|
||||||
if (need_paren)
|
if (need_paren)
|
||||||
|
@ -2813,6 +2801,7 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
SetOperationStmt *op = (SetOperationStmt *) setOp;
|
SetOperationStmt *op = (SetOperationStmt *) setOp;
|
||||||
int subindent;
|
int subindent;
|
||||||
|
bool save_colnamesvisible;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We force parens when nesting two SetOperationStmts, except when the
|
* We force parens when nesting two SetOperationStmts, except when the
|
||||||
|
@ -2846,7 +2835,7 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
else
|
else
|
||||||
subindent = 0;
|
subindent = 0;
|
||||||
|
|
||||||
get_setop_query(op->larg, query, context, resultDesc, colNamesVisible);
|
get_setop_query(op->larg, query, context);
|
||||||
|
|
||||||
if (need_paren)
|
if (need_paren)
|
||||||
appendContextKeyword(context, ") ", -subindent, 0, 0);
|
appendContextKeyword(context, ") ", -subindent, 0, 0);
|
||||||
|
@ -2890,7 +2879,13 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
subindent = 0;
|
subindent = 0;
|
||||||
appendContextKeyword(context, "", subindent, 0, 0);
|
appendContextKeyword(context, "", subindent, 0, 0);
|
||||||
|
|
||||||
get_setop_query(op->rarg, query, context, resultDesc, false);
|
/*
|
||||||
|
* The output column names of the RHS sub-select don't matter.
|
||||||
|
*/
|
||||||
|
save_colnamesvisible = context->colNamesVisible;
|
||||||
|
context->colNamesVisible = false;
|
||||||
|
get_setop_query(op->rarg, query, context);
|
||||||
|
context->colNamesVisible = save_colnamesvisible;
|
||||||
|
|
||||||
if (PRETTY_INDENT(context))
|
if (PRETTY_INDENT(context))
|
||||||
context->indentLevel -= subindent;
|
context->indentLevel -= subindent;
|
||||||
|
@ -2924,20 +2919,31 @@ get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
|
||||||
* Use column-number form if requested by caller. Otherwise, if
|
* Use column-number form if requested by caller. Otherwise, if
|
||||||
* expression is a constant, force it to be dumped with an explicit cast
|
* expression is a constant, force it to be dumped with an explicit cast
|
||||||
* as decoration --- this is because a simple integer constant is
|
* as decoration --- this is because a simple integer constant is
|
||||||
* ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
|
* ambiguous (and will be misinterpreted by findTargetlistEntrySQL92()) if
|
||||||
* dump it without any decoration. If it's anything more complex than a
|
* we dump it without any decoration. Similarly, if it's just a Var,
|
||||||
* simple Var, then force extra parens around it, to ensure it can't be
|
* there is risk of misinterpretation if the column name is reassigned in
|
||||||
* misinterpreted as a cube() or rollup() construct.
|
* the SELECT list, so we may need to force table qualification. And, if
|
||||||
|
* it's anything more complex than a simple Var, then force extra parens
|
||||||
|
* around it, to ensure it can't be misinterpreted as a cube() or rollup()
|
||||||
|
* construct.
|
||||||
*/
|
*/
|
||||||
if (force_colno)
|
if (force_colno)
|
||||||
{
|
{
|
||||||
Assert(!tle->resjunk);
|
Assert(!tle->resjunk);
|
||||||
appendStringInfo(buf, "%d", tle->resno);
|
appendStringInfo(buf, "%d", tle->resno);
|
||||||
}
|
}
|
||||||
else if (expr && IsA(expr, Const))
|
else if (!expr)
|
||||||
|
/* do nothing, probably can't happen */ ;
|
||||||
|
else if (IsA(expr, Const))
|
||||||
get_const_expr((Const *) expr, context, 1);
|
get_const_expr((Const *) expr, context, 1);
|
||||||
else if (!expr || IsA(expr, Var))
|
else if (IsA(expr, Var))
|
||||||
get_rule_expr(expr, context, true);
|
{
|
||||||
|
/* Tell get_variable to check for name conflict */
|
||||||
|
bool save_varinorderby = context->varInOrderBy;
|
||||||
|
context->varInOrderBy = true;
|
||||||
|
(void) get_variable((Var *) expr, 0, false, context);
|
||||||
|
context->varInOrderBy = save_varinorderby;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -3225,8 +3231,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_insert_query_def(Query *query, deparse_context *context,
|
get_insert_query_def(Query *query, deparse_context *context)
|
||||||
bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *select_rte = NULL;
|
RangeTblEntry *select_rte = NULL;
|
||||||
|
@ -3405,7 +3410,7 @@ get_insert_query_def(Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
appendContextKeyword(context, " RETURNING",
|
appendContextKeyword(context, " RETURNING",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
get_target_list(query->returningList, context, NULL, colNamesVisible);
|
get_target_list(query->returningList, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3414,8 +3419,7 @@ get_insert_query_def(Query *query, deparse_context *context,
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_update_query_def(Query *query, deparse_context *context,
|
get_update_query_def(Query *query, deparse_context *context)
|
||||||
bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
|
@ -3485,7 +3489,7 @@ get_update_query_def(Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
appendContextKeyword(context, " RETURNING",
|
appendContextKeyword(context, " RETURNING",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
get_target_list(query->returningList, context, NULL, colNamesVisible);
|
get_target_list(query->returningList, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3645,8 +3649,7 @@ get_update_query_targetlist_def(Query *query, List *targetList,
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_delete_query_def(Query *query, deparse_context *context,
|
get_delete_query_def(Query *query, deparse_context *context)
|
||||||
bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
|
@ -3711,7 +3714,7 @@ get_delete_query_def(Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
appendContextKeyword(context, " RETURNING",
|
appendContextKeyword(context, " RETURNING",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
get_target_list(query->returningList, context, NULL, colNamesVisible);
|
get_target_list(query->returningList, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3963,6 +3966,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
|
||||||
deparse_columns *colinfo;
|
deparse_columns *colinfo;
|
||||||
char *refname;
|
char *refname;
|
||||||
char *attname;
|
char *attname;
|
||||||
|
bool need_prefix;
|
||||||
|
|
||||||
/* Find appropriate nesting depth */
|
/* Find appropriate nesting depth */
|
||||||
netlevelsup = var->varlevelsup + levelsup;
|
netlevelsup = var->varlevelsup + levelsup;
|
||||||
|
@ -4163,7 +4167,42 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
|
||||||
attname = get_rte_attribute_name(rte, attnum);
|
attname = get_rte_attribute_name(rte, attnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refname && (context->varprefix || attname == NULL))
|
need_prefix = (context->varprefix || attname == NULL);
|
||||||
|
/*
|
||||||
|
* If we're considering a plain Var in an ORDER BY (but not GROUP BY)
|
||||||
|
* clause, we may need to add a table-name prefix to prevent
|
||||||
|
* findTargetlistEntrySQL92 from misinterpreting the name as an
|
||||||
|
* output-column name. To avoid cluttering the output with unnecessary
|
||||||
|
* prefixes, do so only if there is a name match to a SELECT tlist item
|
||||||
|
* that is different from the Var.
|
||||||
|
*/
|
||||||
|
if (context->varInOrderBy && !context->inGroupBy && !need_prefix)
|
||||||
|
{
|
||||||
|
int colno = 0;
|
||||||
|
ListCell *l;
|
||||||
|
foreach(l, context->targetList)
|
||||||
|
{
|
||||||
|
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
||||||
|
char *colname;
|
||||||
|
if (tle->resjunk)
|
||||||
|
continue; /* ignore junk entries */
|
||||||
|
colno++;
|
||||||
|
/* This must match colname-choosing logic in get_target_list() */
|
||||||
|
if (context->resultDesc && colno <= context->resultDesc->natts)
|
||||||
|
colname = NameStr(TupleDescAttr(context->resultDesc,
|
||||||
|
colno - 1)->attname);
|
||||||
|
else
|
||||||
|
colname = tle->resname;
|
||||||
|
if (colname && strcmp(colname, attname) == 0 &&
|
||||||
|
!equal(var, tle->expr))
|
||||||
|
{
|
||||||
|
need_prefix = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refname && need_prefix)
|
||||||
{
|
{
|
||||||
appendStringInfoString(buf, quote_identifier(refname));
|
appendStringInfoString(buf, quote_identifier(refname));
|
||||||
appendStringInfoChar(buf, '.');
|
appendStringInfoChar(buf, '.');
|
||||||
|
@ -6727,7 +6766,7 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||||
argnames, argtypes,
|
argnames, argtypes,
|
||||||
expr->funcvariadic,
|
expr->funcvariadic,
|
||||||
&use_variadic,
|
&use_variadic,
|
||||||
context->special_exprkind));
|
context->inGroupBy));
|
||||||
nargs = 0;
|
nargs = 0;
|
||||||
foreach(l, expr->args)
|
foreach(l, expr->args)
|
||||||
{
|
{
|
||||||
|
@ -6770,7 +6809,7 @@ get_proc_expr(CallStmt *stmt, deparse_context *context,
|
||||||
namedArgList, argumentTypes,
|
namedArgList, argumentTypes,
|
||||||
stmt->funcexpr->funcvariadic,
|
stmt->funcexpr->funcvariadic,
|
||||||
&use_variadic,
|
&use_variadic,
|
||||||
context->special_exprkind));
|
context->inGroupBy));
|
||||||
int argNumber = 0;
|
int argNumber = 0;
|
||||||
foreach(argumentCell, finalArgumentList)
|
foreach(argumentCell, finalArgumentList)
|
||||||
{
|
{
|
||||||
|
@ -6832,7 +6871,7 @@ get_agg_expr(Aggref *aggref, deparse_context *context,
|
||||||
NIL, argtypes,
|
NIL, argtypes,
|
||||||
aggref->aggvariadic,
|
aggref->aggvariadic,
|
||||||
&use_variadic,
|
&use_variadic,
|
||||||
context->special_exprkind),
|
context->inGroupBy),
|
||||||
(aggref->aggdistinct != NIL) ? "DISTINCT " : "");
|
(aggref->aggdistinct != NIL) ? "DISTINCT " : "");
|
||||||
|
|
||||||
if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
|
if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
|
||||||
|
@ -6941,7 +6980,7 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
|
||||||
generate_function_name(wfunc->winfnoid, nargs,
|
generate_function_name(wfunc->winfnoid, nargs,
|
||||||
argnames, argtypes,
|
argnames, argtypes,
|
||||||
false, NULL,
|
false, NULL,
|
||||||
context->special_exprkind));
|
context->inGroupBy));
|
||||||
|
|
||||||
/* winstar can be set only in zero-argument aggregates */
|
/* winstar can be set only in zero-argument aggregates */
|
||||||
if (wfunc->winstar)
|
if (wfunc->winstar)
|
||||||
|
@ -6966,7 +7005,7 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
|
||||||
if (wc->name)
|
if (wc->name)
|
||||||
appendStringInfoString(buf, quote_identifier(wc->name));
|
appendStringInfoString(buf, quote_identifier(wc->name));
|
||||||
else
|
else
|
||||||
get_rule_windowspec(wc, context->windowTList, context);
|
get_rule_windowspec(wc, context->targetList, context);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8271,7 +8310,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
|
||||||
appendStringInfo(buf, " TABLESAMPLE %s (",
|
appendStringInfo(buf, " TABLESAMPLE %s (",
|
||||||
generate_function_name(tablesample->tsmhandler, 1,
|
generate_function_name(tablesample->tsmhandler, 1,
|
||||||
NIL, argtypes,
|
NIL, argtypes,
|
||||||
false, NULL, EXPR_KIND_NONE));
|
false, NULL, false));
|
||||||
|
|
||||||
nargs = 0;
|
nargs = 0;
|
||||||
foreach(l, tablesample->args)
|
foreach(l, tablesample->args)
|
||||||
|
@ -8618,12 +8657,14 @@ generate_fragment_name(char *schemaName, char *tableName)
|
||||||
* the output. For non-FuncExpr cases, has_variadic should be false and
|
* the output. For non-FuncExpr cases, has_variadic should be false and
|
||||||
* use_variadic_p can be NULL.
|
* use_variadic_p can be NULL.
|
||||||
*
|
*
|
||||||
|
* inGroupBy must be true if we're deparsing a GROUP BY clause.
|
||||||
|
*
|
||||||
* The result includes all necessary quoting and schema-prefixing.
|
* The result includes all necessary quoting and schema-prefixing.
|
||||||
*/
|
*/
|
||||||
static char *
|
static char *
|
||||||
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
|
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
|
||||||
bool has_variadic, bool *use_variadic_p,
|
bool has_variadic, bool *use_variadic_p,
|
||||||
ParseExprKind special_exprkind)
|
bool inGroupBy)
|
||||||
{
|
{
|
||||||
char *result;
|
char *result;
|
||||||
HeapTuple proctup;
|
HeapTuple proctup;
|
||||||
|
@ -8648,9 +8689,9 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Due to parser hacks to avoid needing to reserve CUBE, we need to force
|
* Due to parser hacks to avoid needing to reserve CUBE, we need to force
|
||||||
* qualification in some special cases.
|
* qualification of some function names within GROUP BY.
|
||||||
*/
|
*/
|
||||||
if (special_exprkind == EXPR_KIND_GROUP_BY)
|
if (inGroupBy)
|
||||||
{
|
{
|
||||||
if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
|
if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
|
||||||
force_qualify = true;
|
force_qualify = true;
|
||||||
|
|
|
@ -67,7 +67,6 @@
|
||||||
#include "parser/parse_node.h"
|
#include "parser/parse_node.h"
|
||||||
#include "parser/parse_agg.h"
|
#include "parser/parse_agg.h"
|
||||||
#include "parser/parse_func.h"
|
#include "parser/parse_func.h"
|
||||||
#include "parser/parse_node.h"
|
|
||||||
#include "parser/parse_oper.h"
|
#include "parser/parse_oper.h"
|
||||||
#include "parser/parse_relation.h"
|
#include "parser/parse_relation.h"
|
||||||
#include "parser/parser.h"
|
#include "parser/parser.h"
|
||||||
|
@ -123,16 +122,18 @@ typedef struct
|
||||||
{
|
{
|
||||||
StringInfo buf; /* output buffer to append to */
|
StringInfo buf; /* output buffer to append to */
|
||||||
List *namespaces; /* List of deparse_namespace nodes */
|
List *namespaces; /* List of deparse_namespace nodes */
|
||||||
|
TupleDesc resultDesc; /* if top level of a view, the view's tupdesc */
|
||||||
|
List *targetList; /* Current query level's SELECT targetlist */
|
||||||
List *windowClause; /* Current query level's WINDOW clause */
|
List *windowClause; /* Current query level's WINDOW clause */
|
||||||
List *windowTList; /* targetlist for resolving WINDOW clause */
|
|
||||||
int prettyFlags; /* enabling of pretty-print functions */
|
int prettyFlags; /* enabling of pretty-print functions */
|
||||||
int wrapColumn; /* max line length, or -1 for no limit */
|
int wrapColumn; /* max line length, or -1 for no limit */
|
||||||
int indentLevel; /* current indent level for prettyprint */
|
int indentLevel; /* current indent level for prettyprint */
|
||||||
bool varprefix; /* true to print prefixes on Vars */
|
bool varprefix; /* true to print prefixes on Vars */
|
||||||
Oid distrelid; /* the distributed table being modified, if valid */
|
Oid distrelid; /* the distributed table being modified, if valid */
|
||||||
int64 shardid; /* a distributed table's shardid, if positive */
|
int64 shardid; /* a distributed table's shardid, if positive */
|
||||||
ParseExprKind special_exprkind; /* set only for exprkinds needing special
|
bool colNamesVisible; /* do we care about output column names? */
|
||||||
* handling */
|
bool inGroupBy; /* deparsing GROUP BY clause? */
|
||||||
|
bool varInOrderBy; /* deparsing simple Var in ORDER BY? */
|
||||||
Bitmapset *appendparents; /* if not null, map child Vars of these relids
|
Bitmapset *appendparents; /* if not null, map child Vars of these relids
|
||||||
* back to the parent rel */
|
* back to the parent rel */
|
||||||
} deparse_context;
|
} deparse_context;
|
||||||
|
@ -364,27 +365,19 @@ static void get_query_def_extended(Query *query, StringInfo buf,
|
||||||
int startIndent);
|
int startIndent);
|
||||||
static void get_values_def(List *values_lists, deparse_context *context);
|
static void get_values_def(List *values_lists, deparse_context *context);
|
||||||
static void get_with_clause(Query *query, deparse_context *context);
|
static void get_with_clause(Query *query, deparse_context *context);
|
||||||
static void get_select_query_def(Query *query, deparse_context *context,
|
static void get_select_query_def(Query *query, deparse_context *context);
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
static void get_insert_query_def(Query *query, deparse_context *context);
|
||||||
static void get_insert_query_def(Query *query, deparse_context *context,
|
static void get_update_query_def(Query *query, deparse_context *context);
|
||||||
bool colNamesVisible);
|
|
||||||
static void get_update_query_def(Query *query, deparse_context *context,
|
|
||||||
bool colNamesVisible);
|
|
||||||
static void get_update_query_targetlist_def(Query *query, List *targetList,
|
static void get_update_query_targetlist_def(Query *query, List *targetList,
|
||||||
deparse_context *context,
|
deparse_context *context,
|
||||||
RangeTblEntry *rte);
|
RangeTblEntry *rte);
|
||||||
static void get_delete_query_def(Query *query, deparse_context *context,
|
static void get_delete_query_def(Query *query, deparse_context *context);
|
||||||
bool colNamesVisible);
|
static void get_merge_query_def(Query *query, deparse_context *context);
|
||||||
static void get_merge_query_def(Query *query, deparse_context *context,
|
|
||||||
bool colNamesVisible);
|
|
||||||
static void get_utility_query_def(Query *query, deparse_context *context);
|
static void get_utility_query_def(Query *query, deparse_context *context);
|
||||||
static void get_basic_select_query(Query *query, deparse_context *context,
|
static void get_basic_select_query(Query *query, deparse_context *context);
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
static void get_target_list(List *targetList, deparse_context *context);
|
||||||
static void get_target_list(List *targetList, deparse_context *context,
|
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
|
||||||
static void get_setop_query(Node *setOp, Query *query,
|
static void get_setop_query(Node *setOp, Query *query,
|
||||||
deparse_context *context,
|
deparse_context *context);
|
||||||
TupleDesc resultDesc, bool colNamesVisible);
|
|
||||||
static Node *get_rule_sortgroupclause(Index ref, List *tlist,
|
static Node *get_rule_sortgroupclause(Index ref, List *tlist,
|
||||||
bool force_colno,
|
bool force_colno,
|
||||||
deparse_context *context);
|
deparse_context *context);
|
||||||
|
@ -479,7 +472,7 @@ static char *generate_fragment_name(char *schemaName, char *tableName);
|
||||||
static char *generate_function_name(Oid funcid, int nargs,
|
static char *generate_function_name(Oid funcid, int nargs,
|
||||||
List *argnames, Oid *argtypes,
|
List *argnames, Oid *argtypes,
|
||||||
bool has_variadic, bool *use_variadic_p,
|
bool has_variadic, bool *use_variadic_p,
|
||||||
ParseExprKind special_exprkind);
|
bool inGroupBy);
|
||||||
static List *get_insert_column_names_list(List *targetList, StringInfo buf, deparse_context *context, RangeTblEntry *rte);
|
static List *get_insert_column_names_list(List *targetList, StringInfo buf, deparse_context *context, RangeTblEntry *rte);
|
||||||
|
|
||||||
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
|
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
|
||||||
|
@ -653,13 +646,16 @@ pg_get_rule_expr(Node *expression)
|
||||||
|
|
||||||
context.buf = buffer;
|
context.buf = buffer;
|
||||||
context.namespaces = NIL;
|
context.namespaces = NIL;
|
||||||
|
context.resultDesc = NULL;
|
||||||
|
context.targetList = NIL;
|
||||||
context.windowClause = NIL;
|
context.windowClause = NIL;
|
||||||
context.windowTList = NIL;
|
|
||||||
context.varprefix = false;
|
context.varprefix = false;
|
||||||
context.prettyFlags = 0;
|
context.prettyFlags = 0;
|
||||||
context.wrapColumn = WRAP_COLUMN_DEFAULT;
|
context.wrapColumn = WRAP_COLUMN_DEFAULT;
|
||||||
context.indentLevel = 0;
|
context.indentLevel = 0;
|
||||||
context.special_exprkind = EXPR_KIND_NONE;
|
context.colNamesVisible = true;
|
||||||
|
context.inGroupBy = false;
|
||||||
|
context.varInOrderBy = false;
|
||||||
context.distrelid = InvalidOid;
|
context.distrelid = InvalidOid;
|
||||||
context.shardid = INVALID_SHARD_ID;
|
context.shardid = INVALID_SHARD_ID;
|
||||||
|
|
||||||
|
@ -2080,14 +2076,17 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
||||||
|
|
||||||
context.buf = buf;
|
context.buf = buf;
|
||||||
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
|
||||||
|
context.resultDesc = NULL;
|
||||||
|
context.targetList = NIL;
|
||||||
context.windowClause = NIL;
|
context.windowClause = NIL;
|
||||||
context.windowTList = NIL;
|
|
||||||
context.varprefix = (parentnamespace != NIL ||
|
context.varprefix = (parentnamespace != NIL ||
|
||||||
list_length(query->rtable) != 1);
|
list_length(query->rtable) != 1);
|
||||||
context.prettyFlags = prettyFlags;
|
context.prettyFlags = prettyFlags;
|
||||||
context.wrapColumn = wrapColumn;
|
context.wrapColumn = wrapColumn;
|
||||||
context.indentLevel = startIndent;
|
context.indentLevel = startIndent;
|
||||||
context.special_exprkind = EXPR_KIND_NONE;
|
context.colNamesVisible = true;
|
||||||
|
context.inGroupBy = false;
|
||||||
|
context.varInOrderBy = false;
|
||||||
context.appendparents = NULL;
|
context.appendparents = NULL;
|
||||||
context.distrelid = distrelid;
|
context.distrelid = distrelid;
|
||||||
context.shardid = shardid;
|
context.shardid = shardid;
|
||||||
|
@ -2097,23 +2096,25 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
|
||||||
switch (query->commandType)
|
switch (query->commandType)
|
||||||
{
|
{
|
||||||
case CMD_SELECT:
|
case CMD_SELECT:
|
||||||
get_select_query_def(query, &context, resultDesc, colNamesVisible);
|
/* We set context.resultDesc only if it's a SELECT */
|
||||||
|
context.resultDesc = resultDesc;
|
||||||
|
get_select_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_UPDATE:
|
case CMD_UPDATE:
|
||||||
get_update_query_def(query, &context, colNamesVisible);
|
get_update_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_INSERT:
|
case CMD_INSERT:
|
||||||
get_insert_query_def(query, &context, colNamesVisible);
|
get_insert_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_DELETE:
|
case CMD_DELETE:
|
||||||
get_delete_query_def(query, &context, colNamesVisible);
|
get_delete_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_MERGE:
|
case CMD_MERGE:
|
||||||
get_merge_query_def(query, &context, colNamesVisible);
|
get_merge_query_def(query, &context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_NOTHING:
|
case CMD_NOTHING:
|
||||||
|
@ -2321,23 +2322,18 @@ get_with_clause(Query *query, deparse_context *context)
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_select_query_def(Query *query, deparse_context *context,
|
get_select_query_def(Query *query, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
List *save_windowclause;
|
|
||||||
List *save_windowtlist;
|
|
||||||
bool force_colno;
|
bool force_colno;
|
||||||
ListCell *l;
|
ListCell *l;
|
||||||
|
|
||||||
/* Insert the WITH clause if given */
|
/* Insert the WITH clause if given */
|
||||||
get_with_clause(query, context);
|
get_with_clause(query, context);
|
||||||
|
|
||||||
/* Set up context for possible window functions */
|
/* Subroutines may need to consult the SELECT targetlist and windowClause */
|
||||||
save_windowclause = context->windowClause;
|
context->targetList = query->targetList;
|
||||||
context->windowClause = query->windowClause;
|
context->windowClause = query->windowClause;
|
||||||
save_windowtlist = context->windowTList;
|
|
||||||
context->windowTList = query->targetList;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the Query node has a setOperations tree, then it's the top level of
|
* If the Query node has a setOperations tree, then it's the top level of
|
||||||
|
@ -2346,14 +2342,13 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||||
*/
|
*/
|
||||||
if (query->setOperations)
|
if (query->setOperations)
|
||||||
{
|
{
|
||||||
get_setop_query(query->setOperations, query, context, resultDesc,
|
get_setop_query(query->setOperations, query, context);
|
||||||
colNamesVisible);
|
|
||||||
/* ORDER BY clauses must be simple in this case */
|
/* ORDER BY clauses must be simple in this case */
|
||||||
force_colno = true;
|
force_colno = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
get_basic_select_query(query, context, resultDesc, colNamesVisible);
|
get_basic_select_query(query, context);
|
||||||
force_colno = false;
|
force_colno = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2443,9 +2438,6 @@ get_select_query_def(Query *query, deparse_context *context,
|
||||||
appendStringInfoString(buf, " SKIP LOCKED");
|
appendStringInfoString(buf, " SKIP LOCKED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context->windowClause = save_windowclause;
|
|
||||||
context->windowTList = save_windowtlist;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2520,8 +2512,7 @@ get_simple_values_rte(Query *query, TupleDesc resultDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
get_basic_select_query(Query *query, deparse_context *context,
|
get_basic_select_query(Query *query, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *values_rte;
|
RangeTblEntry *values_rte;
|
||||||
|
@ -2539,7 +2530,7 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
* VALUES part. This reverses what transformValuesClause() did at parse
|
* VALUES part. This reverses what transformValuesClause() did at parse
|
||||||
* time.
|
* time.
|
||||||
*/
|
*/
|
||||||
values_rte = get_simple_values_rte(query, resultDesc);
|
values_rte = get_simple_values_rte(query, context->resultDesc);
|
||||||
if (values_rte)
|
if (values_rte)
|
||||||
{
|
{
|
||||||
get_values_def(values_rte->values_lists, context);
|
get_values_def(values_rte->values_lists, context);
|
||||||
|
@ -2577,7 +2568,7 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Then we tell what to select (the targetlist) */
|
/* Then we tell what to select (the targetlist) */
|
||||||
get_target_list(query->targetList, context, resultDesc, colNamesVisible);
|
get_target_list(query->targetList, context);
|
||||||
|
|
||||||
/* Add the FROM clause if needed */
|
/* Add the FROM clause if needed */
|
||||||
get_from_clause(query, " FROM ", context);
|
get_from_clause(query, " FROM ", context);
|
||||||
|
@ -2593,15 +2584,15 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
/* Add the GROUP BY clause if given */
|
/* Add the GROUP BY clause if given */
|
||||||
if (query->groupClause != NULL || query->groupingSets != NULL)
|
if (query->groupClause != NULL || query->groupingSets != NULL)
|
||||||
{
|
{
|
||||||
ParseExprKind save_exprkind;
|
bool save_ingroupby;
|
||||||
|
|
||||||
appendContextKeyword(context, " GROUP BY ",
|
appendContextKeyword(context, " GROUP BY ",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
if (query->groupDistinct)
|
if (query->groupDistinct)
|
||||||
appendStringInfoString(buf, "DISTINCT ");
|
appendStringInfoString(buf, "DISTINCT ");
|
||||||
|
|
||||||
save_exprkind = context->special_exprkind;
|
save_ingroupby = context->inGroupBy;
|
||||||
context->special_exprkind = EXPR_KIND_GROUP_BY;
|
context->inGroupBy = true;
|
||||||
|
|
||||||
if (query->groupingSets == NIL)
|
if (query->groupingSets == NIL)
|
||||||
{
|
{
|
||||||
|
@ -2629,7 +2620,7 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context->special_exprkind = save_exprkind;
|
context->inGroupBy = save_ingroupby;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add the HAVING clause if given */
|
/* Add the HAVING clause if given */
|
||||||
|
@ -2648,14 +2639,11 @@ get_basic_select_query(Query *query, deparse_context *context,
|
||||||
/* ----------
|
/* ----------
|
||||||
* get_target_list - Parse back a SELECT target list
|
* get_target_list - Parse back a SELECT target list
|
||||||
*
|
*
|
||||||
* This is also used for RETURNING lists in INSERT/UPDATE/DELETE.
|
* This is also used for RETURNING lists in INSERT/UPDATE/DELETE/MERGE.
|
||||||
*
|
|
||||||
* resultDesc and colNamesVisible are as for get_query_def()
|
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_target_list(List *targetList, deparse_context *context,
|
get_target_list(List *targetList, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
StringInfoData targetbuf;
|
StringInfoData targetbuf;
|
||||||
|
@ -2712,7 +2700,7 @@ get_target_list(List *targetList, deparse_context *context,
|
||||||
* assigned column name explicitly. Otherwise, show it only if
|
* assigned column name explicitly. Otherwise, show it only if
|
||||||
* it's not FigureColname's fallback.
|
* it's not FigureColname's fallback.
|
||||||
*/
|
*/
|
||||||
attname = colNamesVisible ? NULL : "?column?";
|
attname = context->colNamesVisible ? NULL : "?column?";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2721,8 +2709,9 @@ get_target_list(List *targetList, deparse_context *context,
|
||||||
* effects of any column RENAME that's been done on the view).
|
* effects of any column RENAME that's been done on the view).
|
||||||
* Otherwise, just use what we can find in the TLE.
|
* Otherwise, just use what we can find in the TLE.
|
||||||
*/
|
*/
|
||||||
if (resultDesc && colno <= resultDesc->natts)
|
if (context->resultDesc && colno <= context->resultDesc->natts)
|
||||||
colname = NameStr(TupleDescAttr(resultDesc, colno - 1)->attname);
|
colname = NameStr(TupleDescAttr(context->resultDesc,
|
||||||
|
colno - 1)->attname);
|
||||||
else
|
else
|
||||||
colname = tle->resname;
|
colname = tle->resname;
|
||||||
|
|
||||||
|
@ -2790,8 +2779,7 @@ get_target_list(List *targetList, deparse_context *context,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
get_setop_query(Node *setOp, Query *query, deparse_context *context)
|
||||||
TupleDesc resultDesc, bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
bool need_paren;
|
bool need_paren;
|
||||||
|
@ -2816,8 +2804,8 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
subquery->limitCount);
|
subquery->limitCount);
|
||||||
if (need_paren)
|
if (need_paren)
|
||||||
appendStringInfoChar(buf, '(');
|
appendStringInfoChar(buf, '(');
|
||||||
get_query_def(subquery, buf, context->namespaces, resultDesc,
|
get_query_def(subquery, buf, context->namespaces,
|
||||||
colNamesVisible,
|
context->resultDesc, context->colNamesVisible,
|
||||||
context->prettyFlags, context->wrapColumn,
|
context->prettyFlags, context->wrapColumn,
|
||||||
context->indentLevel);
|
context->indentLevel);
|
||||||
if (need_paren)
|
if (need_paren)
|
||||||
|
@ -2827,6 +2815,7 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
SetOperationStmt *op = (SetOperationStmt *) setOp;
|
SetOperationStmt *op = (SetOperationStmt *) setOp;
|
||||||
int subindent;
|
int subindent;
|
||||||
|
bool save_colnamesvisible;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We force parens when nesting two SetOperationStmts, except when the
|
* We force parens when nesting two SetOperationStmts, except when the
|
||||||
|
@ -2860,7 +2849,7 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
else
|
else
|
||||||
subindent = 0;
|
subindent = 0;
|
||||||
|
|
||||||
get_setop_query(op->larg, query, context, resultDesc, colNamesVisible);
|
get_setop_query(op->larg, query, context);
|
||||||
|
|
||||||
if (need_paren)
|
if (need_paren)
|
||||||
appendContextKeyword(context, ") ", -subindent, 0, 0);
|
appendContextKeyword(context, ") ", -subindent, 0, 0);
|
||||||
|
@ -2904,7 +2893,13 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
|
||||||
subindent = 0;
|
subindent = 0;
|
||||||
appendContextKeyword(context, "", subindent, 0, 0);
|
appendContextKeyword(context, "", subindent, 0, 0);
|
||||||
|
|
||||||
get_setop_query(op->rarg, query, context, resultDesc, false);
|
/*
|
||||||
|
* The output column names of the RHS sub-select don't matter.
|
||||||
|
*/
|
||||||
|
save_colnamesvisible = context->colNamesVisible;
|
||||||
|
context->colNamesVisible = false;
|
||||||
|
get_setop_query(op->rarg, query, context);
|
||||||
|
context->colNamesVisible = save_colnamesvisible;
|
||||||
|
|
||||||
if (PRETTY_INDENT(context))
|
if (PRETTY_INDENT(context))
|
||||||
context->indentLevel -= subindent;
|
context->indentLevel -= subindent;
|
||||||
|
@ -2938,20 +2933,31 @@ get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
|
||||||
* Use column-number form if requested by caller. Otherwise, if
|
* Use column-number form if requested by caller. Otherwise, if
|
||||||
* expression is a constant, force it to be dumped with an explicit cast
|
* expression is a constant, force it to be dumped with an explicit cast
|
||||||
* as decoration --- this is because a simple integer constant is
|
* as decoration --- this is because a simple integer constant is
|
||||||
* ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
|
* ambiguous (and will be misinterpreted by findTargetlistEntrySQL92()) if
|
||||||
* dump it without any decoration. If it's anything more complex than a
|
* we dump it without any decoration. Similarly, if it's just a Var,
|
||||||
* simple Var, then force extra parens around it, to ensure it can't be
|
* there is risk of misinterpretation if the column name is reassigned in
|
||||||
* misinterpreted as a cube() or rollup() construct.
|
* the SELECT list, so we may need to force table qualification. And, if
|
||||||
|
* it's anything more complex than a simple Var, then force extra parens
|
||||||
|
* around it, to ensure it can't be misinterpreted as a cube() or rollup()
|
||||||
|
* construct.
|
||||||
*/
|
*/
|
||||||
if (force_colno)
|
if (force_colno)
|
||||||
{
|
{
|
||||||
Assert(!tle->resjunk);
|
Assert(!tle->resjunk);
|
||||||
appendStringInfo(buf, "%d", tle->resno);
|
appendStringInfo(buf, "%d", tle->resno);
|
||||||
}
|
}
|
||||||
else if (expr && IsA(expr, Const))
|
else if (!expr)
|
||||||
|
/* do nothing, probably can't happen */ ;
|
||||||
|
else if (IsA(expr, Const))
|
||||||
get_const_expr((Const *) expr, context, 1);
|
get_const_expr((Const *) expr, context, 1);
|
||||||
else if (!expr || IsA(expr, Var))
|
else if (IsA(expr, Var))
|
||||||
get_rule_expr(expr, context, true);
|
{
|
||||||
|
/* Tell get_variable to check for name conflict */
|
||||||
|
bool save_varinorderby = context->varInOrderBy;
|
||||||
|
context->varInOrderBy = true;
|
||||||
|
(void) get_variable((Var *) expr, 0, false, context);
|
||||||
|
context->varInOrderBy = save_varinorderby;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
@ -3240,8 +3246,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_insert_query_def(Query *query, deparse_context *context,
|
get_insert_query_def(Query *query, deparse_context *context)
|
||||||
bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *select_rte = NULL;
|
RangeTblEntry *select_rte = NULL;
|
||||||
|
@ -3422,7 +3427,7 @@ get_insert_query_def(Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
appendContextKeyword(context, " RETURNING",
|
appendContextKeyword(context, " RETURNING",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
get_target_list(query->returningList, context, NULL, colNamesVisible);
|
get_target_list(query->returningList, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3431,8 +3436,7 @@ get_insert_query_def(Query *query, deparse_context *context,
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_update_query_def(Query *query, deparse_context *context,
|
get_update_query_def(Query *query, deparse_context *context)
|
||||||
bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
|
@ -3501,7 +3505,7 @@ get_update_query_def(Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
appendContextKeyword(context, " RETURNING",
|
appendContextKeyword(context, " RETURNING",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
get_target_list(query->returningList, context, NULL, colNamesVisible);
|
get_target_list(query->returningList, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3661,8 +3665,7 @@ get_update_query_targetlist_def(Query *query, List *targetList,
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_delete_query_def(Query *query, deparse_context *context,
|
get_delete_query_def(Query *query, deparse_context *context)
|
||||||
bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
|
@ -3726,7 +3729,7 @@ get_delete_query_def(Query *query, deparse_context *context,
|
||||||
{
|
{
|
||||||
appendContextKeyword(context, " RETURNING",
|
appendContextKeyword(context, " RETURNING",
|
||||||
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
|
||||||
get_target_list(query->returningList, context, NULL, colNamesVisible);
|
get_target_list(query->returningList, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3736,8 +3739,7 @@ get_delete_query_def(Query *query, deparse_context *context,
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_merge_query_def(Query *query, deparse_context *context,
|
get_merge_query_def(Query *query, deparse_context *context)
|
||||||
bool colNamesVisible)
|
|
||||||
{
|
{
|
||||||
StringInfo buf = context->buf;
|
StringInfo buf = context->buf;
|
||||||
RangeTblEntry *rte;
|
RangeTblEntry *rte;
|
||||||
|
@ -3977,6 +3979,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
|
||||||
deparse_columns *colinfo;
|
deparse_columns *colinfo;
|
||||||
char *refname;
|
char *refname;
|
||||||
char *attname;
|
char *attname;
|
||||||
|
bool need_prefix;
|
||||||
|
|
||||||
/* Find appropriate nesting depth */
|
/* Find appropriate nesting depth */
|
||||||
netlevelsup = var->varlevelsup + levelsup;
|
netlevelsup = var->varlevelsup + levelsup;
|
||||||
|
@ -4177,7 +4180,42 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
|
||||||
attname = get_rte_attribute_name(rte, attnum);
|
attname = get_rte_attribute_name(rte, attnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refname && (context->varprefix || attname == NULL))
|
need_prefix = (context->varprefix || attname == NULL);
|
||||||
|
/*
|
||||||
|
* If we're considering a plain Var in an ORDER BY (but not GROUP BY)
|
||||||
|
* clause, we may need to add a table-name prefix to prevent
|
||||||
|
* findTargetlistEntrySQL92 from misinterpreting the name as an
|
||||||
|
* output-column name. To avoid cluttering the output with unnecessary
|
||||||
|
* prefixes, do so only if there is a name match to a SELECT tlist item
|
||||||
|
* that is different from the Var.
|
||||||
|
*/
|
||||||
|
if (context->varInOrderBy && !context->inGroupBy && !need_prefix)
|
||||||
|
{
|
||||||
|
int colno = 0;
|
||||||
|
ListCell *l;
|
||||||
|
foreach(l, context->targetList)
|
||||||
|
{
|
||||||
|
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
||||||
|
char *colname;
|
||||||
|
if (tle->resjunk)
|
||||||
|
continue; /* ignore junk entries */
|
||||||
|
colno++;
|
||||||
|
/* This must match colname-choosing logic in get_target_list() */
|
||||||
|
if (context->resultDesc && colno <= context->resultDesc->natts)
|
||||||
|
colname = NameStr(TupleDescAttr(context->resultDesc,
|
||||||
|
colno - 1)->attname);
|
||||||
|
else
|
||||||
|
colname = tle->resname;
|
||||||
|
if (colname && strcmp(colname, attname) == 0 &&
|
||||||
|
!equal(var, tle->expr))
|
||||||
|
{
|
||||||
|
need_prefix = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refname && need_prefix)
|
||||||
{
|
{
|
||||||
appendStringInfoString(buf, quote_identifier(refname));
|
appendStringInfoString(buf, quote_identifier(refname));
|
||||||
appendStringInfoChar(buf, '.');
|
appendStringInfoChar(buf, '.');
|
||||||
|
@ -6775,7 +6813,7 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||||
argnames, argtypes,
|
argnames, argtypes,
|
||||||
expr->funcvariadic,
|
expr->funcvariadic,
|
||||||
&use_variadic,
|
&use_variadic,
|
||||||
context->special_exprkind));
|
context->inGroupBy));
|
||||||
nargs = 0;
|
nargs = 0;
|
||||||
foreach(l, expr->args)
|
foreach(l, expr->args)
|
||||||
{
|
{
|
||||||
|
@ -6818,7 +6856,7 @@ get_proc_expr(CallStmt *stmt, deparse_context *context,
|
||||||
namedArgList, argumentTypes,
|
namedArgList, argumentTypes,
|
||||||
stmt->funcexpr->funcvariadic,
|
stmt->funcexpr->funcvariadic,
|
||||||
&use_variadic,
|
&use_variadic,
|
||||||
context->special_exprkind));
|
context->inGroupBy));
|
||||||
int argNumber = 0;
|
int argNumber = 0;
|
||||||
foreach(argumentCell, finalArgumentList)
|
foreach(argumentCell, finalArgumentList)
|
||||||
{
|
{
|
||||||
|
@ -6891,7 +6929,7 @@ get_agg_expr_helper(Aggref *aggref, deparse_context *context,
|
||||||
funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
|
funcname = generate_function_name(aggref->aggfnoid, nargs, NIL,
|
||||||
argtypes, aggref->aggvariadic,
|
argtypes, aggref->aggvariadic,
|
||||||
&use_variadic,
|
&use_variadic,
|
||||||
context->special_exprkind);
|
context->inGroupBy);
|
||||||
|
|
||||||
/* Print the aggregate name, schema-qualified if needed */
|
/* Print the aggregate name, schema-qualified if needed */
|
||||||
appendStringInfo(buf, "%s(%s", funcname,
|
appendStringInfo(buf, "%s(%s", funcname,
|
||||||
|
@ -7032,7 +7070,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
|
||||||
if (!funcname)
|
if (!funcname)
|
||||||
funcname = generate_function_name(wfunc->winfnoid, nargs, argnames,
|
funcname = generate_function_name(wfunc->winfnoid, nargs, argnames,
|
||||||
argtypes, false, NULL,
|
argtypes, false, NULL,
|
||||||
context->special_exprkind);
|
context->inGroupBy);
|
||||||
|
|
||||||
appendStringInfo(buf, "%s(", funcname);
|
appendStringInfo(buf, "%s(", funcname);
|
||||||
|
|
||||||
|
@ -7071,7 +7109,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
|
||||||
if (wc->name)
|
if (wc->name)
|
||||||
appendStringInfoString(buf, quote_identifier(wc->name));
|
appendStringInfoString(buf, quote_identifier(wc->name));
|
||||||
else
|
else
|
||||||
get_rule_windowspec(wc, context->windowTList, context);
|
get_rule_windowspec(wc, context->targetList, context);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8547,7 +8585,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
|
||||||
appendStringInfo(buf, " TABLESAMPLE %s (",
|
appendStringInfo(buf, " TABLESAMPLE %s (",
|
||||||
generate_function_name(tablesample->tsmhandler, 1,
|
generate_function_name(tablesample->tsmhandler, 1,
|
||||||
NIL, argtypes,
|
NIL, argtypes,
|
||||||
false, NULL, EXPR_KIND_NONE));
|
false, NULL, false));
|
||||||
|
|
||||||
nargs = 0;
|
nargs = 0;
|
||||||
foreach(l, tablesample->args)
|
foreach(l, tablesample->args)
|
||||||
|
@ -8894,12 +8932,14 @@ generate_fragment_name(char *schemaName, char *tableName)
|
||||||
* the output. For non-FuncExpr cases, has_variadic should be false and
|
* the output. For non-FuncExpr cases, has_variadic should be false and
|
||||||
* use_variadic_p can be NULL.
|
* use_variadic_p can be NULL.
|
||||||
*
|
*
|
||||||
|
* inGroupBy must be true if we're deparsing a GROUP BY clause.
|
||||||
|
*
|
||||||
* The result includes all necessary quoting and schema-prefixing.
|
* The result includes all necessary quoting and schema-prefixing.
|
||||||
*/
|
*/
|
||||||
static char *
|
static char *
|
||||||
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
|
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
|
||||||
bool has_variadic, bool *use_variadic_p,
|
bool has_variadic, bool *use_variadic_p,
|
||||||
ParseExprKind special_exprkind)
|
bool inGroupBy)
|
||||||
{
|
{
|
||||||
char *result;
|
char *result;
|
||||||
HeapTuple proctup;
|
HeapTuple proctup;
|
||||||
|
@ -8924,9 +8964,9 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Due to parser hacks to avoid needing to reserve CUBE, we need to force
|
* Due to parser hacks to avoid needing to reserve CUBE, we need to force
|
||||||
* qualification in some special cases.
|
* qualification of some function names within GROUP BY.
|
||||||
*/
|
*/
|
||||||
if (special_exprkind == EXPR_KIND_GROUP_BY)
|
if (inGroupBy)
|
||||||
{
|
{
|
||||||
if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
|
if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
|
||||||
force_qualify = true;
|
force_qualify = true;
|
||||||
|
|
|
@ -171,6 +171,7 @@
|
||||||
#include "distributed/repartition_join_execution.h"
|
#include "distributed/repartition_join_execution.h"
|
||||||
#include "distributed/resource_lock.h"
|
#include "distributed/resource_lock.h"
|
||||||
#include "distributed/shared_connection_stats.h"
|
#include "distributed/shared_connection_stats.h"
|
||||||
|
#include "distributed/stats/stat_counters.h"
|
||||||
#include "distributed/subplan_execution.h"
|
#include "distributed/subplan_execution.h"
|
||||||
#include "distributed/transaction_identifier.h"
|
#include "distributed/transaction_identifier.h"
|
||||||
#include "distributed/transaction_management.h"
|
#include "distributed/transaction_management.h"
|
||||||
|
@ -401,7 +402,7 @@ typedef struct WorkerPool
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Placement executions destined for worker node, but not assigned to any
|
* Placement executions destined for worker node, but not assigned to any
|
||||||
* connection and not ready to start.
|
* connection and ready to start.
|
||||||
*/
|
*/
|
||||||
dlist_head readyTaskQueue;
|
dlist_head readyTaskQueue;
|
||||||
int readyTaskCount;
|
int readyTaskCount;
|
||||||
|
@ -492,8 +493,6 @@ typedef struct WorkerSession
|
||||||
} WorkerSession;
|
} WorkerSession;
|
||||||
|
|
||||||
|
|
||||||
struct TaskPlacementExecution;
|
|
||||||
|
|
||||||
/* GUC, determining whether Citus opens 1 connection per task */
|
/* GUC, determining whether Citus opens 1 connection per task */
|
||||||
bool ForceMaxQueryParallelization = false;
|
bool ForceMaxQueryParallelization = false;
|
||||||
int MaxAdaptiveExecutorPoolSize = 16;
|
int MaxAdaptiveExecutorPoolSize = 16;
|
||||||
|
@ -585,7 +584,7 @@ typedef enum TaskPlacementExecutionState
|
||||||
} TaskPlacementExecutionState;
|
} TaskPlacementExecutionState;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TaskPlacementExecution represents the an execution of a command
|
* TaskPlacementExecution represents the execution of a command
|
||||||
* on a shard placement.
|
* on a shard placement.
|
||||||
*/
|
*/
|
||||||
typedef struct TaskPlacementExecution
|
typedef struct TaskPlacementExecution
|
||||||
|
@ -692,7 +691,7 @@ static bool SendNextQuery(TaskPlacementExecution *placementExecution,
|
||||||
WorkerSession *session);
|
WorkerSession *session);
|
||||||
static void ConnectionStateMachine(WorkerSession *session);
|
static void ConnectionStateMachine(WorkerSession *session);
|
||||||
static bool HasUnfinishedTaskForSession(WorkerSession *session);
|
static bool HasUnfinishedTaskForSession(WorkerSession *session);
|
||||||
static void HandleMultiConnectionSuccess(WorkerSession *session);
|
static void HandleMultiConnectionSuccess(WorkerSession *session, bool newConnection);
|
||||||
static bool HasAnyConnectionFailure(WorkerPool *workerPool);
|
static bool HasAnyConnectionFailure(WorkerPool *workerPool);
|
||||||
static void Activate2PCIfModifyingTransactionExpandsToNewNode(WorkerSession *session);
|
static void Activate2PCIfModifyingTransactionExpandsToNewNode(WorkerSession *session);
|
||||||
static bool TransactionModifiedDistributedTable(DistributedExecution *execution);
|
static bool TransactionModifiedDistributedTable(DistributedExecution *execution);
|
||||||
|
@ -727,6 +726,11 @@ static uint64 MicrosecondsBetweenTimestamps(instr_time startTime, instr_time end
|
||||||
static int WorkerPoolCompare(const void *lhsKey, const void *rhsKey);
|
static int WorkerPoolCompare(const void *lhsKey, const void *rhsKey);
|
||||||
static void SetAttributeInputMetadata(DistributedExecution *execution,
|
static void SetAttributeInputMetadata(DistributedExecution *execution,
|
||||||
ShardCommandExecution *shardCommandExecution);
|
ShardCommandExecution *shardCommandExecution);
|
||||||
|
static ExecutionParams * CreateDefaultExecutionParams(RowModifyLevel modLevel,
|
||||||
|
List *taskList,
|
||||||
|
TupleDestination *tupleDest,
|
||||||
|
bool expectResults,
|
||||||
|
ParamListInfo paramListInfo);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1013,11 +1017,11 @@ ExecuteTaskListOutsideTransaction(RowModifyLevel modLevel, List *taskList,
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ExecuteTaskListIntoTupleDestWithParam is a proxy to ExecuteTaskListExtended() which uses
|
* CreateDefaultExecutionParams returns execution params based on given (possibly null)
|
||||||
* bind params from executor state, and with defaults for some of the arguments.
|
* bind params (presumably from executor state) with defaults for some of the arguments.
|
||||||
*/
|
*/
|
||||||
uint64
|
static ExecutionParams *
|
||||||
ExecuteTaskListIntoTupleDestWithParam(RowModifyLevel modLevel, List *taskList,
|
CreateDefaultExecutionParams(RowModifyLevel modLevel, List *taskList,
|
||||||
TupleDestination *tupleDest,
|
TupleDestination *tupleDest,
|
||||||
bool expectResults,
|
bool expectResults,
|
||||||
ParamListInfo paramListInfo)
|
ParamListInfo paramListInfo)
|
||||||
|
@ -1034,6 +1038,24 @@ ExecuteTaskListIntoTupleDestWithParam(RowModifyLevel modLevel, List *taskList,
|
||||||
executionParams->tupleDestination = tupleDest;
|
executionParams->tupleDestination = tupleDest;
|
||||||
executionParams->paramListInfo = paramListInfo;
|
executionParams->paramListInfo = paramListInfo;
|
||||||
|
|
||||||
|
return executionParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ExecuteTaskListIntoTupleDestWithParam is a proxy to ExecuteTaskListExtended() which uses
|
||||||
|
* bind params from executor state, and with defaults for some of the arguments.
|
||||||
|
*/
|
||||||
|
uint64
|
||||||
|
ExecuteTaskListIntoTupleDestWithParam(RowModifyLevel modLevel, List *taskList,
|
||||||
|
TupleDestination *tupleDest,
|
||||||
|
bool expectResults,
|
||||||
|
ParamListInfo paramListInfo)
|
||||||
|
{
|
||||||
|
ExecutionParams *executionParams = CreateDefaultExecutionParams(modLevel, taskList,
|
||||||
|
tupleDest,
|
||||||
|
expectResults,
|
||||||
|
paramListInfo);
|
||||||
return ExecuteTaskListExtended(executionParams);
|
return ExecuteTaskListExtended(executionParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1047,17 +1069,11 @@ ExecuteTaskListIntoTupleDest(RowModifyLevel modLevel, List *taskList,
|
||||||
TupleDestination *tupleDest,
|
TupleDestination *tupleDest,
|
||||||
bool expectResults)
|
bool expectResults)
|
||||||
{
|
{
|
||||||
int targetPoolSize = MaxAdaptiveExecutorPoolSize;
|
ParamListInfo paramListInfo = NULL;
|
||||||
bool localExecutionSupported = true;
|
ExecutionParams *executionParams = CreateDefaultExecutionParams(modLevel, taskList,
|
||||||
ExecutionParams *executionParams = CreateBasicExecutionParams(
|
tupleDest,
|
||||||
modLevel, taskList, targetPoolSize, localExecutionSupported
|
expectResults,
|
||||||
);
|
paramListInfo);
|
||||||
|
|
||||||
executionParams->xactProperties = DecideTransactionPropertiesForTaskList(
|
|
||||||
modLevel, taskList, false);
|
|
||||||
executionParams->expectResults = expectResults;
|
|
||||||
executionParams->tupleDestination = tupleDest;
|
|
||||||
|
|
||||||
return ExecuteTaskListExtended(executionParams);
|
return ExecuteTaskListExtended(executionParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1899,7 +1915,7 @@ RunDistributedExecution(DistributedExecution *execution)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Iterate until all the tasks are finished. Once all the tasks
|
* Iterate until all the tasks are finished. Once all the tasks
|
||||||
* are finished, ensure that that all the connection initializations
|
* are finished, ensure that all the connection initializations
|
||||||
* are also finished. Otherwise, those connections are terminated
|
* are also finished. Otherwise, those connections are terminated
|
||||||
* abruptly before they are established (or failed). Instead, we let
|
* abruptly before they are established (or failed). Instead, we let
|
||||||
* the ConnectionStateMachine() to properly handle them.
|
* the ConnectionStateMachine() to properly handle them.
|
||||||
|
@ -2020,6 +2036,7 @@ ProcessSessionsWithFailedWaitEventSetOperations(DistributedExecution *execution)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
connection->connectionState = MULTI_CONNECTION_FAILED;
|
connection->connectionState = MULTI_CONNECTION_FAILED;
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2795,21 +2812,21 @@ CheckConnectionTimeout(WorkerPool *workerPool)
|
||||||
logLevel = ERROR;
|
logLevel = ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
ereport(logLevel, (errcode(ERRCODE_CONNECTION_FAILURE),
|
|
||||||
errmsg("could not establish any connections to the node "
|
|
||||||
"%s:%d after %u ms", workerPool->nodeName,
|
|
||||||
workerPool->nodePort,
|
|
||||||
NodeConnectionTimeout)));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We hit the connection timeout. In that case, we should not let the
|
* We hit the connection timeout. In that case, we should not let the
|
||||||
* connection establishment to continue because the execution logic
|
* connection establishment to continue because the execution logic
|
||||||
* pretends that failed sessions are not going to be used anymore.
|
* pretends that failed sessions are not going to be used anymore.
|
||||||
*
|
*
|
||||||
* That's why we mark the connection as timed out to trigger the state
|
* That's why we mark the connection as timed out to trigger the state
|
||||||
* changes in the executor.
|
* changes in the executor, if we don't throw an error below.
|
||||||
*/
|
*/
|
||||||
MarkEstablishingSessionsTimedOut(workerPool);
|
MarkEstablishingSessionsTimedOut(workerPool);
|
||||||
|
|
||||||
|
ereport(logLevel, (errcode(ERRCODE_CONNECTION_FAILURE),
|
||||||
|
errmsg("could not establish any connections to the node "
|
||||||
|
"%s:%d after %u ms", workerPool->nodeName,
|
||||||
|
workerPool->nodePort,
|
||||||
|
NodeConnectionTimeout)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2837,6 +2854,7 @@ MarkEstablishingSessionsTimedOut(WorkerPool *workerPool)
|
||||||
connection->connectionState == MULTI_CONNECTION_INITIAL)
|
connection->connectionState == MULTI_CONNECTION_INITIAL)
|
||||||
{
|
{
|
||||||
connection->connectionState = MULTI_CONNECTION_TIMED_OUT;
|
connection->connectionState = MULTI_CONNECTION_TIMED_OUT;
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2994,6 +3012,10 @@ ConnectionStateMachine(WorkerSession *session)
|
||||||
* the state machines might have already progressed and used
|
* the state machines might have already progressed and used
|
||||||
* new pools/sessions instead. That's why we terminate the
|
* new pools/sessions instead. That's why we terminate the
|
||||||
* connection, clear any state associated with it.
|
* connection, clear any state associated with it.
|
||||||
|
*
|
||||||
|
* Note that here we don't increment the failed connection
|
||||||
|
* stat counter because MarkEstablishingSessionsTimedOut()
|
||||||
|
* already did that.
|
||||||
*/
|
*/
|
||||||
connection->connectionState = MULTI_CONNECTION_FAILED;
|
connection->connectionState = MULTI_CONNECTION_FAILED;
|
||||||
break;
|
break;
|
||||||
|
@ -3004,7 +3026,12 @@ ConnectionStateMachine(WorkerSession *session)
|
||||||
ConnStatusType status = PQstatus(connection->pgConn);
|
ConnStatusType status = PQstatus(connection->pgConn);
|
||||||
if (status == CONNECTION_OK)
|
if (status == CONNECTION_OK)
|
||||||
{
|
{
|
||||||
HandleMultiConnectionSuccess(session);
|
/*
|
||||||
|
* Connection was already established, possibly a cached
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
bool newConnection = false;
|
||||||
|
HandleMultiConnectionSuccess(session, newConnection);
|
||||||
UpdateConnectionWaitFlags(session,
|
UpdateConnectionWaitFlags(session,
|
||||||
WL_SOCKET_READABLE | WL_SOCKET_WRITEABLE);
|
WL_SOCKET_READABLE | WL_SOCKET_WRITEABLE);
|
||||||
break;
|
break;
|
||||||
|
@ -3012,6 +3039,7 @@ ConnectionStateMachine(WorkerSession *session)
|
||||||
else if (status == CONNECTION_BAD)
|
else if (status == CONNECTION_BAD)
|
||||||
{
|
{
|
||||||
connection->connectionState = MULTI_CONNECTION_FAILED;
|
connection->connectionState = MULTI_CONNECTION_FAILED;
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_FAILED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3027,6 +3055,7 @@ ConnectionStateMachine(WorkerSession *session)
|
||||||
if (pollMode == PGRES_POLLING_FAILED)
|
if (pollMode == PGRES_POLLING_FAILED)
|
||||||
{
|
{
|
||||||
connection->connectionState = MULTI_CONNECTION_FAILED;
|
connection->connectionState = MULTI_CONNECTION_FAILED;
|
||||||
|
IncrementStatCounterForMyDb(STAT_CONNECTION_ESTABLISHMENT_FAILED);
|
||||||
}
|
}
|
||||||
else if (pollMode == PGRES_POLLING_READING)
|
else if (pollMode == PGRES_POLLING_READING)
|
||||||
{
|
{
|
||||||
|
@ -3044,7 +3073,12 @@ ConnectionStateMachine(WorkerSession *session)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleMultiConnectionSuccess(session);
|
/*
|
||||||
|
* Connection was not established befoore (!= CONNECTION_OK)
|
||||||
|
* but PQconnectPoll() did so now.
|
||||||
|
*/
|
||||||
|
bool newConnection = true;
|
||||||
|
HandleMultiConnectionSuccess(session, newConnection);
|
||||||
UpdateConnectionWaitFlags(session,
|
UpdateConnectionWaitFlags(session,
|
||||||
WL_SOCKET_READABLE | WL_SOCKET_WRITEABLE);
|
WL_SOCKET_READABLE | WL_SOCKET_WRITEABLE);
|
||||||
|
|
||||||
|
@ -3100,7 +3134,7 @@ ConnectionStateMachine(WorkerSession *session)
|
||||||
*
|
*
|
||||||
* We can only retry connection when the remote transaction has
|
* We can only retry connection when the remote transaction has
|
||||||
* not started over the connection. Otherwise, we'd have to deal
|
* not started over the connection. Otherwise, we'd have to deal
|
||||||
* with restoring the transaction state, which iis beyond our
|
* with restoring the transaction state, which is beyond our
|
||||||
* purpose at this time.
|
* purpose at this time.
|
||||||
*/
|
*/
|
||||||
RemoteTransaction *transaction = &connection->remoteTransaction;
|
RemoteTransaction *transaction = &connection->remoteTransaction;
|
||||||
|
@ -3122,6 +3156,11 @@ ConnectionStateMachine(WorkerSession *session)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here we don't increment the connection stat counter for failed
|
||||||
|
* connections because we don't track the connections that we could
|
||||||
|
* establish but lost later.
|
||||||
|
*/
|
||||||
connection->connectionState = MULTI_CONNECTION_FAILED;
|
connection->connectionState = MULTI_CONNECTION_FAILED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3284,12 +3323,12 @@ HasUnfinishedTaskForSession(WorkerSession *session)
|
||||||
* connection's state.
|
* connection's state.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
HandleMultiConnectionSuccess(WorkerSession *session)
|
HandleMultiConnectionSuccess(WorkerSession *session, bool newConnection)
|
||||||
{
|
{
|
||||||
MultiConnection *connection = session->connection;
|
MultiConnection *connection = session->connection;
|
||||||
WorkerPool *workerPool = session->workerPool;
|
WorkerPool *workerPool = session->workerPool;
|
||||||
|
|
||||||
MarkConnectionConnected(connection);
|
MarkConnectionConnected(connection, newConnection);
|
||||||
|
|
||||||
ereport(DEBUG4, (errmsg("established connection to %s:%d for "
|
ereport(DEBUG4, (errmsg("established connection to %s:%d for "
|
||||||
"session %ld in %ld microseconds",
|
"session %ld in %ld microseconds",
|
||||||
|
|
|
@ -43,8 +43,9 @@
|
||||||
#include "distributed/multi_executor.h"
|
#include "distributed/multi_executor.h"
|
||||||
#include "distributed/multi_router_planner.h"
|
#include "distributed/multi_router_planner.h"
|
||||||
#include "distributed/multi_server_executor.h"
|
#include "distributed/multi_server_executor.h"
|
||||||
#include "distributed/query_stats.h"
|
|
||||||
#include "distributed/shard_utils.h"
|
#include "distributed/shard_utils.h"
|
||||||
|
#include "distributed/stats/query_stats.h"
|
||||||
|
#include "distributed/stats/stat_counters.h"
|
||||||
#include "distributed/subplan_execution.h"
|
#include "distributed/subplan_execution.h"
|
||||||
#include "distributed/worker_log_messages.h"
|
#include "distributed/worker_log_messages.h"
|
||||||
#include "distributed/worker_protocol.h"
|
#include "distributed/worker_protocol.h"
|
||||||
|
@ -206,7 +207,7 @@ CitusBeginScan(CustomScanState *node, EState *estate, int eflags)
|
||||||
if (distributedPlan->modifyQueryViaCoordinatorOrRepartition != NULL)
|
if (distributedPlan->modifyQueryViaCoordinatorOrRepartition != NULL)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* INSERT..SELECT via coordinator or re-partitioning are special because
|
* INSERT..SELECT / MERGE via coordinator or re-partitioning are special because
|
||||||
* the SELECT part is planned separately.
|
* the SELECT part is planned separately.
|
||||||
*/
|
*/
|
||||||
return;
|
return;
|
||||||
|
@ -262,8 +263,19 @@ CitusExecScan(CustomScanState *node)
|
||||||
|
|
||||||
if (!scanState->finishedRemoteScan)
|
if (!scanState->finishedRemoteScan)
|
||||||
{
|
{
|
||||||
|
bool isMultiTaskPlan = IsMultiTaskPlan(scanState->distributedPlan);
|
||||||
|
|
||||||
AdaptiveExecutor(scanState);
|
AdaptiveExecutor(scanState);
|
||||||
|
|
||||||
|
if (isMultiTaskPlan)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
|
||||||
scanState->finishedRemoteScan = true;
|
scanState->finishedRemoteScan = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ TaskListRequiresRollback(List *taskList)
|
||||||
}
|
}
|
||||||
|
|
||||||
Task *task = (Task *) linitial(taskList);
|
Task *task = (Task *) linitial(taskList);
|
||||||
if (task->cannotBeExecutedInTransction)
|
if (task->cannotBeExecutedInTransaction)
|
||||||
{
|
{
|
||||||
/* vacuum, create index concurrently etc. */
|
/* vacuum, create index concurrently etc. */
|
||||||
return false;
|
return false;
|
||||||
|
@ -165,7 +165,7 @@ TaskListCannotBeExecutedInTransaction(List *taskList)
|
||||||
Task *task = NULL;
|
Task *task = NULL;
|
||||||
foreach_declared_ptr(task, taskList)
|
foreach_declared_ptr(task, taskList)
|
||||||
{
|
{
|
||||||
if (task->cannotBeExecutedInTransction)
|
if (task->cannotBeExecutedInTransaction)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
#include "distributed/repartition_executor.h"
|
#include "distributed/repartition_executor.h"
|
||||||
#include "distributed/resource_lock.h"
|
#include "distributed/resource_lock.h"
|
||||||
#include "distributed/shardinterval_utils.h"
|
#include "distributed/shardinterval_utils.h"
|
||||||
|
#include "distributed/stats/stat_counters.h"
|
||||||
#include "distributed/subplan_execution.h"
|
#include "distributed/subplan_execution.h"
|
||||||
#include "distributed/transaction_management.h"
|
#include "distributed/transaction_management.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
|
@ -178,6 +179,22 @@ NonPushableInsertSelectExecScan(CustomScanState *node)
|
||||||
targetRelation,
|
targetRelation,
|
||||||
binaryFormat);
|
binaryFormat);
|
||||||
|
|
||||||
|
if (list_length(distSelectTaskList) <= 1)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Probably we will never get here for a repartitioned
|
||||||
|
* INSERT..SELECT because when the source is a single shard
|
||||||
|
* table, we should most probably choose to use
|
||||||
|
* MODIFY_WITH_SELECT_VIA_COORDINATOR, but we still keep this
|
||||||
|
* here.
|
||||||
|
*/
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* At this point select query has been executed on workers and results
|
* At this point select query has been executed on workers and results
|
||||||
* have been fetched in such a way that they are colocated with corresponding
|
* have been fetched in such a way that they are colocated with corresponding
|
||||||
|
@ -198,6 +215,15 @@ NonPushableInsertSelectExecScan(CustomScanState *node)
|
||||||
taskList, tupleDest,
|
taskList, tupleDest,
|
||||||
hasReturning);
|
hasReturning);
|
||||||
|
|
||||||
|
if (list_length(taskList) <= 1)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
|
||||||
executorState->es_processed = rowsInserted;
|
executorState->es_processed = rowsInserted;
|
||||||
|
|
||||||
if (SortReturning && hasReturning)
|
if (SortReturning && hasReturning)
|
||||||
|
@ -272,6 +298,15 @@ NonPushableInsertSelectExecScan(CustomScanState *node)
|
||||||
SortTupleStore(scanState);
|
SortTupleStore(scanState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (list_length(prunedTaskList) <= 1)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -313,6 +348,12 @@ ExecutePlanIntoColocatedIntermediateResults(Oid targetRelationId,
|
||||||
int partitionColumnIndex = PartitionColumnIndexFromColumnList(targetRelationId,
|
int partitionColumnIndex = PartitionColumnIndexFromColumnList(targetRelationId,
|
||||||
columnNameList);
|
columnNameList);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't track query counters for the COPY commands that are executed to
|
||||||
|
* prepare intermediate results.
|
||||||
|
*/
|
||||||
|
const bool trackQueryCounters = false;
|
||||||
|
|
||||||
/* set up a DestReceiver that copies into the intermediate table */
|
/* set up a DestReceiver that copies into the intermediate table */
|
||||||
const bool publishableData = true;
|
const bool publishableData = true;
|
||||||
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(targetRelationId,
|
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(targetRelationId,
|
||||||
|
@ -320,7 +361,8 @@ ExecutePlanIntoColocatedIntermediateResults(Oid targetRelationId,
|
||||||
partitionColumnIndex,
|
partitionColumnIndex,
|
||||||
executorState,
|
executorState,
|
||||||
intermediateResultIdPrefix,
|
intermediateResultIdPrefix,
|
||||||
publishableData);
|
publishableData,
|
||||||
|
trackQueryCounters);
|
||||||
|
|
||||||
ExecutePlanIntoDestReceiver(selectPlan, paramListInfo, (DestReceiver *) copyDest);
|
ExecutePlanIntoDestReceiver(selectPlan, paramListInfo, (DestReceiver *) copyDest);
|
||||||
|
|
||||||
|
@ -349,13 +391,20 @@ ExecutePlanIntoRelation(Oid targetRelationId, List *insertTargetList,
|
||||||
int partitionColumnIndex = PartitionColumnIndexFromColumnList(targetRelationId,
|
int partitionColumnIndex = PartitionColumnIndexFromColumnList(targetRelationId,
|
||||||
columnNameList);
|
columnNameList);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We want to track query counters for the COPY commands that are executed to
|
||||||
|
* perform the final INSERT for such INSERT..SELECT queries.
|
||||||
|
*/
|
||||||
|
const bool trackQueryCounters = true;
|
||||||
|
|
||||||
/* set up a DestReceiver that copies into the distributed table */
|
/* set up a DestReceiver that copies into the distributed table */
|
||||||
const bool publishableData = true;
|
const bool publishableData = true;
|
||||||
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(targetRelationId,
|
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(targetRelationId,
|
||||||
columnNameList,
|
columnNameList,
|
||||||
partitionColumnIndex,
|
partitionColumnIndex,
|
||||||
executorState, NULL,
|
executorState, NULL,
|
||||||
publishableData);
|
publishableData,
|
||||||
|
trackQueryCounters);
|
||||||
|
|
||||||
ExecutePlanIntoDestReceiver(selectPlan, paramListInfo, (DestReceiver *) copyDest);
|
ExecutePlanIntoDestReceiver(selectPlan, paramListInfo, (DestReceiver *) copyDest);
|
||||||
|
|
||||||
|
|
|
@ -295,7 +295,6 @@ PrepareIntermediateResultBroadcast(RemoteFileDestReceiver *resultDest)
|
||||||
if (resultDest->writeLocalFile)
|
if (resultDest->writeLocalFile)
|
||||||
{
|
{
|
||||||
const int fileFlags = (O_APPEND | O_CREAT | O_RDWR | O_TRUNC | PG_BINARY);
|
const int fileFlags = (O_APPEND | O_CREAT | O_RDWR | O_TRUNC | PG_BINARY);
|
||||||
const int fileMode = (S_IRUSR | S_IWUSR);
|
|
||||||
|
|
||||||
/* make sure the directory exists */
|
/* make sure the directory exists */
|
||||||
CreateIntermediateResultsDirectory();
|
CreateIntermediateResultsDirectory();
|
||||||
|
@ -303,8 +302,7 @@ PrepareIntermediateResultBroadcast(RemoteFileDestReceiver *resultDest)
|
||||||
const char *fileName = QueryResultFileName(resultId);
|
const char *fileName = QueryResultFileName(resultId);
|
||||||
|
|
||||||
resultDest->fileCompat = FileCompatFromFileStart(FileOpenForTransmit(fileName,
|
resultDest->fileCompat = FileCompatFromFileStart(FileOpenForTransmit(fileName,
|
||||||
fileFlags,
|
fileFlags));
|
||||||
fileMode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkerNode *workerNode = NULL;
|
WorkerNode *workerNode = NULL;
|
||||||
|
@ -606,7 +604,7 @@ CreateIntermediateResultsDirectory(void)
|
||||||
{
|
{
|
||||||
char *resultDirectory = IntermediateResultsDirectory();
|
char *resultDirectory = IntermediateResultsDirectory();
|
||||||
|
|
||||||
int makeOK = mkdir(resultDirectory, S_IRWXU);
|
int makeOK = MakePGDirectory(resultDirectory);
|
||||||
if (makeOK != 0)
|
if (makeOK != 0)
|
||||||
{
|
{
|
||||||
if (errno == EEXIST)
|
if (errno == EEXIST)
|
||||||
|
@ -976,7 +974,6 @@ FetchRemoteIntermediateResult(MultiConnection *connection, char *resultId)
|
||||||
|
|
||||||
StringInfo copyCommand = makeStringInfo();
|
StringInfo copyCommand = makeStringInfo();
|
||||||
const int fileFlags = (O_APPEND | O_CREAT | O_RDWR | O_TRUNC | PG_BINARY);
|
const int fileFlags = (O_APPEND | O_CREAT | O_RDWR | O_TRUNC | PG_BINARY);
|
||||||
const int fileMode = (S_IRUSR | S_IWUSR);
|
|
||||||
|
|
||||||
PGconn *pgConn = connection->pgConn;
|
PGconn *pgConn = connection->pgConn;
|
||||||
int socket = PQsocket(pgConn);
|
int socket = PQsocket(pgConn);
|
||||||
|
@ -998,7 +995,7 @@ FetchRemoteIntermediateResult(MultiConnection *connection, char *resultId)
|
||||||
|
|
||||||
PQclear(result);
|
PQclear(result);
|
||||||
|
|
||||||
File fileDesc = FileOpenForTransmit(localPath, fileFlags, fileMode);
|
File fileDesc = FileOpenForTransmit(localPath, fileFlags);
|
||||||
FileCompat fileCompat = FileCompatFromFileStart(fileDesc);
|
FileCompat fileCompat = FileCompatFromFileStart(fileDesc);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
|
|
|
@ -104,8 +104,8 @@
|
||||||
#include "distributed/query_utils.h"
|
#include "distributed/query_utils.h"
|
||||||
#include "distributed/relation_access_tracking.h"
|
#include "distributed/relation_access_tracking.h"
|
||||||
#include "distributed/remote_commands.h" /* to access LogRemoteCommands */
|
#include "distributed/remote_commands.h" /* to access LogRemoteCommands */
|
||||||
|
#include "distributed/stats/stat_tenants.h"
|
||||||
#include "distributed/transaction_management.h"
|
#include "distributed/transaction_management.h"
|
||||||
#include "distributed/utils/citus_stat_tenants.h"
|
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_protocol.h"
|
#include "distributed/worker_protocol.h"
|
||||||
|
|
||||||
|
@ -569,7 +569,7 @@ LogLocalCommand(Task *task)
|
||||||
*
|
*
|
||||||
* One slightly different case is modifications to replicated tables
|
* One slightly different case is modifications to replicated tables
|
||||||
* (e.g., reference tables) where a single task ends in two separate tasks
|
* (e.g., reference tables) where a single task ends in two separate tasks
|
||||||
* and the local task is added to localTaskList and the remaning ones to
|
* and the local task is added to localTaskList and the remaining ones to
|
||||||
* the remoteTaskList.
|
* the remoteTaskList.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "distributed/multi_partitioning_utils.h"
|
#include "distributed/multi_partitioning_utils.h"
|
||||||
#include "distributed/multi_router_planner.h"
|
#include "distributed/multi_router_planner.h"
|
||||||
#include "distributed/repartition_executor.h"
|
#include "distributed/repartition_executor.h"
|
||||||
|
#include "distributed/stats/stat_counters.h"
|
||||||
#include "distributed/subplan_execution.h"
|
#include "distributed/subplan_execution.h"
|
||||||
|
|
||||||
static void ExecuteSourceAtWorkerAndRepartition(CitusScanState *scanState);
|
static void ExecuteSourceAtWorkerAndRepartition(CitusScanState *scanState);
|
||||||
|
@ -166,6 +167,21 @@ ExecuteSourceAtWorkerAndRepartition(CitusScanState *scanState)
|
||||||
distSourceTaskList, partitionColumnIndex,
|
distSourceTaskList, partitionColumnIndex,
|
||||||
targetRelation, binaryFormat);
|
targetRelation, binaryFormat);
|
||||||
|
|
||||||
|
if (list_length(distSourceTaskList) <= 1)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Probably we will never get here for a repartitioned MERGE
|
||||||
|
* because when the source is a single shard table, we should
|
||||||
|
* most probably choose to use ExecuteSourceAtCoordAndRedistribution(),
|
||||||
|
* but we still keep this here.
|
||||||
|
*/
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
|
||||||
ereport(DEBUG1, (errmsg("Executing final MERGE on workers using "
|
ereport(DEBUG1, (errmsg("Executing final MERGE on workers using "
|
||||||
"intermediate results")));
|
"intermediate results")));
|
||||||
|
|
||||||
|
@ -193,6 +209,16 @@ ExecuteSourceAtWorkerAndRepartition(CitusScanState *scanState)
|
||||||
tupleDest,
|
tupleDest,
|
||||||
hasReturning,
|
hasReturning,
|
||||||
paramListInfo);
|
paramListInfo);
|
||||||
|
|
||||||
|
if (list_length(taskList) <= 1)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
|
||||||
executorState->es_processed = rowsMerged;
|
executorState->es_processed = rowsMerged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +313,11 @@ ExecuteSourceAtCoordAndRedistribution(CitusScanState *scanState)
|
||||||
|
|
||||||
if (prunedTaskList == NIL)
|
if (prunedTaskList == NIL)
|
||||||
{
|
{
|
||||||
/* No task to execute */
|
/*
|
||||||
|
* No task to execute, but we still increment STAT_QUERY_EXECUTION_SINGLE_SHARD
|
||||||
|
* as per our convention.
|
||||||
|
*/
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +337,16 @@ ExecuteSourceAtCoordAndRedistribution(CitusScanState *scanState)
|
||||||
tupleDest,
|
tupleDest,
|
||||||
hasReturning,
|
hasReturning,
|
||||||
paramListInfo);
|
paramListInfo);
|
||||||
|
|
||||||
|
if (list_length(prunedTaskList) == 1)
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_SINGLE_SHARD);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncrementStatCounterForMyDb(STAT_QUERY_EXECUTION_MULTI_SHARD);
|
||||||
|
}
|
||||||
|
|
||||||
executorState->es_processed = rowsMerged;
|
executorState->es_processed = rowsMerged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,6 +372,12 @@ ExecuteMergeSourcePlanIntoColocatedIntermediateResults(Oid targetRelationId,
|
||||||
List *columnNameList =
|
List *columnNameList =
|
||||||
BuildColumnNameListFromTargetList(targetRelationId, sourceTargetList);
|
BuildColumnNameListFromTargetList(targetRelationId, sourceTargetList);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't track query counters for the COPY commands that are executed to
|
||||||
|
* prepare intermediate results.
|
||||||
|
*/
|
||||||
|
const bool trackQueryCounters = false;
|
||||||
|
|
||||||
/* set up a DestReceiver that copies into the intermediate file */
|
/* set up a DestReceiver that copies into the intermediate file */
|
||||||
const bool publishableData = false;
|
const bool publishableData = false;
|
||||||
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(targetRelationId,
|
CitusCopyDestReceiver *copyDest = CreateCitusCopyDestReceiver(targetRelationId,
|
||||||
|
@ -339,7 +385,8 @@ ExecuteMergeSourcePlanIntoColocatedIntermediateResults(Oid targetRelationId,
|
||||||
partitionColumnIndex,
|
partitionColumnIndex,
|
||||||
executorState,
|
executorState,
|
||||||
intermediateResultIdPrefix,
|
intermediateResultIdPrefix,
|
||||||
publishableData);
|
publishableData,
|
||||||
|
trackQueryCounters);
|
||||||
|
|
||||||
/* We can skip when writing to intermediate files */
|
/* We can skip when writing to intermediate files */
|
||||||
copyDest->skipCoercions = true;
|
copyDest->skipCoercions = true;
|
||||||
|
|
|
@ -168,7 +168,7 @@ CitusExecutorRun(QueryDesc *queryDesc,
|
||||||
executorBoundParams = queryDesc->params;
|
executorBoundParams = queryDesc->params;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We do some potentially time consuming operations our self now before we hand of
|
* We do some potentially time consuming operations ourself now before we hand off
|
||||||
* control to postgres' executor. To make sure that time spent is accurately measured
|
* control to postgres' executor. To make sure that time spent is accurately measured
|
||||||
* we remove the totaltime instrumentation from the queryDesc. Instead we will start
|
* we remove the totaltime instrumentation from the queryDesc. Instead we will start
|
||||||
* and stop the instrumentation of the total time and put it back on the queryDesc
|
* and stop the instrumentation of the total time and put it back on the queryDesc
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
|
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
|
#include "common/file_perm.h"
|
||||||
#include "libpq/libpq.h"
|
#include "libpq/libpq.h"
|
||||||
#include "libpq/pqformat.h"
|
#include "libpq/pqformat.h"
|
||||||
#include "storage/fd.h"
|
#include "storage/fd.h"
|
||||||
|
@ -48,8 +49,7 @@ RedirectCopyDataToRegularFile(const char *filename)
|
||||||
{
|
{
|
||||||
StringInfo copyData = makeStringInfo();
|
StringInfo copyData = makeStringInfo();
|
||||||
const int fileFlags = (O_APPEND | O_CREAT | O_RDWR | O_TRUNC | PG_BINARY);
|
const int fileFlags = (O_APPEND | O_CREAT | O_RDWR | O_TRUNC | PG_BINARY);
|
||||||
const int fileMode = (S_IRUSR | S_IWUSR);
|
File fileDesc = FileOpenForTransmit(filename, fileFlags);
|
||||||
File fileDesc = FileOpenForTransmit(filename, fileFlags, fileMode);
|
|
||||||
FileCompat fileCompat = FileCompatFromFileStart(fileDesc);
|
FileCompat fileCompat = FileCompatFromFileStart(fileDesc);
|
||||||
|
|
||||||
SendCopyInStart();
|
SendCopyInStart();
|
||||||
|
@ -92,7 +92,7 @@ SendRegularFile(const char *filename)
|
||||||
const int fileMode = 0;
|
const int fileMode = 0;
|
||||||
|
|
||||||
/* we currently do not check if the caller has permissions for this file */
|
/* we currently do not check if the caller has permissions for this file */
|
||||||
File fileDesc = FileOpenForTransmit(filename, fileFlags, fileMode);
|
File fileDesc = FileOpenForTransmitPerm(filename, fileFlags, fileMode);
|
||||||
FileCompat fileCompat = FileCompatFromFileStart(fileDesc);
|
FileCompat fileCompat = FileCompatFromFileStart(fileDesc);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -136,12 +136,23 @@ FreeStringInfo(StringInfo stringInfo)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FileOpenForTransmit opens file with the given filename and flags. On success,
|
* Open a file with FileOpenForTransmitPerm() and pass default file mode for
|
||||||
* the function returns the internal file handle for the opened file. On failure
|
* the fileMode parameter.
|
||||||
* the function errors out.
|
|
||||||
*/
|
*/
|
||||||
File
|
File
|
||||||
FileOpenForTransmit(const char *filename, int fileFlags, int fileMode)
|
FileOpenForTransmit(const char *filename, int fileFlags)
|
||||||
|
{
|
||||||
|
return FileOpenForTransmitPerm(filename, fileFlags, pg_file_create_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FileOpenForTransmitPerm opens file with the given filename and flags. On
|
||||||
|
* success, the function returns the internal file handle for the opened file.
|
||||||
|
* On failure the function errors out.
|
||||||
|
*/
|
||||||
|
File
|
||||||
|
FileOpenForTransmitPerm(const char *filename, int fileFlags, int fileMode)
|
||||||
{
|
{
|
||||||
struct stat fileStat;
|
struct stat fileStat;
|
||||||
|
|
||||||
|
|
|
@ -699,7 +699,6 @@ SupportedDependencyByCitus(const ObjectAddress *address)
|
||||||
|
|
||||||
case OCLASS_DATABASE:
|
case OCLASS_DATABASE:
|
||||||
{
|
{
|
||||||
/* only to propagate its owner */
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,15 +20,18 @@
|
||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/objectaddress.h"
|
#include "catalog/objectaddress.h"
|
||||||
|
#include "catalog/pg_database.h"
|
||||||
#include "catalog/pg_extension_d.h"
|
#include "catalog/pg_extension_d.h"
|
||||||
#include "catalog/pg_namespace.h"
|
#include "catalog/pg_namespace.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
|
#include "commands/dbcommands.h"
|
||||||
#include "commands/extension.h"
|
#include "commands/extension.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "nodes/pg_list.h"
|
#include "nodes/pg_list.h"
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
|
#include "postmaster/postmaster.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/fmgroids.h"
|
#include "utils/fmgroids.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
|
@ -47,24 +50,58 @@
|
||||||
#include "distributed/metadata/pg_dist_object.h"
|
#include "distributed/metadata/pg_dist_object.h"
|
||||||
#include "distributed/metadata_cache.h"
|
#include "distributed/metadata_cache.h"
|
||||||
#include "distributed/metadata_sync.h"
|
#include "distributed/metadata_sync.h"
|
||||||
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_transaction.h"
|
#include "distributed/worker_transaction.h"
|
||||||
|
|
||||||
|
static char * CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress,
|
||||||
static char * CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress);
|
char *objectName);
|
||||||
static int ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
|
static int ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
|
||||||
Datum *paramValues);
|
Datum *paramValues);
|
||||||
static bool IsObjectDistributed(const ObjectAddress *address);
|
static bool IsObjectDistributed(const ObjectAddress *address);
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(mark_object_distributed);
|
||||||
PG_FUNCTION_INFO_V1(citus_unmark_object_distributed);
|
PG_FUNCTION_INFO_V1(citus_unmark_object_distributed);
|
||||||
PG_FUNCTION_INFO_V1(master_unmark_object_distributed);
|
PG_FUNCTION_INFO_V1(master_unmark_object_distributed);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* citus_unmark_object_distributed(classid oid, objid oid, objsubid int)
|
* mark_object_distributed adds an object to pg_dist_object
|
||||||
|
* in all of the nodes, for the connections to the other nodes this function
|
||||||
|
* uses the user passed.
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
mark_object_distributed(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
CheckCitusVersion(ERROR);
|
||||||
|
EnsureSuperUser();
|
||||||
|
|
||||||
|
Oid classId = PG_GETARG_OID(0);
|
||||||
|
text *objectNameText = PG_GETARG_TEXT_P(1);
|
||||||
|
char *objectName = text_to_cstring(objectNameText);
|
||||||
|
Oid objectId = PG_GETARG_OID(2);
|
||||||
|
ObjectAddress *objectAddress = palloc0(sizeof(ObjectAddress));
|
||||||
|
ObjectAddressSet(*objectAddress, classId, objectId);
|
||||||
|
text *connectionUserText = PG_GETARG_TEXT_P(3);
|
||||||
|
char *connectionUser = text_to_cstring(connectionUserText);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is called when a query is run from a Citus non-main database.
|
||||||
|
* We need to insert into local pg_dist_object over a connection to make sure
|
||||||
|
* 2PC still works.
|
||||||
|
*/
|
||||||
|
bool useConnectionForLocalQuery = true;
|
||||||
|
MarkObjectDistributedWithName(objectAddress, objectName, useConnectionForLocalQuery,
|
||||||
|
connectionUser);
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* citus_unmark_object_distributed(classid oid, objid oid, objsubid int,checkobjectexistence bool)
|
||||||
*
|
*
|
||||||
* removes the entry for an object address from pg_dist_object. Only removes the entry if
|
* Removes the entry for an object address from pg_dist_object. If checkobjectexistence is true,
|
||||||
* the object does not exist anymore.
|
* throws an error if the object still exists.
|
||||||
*/
|
*/
|
||||||
Datum
|
Datum
|
||||||
citus_unmark_object_distributed(PG_FUNCTION_ARGS)
|
citus_unmark_object_distributed(PG_FUNCTION_ARGS)
|
||||||
|
@ -73,6 +110,19 @@ citus_unmark_object_distributed(PG_FUNCTION_ARGS)
|
||||||
Oid objid = PG_GETARG_OID(1);
|
Oid objid = PG_GETARG_OID(1);
|
||||||
int32 objsubid = PG_GETARG_INT32(2);
|
int32 objsubid = PG_GETARG_INT32(2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SQL function master_unmark_object_distributed doesn't expect the
|
||||||
|
* 4th argument but SQL function citus_unmark_object_distributed does
|
||||||
|
* so as checkobjectexistence argument. For this reason, we try to
|
||||||
|
* get the 4th argument only if this C function is called with 4
|
||||||
|
* arguments.
|
||||||
|
*/
|
||||||
|
bool checkObjectExistence = true;
|
||||||
|
if (PG_NARGS() == 4)
|
||||||
|
{
|
||||||
|
checkObjectExistence = PG_GETARG_BOOL(3);
|
||||||
|
}
|
||||||
|
|
||||||
ObjectAddress address = { 0 };
|
ObjectAddress address = { 0 };
|
||||||
ObjectAddressSubSet(address, classid, objid, objsubid);
|
ObjectAddressSubSet(address, classid, objid, objsubid);
|
||||||
|
|
||||||
|
@ -82,7 +132,7 @@ citus_unmark_object_distributed(PG_FUNCTION_ARGS)
|
||||||
PG_RETURN_VOID();
|
PG_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ObjectExists(&address))
|
if (checkObjectExistence && ObjectExists(&address))
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errmsg("object still exists"),
|
ereport(ERROR, (errmsg("object still exists"),
|
||||||
errdetail("the %s \"%s\" still exists",
|
errdetail("the %s \"%s\" still exists",
|
||||||
|
@ -150,7 +200,7 @@ ObjectExists(const ObjectAddress *address)
|
||||||
/*
|
/*
|
||||||
* MarkObjectDistributed marks an object as a distributed object. Marking is done
|
* MarkObjectDistributed marks an object as a distributed object. Marking is done
|
||||||
* by adding appropriate entries to citus.pg_dist_object and also marking the object
|
* by adding appropriate entries to citus.pg_dist_object and also marking the object
|
||||||
* as distributed by opening a connection using current user to all of the workers
|
* as distributed by opening a connection using current user to all remote nodes
|
||||||
* with metadata if object propagation is on.
|
* with metadata if object propagation is on.
|
||||||
*
|
*
|
||||||
* This function should be used if the user creating the given object. If you want
|
* This function should be used if the user creating the given object. If you want
|
||||||
|
@ -158,14 +208,52 @@ ObjectExists(const ObjectAddress *address)
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
MarkObjectDistributed(const ObjectAddress *distAddress)
|
MarkObjectDistributed(const ObjectAddress *distAddress)
|
||||||
|
{
|
||||||
|
bool useConnectionForLocalQuery = false;
|
||||||
|
MarkObjectDistributedWithName(distAddress, "", useConnectionForLocalQuery,
|
||||||
|
CurrentUserName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MarkObjectDistributedWithName marks an object as a distributed object.
|
||||||
|
* Same as MarkObjectDistributed but this function also allows passing an objectName
|
||||||
|
* that is used in case the object does not exists for the current transaction.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
MarkObjectDistributedWithName(const ObjectAddress *distAddress, char *objectName,
|
||||||
|
bool useConnectionForLocalQuery, char *connectionUser)
|
||||||
|
{
|
||||||
|
if (!CitusHasBeenLoaded())
|
||||||
|
{
|
||||||
|
elog(ERROR, "Cannot mark object distributed because Citus has not been loaded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When a query is run from a Citus non-main database we need to insert into pg_dist_object
|
||||||
|
* over a connection to make sure 2PC still works.
|
||||||
|
*/
|
||||||
|
if (useConnectionForLocalQuery)
|
||||||
|
{
|
||||||
|
StringInfo insertQuery = makeStringInfo();
|
||||||
|
appendStringInfo(insertQuery,
|
||||||
|
"INSERT INTO pg_catalog.pg_dist_object (classid, objid, objsubid)"
|
||||||
|
"VALUES (%d, %d, %d) ON CONFLICT DO NOTHING",
|
||||||
|
distAddress->classId, distAddress->objectId,
|
||||||
|
distAddress->objectSubId);
|
||||||
|
SendCommandToWorker(LocalHostName, PostPortNumber, insertQuery->data);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
MarkObjectDistributedLocally(distAddress);
|
MarkObjectDistributedLocally(distAddress);
|
||||||
|
}
|
||||||
|
|
||||||
if (EnableMetadataSync)
|
if (EnableMetadataSync)
|
||||||
{
|
{
|
||||||
char *workerPgDistObjectUpdateCommand =
|
char *workerPgDistObjectUpdateCommand =
|
||||||
CreatePgDistObjectEntryCommand(distAddress);
|
CreatePgDistObjectEntryCommand(distAddress, objectName);
|
||||||
SendCommandToWorkersWithMetadata(workerPgDistObjectUpdateCommand);
|
SendCommandToRemoteMetadataNodesParams(workerPgDistObjectUpdateCommand,
|
||||||
|
connectionUser, 0, NULL, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +261,7 @@ MarkObjectDistributed(const ObjectAddress *distAddress)
|
||||||
/*
|
/*
|
||||||
* MarkObjectDistributedViaSuperUser marks an object as a distributed object. Marking
|
* MarkObjectDistributedViaSuperUser marks an object as a distributed object. Marking
|
||||||
* is done by adding appropriate entries to citus.pg_dist_object and also marking the
|
* is done by adding appropriate entries to citus.pg_dist_object and also marking the
|
||||||
* object as distributed by opening a connection using super user to all of the workers
|
* object as distributed by opening a connection using super user to all remote nodes
|
||||||
* with metadata if object propagation is on.
|
* with metadata if object propagation is on.
|
||||||
*
|
*
|
||||||
* This function should be used to mark dependent object as distributed. If you want
|
* This function should be used to mark dependent object as distributed. If you want
|
||||||
|
@ -187,8 +275,8 @@ MarkObjectDistributedViaSuperUser(const ObjectAddress *distAddress)
|
||||||
if (EnableMetadataSync)
|
if (EnableMetadataSync)
|
||||||
{
|
{
|
||||||
char *workerPgDistObjectUpdateCommand =
|
char *workerPgDistObjectUpdateCommand =
|
||||||
CreatePgDistObjectEntryCommand(distAddress);
|
CreatePgDistObjectEntryCommand(distAddress, "");
|
||||||
SendCommandToWorkersWithMetadataViaSuperUser(workerPgDistObjectUpdateCommand);
|
SendCommandToRemoteNodesWithMetadataViaSuperUser(workerPgDistObjectUpdateCommand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,17 +366,21 @@ ShouldMarkRelationDistributed(Oid relationId)
|
||||||
* for the given object address.
|
* for the given object address.
|
||||||
*/
|
*/
|
||||||
static char *
|
static char *
|
||||||
CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress)
|
CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress, char *objectName)
|
||||||
{
|
{
|
||||||
/* create a list by adding the address of value to not to have warning */
|
/* create a list by adding the address of value to not to have warning */
|
||||||
List *objectAddressList =
|
List *objectAddressList =
|
||||||
list_make1((ObjectAddress *) objectAddress);
|
list_make1((ObjectAddress *) objectAddress);
|
||||||
|
|
||||||
|
/* names also require a list so we create a nested list here */
|
||||||
|
List *objectNameList = list_make1(list_make1((char *) objectName));
|
||||||
List *distArgumetIndexList = list_make1_int(INVALID_DISTRIBUTION_ARGUMENT_INDEX);
|
List *distArgumetIndexList = list_make1_int(INVALID_DISTRIBUTION_ARGUMENT_INDEX);
|
||||||
List *colocationIdList = list_make1_int(INVALID_COLOCATION_ID);
|
List *colocationIdList = list_make1_int(INVALID_COLOCATION_ID);
|
||||||
List *forceDelegationList = list_make1_int(NO_FORCE_PUSHDOWN);
|
List *forceDelegationList = list_make1_int(NO_FORCE_PUSHDOWN);
|
||||||
|
|
||||||
char *workerPgDistObjectUpdateCommand =
|
char *workerPgDistObjectUpdateCommand =
|
||||||
MarkObjectsDistributedCreateCommand(objectAddressList,
|
MarkObjectsDistributedCreateCommand(objectAddressList,
|
||||||
|
objectNameList,
|
||||||
distArgumetIndexList,
|
distArgumetIndexList,
|
||||||
colocationIdList,
|
colocationIdList,
|
||||||
forceDelegationList);
|
forceDelegationList);
|
||||||
|
@ -358,6 +450,42 @@ ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UnmarkNodeWideObjectsDistributed deletes pg_dist_object records
|
||||||
|
* for all distributed objects in given Drop stmt node.
|
||||||
|
*
|
||||||
|
* Today we only expect DropRoleStmt and DropdbStmt to get here.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
UnmarkNodeWideObjectsDistributed(Node *node)
|
||||||
|
{
|
||||||
|
if (IsA(node, DropRoleStmt))
|
||||||
|
{
|
||||||
|
DropRoleStmt *stmt = castNode(DropRoleStmt, node);
|
||||||
|
List *allDropRoles = stmt->roles;
|
||||||
|
|
||||||
|
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
|
||||||
|
if (list_length(distributedDropRoles) > 0)
|
||||||
|
{
|
||||||
|
UnmarkRolesDistributed(distributedDropRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsA(node, DropdbStmt))
|
||||||
|
{
|
||||||
|
DropdbStmt *stmt = castNode(DropdbStmt, node);
|
||||||
|
char *dbName = stmt->dbname;
|
||||||
|
|
||||||
|
Oid dbOid = get_database_oid(dbName, stmt->missing_ok);
|
||||||
|
ObjectAddress *dbObjectAddress = palloc0(sizeof(ObjectAddress));
|
||||||
|
ObjectAddressSet(*dbObjectAddress, DatabaseRelationId, dbOid);
|
||||||
|
if (IsAnyObjectDistributed(list_make1(dbObjectAddress)))
|
||||||
|
{
|
||||||
|
UnmarkObjectDistributed(dbObjectAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UnmarkObjectDistributed removes the entry from pg_dist_object that marks this object as
|
* UnmarkObjectDistributed removes the entry from pg_dist_object that marks this object as
|
||||||
* distributed. This will prevent updates to that object to be propagated to the worker.
|
* distributed. This will prevent updates to that object to be propagated to the worker.
|
||||||
|
@ -445,6 +573,38 @@ IsAnyObjectDistributed(const List *addresses)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IsAnyParentObjectDistributed - true if at least one of the
|
||||||
|
* given addresses is distributed. If an address has a non-zero
|
||||||
|
* objectSubId, it checks the parent object (the object with
|
||||||
|
* the same classId and objid, but with objectSubId = 0). For
|
||||||
|
* example, a column address will check the table address.
|
||||||
|
* If the address has a zero objectSubId, it checks the address
|
||||||
|
* itself.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
IsAnyParentObjectDistributed(const List *addresses)
|
||||||
|
{
|
||||||
|
bool isDistributed = false;
|
||||||
|
ListCell *lc = NULL;
|
||||||
|
foreach(lc, addresses)
|
||||||
|
{
|
||||||
|
ObjectAddress *address = (ObjectAddress *) lfirst(lc);
|
||||||
|
int32 savedObjectSubId = address->objectSubId;
|
||||||
|
address->objectSubId = 0;
|
||||||
|
isDistributed = IsObjectDistributed(address);
|
||||||
|
address->objectSubId = savedObjectSubId;
|
||||||
|
|
||||||
|
if (isDistributed)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDistributed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GetDistributedObjectAddressList returns a list of ObjectAddresses that contains all
|
* GetDistributedObjectAddressList returns a list of ObjectAddresses that contains all
|
||||||
* distributed objects as marked in pg_dist_object
|
* distributed objects as marked in pg_dist_object
|
||||||
|
|
|
@ -51,6 +51,10 @@
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/palloc.h"
|
#include "utils/palloc.h"
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
|
#include "utils/relmapper.h"
|
||||||
|
#include "utils/resowner.h"
|
||||||
|
#include "utils/syscache.h"
|
||||||
|
#include "utils/typcache.h"
|
||||||
|
|
||||||
#include "citus_version.h"
|
#include "citus_version.h"
|
||||||
#include "pg_version_compat.h"
|
#include "pg_version_compat.h"
|
||||||
|
@ -75,6 +79,7 @@
|
||||||
#include "distributed/pg_dist_partition.h"
|
#include "distributed/pg_dist_partition.h"
|
||||||
#include "distributed/pg_dist_placement.h"
|
#include "distributed/pg_dist_placement.h"
|
||||||
#include "distributed/pg_dist_shard.h"
|
#include "distributed/pg_dist_shard.h"
|
||||||
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/shardinterval_utils.h"
|
#include "distributed/shardinterval_utils.h"
|
||||||
#include "distributed/shared_library_init.h"
|
#include "distributed/shared_library_init.h"
|
||||||
#include "distributed/utils/array_type.h"
|
#include "distributed/utils/array_type.h"
|
||||||
|
@ -82,13 +87,10 @@
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_manager.h"
|
#include "distributed/worker_manager.h"
|
||||||
#include "distributed/worker_protocol.h"
|
#include "distributed/worker_protocol.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM < PG_VERSION_16
|
#if PG_VERSION_NUM < PG_VERSION_16
|
||||||
#include "utils/relfilenodemap.h"
|
#include "utils/relfilenodemap.h"
|
||||||
#endif
|
#endif
|
||||||
#include "utils/relmapper.h"
|
|
||||||
#include "utils/resowner.h"
|
|
||||||
#include "utils/syscache.h"
|
|
||||||
#include "utils/typcache.h"
|
|
||||||
|
|
||||||
|
|
||||||
/* user configuration */
|
/* user configuration */
|
||||||
|
@ -520,8 +522,7 @@ IsCitusTableTypeCacheEntry(CitusTableCacheEntry *tableEntry, CitusTableType tabl
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* HasDistributionKey returs true if given Citus table doesn't have a
|
* HasDistributionKey returns true if given Citus table has a distribution key.
|
||||||
* distribution key.
|
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
HasDistributionKey(Oid relationId)
|
HasDistributionKey(Oid relationId)
|
||||||
|
@ -537,8 +538,8 @@ HasDistributionKey(Oid relationId)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* HasDistributionKey returs true if given cache entry identifies a Citus
|
* HasDistributionKeyCacheEntry returns true if given cache entry identifies a
|
||||||
* table that doesn't have a distribution key.
|
* Citus table that has a distribution key.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
HasDistributionKeyCacheEntry(CitusTableCacheEntry *tableEntry)
|
HasDistributionKeyCacheEntry(CitusTableCacheEntry *tableEntry)
|
||||||
|
|
|
@ -32,12 +32,15 @@
|
||||||
#include "catalog/pg_attrdef.h"
|
#include "catalog/pg_attrdef.h"
|
||||||
#include "catalog/pg_collation.h"
|
#include "catalog/pg_collation.h"
|
||||||
#include "catalog/pg_constraint.h"
|
#include "catalog/pg_constraint.h"
|
||||||
|
#include "catalog/pg_database.h"
|
||||||
|
#include "catalog/pg_database_d.h"
|
||||||
#include "catalog/pg_depend.h"
|
#include "catalog/pg_depend.h"
|
||||||
#include "catalog/pg_foreign_server.h"
|
#include "catalog/pg_foreign_server.h"
|
||||||
#include "catalog/pg_namespace.h"
|
#include "catalog/pg_namespace.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/async.h"
|
#include "commands/async.h"
|
||||||
|
#include "commands/dbcommands.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
#include "foreign/foreign.h"
|
#include "foreign/foreign.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
|
@ -80,6 +83,7 @@
|
||||||
#include "distributed/pg_dist_shard.h"
|
#include "distributed/pg_dist_shard.h"
|
||||||
#include "distributed/relation_access_tracking.h"
|
#include "distributed/relation_access_tracking.h"
|
||||||
#include "distributed/remote_commands.h"
|
#include "distributed/remote_commands.h"
|
||||||
|
#include "distributed/remote_transaction.h"
|
||||||
#include "distributed/resource_lock.h"
|
#include "distributed/resource_lock.h"
|
||||||
#include "distributed/tenant_schema_metadata.h"
|
#include "distributed/tenant_schema_metadata.h"
|
||||||
#include "distributed/utils/array_type.h"
|
#include "distributed/utils/array_type.h"
|
||||||
|
@ -120,6 +124,7 @@ static List * GetObjectsForGrantStmt(ObjectType objectType, Oid objectId);
|
||||||
static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission);
|
static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission);
|
||||||
static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid,
|
static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid,
|
||||||
AclItem *aclItem);
|
AclItem *aclItem);
|
||||||
|
static List * GenerateGrantOnDatabaseFromAclItem(Oid databaseOid, AclItem *aclItem);
|
||||||
static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid,
|
static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid,
|
||||||
AclItem *aclItem);
|
AclItem *aclItem);
|
||||||
static List * GrantOnSequenceDDLCommands(Oid sequenceOid);
|
static List * GrantOnSequenceDDLCommands(Oid sequenceOid);
|
||||||
|
@ -134,7 +139,7 @@ static bool ShouldSkipMetadataChecks(void);
|
||||||
static void EnsurePartitionMetadataIsSane(Oid relationId, char distributionMethod,
|
static void EnsurePartitionMetadataIsSane(Oid relationId, char distributionMethod,
|
||||||
int colocationId, char replicationModel,
|
int colocationId, char replicationModel,
|
||||||
Var *distributionKey);
|
Var *distributionKey);
|
||||||
static void EnsureCoordinatorInitiatedOperation(void);
|
static void EnsureCitusInitiatedOperation(void);
|
||||||
static void EnsureShardMetadataIsSane(Oid relationId, int64 shardId, char storageType,
|
static void EnsureShardMetadataIsSane(Oid relationId, int64 shardId, char storageType,
|
||||||
text *shardMinValue,
|
text *shardMinValue,
|
||||||
text *shardMaxValue);
|
text *shardMaxValue);
|
||||||
|
@ -179,6 +184,7 @@ PG_FUNCTION_INFO_V1(citus_internal_delete_colocation_metadata);
|
||||||
PG_FUNCTION_INFO_V1(citus_internal_add_tenant_schema);
|
PG_FUNCTION_INFO_V1(citus_internal_add_tenant_schema);
|
||||||
PG_FUNCTION_INFO_V1(citus_internal_delete_tenant_schema);
|
PG_FUNCTION_INFO_V1(citus_internal_delete_tenant_schema);
|
||||||
PG_FUNCTION_INFO_V1(citus_internal_update_none_dist_table_metadata);
|
PG_FUNCTION_INFO_V1(citus_internal_update_none_dist_table_metadata);
|
||||||
|
PG_FUNCTION_INFO_V1(citus_internal_database_command);
|
||||||
|
|
||||||
|
|
||||||
static bool got_SIGTERM = false;
|
static bool got_SIGTERM = false;
|
||||||
|
@ -486,19 +492,7 @@ stop_metadata_sync_to_node(PG_FUNCTION_ARGS)
|
||||||
bool
|
bool
|
||||||
ClusterHasKnownMetadataWorkers()
|
ClusterHasKnownMetadataWorkers()
|
||||||
{
|
{
|
||||||
bool workerWithMetadata = false;
|
return !IsCoordinator() || HasMetadataWorkers();
|
||||||
|
|
||||||
if (!IsCoordinator())
|
|
||||||
{
|
|
||||||
workerWithMetadata = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workerWithMetadata || HasMetadataWorkers())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -895,6 +889,7 @@ NodeListIdempotentInsertCommand(List *workerNodeList)
|
||||||
*/
|
*/
|
||||||
char *
|
char *
|
||||||
MarkObjectsDistributedCreateCommand(List *addresses,
|
MarkObjectsDistributedCreateCommand(List *addresses,
|
||||||
|
List *namesArg,
|
||||||
List *distributionArgumentIndexes,
|
List *distributionArgumentIndexes,
|
||||||
List *colocationIds,
|
List *colocationIds,
|
||||||
List *forceDelegations)
|
List *forceDelegations)
|
||||||
|
@ -919,9 +914,25 @@ MarkObjectsDistributedCreateCommand(List *addresses,
|
||||||
int forceDelegation = list_nth_int(forceDelegations, currentObjectCounter);
|
int forceDelegation = list_nth_int(forceDelegations, currentObjectCounter);
|
||||||
List *names = NIL;
|
List *names = NIL;
|
||||||
List *args = NIL;
|
List *args = NIL;
|
||||||
|
char *objectType = NULL;
|
||||||
|
|
||||||
char *objectType = getObjectTypeDescription(address, false);
|
if (IsMainDBCommand)
|
||||||
getObjectIdentityParts(address, &names, &args, false);
|
{
|
||||||
|
/*
|
||||||
|
* When we try to distribute an object that's being created in a non Citus
|
||||||
|
* main database, we cannot find the name, since the object is not visible
|
||||||
|
* in Citus main database.
|
||||||
|
* Because of that we need to pass the name to this function.
|
||||||
|
*/
|
||||||
|
names = list_nth(namesArg, currentObjectCounter);
|
||||||
|
bool missingOk = false;
|
||||||
|
objectType = getObjectTypeDescription(address, missingOk);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
objectType = getObjectTypeDescription(address, false);
|
||||||
|
getObjectIdentityParts(address, &names, &args, IsMainDBCommand);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isFirstObject)
|
if (!isFirstObject)
|
||||||
{
|
{
|
||||||
|
@ -976,7 +987,7 @@ MarkObjectsDistributedCreateCommand(List *addresses,
|
||||||
appendStringInfo(insertDistributedObjectsCommand, ") ");
|
appendStringInfo(insertDistributedObjectsCommand, ") ");
|
||||||
|
|
||||||
appendStringInfo(insertDistributedObjectsCommand,
|
appendStringInfo(insertDistributedObjectsCommand,
|
||||||
"SELECT citus_internal_add_object_metadata("
|
"SELECT citus_internal.add_object_metadata("
|
||||||
"typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) "
|
"typetext, objnames, objargs, distargumentindex::int, colocationid::int, force_delegation::bool) "
|
||||||
"FROM distributed_object_data;");
|
"FROM distributed_object_data;");
|
||||||
|
|
||||||
|
@ -1001,7 +1012,7 @@ citus_internal_add_object_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed for executing as a separate command */
|
/* this UDF is not allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ensure given distributionArgumentIndex and colocationId values are
|
* Ensure given distributionArgumentIndex and colocationId values are
|
||||||
|
@ -1111,7 +1122,7 @@ DistributionCreateCommand(CitusTableCacheEntry *cacheEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
appendStringInfo(insertDistributionCommand,
|
appendStringInfo(insertDistributionCommand,
|
||||||
"SELECT citus_internal_add_partition_metadata "
|
"SELECT citus_internal.add_partition_metadata "
|
||||||
"(%s::regclass, '%c', %s, %d, '%c')",
|
"(%s::regclass, '%c', %s, %d, '%c')",
|
||||||
quote_literal_cstr(qualifiedRelationName),
|
quote_literal_cstr(qualifiedRelationName),
|
||||||
distributionMethod,
|
distributionMethod,
|
||||||
|
@ -1153,7 +1164,7 @@ DistributionDeleteMetadataCommand(Oid relationId)
|
||||||
char *qualifiedRelationName = generate_qualified_relation_name(relationId);
|
char *qualifiedRelationName = generate_qualified_relation_name(relationId);
|
||||||
|
|
||||||
appendStringInfo(deleteCommand,
|
appendStringInfo(deleteCommand,
|
||||||
"SELECT pg_catalog.citus_internal_delete_partition_metadata(%s)",
|
"SELECT citus_internal.delete_partition_metadata(%s)",
|
||||||
quote_literal_cstr(qualifiedRelationName));
|
quote_literal_cstr(qualifiedRelationName));
|
||||||
|
|
||||||
return deleteCommand->data;
|
return deleteCommand->data;
|
||||||
|
@ -1236,7 +1247,7 @@ ShardListInsertCommand(List *shardIntervalList)
|
||||||
appendStringInfo(insertPlacementCommand, ") ");
|
appendStringInfo(insertPlacementCommand, ") ");
|
||||||
|
|
||||||
appendStringInfo(insertPlacementCommand,
|
appendStringInfo(insertPlacementCommand,
|
||||||
"SELECT citus_internal_add_placement_metadata("
|
"SELECT citus_internal.add_placement_metadata("
|
||||||
"shardid, shardlength, groupid, placementid) "
|
"shardid, shardlength, groupid, placementid) "
|
||||||
"FROM placement_data;");
|
"FROM placement_data;");
|
||||||
|
|
||||||
|
@ -1292,7 +1303,7 @@ ShardListInsertCommand(List *shardIntervalList)
|
||||||
appendStringInfo(insertShardCommand, ") ");
|
appendStringInfo(insertShardCommand, ") ");
|
||||||
|
|
||||||
appendStringInfo(insertShardCommand,
|
appendStringInfo(insertShardCommand,
|
||||||
"SELECT citus_internal_add_shard_metadata(relationname, shardid, "
|
"SELECT citus_internal.add_shard_metadata(relationname, shardid, "
|
||||||
"storagetype, shardminvalue, shardmaxvalue) "
|
"storagetype, shardminvalue, shardmaxvalue) "
|
||||||
"FROM shard_data;");
|
"FROM shard_data;");
|
||||||
|
|
||||||
|
@ -1331,7 +1342,7 @@ ShardDeleteCommandList(ShardInterval *shardInterval)
|
||||||
|
|
||||||
StringInfo deleteShardCommand = makeStringInfo();
|
StringInfo deleteShardCommand = makeStringInfo();
|
||||||
appendStringInfo(deleteShardCommand,
|
appendStringInfo(deleteShardCommand,
|
||||||
"SELECT citus_internal_delete_shard_metadata(%ld);", shardId);
|
"SELECT citus_internal.delete_shard_metadata(%ld);", shardId);
|
||||||
|
|
||||||
return list_make1(deleteShardCommand->data);
|
return list_make1(deleteShardCommand->data);
|
||||||
}
|
}
|
||||||
|
@ -1401,7 +1412,7 @@ ColocationIdUpdateCommand(Oid relationId, uint32 colocationId)
|
||||||
StringInfo command = makeStringInfo();
|
StringInfo command = makeStringInfo();
|
||||||
char *qualifiedRelationName = generate_qualified_relation_name(relationId);
|
char *qualifiedRelationName = generate_qualified_relation_name(relationId);
|
||||||
appendStringInfo(command,
|
appendStringInfo(command,
|
||||||
"SELECT citus_internal_update_relation_colocation(%s::regclass, %d)",
|
"SELECT citus_internal.update_relation_colocation(%s::regclass, %d)",
|
||||||
quote_literal_cstr(qualifiedRelationName), colocationId);
|
quote_literal_cstr(qualifiedRelationName), colocationId);
|
||||||
|
|
||||||
return command->data;
|
return command->data;
|
||||||
|
@ -2153,6 +2164,92 @@ GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid, AclItem *aclItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GrantOnDatabaseDDLCommands creates a list of ddl command for replicating the permissions
|
||||||
|
* of roles on databases.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
GrantOnDatabaseDDLCommands(Oid databaseOid)
|
||||||
|
{
|
||||||
|
HeapTuple databaseTuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(databaseOid));
|
||||||
|
bool isNull = true;
|
||||||
|
Datum aclDatum = SysCacheGetAttr(DATABASEOID, databaseTuple, Anum_pg_database_datacl,
|
||||||
|
&isNull);
|
||||||
|
if (isNull)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(databaseTuple);
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
Acl *acl = DatumGetAclPCopy(aclDatum);
|
||||||
|
AclItem *aclDat = ACL_DAT(acl);
|
||||||
|
int aclNum = ACL_NUM(acl);
|
||||||
|
List *commands = NIL;
|
||||||
|
|
||||||
|
ReleaseSysCache(databaseTuple);
|
||||||
|
|
||||||
|
for (int i = 0; i < aclNum; i++)
|
||||||
|
{
|
||||||
|
commands = list_concat(commands,
|
||||||
|
GenerateGrantOnDatabaseFromAclItem(
|
||||||
|
databaseOid, &aclDat[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GenerateGrantOnDatabaseFromAclItem generates a query string for replicating a users permissions
|
||||||
|
* on a database.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
GenerateGrantOnDatabaseFromAclItem(Oid databaseOid, AclItem *aclItem)
|
||||||
|
{
|
||||||
|
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_DATABASE;
|
||||||
|
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_DATABASE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* seems unlikely but we check if there is a grant option in the list without the actual permission
|
||||||
|
*/
|
||||||
|
Assert(!(grants & ACL_CONNECT) || (permissions & ACL_CONNECT));
|
||||||
|
Assert(!(grants & ACL_CREATE) || (permissions & ACL_CREATE));
|
||||||
|
Assert(!(grants & ACL_CREATE_TEMP) || (permissions & ACL_CREATE_TEMP));
|
||||||
|
Oid granteeOid = aclItem->ai_grantee;
|
||||||
|
List *queries = NIL;
|
||||||
|
|
||||||
|
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
|
||||||
|
|
||||||
|
if (permissions & ACL_CONNECT)
|
||||||
|
{
|
||||||
|
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||||
|
OBJECT_DATABASE, granteeOid, databaseOid,
|
||||||
|
"CONNECT",
|
||||||
|
grants & ACL_CONNECT));
|
||||||
|
queries = lappend(queries, query);
|
||||||
|
}
|
||||||
|
if (permissions & ACL_CREATE)
|
||||||
|
{
|
||||||
|
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||||
|
OBJECT_DATABASE, granteeOid, databaseOid,
|
||||||
|
"CREATE",
|
||||||
|
grants & ACL_CREATE));
|
||||||
|
queries = lappend(queries, query);
|
||||||
|
}
|
||||||
|
if (permissions & ACL_CREATE_TEMP)
|
||||||
|
{
|
||||||
|
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||||
|
OBJECT_DATABASE, granteeOid, databaseOid,
|
||||||
|
"TEMPORARY",
|
||||||
|
grants & ACL_CREATE_TEMP));
|
||||||
|
queries = lappend(queries, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
queries = lappend(queries, "RESET ROLE");
|
||||||
|
|
||||||
|
return queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GenerateGrantStmtForRights is the function for creating GrantStmt's for all
|
* GenerateGrantStmtForRights is the function for creating GrantStmt's for all
|
||||||
* types of objects that are supported. It takes parameters to fill a GrantStmt's
|
* types of objects that are supported. It takes parameters to fill a GrantStmt's
|
||||||
|
@ -2226,6 +2323,11 @@ GetObjectsForGrantStmt(ObjectType objectType, Oid objectId)
|
||||||
return list_make1(sequence);
|
return list_make1(sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OBJECT_DATABASE:
|
||||||
|
{
|
||||||
|
return list_make1(makeString(get_database_name(objectId)));
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
elog(ERROR, "unsupported object type for GRANT");
|
elog(ERROR, "unsupported object type for GRANT");
|
||||||
|
@ -3199,7 +3301,7 @@ citus_internal_add_partition_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
|
|
||||||
if (distributionMethod == DISTRIBUTE_BY_NONE && distributionColumnVar != NULL)
|
if (distributionMethod == DISTRIBUTE_BY_NONE && distributionColumnVar != NULL)
|
||||||
{
|
{
|
||||||
|
@ -3315,7 +3417,7 @@ citus_internal_delete_partition_metadata(PG_FUNCTION_ARGS)
|
||||||
|
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
DeletePartitionRow(relationId);
|
DeletePartitionRow(relationId);
|
||||||
|
@ -3363,7 +3465,7 @@ citus_internal_add_shard_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Even if the table owner is a malicious user and the shard metadata is
|
* Even if the table owner is a malicious user and the shard metadata is
|
||||||
|
@ -3381,19 +3483,13 @@ citus_internal_add_shard_metadata(PG_FUNCTION_ARGS)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EnsureCoordinatorInitiatedOperation is a helper function which ensures that
|
* EnsureCitusInitiatedOperation is a helper function which ensures that
|
||||||
* the execution is initiated by the coordinator on a worker node.
|
* the execution is initiated by Citus.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
EnsureCoordinatorInitiatedOperation(void)
|
EnsureCitusInitiatedOperation(void)
|
||||||
{
|
{
|
||||||
/*
|
if (!(IsCitusInternalBackend() || IsRebalancerInternalBackend()))
|
||||||
* We are restricting the operation to only MX workers with the local group id
|
|
||||||
* check. The other two checks are to ensure that the operation is initiated
|
|
||||||
* by the coordinator.
|
|
||||||
*/
|
|
||||||
if (!(IsCitusInternalBackend() || IsRebalancerInternalBackend()) ||
|
|
||||||
GetLocalGroupId() == COORDINATOR_GROUP_ID)
|
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||||
errmsg("This is an internal Citus function can only be "
|
errmsg("This is an internal Citus function can only be "
|
||||||
|
@ -3574,7 +3670,7 @@ citus_internal_delete_placement_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteShardPlacementRow(placementId);
|
DeleteShardPlacementRow(placementId);
|
||||||
|
@ -3622,7 +3718,7 @@ citus_internal_add_placement_metadata_internal(int64 shardId, int64 shardLength,
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Even if the table owner is a malicious user, as long as the shard placements
|
* Even if the table owner is a malicious user, as long as the shard placements
|
||||||
|
@ -3717,7 +3813,7 @@ citus_internal_update_placement_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
|
|
||||||
if (!ShardExists(shardId))
|
if (!ShardExists(shardId))
|
||||||
{
|
{
|
||||||
|
@ -3781,7 +3877,7 @@ citus_internal_delete_shard_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
|
|
||||||
if (!ShardExists(shardId))
|
if (!ShardExists(shardId))
|
||||||
{
|
{
|
||||||
|
@ -3824,7 +3920,7 @@ citus_internal_update_relation_colocation(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
|
|
||||||
/* ensure that the table is in pg_dist_partition */
|
/* ensure that the table is in pg_dist_partition */
|
||||||
char partitionMethod = PartitionMethodViaCatalog(relationId);
|
char partitionMethod = PartitionMethodViaCatalog(relationId);
|
||||||
|
@ -3890,7 +3986,7 @@ citus_internal_add_colocation_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
InsertColocationGroupLocally(colocationId, shardCount, replicationFactor,
|
InsertColocationGroupLocally(colocationId, shardCount, replicationFactor,
|
||||||
|
@ -3915,7 +4011,7 @@ citus_internal_delete_colocation_metadata(PG_FUNCTION_ARGS)
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
/* this UDF is not allowed allowed for executing as a separate command */
|
/* this UDF is not allowed allowed for executing as a separate command */
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteColocationGroupLocally(colocationId);
|
DeleteColocationGroupLocally(colocationId);
|
||||||
|
@ -3994,7 +4090,7 @@ citus_internal_update_none_dist_table_metadata(PG_FUNCTION_ARGS)
|
||||||
|
|
||||||
if (!ShouldSkipMetadataChecks())
|
if (!ShouldSkipMetadataChecks())
|
||||||
{
|
{
|
||||||
EnsureCoordinatorInitiatedOperation();
|
EnsureCitusInitiatedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateNoneDistTableMetadata(relationId, replicationModel,
|
UpdateNoneDistTableMetadata(relationId, replicationModel,
|
||||||
|
@ -4004,6 +4100,70 @@ citus_internal_update_none_dist_table_metadata(PG_FUNCTION_ARGS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* citus_internal_database_command is an internal UDF to
|
||||||
|
* create a database in an idempotent maner without
|
||||||
|
* transaction block restrictions.
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
citus_internal_database_command(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
CheckCitusVersion(ERROR);
|
||||||
|
|
||||||
|
if (!ShouldSkipMetadataChecks())
|
||||||
|
{
|
||||||
|
EnsureCitusInitiatedOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_ENSURE_ARGNOTNULL(0, "command");
|
||||||
|
|
||||||
|
text *commandText = PG_GETARG_TEXT_P(0);
|
||||||
|
char *command = text_to_cstring(commandText);
|
||||||
|
Node *parseTree = ParseTreeNode(command);
|
||||||
|
|
||||||
|
int saveNestLevel = NewGUCNestLevel();
|
||||||
|
|
||||||
|
set_config_option("citus.enable_ddl_propagation", "off",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
|
||||||
|
set_config_option("citus.enable_create_database_propagation", "off",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* createdb() uses ParseState to report the error position for the
|
||||||
|
* input command and the position is reported to be 0 when it's provided as NULL.
|
||||||
|
* We're okay with that because we don't expect this UDF to be called with an incorrect
|
||||||
|
* DDL command.
|
||||||
|
*/
|
||||||
|
ParseState *pstate = NULL;
|
||||||
|
|
||||||
|
if (IsA(parseTree, CreatedbStmt))
|
||||||
|
{
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, parseTree);
|
||||||
|
|
||||||
|
bool missingOk = true;
|
||||||
|
Oid databaseOid = get_database_oid(stmt->dbname, missingOk);
|
||||||
|
|
||||||
|
if (!OidIsValid(databaseOid))
|
||||||
|
{
|
||||||
|
createdb(pstate, (CreatedbStmt *) parseTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("citus_internal.database_command() can only be used "
|
||||||
|
"for CREATE DATABASE command by Citus.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rollback GUCs to the state before this session */
|
||||||
|
AtEOXact_GUC(true, saveNestLevel);
|
||||||
|
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SyncNewColocationGroup synchronizes a new pg_dist_colocation entry to a worker.
|
* SyncNewColocationGroup synchronizes a new pg_dist_colocation entry to a worker.
|
||||||
*/
|
*/
|
||||||
|
@ -4034,7 +4194,7 @@ ColocationGroupCreateCommand(uint32 colocationId, int shardCount, int replicatio
|
||||||
StringInfo insertColocationCommand = makeStringInfo();
|
StringInfo insertColocationCommand = makeStringInfo();
|
||||||
|
|
||||||
appendStringInfo(insertColocationCommand,
|
appendStringInfo(insertColocationCommand,
|
||||||
"SELECT pg_catalog.citus_internal_add_colocation_metadata("
|
"SELECT citus_internal.add_colocation_metadata("
|
||||||
"%d, %d, %d, %s, %s)",
|
"%d, %d, %d, %s, %s)",
|
||||||
colocationId,
|
colocationId,
|
||||||
shardCount,
|
shardCount,
|
||||||
|
@ -4146,7 +4306,7 @@ ColocationGroupDeleteCommand(uint32 colocationId)
|
||||||
StringInfo deleteColocationCommand = makeStringInfo();
|
StringInfo deleteColocationCommand = makeStringInfo();
|
||||||
|
|
||||||
appendStringInfo(deleteColocationCommand,
|
appendStringInfo(deleteColocationCommand,
|
||||||
"SELECT pg_catalog.citus_internal_delete_colocation_metadata(%d)",
|
"SELECT citus_internal.delete_colocation_metadata(%d)",
|
||||||
colocationId);
|
colocationId);
|
||||||
|
|
||||||
return deleteColocationCommand->data;
|
return deleteColocationCommand->data;
|
||||||
|
@ -4162,7 +4322,7 @@ TenantSchemaInsertCommand(Oid schemaId, uint32 colocationId)
|
||||||
{
|
{
|
||||||
StringInfo command = makeStringInfo();
|
StringInfo command = makeStringInfo();
|
||||||
appendStringInfo(command,
|
appendStringInfo(command,
|
||||||
"SELECT pg_catalog.citus_internal_add_tenant_schema(%s, %u)",
|
"SELECT citus_internal.add_tenant_schema(%s, %u)",
|
||||||
RemoteSchemaIdExpressionById(schemaId), colocationId);
|
RemoteSchemaIdExpressionById(schemaId), colocationId);
|
||||||
|
|
||||||
return command->data;
|
return command->data;
|
||||||
|
@ -4178,7 +4338,7 @@ TenantSchemaDeleteCommand(char *schemaName)
|
||||||
{
|
{
|
||||||
StringInfo command = makeStringInfo();
|
StringInfo command = makeStringInfo();
|
||||||
appendStringInfo(command,
|
appendStringInfo(command,
|
||||||
"SELECT pg_catalog.citus_internal_delete_tenant_schema(%s)",
|
"SELECT citus_internal.delete_tenant_schema(%s)",
|
||||||
RemoteSchemaIdExpressionByName(schemaName));
|
RemoteSchemaIdExpressionByName(schemaName));
|
||||||
|
|
||||||
return command->data;
|
return command->data;
|
||||||
|
@ -4195,7 +4355,7 @@ UpdateNoneDistTableMetadataCommand(Oid relationId, char replicationModel,
|
||||||
{
|
{
|
||||||
StringInfo command = makeStringInfo();
|
StringInfo command = makeStringInfo();
|
||||||
appendStringInfo(command,
|
appendStringInfo(command,
|
||||||
"SELECT pg_catalog.citus_internal_update_none_dist_table_metadata(%s, '%c', %u, %s)",
|
"SELECT citus_internal.update_none_dist_table_metadata(%s, '%c', %u, %s)",
|
||||||
RemoteTableIdExpression(relationId), replicationModel, colocationId,
|
RemoteTableIdExpression(relationId), replicationModel, colocationId,
|
||||||
autoConverted ? "true" : "false");
|
autoConverted ? "true" : "false");
|
||||||
|
|
||||||
|
@ -4213,7 +4373,7 @@ AddPlacementMetadataCommand(uint64 shardId, uint64 placementId,
|
||||||
{
|
{
|
||||||
StringInfo command = makeStringInfo();
|
StringInfo command = makeStringInfo();
|
||||||
appendStringInfo(command,
|
appendStringInfo(command,
|
||||||
"SELECT citus_internal_add_placement_metadata(%ld, %ld, %d, %ld)",
|
"SELECT citus_internal.add_placement_metadata(%ld, %ld, %d, %ld)",
|
||||||
shardId, shardLength, groupId, placementId);
|
shardId, shardLength, groupId, placementId);
|
||||||
return command->data;
|
return command->data;
|
||||||
}
|
}
|
||||||
|
@ -4228,7 +4388,7 @@ DeletePlacementMetadataCommand(uint64 placementId)
|
||||||
{
|
{
|
||||||
StringInfo command = makeStringInfo();
|
StringInfo command = makeStringInfo();
|
||||||
appendStringInfo(command,
|
appendStringInfo(command,
|
||||||
"SELECT pg_catalog.citus_internal_delete_placement_metadata(%ld)",
|
"SELECT citus_internal.delete_placement_metadata(%ld)",
|
||||||
placementId);
|
placementId);
|
||||||
return command->data;
|
return command->data;
|
||||||
}
|
}
|
||||||
|
@ -4485,7 +4645,7 @@ void
|
||||||
SendOrCollectCommandListToMetadataNodes(MetadataSyncContext *context, List *commands)
|
SendOrCollectCommandListToMetadataNodes(MetadataSyncContext *context, List *commands)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* do not send any command to workers if we collcet commands.
|
* do not send any command to workers if we collect commands.
|
||||||
* Collect commands into metadataSyncContext's collected command
|
* Collect commands into metadataSyncContext's collected command
|
||||||
* list.
|
* list.
|
||||||
*/
|
*/
|
||||||
|
@ -4843,7 +5003,7 @@ SendColocationMetadataCommands(MetadataSyncContext *context)
|
||||||
}
|
}
|
||||||
|
|
||||||
appendStringInfo(colocationGroupCreateCommand,
|
appendStringInfo(colocationGroupCreateCommand,
|
||||||
") SELECT pg_catalog.citus_internal_add_colocation_metadata("
|
") SELECT citus_internal.add_colocation_metadata("
|
||||||
"colocationid, shardcount, replicationfactor, "
|
"colocationid, shardcount, replicationfactor, "
|
||||||
"distributioncolumntype, coalesce(c.oid, 0)) "
|
"distributioncolumntype, coalesce(c.oid, 0)) "
|
||||||
"FROM colocation_group_data d LEFT JOIN pg_collation c "
|
"FROM colocation_group_data d LEFT JOIN pg_collation c "
|
||||||
|
@ -4894,7 +5054,7 @@ SendTenantSchemaMetadataCommands(MetadataSyncContext *context)
|
||||||
|
|
||||||
StringInfo insertTenantSchemaCommand = makeStringInfo();
|
StringInfo insertTenantSchemaCommand = makeStringInfo();
|
||||||
appendStringInfo(insertTenantSchemaCommand,
|
appendStringInfo(insertTenantSchemaCommand,
|
||||||
"SELECT pg_catalog.citus_internal_add_tenant_schema(%s, %u)",
|
"SELECT citus_internal.add_tenant_schema(%s, %u)",
|
||||||
RemoteSchemaIdExpressionById(tenantSchemaForm->schemaid),
|
RemoteSchemaIdExpressionById(tenantSchemaForm->schemaid),
|
||||||
tenantSchemaForm->colocationid);
|
tenantSchemaForm->colocationid);
|
||||||
|
|
||||||
|
@ -5103,6 +5263,7 @@ SendDistObjectCommands(MetadataSyncContext *context)
|
||||||
|
|
||||||
char *workerMetadataUpdateCommand =
|
char *workerMetadataUpdateCommand =
|
||||||
MarkObjectsDistributedCreateCommand(list_make1(address),
|
MarkObjectsDistributedCreateCommand(list_make1(address),
|
||||||
|
NIL,
|
||||||
list_make1_int(distributionArgumentIndex),
|
list_make1_int(distributionArgumentIndex),
|
||||||
list_make1_int(colocationId),
|
list_make1_int(colocationId),
|
||||||
list_make1_int(forceDelegation));
|
list_make1_int(forceDelegation));
|
||||||
|
|
|
@ -23,16 +23,12 @@
|
||||||
#include "access/sysattr.h"
|
#include "access/sysattr.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
|
#include "catalog/index.h"
|
||||||
#include "catalog/indexing.h"
|
#include "catalog/indexing.h"
|
||||||
#include "catalog/pg_authid.h"
|
#include "catalog/pg_authid.h"
|
||||||
#include "catalog/pg_constraint.h"
|
#include "catalog/pg_constraint.h"
|
||||||
#include "catalog/pg_extension.h"
|
#include "catalog/pg_extension.h"
|
||||||
#include "catalog/pg_namespace.h"
|
#include "catalog/pg_namespace.h"
|
||||||
|
|
||||||
#include "pg_version_constants.h"
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
|
||||||
#include "catalog/pg_proc_d.h"
|
|
||||||
#endif
|
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/extension.h"
|
#include "commands/extension.h"
|
||||||
#include "commands/sequence.h"
|
#include "commands/sequence.h"
|
||||||
|
@ -50,6 +46,8 @@
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
#include "distributed/background_jobs.h"
|
#include "distributed/background_jobs.h"
|
||||||
#include "distributed/citus_nodes.h"
|
#include "distributed/citus_nodes.h"
|
||||||
#include "distributed/citus_safe_lib.h"
|
#include "distributed/citus_safe_lib.h"
|
||||||
|
@ -83,6 +81,10 @@
|
||||||
#include "distributed/worker_manager.h"
|
#include "distributed/worker_manager.h"
|
||||||
#include "distributed/worker_protocol.h"
|
#include "distributed/worker_protocol.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
#include "catalog/pg_proc_d.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define DISK_SPACE_FIELDS 2
|
#define DISK_SPACE_FIELDS 2
|
||||||
|
|
||||||
/* Local functions forward declarations */
|
/* Local functions forward declarations */
|
||||||
|
@ -90,11 +92,11 @@ static uint64 * AllocateUint64(uint64 value);
|
||||||
static void RecordDistributedRelationDependencies(Oid distributedRelationId);
|
static void RecordDistributedRelationDependencies(Oid distributedRelationId);
|
||||||
static GroupShardPlacement * TupleToGroupShardPlacement(TupleDesc tupleDesc,
|
static GroupShardPlacement * TupleToGroupShardPlacement(TupleDesc tupleDesc,
|
||||||
HeapTuple heapTuple);
|
HeapTuple heapTuple);
|
||||||
static bool DistributedTableSize(Oid relationId, SizeQueryType sizeQueryType,
|
static bool DistributedRelationSize(Oid relationId, SizeQueryType sizeQueryType,
|
||||||
bool failOnError, uint64 *tableSize);
|
bool failOnError, uint64 *relationSize);
|
||||||
static bool DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
static bool DistributedRelationSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
||||||
SizeQueryType sizeQueryType, bool failOnError,
|
SizeQueryType sizeQueryType, bool failOnError,
|
||||||
uint64 *tableSize);
|
uint64 *relationSize);
|
||||||
static List * ShardIntervalsOnWorkerGroup(WorkerNode *workerNode, Oid relationId);
|
static List * ShardIntervalsOnWorkerGroup(WorkerNode *workerNode, Oid relationId);
|
||||||
static char * GenerateShardIdNameValuesForShardList(List *shardIntervalList,
|
static char * GenerateShardIdNameValuesForShardList(List *shardIntervalList,
|
||||||
bool firstValue);
|
bool firstValue);
|
||||||
|
@ -284,7 +286,7 @@ citus_shard_sizes(PG_FUNCTION_ARGS)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* citus_total_relation_size accepts a table name and returns a distributed table
|
* citus_total_relation_size accepts a distributed table name and returns a distributed table
|
||||||
* and its indexes' total relation size.
|
* and its indexes' total relation size.
|
||||||
*/
|
*/
|
||||||
Datum
|
Datum
|
||||||
|
@ -296,20 +298,20 @@ citus_total_relation_size(PG_FUNCTION_ARGS)
|
||||||
bool failOnError = PG_GETARG_BOOL(1);
|
bool failOnError = PG_GETARG_BOOL(1);
|
||||||
|
|
||||||
SizeQueryType sizeQueryType = TOTAL_RELATION_SIZE;
|
SizeQueryType sizeQueryType = TOTAL_RELATION_SIZE;
|
||||||
uint64 tableSize = 0;
|
uint64 relationSize = 0;
|
||||||
|
|
||||||
if (!DistributedTableSize(relationId, sizeQueryType, failOnError, &tableSize))
|
if (!DistributedRelationSize(relationId, sizeQueryType, failOnError, &relationSize))
|
||||||
{
|
{
|
||||||
Assert(!failOnError);
|
Assert(!failOnError);
|
||||||
PG_RETURN_NULL();
|
PG_RETURN_NULL();
|
||||||
}
|
}
|
||||||
|
|
||||||
PG_RETURN_INT64(tableSize);
|
PG_RETURN_INT64(relationSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* citus_table_size accepts a table name and returns a distributed table's total
|
* citus_table_size accepts a distributed table name and returns a distributed table's total
|
||||||
* relation size.
|
* relation size.
|
||||||
*/
|
*/
|
||||||
Datum
|
Datum
|
||||||
|
@ -320,21 +322,24 @@ citus_table_size(PG_FUNCTION_ARGS)
|
||||||
Oid relationId = PG_GETARG_OID(0);
|
Oid relationId = PG_GETARG_OID(0);
|
||||||
bool failOnError = true;
|
bool failOnError = true;
|
||||||
SizeQueryType sizeQueryType = TABLE_SIZE;
|
SizeQueryType sizeQueryType = TABLE_SIZE;
|
||||||
uint64 tableSize = 0;
|
uint64 relationSize = 0;
|
||||||
|
|
||||||
if (!DistributedTableSize(relationId, sizeQueryType, failOnError, &tableSize))
|
/* We do not check if relation is really a table, like PostgreSQL is doing. */
|
||||||
|
if (!DistributedRelationSize(relationId, sizeQueryType, failOnError, &relationSize))
|
||||||
{
|
{
|
||||||
Assert(!failOnError);
|
Assert(!failOnError);
|
||||||
PG_RETURN_NULL();
|
PG_RETURN_NULL();
|
||||||
}
|
}
|
||||||
|
|
||||||
PG_RETURN_INT64(tableSize);
|
PG_RETURN_INT64(relationSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* citus_relation_size accept a table name and returns a relation's 'main'
|
* citus_relation_size accept a distributed relation name and returns a relation's 'main'
|
||||||
* fork's size.
|
* fork's size.
|
||||||
|
*
|
||||||
|
* Input relation is allowed to be an index on a distributed table too.
|
||||||
*/
|
*/
|
||||||
Datum
|
Datum
|
||||||
citus_relation_size(PG_FUNCTION_ARGS)
|
citus_relation_size(PG_FUNCTION_ARGS)
|
||||||
|
@ -346,7 +351,7 @@ citus_relation_size(PG_FUNCTION_ARGS)
|
||||||
SizeQueryType sizeQueryType = RELATION_SIZE;
|
SizeQueryType sizeQueryType = RELATION_SIZE;
|
||||||
uint64 relationSize = 0;
|
uint64 relationSize = 0;
|
||||||
|
|
||||||
if (!DistributedTableSize(relationId, sizeQueryType, failOnError, &relationSize))
|
if (!DistributedRelationSize(relationId, sizeQueryType, failOnError, &relationSize))
|
||||||
{
|
{
|
||||||
Assert(!failOnError);
|
Assert(!failOnError);
|
||||||
PG_RETURN_NULL();
|
PG_RETURN_NULL();
|
||||||
|
@ -508,13 +513,16 @@ ReceiveShardIdAndSizeResults(List *connectionList, Tuplestorestate *tupleStore,
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DistributedTableSize is helper function for each kind of citus size functions.
|
* DistributedRelationSize is helper function for each kind of citus size
|
||||||
* It first checks whether the table is distributed and size query can be run on
|
* functions. It first checks whether the relation is a distributed table or an
|
||||||
* it. Connection to each node has to be established to get the size of the table.
|
* index belonging to a distributed table and size query can be run on it.
|
||||||
|
* Connection to each node has to be established to get the size of the
|
||||||
|
* relation.
|
||||||
|
* Input relation is allowed to be an index on a distributed table too.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
DistributedTableSize(Oid relationId, SizeQueryType sizeQueryType, bool failOnError,
|
DistributedRelationSize(Oid relationId, SizeQueryType sizeQueryType,
|
||||||
uint64 *tableSize)
|
bool failOnError, uint64 *relationSize)
|
||||||
{
|
{
|
||||||
int logLevel = WARNING;
|
int logLevel = WARNING;
|
||||||
|
|
||||||
|
@ -540,7 +548,7 @@ DistributedTableSize(Oid relationId, SizeQueryType sizeQueryType, bool failOnErr
|
||||||
if (relation == NULL)
|
if (relation == NULL)
|
||||||
{
|
{
|
||||||
ereport(logLevel,
|
ereport(logLevel,
|
||||||
(errmsg("could not compute table size: relation does not exist")));
|
(errmsg("could not compute relation size: relation does not exist")));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -555,7 +563,8 @@ DistributedTableSize(Oid relationId, SizeQueryType sizeQueryType, bool failOnErr
|
||||||
{
|
{
|
||||||
uint64 relationSizeOnNode = 0;
|
uint64 relationSizeOnNode = 0;
|
||||||
|
|
||||||
bool gotSize = DistributedTableSizeOnWorker(workerNode, relationId, sizeQueryType,
|
bool gotSize = DistributedRelationSizeOnWorker(workerNode, relationId,
|
||||||
|
sizeQueryType,
|
||||||
failOnError, &relationSizeOnNode);
|
failOnError, &relationSizeOnNode);
|
||||||
if (!gotSize)
|
if (!gotSize)
|
||||||
{
|
{
|
||||||
|
@ -565,21 +574,22 @@ DistributedTableSize(Oid relationId, SizeQueryType sizeQueryType, bool failOnErr
|
||||||
sumOfSizes += relationSizeOnNode;
|
sumOfSizes += relationSizeOnNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
*tableSize = sumOfSizes;
|
*relationSize = sumOfSizes;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DistributedTableSizeOnWorker gets the workerNode and relationId to calculate
|
* DistributedRelationSizeOnWorker gets the workerNode and relationId to calculate
|
||||||
* size of that relation on the given workerNode by summing up the size of each
|
* size of that relation on the given workerNode by summing up the size of each
|
||||||
* shard placement.
|
* shard placement.
|
||||||
|
* Input relation is allowed to be an index on a distributed table too.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
DistributedRelationSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
||||||
SizeQueryType sizeQueryType,
|
SizeQueryType sizeQueryType,
|
||||||
bool failOnError, uint64 *tableSize)
|
bool failOnError, uint64 *relationSize)
|
||||||
{
|
{
|
||||||
int logLevel = WARNING;
|
int logLevel = WARNING;
|
||||||
|
|
||||||
|
@ -593,6 +603,17 @@ DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
||||||
uint32 connectionFlag = 0;
|
uint32 connectionFlag = 0;
|
||||||
PGresult *result = NULL;
|
PGresult *result = NULL;
|
||||||
|
|
||||||
|
/* if the relation is an index, update relationId and define indexId */
|
||||||
|
Oid indexId = InvalidOid;
|
||||||
|
Oid relKind = get_rel_relkind(relationId);
|
||||||
|
if (relKind == RELKIND_INDEX || relKind == RELKIND_PARTITIONED_INDEX)
|
||||||
|
{
|
||||||
|
indexId = relationId;
|
||||||
|
|
||||||
|
bool missingOk = false;
|
||||||
|
relationId = IndexGetRelation(indexId, missingOk);
|
||||||
|
}
|
||||||
|
|
||||||
List *shardIntervalsOnNode = ShardIntervalsOnWorkerGroup(workerNode, relationId);
|
List *shardIntervalsOnNode = ShardIntervalsOnWorkerGroup(workerNode, relationId);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -600,21 +621,22 @@ DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
||||||
* But citus size functions shouldn't include them, like PG.
|
* But citus size functions shouldn't include them, like PG.
|
||||||
*/
|
*/
|
||||||
bool optimizePartitionCalculations = false;
|
bool optimizePartitionCalculations = false;
|
||||||
StringInfo tableSizeQuery = GenerateSizeQueryOnMultiplePlacements(
|
StringInfo relationSizeQuery = GenerateSizeQueryOnMultiplePlacements(
|
||||||
shardIntervalsOnNode,
|
shardIntervalsOnNode,
|
||||||
|
indexId,
|
||||||
sizeQueryType,
|
sizeQueryType,
|
||||||
optimizePartitionCalculations);
|
optimizePartitionCalculations);
|
||||||
|
|
||||||
MultiConnection *connection = GetNodeConnection(connectionFlag, workerNodeName,
|
MultiConnection *connection = GetNodeConnection(connectionFlag, workerNodeName,
|
||||||
workerNodePort);
|
workerNodePort);
|
||||||
int queryResult = ExecuteOptionalRemoteCommand(connection, tableSizeQuery->data,
|
int queryResult = ExecuteOptionalRemoteCommand(connection, relationSizeQuery->data,
|
||||||
&result);
|
&result);
|
||||||
|
|
||||||
if (queryResult != 0)
|
if (queryResult != 0)
|
||||||
{
|
{
|
||||||
ereport(logLevel, (errcode(ERRCODE_CONNECTION_FAILURE),
|
ereport(logLevel, (errcode(ERRCODE_CONNECTION_FAILURE),
|
||||||
errmsg("could not connect to %s:%d to get size of "
|
errmsg("could not connect to %s:%d to get size of "
|
||||||
"table \"%s\"",
|
"relation \"%s\"",
|
||||||
workerNodeName, workerNodePort,
|
workerNodeName, workerNodePort,
|
||||||
get_rel_name(relationId))));
|
get_rel_name(relationId))));
|
||||||
|
|
||||||
|
@ -628,19 +650,19 @@ DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
||||||
ClearResults(connection, failOnError);
|
ClearResults(connection, failOnError);
|
||||||
|
|
||||||
ereport(logLevel, (errcode(ERRCODE_CONNECTION_FAILURE),
|
ereport(logLevel, (errcode(ERRCODE_CONNECTION_FAILURE),
|
||||||
errmsg("cannot parse size of table \"%s\" from %s:%d",
|
errmsg("cannot parse size of relation \"%s\" from %s:%d",
|
||||||
get_rel_name(relationId), workerNodeName,
|
get_rel_name(relationId), workerNodeName,
|
||||||
workerNodePort)));
|
workerNodePort)));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringInfo tableSizeStringInfo = (StringInfo) linitial(sizeList);
|
StringInfo relationSizeStringInfo = (StringInfo) linitial(sizeList);
|
||||||
char *tableSizeString = tableSizeStringInfo->data;
|
char *relationSizeString = relationSizeStringInfo->data;
|
||||||
|
|
||||||
if (strlen(tableSizeString) > 0)
|
if (strlen(relationSizeString) > 0)
|
||||||
{
|
{
|
||||||
*tableSize = SafeStringToUint64(tableSizeString);
|
*relationSize = SafeStringToUint64(relationSizeString);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -649,7 +671,7 @@ DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId,
|
||||||
* being executed. For this case we get an empty string as table size.
|
* being executed. For this case we get an empty string as table size.
|
||||||
* We can take that as zero to prevent any unnecessary errors.
|
* We can take that as zero to prevent any unnecessary errors.
|
||||||
*/
|
*/
|
||||||
*tableSize = 0;
|
*relationSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PQclear(result);
|
PQclear(result);
|
||||||
|
@ -734,7 +756,7 @@ ShardIntervalsOnWorkerGroup(WorkerNode *workerNode, Oid relationId)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GenerateSizeQueryOnMultiplePlacements generates a select size query to get
|
* GenerateSizeQueryOnMultiplePlacements generates a select size query to get
|
||||||
* size of multiple tables. Note that, different size functions supported by PG
|
* size of multiple relations. Note that, different size functions supported by PG
|
||||||
* are also supported by this function changing the size query type given as the
|
* are also supported by this function changing the size query type given as the
|
||||||
* last parameter to function. Depending on the sizeQueryType enum parameter, the
|
* last parameter to function. Depending on the sizeQueryType enum parameter, the
|
||||||
* generated query will call one of the functions: pg_relation_size,
|
* generated query will call one of the functions: pg_relation_size,
|
||||||
|
@ -742,9 +764,13 @@ ShardIntervalsOnWorkerGroup(WorkerNode *workerNode, Oid relationId)
|
||||||
* This function uses UDFs named worker_partitioned_*_size for partitioned tables,
|
* This function uses UDFs named worker_partitioned_*_size for partitioned tables,
|
||||||
* if the parameter optimizePartitionCalculations is true. The UDF to be called is
|
* if the parameter optimizePartitionCalculations is true. The UDF to be called is
|
||||||
* determined by the parameter sizeQueryType.
|
* determined by the parameter sizeQueryType.
|
||||||
|
*
|
||||||
|
* indexId is provided if we're interested in the size of an index, not the whole
|
||||||
|
* table.
|
||||||
*/
|
*/
|
||||||
StringInfo
|
StringInfo
|
||||||
GenerateSizeQueryOnMultiplePlacements(List *shardIntervalList,
|
GenerateSizeQueryOnMultiplePlacements(List *shardIntervalList,
|
||||||
|
Oid indexId,
|
||||||
SizeQueryType sizeQueryType,
|
SizeQueryType sizeQueryType,
|
||||||
bool optimizePartitionCalculations)
|
bool optimizePartitionCalculations)
|
||||||
{
|
{
|
||||||
|
@ -768,16 +794,20 @@ GenerateSizeQueryOnMultiplePlacements(List *shardIntervalList,
|
||||||
*/
|
*/
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* we need to build the shard relation name, being an index or table */
|
||||||
|
Oid objectId = OidIsValid(indexId) ? indexId : shardInterval->relationId;
|
||||||
|
|
||||||
uint64 shardId = shardInterval->shardId;
|
uint64 shardId = shardInterval->shardId;
|
||||||
Oid schemaId = get_rel_namespace(shardInterval->relationId);
|
Oid schemaId = get_rel_namespace(objectId);
|
||||||
char *schemaName = get_namespace_name(schemaId);
|
char *schemaName = get_namespace_name(schemaId);
|
||||||
char *shardName = get_rel_name(shardInterval->relationId);
|
char *shardName = get_rel_name(objectId);
|
||||||
AppendShardIdToName(&shardName, shardId);
|
AppendShardIdToName(&shardName, shardId);
|
||||||
|
|
||||||
char *shardQualifiedName = quote_qualified_identifier(schemaName, shardName);
|
char *shardQualifiedName = quote_qualified_identifier(schemaName, shardName);
|
||||||
char *quotedShardName = quote_literal_cstr(shardQualifiedName);
|
char *quotedShardName = quote_literal_cstr(shardQualifiedName);
|
||||||
|
|
||||||
/* for partitoned tables, we will call worker_partitioned_... size functions */
|
/* for partitioned tables, we will call worker_partitioned_... size functions */
|
||||||
if (optimizePartitionCalculations && PartitionedTable(shardInterval->relationId))
|
if (optimizePartitionCalculations && PartitionedTable(shardInterval->relationId))
|
||||||
{
|
{
|
||||||
partitionedShardNames = lappend(partitionedShardNames, quotedShardName);
|
partitionedShardNames = lappend(partitionedShardNames, quotedShardName);
|
||||||
|
@ -1012,19 +1042,40 @@ AppendShardIdNameValues(StringInfo selectQuery, ShardInterval *shardInterval)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ErrorIfNotSuitableToGetSize determines whether the table is suitable to find
|
* ErrorIfNotSuitableToGetSize determines whether the relation is suitable to find
|
||||||
* its' size with internal functions.
|
* its' size with internal functions.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
ErrorIfNotSuitableToGetSize(Oid relationId)
|
ErrorIfNotSuitableToGetSize(Oid relationId)
|
||||||
{
|
{
|
||||||
if (!IsCitusTable(relationId))
|
if (!IsCitusTable(relationId))
|
||||||
|
{
|
||||||
|
Oid relKind = get_rel_relkind(relationId);
|
||||||
|
if (relKind != RELKIND_INDEX && relKind != RELKIND_PARTITIONED_INDEX)
|
||||||
{
|
{
|
||||||
char *relationName = get_rel_name(relationId);
|
char *relationName = get_rel_name(relationId);
|
||||||
char *escapedQueryString = quote_literal_cstr(relationName);
|
char *escapedRelationName = quote_literal_cstr(relationName);
|
||||||
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
|
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
|
||||||
errmsg("cannot calculate the size because relation %s is not "
|
errmsg(
|
||||||
"distributed", escapedQueryString)));
|
"cannot calculate the size because relation %s "
|
||||||
|
"is not distributed",
|
||||||
|
escapedRelationName)));
|
||||||
|
}
|
||||||
|
bool missingOk = false;
|
||||||
|
Oid indexId = relationId;
|
||||||
|
relationId = IndexGetRelation(relationId, missingOk);
|
||||||
|
if (!IsCitusTable(relationId))
|
||||||
|
{
|
||||||
|
char *tableName = get_rel_name(relationId);
|
||||||
|
char *escapedTableName = quote_literal_cstr(tableName);
|
||||||
|
char *indexName = get_rel_name(indexId);
|
||||||
|
char *escapedIndexName = quote_literal_cstr(indexName);
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||||
|
errmsg(
|
||||||
|
"cannot calculate the size because table %s for "
|
||||||
|
"index %s is not distributed",
|
||||||
|
escapedTableName, escapedIndexName)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2377,14 +2428,32 @@ UpdateNoneDistTableMetadata(Oid relationId, char replicationModel, uint32 coloca
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check that the current user has `mode` permissions on relationId, error out
|
* Check that the current user has `mode` permissions on relationId.
|
||||||
* if not. Superusers always have such permissions.
|
* If not, also check relationId's attributes with `mask`, error out
|
||||||
|
* privileges are not defined.
|
||||||
|
* ACL mask is used because we assume that user has enough privilege
|
||||||
|
* to distribute a table when either ACL_INSERT on the TABLE or
|
||||||
|
* ACL_INSERT on ALL attributes.
|
||||||
|
* In other situations, having a single attribute privilege is enough.
|
||||||
|
* Superusers always have such permissions.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
EnsureTablePermissions(Oid relationId, AclMode mode)
|
EnsureTablePermissions(Oid relationId, AclMode mode, AclMaskHow mask)
|
||||||
{
|
{
|
||||||
AclResult aclresult = pg_class_aclcheck(relationId, GetUserId(), mode);
|
AclResult aclresult = pg_class_aclcheck(relationId, GetUserId(), mode);
|
||||||
|
|
||||||
|
if (aclresult == ACLCHECK_OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Also check the attributes: for example "GRANT ALL(a)" has no table level
|
||||||
|
* right but user is still allowed to lock table as needed. PostgreSQL will
|
||||||
|
* still enforce ACL later so it's safe.
|
||||||
|
*/
|
||||||
|
aclresult = pg_attribute_aclcheck_all(relationId, GetUserId(), mode, mask);
|
||||||
|
|
||||||
if (aclresult != ACLCHECK_OK)
|
if (aclresult != ACLCHECK_OK)
|
||||||
{
|
{
|
||||||
aclcheck_error(aclresult, OBJECT_TABLE, get_rel_name(relationId));
|
aclcheck_error(aclresult, OBJECT_TABLE, get_rel_name(relationId));
|
||||||
|
|
|
@ -167,6 +167,7 @@ PG_FUNCTION_INFO_V1(citus_nodeport_for_nodeid);
|
||||||
PG_FUNCTION_INFO_V1(citus_coordinator_nodeid);
|
PG_FUNCTION_INFO_V1(citus_coordinator_nodeid);
|
||||||
PG_FUNCTION_INFO_V1(citus_is_coordinator);
|
PG_FUNCTION_INFO_V1(citus_is_coordinator);
|
||||||
PG_FUNCTION_INFO_V1(citus_internal_mark_node_not_synced);
|
PG_FUNCTION_INFO_V1(citus_internal_mark_node_not_synced);
|
||||||
|
PG_FUNCTION_INFO_V1(citus_is_primary_node);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DefaultNodeMetadata creates a NodeMetadata struct with the fields set to
|
* DefaultNodeMetadata creates a NodeMetadata struct with the fields set to
|
||||||
|
@ -1521,7 +1522,7 @@ get_shard_id_for_distribution_column(PG_FUNCTION_ARGS)
|
||||||
}
|
}
|
||||||
|
|
||||||
Oid relationId = PG_GETARG_OID(0);
|
Oid relationId = PG_GETARG_OID(0);
|
||||||
EnsureTablePermissions(relationId, ACL_SELECT);
|
EnsureTablePermissions(relationId, ACL_SELECT, ACLMASK_ANY);
|
||||||
|
|
||||||
if (!IsCitusTable(relationId))
|
if (!IsCitusTable(relationId))
|
||||||
{
|
{
|
||||||
|
@ -1665,6 +1666,36 @@ citus_is_coordinator(PG_FUNCTION_ARGS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* citus_is_primary_node returns whether the current node is a primary for
|
||||||
|
* a given group_id. We consider the node a primary if it has
|
||||||
|
* pg_dist_node entries marked as primary
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
citus_is_primary_node(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
CheckCitusVersion(ERROR);
|
||||||
|
|
||||||
|
int32 groupId = GetLocalGroupId();
|
||||||
|
WorkerNode *workerNode = PrimaryNodeForGroup(groupId, NULL);
|
||||||
|
if (workerNode == NULL)
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("could not find the current node in pg_dist_node"),
|
||||||
|
errdetail("If this is the coordinator node, consider adding it "
|
||||||
|
"into the metadata by using citus_set_coordinator_host() "
|
||||||
|
"UDF. Otherwise, if you're going to use this node as a "
|
||||||
|
"worker node for a new cluster, make sure to add this "
|
||||||
|
"node into the metadata from the coordinator by using "
|
||||||
|
"citus_add_node() UDF.")));
|
||||||
|
PG_RETURN_NULL();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPrimary = workerNode->nodeId == GetLocalNodeId();
|
||||||
|
|
||||||
|
PG_RETURN_BOOL(isPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EnsureParentSessionHasExclusiveLockOnPgDistNode ensures given session id
|
* EnsureParentSessionHasExclusiveLockOnPgDistNode ensures given session id
|
||||||
* holds Exclusive lock on pg_dist_node.
|
* holds Exclusive lock on pg_dist_node.
|
||||||
|
@ -1701,7 +1732,7 @@ EnsureParentSessionHasExclusiveLockOnPgDistNode(pid_t parentSessionPid)
|
||||||
if (!parentHasExclusiveLock)
|
if (!parentHasExclusiveLock)
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errmsg("lock is not held by the caller. Unexpected caller "
|
ereport(ERROR, (errmsg("lock is not held by the caller. Unexpected caller "
|
||||||
"for citus_internal_mark_node_not_synced")));
|
"for citus_internal.mark_node_not_synced")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1760,6 +1791,10 @@ citus_internal_mark_node_not_synced(PG_FUNCTION_ARGS)
|
||||||
/*
|
/*
|
||||||
* FindWorkerNode searches over the worker nodes and returns the workerNode
|
* FindWorkerNode searches over the worker nodes and returns the workerNode
|
||||||
* if it already exists. Else, the function returns NULL.
|
* if it already exists. Else, the function returns NULL.
|
||||||
|
*
|
||||||
|
* NOTE: A special case that this handles is when nodeName and nodePort are set
|
||||||
|
* to LocalHostName and PostPortNumber. In that case we return the primary node
|
||||||
|
* for the local group.
|
||||||
*/
|
*/
|
||||||
WorkerNode *
|
WorkerNode *
|
||||||
FindWorkerNode(const char *nodeName, int32 nodePort)
|
FindWorkerNode(const char *nodeName, int32 nodePort)
|
||||||
|
@ -1782,6 +1817,11 @@ FindWorkerNode(const char *nodeName, int32 nodePort)
|
||||||
return workerNode;
|
return workerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(LocalHostName, nodeName) == 0 && nodePort == PostPortNumber)
|
||||||
|
{
|
||||||
|
return PrimaryNodeForGroup(GetLocalGroupId(), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2752,6 +2792,25 @@ EnsureCoordinator(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EnsurePropagationToCoordinator checks whether the coordinator is added to the
|
||||||
|
* metadata if we're not on the coordinator.
|
||||||
|
*
|
||||||
|
* Given that metadata syncing skips syncing metadata to the coordinator, we need
|
||||||
|
* too make sure that the coordinator is added to the metadata before propagating
|
||||||
|
* a command from a worker. For this reason, today we use this only for the commands
|
||||||
|
* that we support propagating from workers.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
EnsurePropagationToCoordinator(void)
|
||||||
|
{
|
||||||
|
if (!IsCoordinator())
|
||||||
|
{
|
||||||
|
EnsureCoordinatorIsInMetadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EnsureCoordinatorIsInMetadata checks whether the coordinator is added to the
|
* EnsureCoordinatorIsInMetadata checks whether the coordinator is added to the
|
||||||
* metadata, which is required for many operations.
|
* metadata, which is required for many operations.
|
||||||
|
@ -2761,12 +2820,24 @@ EnsureCoordinatorIsInMetadata(void)
|
||||||
{
|
{
|
||||||
bool isCoordinatorInMetadata = false;
|
bool isCoordinatorInMetadata = false;
|
||||||
PrimaryNodeForGroup(COORDINATOR_GROUP_ID, &isCoordinatorInMetadata);
|
PrimaryNodeForGroup(COORDINATOR_GROUP_ID, &isCoordinatorInMetadata);
|
||||||
if (!isCoordinatorInMetadata)
|
if (isCoordinatorInMetadata)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* be more descriptive when we're not on coordinator */
|
||||||
|
if (IsCoordinator())
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errmsg("coordinator is not added to the metadata"),
|
ereport(ERROR, (errmsg("coordinator is not added to the metadata"),
|
||||||
errhint("Use SELECT citus_set_coordinator_host('<hostname>') "
|
errhint("Use SELECT citus_set_coordinator_host('<hostname>') "
|
||||||
"to configure the coordinator hostname")));
|
"to configure the coordinator hostname")));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("coordinator is not added to the metadata"),
|
||||||
|
errhint("Use SELECT citus_set_coordinator_host('<hostname>') "
|
||||||
|
"on coordinator to configure the coordinator hostname")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -160,13 +160,6 @@ CreateShardsWithRoundRobinPolicy(Oid distributedTableId, int32 shardCount,
|
||||||
"replication factor.")));
|
"replication factor.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if we have enough nodes, add an extra placement attempt for backup */
|
|
||||||
uint32 placementAttemptCount = (uint32) replicationFactor;
|
|
||||||
if (workerNodeCount > replicationFactor)
|
|
||||||
{
|
|
||||||
placementAttemptCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* set shard storage type according to relation type */
|
/* set shard storage type according to relation type */
|
||||||
char shardStorageType = ShardStorageType(distributedTableId);
|
char shardStorageType = ShardStorageType(distributedTableId);
|
||||||
|
|
||||||
|
|
|
@ -426,10 +426,9 @@ ExecuteDropShardPlacementCommandRemotely(ShardPlacement *shardPlacement,
|
||||||
errdetail("Marking this shard placement for "
|
errdetail("Marking this shard placement for "
|
||||||
"deletion")));
|
"deletion")));
|
||||||
|
|
||||||
InsertCleanupRecordInCurrentTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupOnSuccessRecordInCurrentTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
shardRelationName,
|
shardRelationName,
|
||||||
shardPlacement->groupId,
|
shardPlacement->groupId);
|
||||||
CLEANUP_DEFERRED_ON_SUCCESS);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include "catalog/pg_constraint.h"
|
#include "catalog/pg_constraint.h"
|
||||||
#include "catalog/pg_index.h"
|
#include "catalog/pg_index.h"
|
||||||
#include "catalog/pg_namespace.h"
|
#include "catalog/pg_namespace.h"
|
||||||
|
#include "catalog/pg_seclabel.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/sequence.h"
|
#include "commands/sequence.h"
|
||||||
#include "foreign/foreign.h"
|
#include "foreign/foreign.h"
|
||||||
|
@ -57,6 +58,7 @@
|
||||||
#include "distributed/citus_ruleutils.h"
|
#include "distributed/citus_ruleutils.h"
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
#include "distributed/coordinator_protocol.h"
|
#include "distributed/coordinator_protocol.h"
|
||||||
|
#include "distributed/deparser.h"
|
||||||
#include "distributed/listutils.h"
|
#include "distributed/listutils.h"
|
||||||
#include "distributed/metadata_cache.h"
|
#include "distributed/metadata_cache.h"
|
||||||
#include "distributed/metadata_sync.h"
|
#include "distributed/metadata_sync.h"
|
||||||
|
@ -83,6 +85,7 @@ static char * CitusCreateAlterColumnarTableSet(char *qualifiedRelationName,
|
||||||
const ColumnarOptions *options);
|
const ColumnarOptions *options);
|
||||||
static char * GetTableDDLCommandColumnar(void *context);
|
static char * GetTableDDLCommandColumnar(void *context);
|
||||||
static TableDDLCommand * ColumnarGetTableOptionsDDL(Oid relationId);
|
static TableDDLCommand * ColumnarGetTableOptionsDDL(Oid relationId);
|
||||||
|
static List * CreateSecurityLabelCommands(Oid relationId);
|
||||||
|
|
||||||
/* exports for SQL callable functions */
|
/* exports for SQL callable functions */
|
||||||
PG_FUNCTION_INFO_V1(master_get_table_metadata);
|
PG_FUNCTION_INFO_V1(master_get_table_metadata);
|
||||||
|
@ -665,6 +668,9 @@ GetPreLoadTableCreationCommands(Oid relationId,
|
||||||
List *policyCommands = CreatePolicyCommands(relationId);
|
List *policyCommands = CreatePolicyCommands(relationId);
|
||||||
tableDDLEventList = list_concat(tableDDLEventList, policyCommands);
|
tableDDLEventList = list_concat(tableDDLEventList, policyCommands);
|
||||||
|
|
||||||
|
List *securityLabelCommands = CreateSecurityLabelCommands(relationId);
|
||||||
|
tableDDLEventList = list_concat(tableDDLEventList, securityLabelCommands);
|
||||||
|
|
||||||
/* revert back to original search_path */
|
/* revert back to original search_path */
|
||||||
PopEmptySearchPath(saveNestLevel);
|
PopEmptySearchPath(saveNestLevel);
|
||||||
|
|
||||||
|
@ -833,6 +839,109 @@ GetTableRowLevelSecurityCommands(Oid relationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateSecurityLabelCommands - return the SECURITY LABEL commands on
|
||||||
|
* the table identified by relationId. It is used by GetPreLoadTableCreationCommands()
|
||||||
|
* to reconstruct the security labels on the table and its columns.
|
||||||
|
*/
|
||||||
|
static List *
|
||||||
|
CreateSecurityLabelCommands(Oid relationId)
|
||||||
|
{
|
||||||
|
List *securityLabelCommands = NIL;
|
||||||
|
|
||||||
|
if (!RegularTable(relationId)) /* should be an Assert ? */
|
||||||
|
{
|
||||||
|
return securityLabelCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
Relation pg_seclabel = table_open(SecLabelRelationId, AccessShareLock);
|
||||||
|
ScanKeyData skey[1];
|
||||||
|
ScanKeyInit(&skey[0], Anum_pg_seclabel_objoid, BTEqualStrategyNumber, F_OIDEQ,
|
||||||
|
ObjectIdGetDatum(relationId));
|
||||||
|
SysScanDesc scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId,
|
||||||
|
true, NULL, 1, &skey[0]);
|
||||||
|
HeapTuple tuple = NULL;
|
||||||
|
List *table_name = NIL;
|
||||||
|
Relation relation = NULL;
|
||||||
|
TupleDesc tupleDescriptor = NULL;
|
||||||
|
List *securityLabelStmts = NULL;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
|
||||||
|
{
|
||||||
|
SecLabelStmt *secLabelStmt = makeNode(SecLabelStmt);
|
||||||
|
|
||||||
|
if (relation == NULL)
|
||||||
|
{
|
||||||
|
relation = relation_open(relationId, AccessShareLock);
|
||||||
|
if (!RelationIsVisible(relationId))
|
||||||
|
{
|
||||||
|
char *nsname = get_namespace_name(RelationGetNamespace(relation));
|
||||||
|
table_name = lappend(table_name, makeString(nsname));
|
||||||
|
}
|
||||||
|
char *relname = get_rel_name(relationId);
|
||||||
|
table_name = lappend(table_name, makeString(relname));
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum datumArray[Natts_pg_seclabel];
|
||||||
|
bool isNullArray[Natts_pg_seclabel];
|
||||||
|
|
||||||
|
heap_deform_tuple(tuple, RelationGetDescr(pg_seclabel), datumArray,
|
||||||
|
isNullArray);
|
||||||
|
int subObjectId = DatumGetInt32(
|
||||||
|
datumArray[Anum_pg_seclabel_objsubid - 1]);
|
||||||
|
secLabelStmt->provider = TextDatumGetCString(
|
||||||
|
datumArray[Anum_pg_seclabel_provider - 1]);
|
||||||
|
secLabelStmt->label = TextDatumGetCString(
|
||||||
|
datumArray[Anum_pg_seclabel_label - 1]);
|
||||||
|
|
||||||
|
if (subObjectId > 0)
|
||||||
|
{
|
||||||
|
/* Its a column; construct the name */
|
||||||
|
secLabelStmt->objtype = OBJECT_COLUMN;
|
||||||
|
List *col_name = list_copy(table_name);
|
||||||
|
|
||||||
|
if (tupleDescriptor == NULL)
|
||||||
|
{
|
||||||
|
tupleDescriptor = RelationGetDescr(relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Form_pg_attribute attrForm = TupleDescAttr(tupleDescriptor, subObjectId - 1);
|
||||||
|
char *attributeName = NameStr(attrForm->attname);
|
||||||
|
col_name = lappend(col_name, makeString(attributeName));
|
||||||
|
|
||||||
|
secLabelStmt->object = (Node *) col_name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert(subObjectId == 0);
|
||||||
|
secLabelStmt->objtype = OBJECT_TABLE;
|
||||||
|
secLabelStmt->object = (Node *) table_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
securityLabelStmts = lappend(securityLabelStmts, secLabelStmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(lc, securityLabelStmts)
|
||||||
|
{
|
||||||
|
Node *stmt = (Node *) lfirst(lc);
|
||||||
|
char *secLabelStmtString = DeparseTreeNode(stmt);
|
||||||
|
TableDDLCommand *secLabelCommand = makeTableDDLCommandString(secLabelStmtString);
|
||||||
|
securityLabelCommands = lappend(securityLabelCommands, secLabelCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
systable_endscan(scan);
|
||||||
|
table_close(pg_seclabel, AccessShareLock);
|
||||||
|
|
||||||
|
if (relation != NULL)
|
||||||
|
{
|
||||||
|
relation_close(relation, AccessShareLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return securityLabelCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IndexImpliedByAConstraint is a helper function to be used while scanning
|
* IndexImpliedByAConstraint is a helper function to be used while scanning
|
||||||
* pg_index. It returns true if the index identified by the given indexForm is
|
* pg_index. It returns true if the index identified by the given indexForm is
|
||||||
|
|
|
@ -92,6 +92,8 @@ static bool TryDropReplicationSlotOutsideTransaction(char *replicationSlotName,
|
||||||
char *nodeName,
|
char *nodeName,
|
||||||
int nodePort);
|
int nodePort);
|
||||||
static bool TryDropUserOutsideTransaction(char *username, char *nodeName, int nodePort);
|
static bool TryDropUserOutsideTransaction(char *username, char *nodeName, int nodePort);
|
||||||
|
static bool TryDropDatabaseOutsideTransaction(char *databaseName, char *nodeName,
|
||||||
|
int nodePort);
|
||||||
|
|
||||||
static CleanupRecord * GetCleanupRecordByNameAndType(char *objectName,
|
static CleanupRecord * GetCleanupRecordByNameAndType(char *objectName,
|
||||||
CleanupObject type);
|
CleanupObject type);
|
||||||
|
@ -141,7 +143,6 @@ Datum
|
||||||
citus_cleanup_orphaned_resources(PG_FUNCTION_ARGS)
|
citus_cleanup_orphaned_resources(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
CheckCitusVersion(ERROR);
|
CheckCitusVersion(ERROR);
|
||||||
EnsureCoordinator();
|
|
||||||
PreventInTransactionBlock(true, "citus_cleanup_orphaned_resources");
|
PreventInTransactionBlock(true, "citus_cleanup_orphaned_resources");
|
||||||
|
|
||||||
int droppedCount = DropOrphanedResourcesForCleanup();
|
int droppedCount = DropOrphanedResourcesForCleanup();
|
||||||
|
@ -245,12 +246,6 @@ TryDropOrphanedResources()
|
||||||
static int
|
static int
|
||||||
DropOrphanedResourcesForCleanup()
|
DropOrphanedResourcesForCleanup()
|
||||||
{
|
{
|
||||||
/* Only runs on Coordinator */
|
|
||||||
if (!IsCoordinator())
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
List *cleanupRecordList = ListCleanupRecords();
|
List *cleanupRecordList = ListCleanupRecords();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -452,15 +447,15 @@ CompareCleanupRecordsByObjectType(const void *leftElement, const void *rightElem
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* InsertCleanupRecordInCurrentTransaction inserts a new pg_dist_cleanup entry
|
* InsertCleanupOnSuccessRecordInCurrentTransaction inserts a new pg_dist_cleanup entry
|
||||||
* as part of the current transaction. This is primarily useful for deferred drop scenarios,
|
* as part of the current transaction. This is primarily useful for deferred drop scenarios,
|
||||||
* since these records would roll back in case of operation failure.
|
* since these records would roll back in case of operation failure. And for the same reason,
|
||||||
|
* always sets the policy type to CLEANUP_DEFERRED_ON_SUCCESS.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
InsertCleanupRecordInCurrentTransaction(CleanupObject objectType,
|
InsertCleanupOnSuccessRecordInCurrentTransaction(CleanupObject objectType,
|
||||||
char *objectName,
|
char *objectName,
|
||||||
int nodeGroupId,
|
int nodeGroupId)
|
||||||
CleanupPolicy policy)
|
|
||||||
{
|
{
|
||||||
/* We must have a valid OperationId. Any operation requring cleanup
|
/* We must have a valid OperationId. Any operation requring cleanup
|
||||||
* will call RegisterOperationNeedingCleanup.
|
* will call RegisterOperationNeedingCleanup.
|
||||||
|
@ -482,7 +477,8 @@ InsertCleanupRecordInCurrentTransaction(CleanupObject objectType,
|
||||||
values[Anum_pg_dist_cleanup_object_type - 1] = Int32GetDatum(objectType);
|
values[Anum_pg_dist_cleanup_object_type - 1] = Int32GetDatum(objectType);
|
||||||
values[Anum_pg_dist_cleanup_object_name - 1] = CStringGetTextDatum(objectName);
|
values[Anum_pg_dist_cleanup_object_name - 1] = CStringGetTextDatum(objectName);
|
||||||
values[Anum_pg_dist_cleanup_node_group_id - 1] = Int32GetDatum(nodeGroupId);
|
values[Anum_pg_dist_cleanup_node_group_id - 1] = Int32GetDatum(nodeGroupId);
|
||||||
values[Anum_pg_dist_cleanup_policy_type - 1] = Int32GetDatum(policy);
|
values[Anum_pg_dist_cleanup_policy_type - 1] =
|
||||||
|
Int32GetDatum(CLEANUP_DEFERRED_ON_SUCCESS);
|
||||||
|
|
||||||
/* open cleanup relation and insert new tuple */
|
/* open cleanup relation and insert new tuple */
|
||||||
Oid relationId = DistCleanupRelationId();
|
Oid relationId = DistCleanupRelationId();
|
||||||
|
@ -499,14 +495,15 @@ InsertCleanupRecordInCurrentTransaction(CleanupObject objectType,
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* InsertCleanupRecordInSubtransaction inserts a new pg_dist_cleanup entry in a
|
* InsertCleanupRecordOutsideTransaction inserts a new pg_dist_cleanup entry in a
|
||||||
* separate transaction to ensure the record persists after rollback. We should
|
* separate transaction to ensure the record persists after rollback. We should
|
||||||
* delete these records if the operation completes successfully.
|
* delete these records if the operation completes successfully.
|
||||||
*
|
*
|
||||||
* For failure scenarios, use a subtransaction (direct insert via localhost).
|
* This is used in scenarios where we need to cleanup resources on operation
|
||||||
|
* completion (CLEANUP_ALWAYS) or on failure (CLEANUP_ON_FAILURE).
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
InsertCleanupRecordInSubtransaction(CleanupObject objectType,
|
InsertCleanupRecordOutsideTransaction(CleanupObject objectType,
|
||||||
char *objectName,
|
char *objectName,
|
||||||
int nodeGroupId,
|
int nodeGroupId,
|
||||||
CleanupPolicy policy)
|
CleanupPolicy policy)
|
||||||
|
@ -516,6 +513,9 @@ InsertCleanupRecordInSubtransaction(CleanupObject objectType,
|
||||||
*/
|
*/
|
||||||
Assert(CurrentOperationId != INVALID_OPERATION_ID);
|
Assert(CurrentOperationId != INVALID_OPERATION_ID);
|
||||||
|
|
||||||
|
/* assert the circumstance noted in function comment */
|
||||||
|
Assert(policy == CLEANUP_ALWAYS || policy == CLEANUP_ON_FAILURE);
|
||||||
|
|
||||||
StringInfo sequenceName = makeStringInfo();
|
StringInfo sequenceName = makeStringInfo();
|
||||||
appendStringInfo(sequenceName, "%s.%s",
|
appendStringInfo(sequenceName, "%s.%s",
|
||||||
PG_CATALOG,
|
PG_CATALOG,
|
||||||
|
@ -603,6 +603,12 @@ TryDropResourceByCleanupRecordOutsideTransaction(CleanupRecord *record,
|
||||||
return TryDropUserOutsideTransaction(record->objectName, nodeName, nodePort);
|
return TryDropUserOutsideTransaction(record->objectName, nodeName, nodePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CLEANUP_OBJECT_DATABASE:
|
||||||
|
{
|
||||||
|
return TryDropDatabaseOutsideTransaction(record->objectName, nodeName,
|
||||||
|
nodePort);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
ereport(WARNING, (errmsg(
|
ereport(WARNING, (errmsg(
|
||||||
|
@ -883,6 +889,69 @@ TryDropUserOutsideTransaction(char *username,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TryDropDatabaseOutsideTransaction drops the database with the given name
|
||||||
|
* if it exists.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
TryDropDatabaseOutsideTransaction(char *databaseName, char *nodeName, int nodePort)
|
||||||
|
{
|
||||||
|
int connectionFlags = (OUTSIDE_TRANSACTION | FORCE_NEW_CONNECTION);
|
||||||
|
MultiConnection *connection = GetNodeUserDatabaseConnection(connectionFlags,
|
||||||
|
nodeName, nodePort,
|
||||||
|
CitusExtensionOwnerName(),
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
if (PQstatus(connection->pgConn) != CONNECTION_OK)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We want to disable DDL propagation and set lock_timeout before issuing
|
||||||
|
* the DROP DATABASE command but we cannot do so in a way that's scoped
|
||||||
|
* to the DROP DATABASE command. This is because, we cannot use a
|
||||||
|
* transaction block for the DROP DATABASE command.
|
||||||
|
*
|
||||||
|
* For this reason, to avoid leaking the lock_timeout and DDL propagation
|
||||||
|
* settings to future commands, we force the connection to close at the end
|
||||||
|
* of the transaction.
|
||||||
|
*/
|
||||||
|
ForceConnectionCloseAtTransactionEnd(connection);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The DROP DATABASE command should not propagate, so we disable DDL
|
||||||
|
* propagation.
|
||||||
|
*/
|
||||||
|
List *commandList = list_make3(
|
||||||
|
"SET lock_timeout TO '1s'",
|
||||||
|
"SET citus.enable_ddl_propagation TO OFF;",
|
||||||
|
psprintf("DROP DATABASE IF EXISTS %s;", quote_identifier(databaseName))
|
||||||
|
);
|
||||||
|
|
||||||
|
bool executeCommand = true;
|
||||||
|
|
||||||
|
const char *commandString = NULL;
|
||||||
|
foreach_declared_ptr(commandString, commandList)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Cannot use SendOptionalCommandListToWorkerOutsideTransactionWithConnection()
|
||||||
|
* because we don't want to open a transaction block on remote nodes as DROP
|
||||||
|
* DATABASE commands cannot be run inside a transaction block.
|
||||||
|
*/
|
||||||
|
if (ExecuteOptionalRemoteCommand(connection, commandString, NULL) !=
|
||||||
|
RESPONSE_OKAY)
|
||||||
|
{
|
||||||
|
executeCommand = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseConnection(connection);
|
||||||
|
return executeCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ErrorIfCleanupRecordForShardExists errors out if a cleanup record for the given
|
* ErrorIfCleanupRecordForShardExists errors out if a cleanup record for the given
|
||||||
* shard name exists.
|
* shard name exists.
|
||||||
|
|
|
@ -733,7 +733,7 @@ CreateSplitShardsForShardGroup(List *shardGroupSplitIntervalListList,
|
||||||
workerPlacementNode->workerPort)));
|
workerPlacementNode->workerPort)));
|
||||||
}
|
}
|
||||||
|
|
||||||
InsertCleanupRecordInSubtransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupRecordOutsideTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
ConstructQualifiedShardName(
|
ConstructQualifiedShardName(
|
||||||
shardInterval),
|
shardInterval),
|
||||||
workerPlacementNode->groupId,
|
workerPlacementNode->groupId,
|
||||||
|
@ -1314,7 +1314,7 @@ DropShardListMetadata(List *shardIntervalList)
|
||||||
{
|
{
|
||||||
ListCell *commandCell = NULL;
|
ListCell *commandCell = NULL;
|
||||||
|
|
||||||
/* send the commands one by one (calls citus_internal_delete_shard_metadata internally) */
|
/* send the commands one by one (calls citus_internal.delete_shard_metadata internally) */
|
||||||
List *shardMetadataDeleteCommandList = ShardDeleteCommandList(shardInterval);
|
List *shardMetadataDeleteCommandList = ShardDeleteCommandList(shardInterval);
|
||||||
foreach(commandCell, shardMetadataDeleteCommandList)
|
foreach(commandCell, shardMetadataDeleteCommandList)
|
||||||
{
|
{
|
||||||
|
@ -1717,7 +1717,7 @@ CreateDummyShardsForShardGroup(HTAB *mapOfPlacementToDummyShardList,
|
||||||
/* Log shard in pg_dist_cleanup. Given dummy shards are transient resources,
|
/* Log shard in pg_dist_cleanup. Given dummy shards are transient resources,
|
||||||
* we want to cleanup irrespective of operation success or failure.
|
* we want to cleanup irrespective of operation success or failure.
|
||||||
*/
|
*/
|
||||||
InsertCleanupRecordInSubtransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupRecordOutsideTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
ConstructQualifiedShardName(
|
ConstructQualifiedShardName(
|
||||||
shardInterval),
|
shardInterval),
|
||||||
workerPlacementNode->groupId,
|
workerPlacementNode->groupId,
|
||||||
|
@ -1780,7 +1780,7 @@ CreateDummyShardsForShardGroup(HTAB *mapOfPlacementToDummyShardList,
|
||||||
/* Log shard in pg_dist_cleanup. Given dummy shards are transient resources,
|
/* Log shard in pg_dist_cleanup. Given dummy shards are transient resources,
|
||||||
* we want to cleanup irrespective of operation success or failure.
|
* we want to cleanup irrespective of operation success or failure.
|
||||||
*/
|
*/
|
||||||
InsertCleanupRecordInSubtransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupRecordOutsideTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
ConstructQualifiedShardName(
|
ConstructQualifiedShardName(
|
||||||
shardInterval),
|
shardInterval),
|
||||||
sourceWorkerNode->groupId,
|
sourceWorkerNode->groupId,
|
||||||
|
|
|
@ -604,10 +604,10 @@ InsertDeferredDropCleanupRecordsForShards(List *shardIntervalList)
|
||||||
* We also log cleanup record in the current transaction. If the current transaction rolls back,
|
* We also log cleanup record in the current transaction. If the current transaction rolls back,
|
||||||
* we do not generate a record at all.
|
* we do not generate a record at all.
|
||||||
*/
|
*/
|
||||||
InsertCleanupRecordInCurrentTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupOnSuccessRecordInCurrentTransaction(
|
||||||
|
CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
qualifiedShardName,
|
qualifiedShardName,
|
||||||
placement->groupId,
|
placement->groupId);
|
||||||
CLEANUP_DEFERRED_ON_SUCCESS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -634,10 +634,9 @@ InsertCleanupRecordsForShardPlacementsOnNode(List *shardIntervalList,
|
||||||
* We also log cleanup record in the current transaction. If the current transaction rolls back,
|
* We also log cleanup record in the current transaction. If the current transaction rolls back,
|
||||||
* we do not generate a record at all.
|
* we do not generate a record at all.
|
||||||
*/
|
*/
|
||||||
InsertCleanupRecordInCurrentTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupOnSuccessRecordInCurrentTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
qualifiedShardName,
|
qualifiedShardName,
|
||||||
groupId,
|
groupId);
|
||||||
CLEANUP_DEFERRED_ON_SUCCESS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -804,7 +803,12 @@ ShardListSizeInBytes(List *shardList, char *workerNodeName, uint32
|
||||||
|
|
||||||
/* we skip child tables of a partitioned table if this boolean variable is true */
|
/* we skip child tables of a partitioned table if this boolean variable is true */
|
||||||
bool optimizePartitionCalculations = true;
|
bool optimizePartitionCalculations = true;
|
||||||
|
|
||||||
|
/* we're interested in whole table, not a particular index */
|
||||||
|
Oid indexId = InvalidOid;
|
||||||
|
|
||||||
StringInfo tableSizeQuery = GenerateSizeQueryOnMultiplePlacements(shardList,
|
StringInfo tableSizeQuery = GenerateSizeQueryOnMultiplePlacements(shardList,
|
||||||
|
indexId,
|
||||||
TOTAL_RELATION_SIZE,
|
TOTAL_RELATION_SIZE,
|
||||||
optimizePartitionCalculations);
|
optimizePartitionCalculations);
|
||||||
|
|
||||||
|
@ -1388,9 +1392,10 @@ CopyShardTablesViaLogicalReplication(List *shardIntervalList, char *sourceNodeNa
|
||||||
char *tableOwner = TableOwner(shardInterval->relationId);
|
char *tableOwner = TableOwner(shardInterval->relationId);
|
||||||
|
|
||||||
/* drop the shard we created on the target, in case of failure */
|
/* drop the shard we created on the target, in case of failure */
|
||||||
InsertCleanupRecordInSubtransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupRecordOutsideTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
ConstructQualifiedShardName(shardInterval),
|
ConstructQualifiedShardName(shardInterval),
|
||||||
GroupForNode(targetNodeName, targetNodePort),
|
GroupForNode(targetNodeName,
|
||||||
|
targetNodePort),
|
||||||
CLEANUP_ON_FAILURE);
|
CLEANUP_ON_FAILURE);
|
||||||
|
|
||||||
SendCommandListToWorkerOutsideTransaction(targetNodeName, targetNodePort,
|
SendCommandListToWorkerOutsideTransaction(targetNodeName, targetNodePort,
|
||||||
|
@ -1461,9 +1466,10 @@ CopyShardTablesViaBlockWrites(List *shardIntervalList, char *sourceNodeName,
|
||||||
char *tableOwner = TableOwner(shardInterval->relationId);
|
char *tableOwner = TableOwner(shardInterval->relationId);
|
||||||
|
|
||||||
/* drop the shard we created on the target, in case of failure */
|
/* drop the shard we created on the target, in case of failure */
|
||||||
InsertCleanupRecordInSubtransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
InsertCleanupRecordOutsideTransaction(CLEANUP_OBJECT_SHARD_PLACEMENT,
|
||||||
ConstructQualifiedShardName(shardInterval),
|
ConstructQualifiedShardName(shardInterval),
|
||||||
GroupForNode(targetNodeName, targetNodePort),
|
GroupForNode(targetNodeName,
|
||||||
|
targetNodePort),
|
||||||
CLEANUP_ON_FAILURE);
|
CLEANUP_ON_FAILURE);
|
||||||
|
|
||||||
SendCommandListToWorkerOutsideTransaction(targetNodeName, targetNodePort,
|
SendCommandListToWorkerOutsideTransaction(targetNodeName, targetNodePort,
|
||||||
|
@ -2041,7 +2047,7 @@ UpdateColocatedShardPlacementMetadataOnWorkers(int64 shardId,
|
||||||
StringInfo updateCommand = makeStringInfo();
|
StringInfo updateCommand = makeStringInfo();
|
||||||
|
|
||||||
appendStringInfo(updateCommand,
|
appendStringInfo(updateCommand,
|
||||||
"SELECT citus_internal_update_placement_metadata(%ld, %d, %d)",
|
"SELECT citus_internal.update_placement_metadata(%ld, %d, %d)",
|
||||||
colocatedShard->shardId,
|
colocatedShard->shardId,
|
||||||
sourceGroupId, targetGroupId);
|
sourceGroupId, targetGroupId);
|
||||||
SendCommandToWorkersWithMetadata(updateCommand->data);
|
SendCommandToWorkersWithMetadata(updateCommand->data);
|
||||||
|
|
|
@ -108,7 +108,7 @@ master_create_empty_shard(PG_FUNCTION_ARGS)
|
||||||
|
|
||||||
Oid relationId = ResolveRelationId(relationNameText, false);
|
Oid relationId = ResolveRelationId(relationNameText, false);
|
||||||
|
|
||||||
EnsureTablePermissions(relationId, ACL_INSERT);
|
EnsureTablePermissions(relationId, ACL_INSERT, ACLMASK_ALL);
|
||||||
CheckDistributedTable(relationId);
|
CheckDistributedTable(relationId);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -40,6 +40,15 @@ worker_copy_table_to_node(PG_FUNCTION_ARGS)
|
||||||
Oid relationId = PG_GETARG_OID(0);
|
Oid relationId = PG_GETARG_OID(0);
|
||||||
uint32_t targetNodeId = PG_GETARG_INT32(1);
|
uint32_t targetNodeId = PG_GETARG_INT32(1);
|
||||||
|
|
||||||
|
if (IsCitusTable(relationId))
|
||||||
|
{
|
||||||
|
char *qualifiedRelationName = generate_qualified_relation_name(relationId);
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("table %s is a Citus table, only copies of "
|
||||||
|
"shards or regular postgres tables are supported",
|
||||||
|
qualifiedRelationName)));
|
||||||
|
}
|
||||||
|
|
||||||
Oid schemaOid = get_rel_namespace(relationId);
|
Oid schemaOid = get_rel_namespace(relationId);
|
||||||
char *relationSchemaName = get_namespace_name(schemaOid);
|
char *relationSchemaName = get_namespace_name(schemaOid);
|
||||||
char *relationName = get_rel_name(relationId);
|
char *relationName = get_rel_name(relationId);
|
||||||
|
|
|
@ -182,7 +182,7 @@ ActivePrimaryNodeList(LOCKMODE lockMode)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ActivePrimaryRemoteNodeList returns a list of all active primary nodes in
|
* ActivePrimaryRemoteNodeList returns a list of all active primary nodes in
|
||||||
* workerNodeHash.
|
* workerNodeHash except the local one.
|
||||||
*/
|
*/
|
||||||
List *
|
List *
|
||||||
ActivePrimaryRemoteNodeList(LOCKMODE lockMode)
|
ActivePrimaryRemoteNodeList(LOCKMODE lockMode)
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
#include "distributed/multi_physical_planner.h"
|
#include "distributed/multi_physical_planner.h"
|
||||||
#include "distributed/multi_router_planner.h"
|
#include "distributed/multi_router_planner.h"
|
||||||
#include "distributed/shard_utils.h"
|
#include "distributed/shard_utils.h"
|
||||||
#include "distributed/utils/citus_stat_tenants.h"
|
#include "distributed/stats/stat_tenants.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,18 @@
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
#include "nodes/pg_list.h"
|
#include "nodes/pg_list.h"
|
||||||
|
#include "optimizer/optimizer.h"
|
||||||
|
#include "optimizer/pathnode.h"
|
||||||
|
#include "optimizer/plancat.h"
|
||||||
|
#include "optimizer/planmain.h"
|
||||||
|
#include "optimizer/planner.h"
|
||||||
|
#include "parser/parse_type.h"
|
||||||
|
#include "parser/parsetree.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/datum.h"
|
||||||
|
#include "utils/lsyscache.h"
|
||||||
|
#include "utils/memutils.h"
|
||||||
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
#include "pg_version_constants.h"
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
|
@ -54,24 +66,13 @@
|
||||||
#include "distributed/recursive_planning.h"
|
#include "distributed/recursive_planning.h"
|
||||||
#include "distributed/shard_utils.h"
|
#include "distributed/shard_utils.h"
|
||||||
#include "distributed/shardinterval_utils.h"
|
#include "distributed/shardinterval_utils.h"
|
||||||
#include "distributed/utils/citus_stat_tenants.h"
|
#include "distributed/stats/stat_tenants.h"
|
||||||
#include "distributed/version_compat.h"
|
#include "distributed/version_compat.h"
|
||||||
#include "distributed/worker_shard_visibility.h"
|
#include "distributed/worker_shard_visibility.h"
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_16
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
#include "parser/parse_relation.h"
|
#include "parser/parse_relation.h"
|
||||||
#endif
|
#endif
|
||||||
#include "optimizer/optimizer.h"
|
|
||||||
#include "optimizer/pathnode.h"
|
|
||||||
#include "optimizer/plancat.h"
|
|
||||||
#include "optimizer/planmain.h"
|
|
||||||
#include "optimizer/planner.h"
|
|
||||||
#include "parser/parse_type.h"
|
|
||||||
#include "parser/parsetree.h"
|
|
||||||
#include "utils/builtins.h"
|
|
||||||
#include "utils/datum.h"
|
|
||||||
#include "utils/lsyscache.h"
|
|
||||||
#include "utils/memutils.h"
|
|
||||||
#include "utils/syscache.h"
|
|
||||||
|
|
||||||
|
|
||||||
/* RouterPlanType is used to determine the router plan to invoke */
|
/* RouterPlanType is used to determine the router plan to invoke */
|
||||||
|
|
|
@ -155,7 +155,7 @@ GeneratePlaceHolderPlannedStmt(Query *parse)
|
||||||
* being a fast path router query.
|
* being a fast path router query.
|
||||||
* The requirements for the fast path query can be listed below:
|
* The requirements for the fast path query can be listed below:
|
||||||
*
|
*
|
||||||
* - SELECT query without CTES, sublinks-subqueries, set operations
|
* - SELECT/UPDATE/DELETE query without CTES, sublinks-subqueries, set operations
|
||||||
* - The query should touch only a single hash distributed or reference table
|
* - The query should touch only a single hash distributed or reference table
|
||||||
* - The distribution with equality operator should be in the WHERE clause
|
* - The distribution with equality operator should be in the WHERE clause
|
||||||
* and it should be ANDed with any other filters. Also, the distribution
|
* and it should be ANDed with any other filters. Also, the distribution
|
||||||
|
@ -252,7 +252,7 @@ FastPathRouterQuery(Query *query, Node **distributionKeyValue)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Distribution column must be used in a simple equality match check and it must be
|
* Distribution column must be used in a simple equality match check and it must be
|
||||||
* place at top level conjustion operator. In simple words, we should have
|
* place at top level conjunction operator. In simple words, we should have
|
||||||
* WHERE dist_key = VALUE [AND ....];
|
* WHERE dist_key = VALUE [AND ....];
|
||||||
*
|
*
|
||||||
* We're also not allowing any other appearances of the distribution key in the quals.
|
* We're also not allowing any other appearances of the distribution key in the quals.
|
||||||
|
|