Compare commits
575 Commits
overviews-
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
667ec0e32c | ||
|
59b3ccb147 | ||
|
2c1dbfde92 | ||
|
a21616c0e7 | ||
|
a9d59c11c7 | ||
|
33d85d5d7a | ||
|
4c0a2f2481 | ||
|
67139ef1e6 | ||
|
26e49dce69 | ||
|
4c0f6ecdd9 | ||
|
9fb59a49d0 | ||
|
1ae67b846f | ||
|
73b82be89a | ||
|
bd793f72e5 | ||
|
c73bc315ec | ||
|
bb1c644f5b | ||
|
c9ae99e4a7 | ||
|
5c3df02257 | ||
|
cdb9e683ab | ||
|
7067feb626 | ||
|
d3ffcbc97e | ||
|
dcd46923c4 | ||
|
9bbf622bc4 | ||
|
fa765a68ea | ||
|
88c212f3ba | ||
|
a15b42d8f3 | ||
|
3ad36e2a31 | ||
|
59a47c863a | ||
|
4efecfbbab | ||
|
3d04103dbd | ||
|
84ad0a87f9 | ||
|
0f0797d8a6 | ||
|
da5a566ecf | ||
|
38f26f4e8b | ||
|
422d1a0acd | ||
|
f790dc1872 | ||
|
2fcdd56338 | ||
|
47f06f40df | ||
|
998402e531 | ||
|
bd4eeb3f9e | ||
|
b9615a912f | ||
|
39c071f790 | ||
|
e37cdb4ca0 | ||
|
c8410541a4 | ||
|
55ebc73f9e | ||
|
027256dbb7 | ||
|
076cacc6f1 | ||
|
741d171f4a | ||
|
093e0bcb36 | ||
|
ca1d5b0c91 | ||
|
cd62071757 | ||
|
4c15fafd72 | ||
|
b246ada312 | ||
|
ecf0a06448 | ||
|
ec0d94dd0e | ||
|
c5bbb180c2 | ||
|
9fa532623d | ||
|
921800e6a3 | ||
|
dfd26d930e | ||
|
c0270068c4 | ||
|
9010642326 | ||
|
8974986d61 | ||
|
fea2474117 | ||
|
654fa3ac02 | ||
|
24cb6cf9c1 | ||
|
1c7b0c71ec | ||
|
5e3adba2d4 | ||
|
89c9025431 | ||
|
6ebffe1b2c | ||
|
b3503ea429 | ||
|
3c869e3d5a | ||
|
dac135890d | ||
|
9372c9f4e0 | ||
|
06b6d9140d | ||
|
beb985b64f | ||
|
eea750c904 | ||
|
da570c50aa | ||
|
d549ecced4 | ||
|
fde12c818a | ||
|
e99e4a02c9 | ||
|
fa5f0bcbc1 | ||
|
adca727169 | ||
|
0893fae025 | ||
|
e69d3b1c30 | ||
|
a57431a04d | ||
|
064cc2a76b | ||
|
7f9905cbd5 | ||
|
6e455efcdd | ||
|
1b86517007 | ||
|
64e185b841 | ||
|
38d8a470bc | ||
|
4920029560 | ||
|
9b91704480 | ||
|
a340f98b96 | ||
|
57dcfc2a35 | ||
|
3ae7b1fe05 | ||
|
f7d10adbc2 | ||
|
203bd64058 | ||
|
19c6de2096 | ||
|
e231500c46 | ||
|
4b9c048235 | ||
|
5897d8a9cb | ||
|
980657c4a4 | ||
|
a4a4a1dff1 | ||
|
0362825b8c | ||
|
9eab5e2533 | ||
|
0d455d9521 | ||
|
364933b6b9 | ||
|
18f9f79fe8 | ||
|
a032cc08e0 | ||
|
9dc547c838 | ||
|
d3ae4b808c | ||
|
26f91001ee | ||
|
43b242e610 | ||
|
e14c49b223 | ||
|
8c7a75dd0c | ||
|
dbed2d1ede | ||
|
1c27443ff4 | ||
|
944b111bb1 | ||
|
d5ecb39250 | ||
|
0ad7cd485d | ||
|
6e9ee296c5 | ||
|
d87d27d7e5 | ||
|
5c9f6964a3 | ||
|
d76e3ccc3e | ||
|
eba6cf4565 | ||
|
6700d24232 | ||
|
511e24a40e | ||
|
80f01d4a51 | ||
|
d4bc69cd3c | ||
|
8730292097 | ||
|
efd757ffa3 | ||
|
40a2ba9569 | ||
|
35b2b7e589 | ||
|
4100b66f3b | ||
|
b1830e49af | ||
|
70f1e00980 | ||
|
8a4a59b340 | ||
|
f06b899605 | ||
|
09076924c0 | ||
|
d5b3953568 | ||
|
c64c61781e | ||
|
82d8f7e1aa | ||
|
e63f5040d9 | ||
|
3cde55ed3a | ||
|
a91f14371a | ||
|
899a12fc53 | ||
|
01258530ff | ||
|
90b58fcdec | ||
|
29e2ee670f | ||
|
e98b18fd25 | ||
|
e6f07d4f96 | ||
|
c9b01592e7 | ||
|
e2beefdb53 | ||
|
73ee4107d4 | ||
|
aa481f4219 | ||
|
bac7d79ab3 | ||
|
98b2b02199 | ||
|
10ed64789d | ||
|
0f66c54ce5 | ||
|
03bc3436cd | ||
|
57f420ab4c | ||
|
c740cfa499 | ||
|
eea0198864 | ||
|
f75756138a | ||
|
28376ffecb | ||
|
f20a007160 | ||
|
cf6b00dfd8 | ||
|
cc5fb37abb | ||
|
b839235801 | ||
|
d5f84784f0 | ||
|
7bb0b93f8b | ||
|
a26b96347a | ||
|
77868a541d | ||
|
b76b754811 | ||
|
664468b1e5 | ||
|
d85faa8f85 | ||
|
f7ab3d28a8 | ||
|
144c4e722b | ||
|
e22aaee5f5 | ||
|
da9ff0c750 | ||
|
e6cb03300b | ||
|
bc7031d872 | ||
|
427ad94235 | ||
|
1375894e99 | ||
|
f285c806fc | ||
|
acf1f2dbd4 | ||
|
eb02ca4d86 | ||
|
b475eb4356 | ||
|
510a9d12e4 | ||
|
77213f0588 | ||
|
8ff0ad74af | ||
|
a25efc3fc5 | ||
|
87dec4dbe4 | ||
|
f2e8d029eb | ||
|
036127af9c | ||
|
2ce4d8ee14 | ||
|
a8966270bd | ||
|
11b30c8f59 | ||
|
415220777e | ||
|
1dcc23db15 | ||
|
164fba197f | ||
|
7f9a748119 | ||
|
409b067721 | ||
|
21c319eb9a | ||
|
dac1f5bffc | ||
|
9469871029 | ||
|
021c7318a3 | ||
|
29397ff929 | ||
|
34684507cb | ||
|
a4bcf4c78f | ||
|
e787b1d097 | ||
|
8245156038 | ||
|
9248ce76bd | ||
|
d1eb740a00 | ||
|
cb019ff194 | ||
|
1a99151d0e | ||
|
8fac0c89eb | ||
|
9686ad5cfe | ||
|
0cecfdb112 | ||
|
ccbaf785cf | ||
|
aed88fd958 | ||
|
3b4669df11 | ||
|
9f904b5926 | ||
|
4802501387 | ||
|
b68ce72889 | ||
|
7a23ea815c | ||
|
2533d0996c | ||
|
048234cd80 | ||
|
5a12033609 | ||
|
a580bedefc | ||
|
0e891eff7f | ||
|
52b3290d26 | ||
|
186ee37a57 | ||
|
0898881470 | ||
|
4dfd71639a | ||
|
c6b90aac8a | ||
|
8a87f96f04 | ||
|
7c10fcc363 | ||
|
2f178bd89e | ||
|
32489c4eab | ||
|
afa52aa92b | ||
|
746dbea434 | ||
|
f9bd469ea9 | ||
|
402d97daa6 | ||
|
e41d2ec019 | ||
|
3c460f1a85 | ||
|
076207c49c | ||
|
ce1e9ac41c | ||
|
0f33ee8b22 | ||
|
a32dea0282 | ||
|
3a255df9d0 | ||
|
c4e2549dc8 | ||
|
2e9f642378 | ||
|
99096d41e0 | ||
|
3a10ef7e76 | ||
|
a20676f391 | ||
|
37004db047 | ||
|
1189d70b2a | ||
|
8cfc8e65cf | ||
|
32db4fd81e | ||
|
a1e3e9a8df | ||
|
6e34e16b8d | ||
|
70220e04c1 | ||
|
a5cb9f268d | ||
|
d2d909145d | ||
|
34dec227c4 | ||
|
99e92e2505 | ||
|
c58a084102 | ||
|
b7907ff82f | ||
|
12d955075a | ||
|
10a4d85c01 | ||
|
524bb6ad42 | ||
|
4da89d8abd | ||
|
c7311ba48e | ||
|
2eae7876e2 | ||
|
91c3b86d45 | ||
|
f55d789c41 | ||
|
3eb8ab24d8 | ||
|
0f1c98c743 | ||
|
8ecd2cd5e2 | ||
|
f4be59cae0 | ||
|
fe66b2865a | ||
|
2be9d2d81a | ||
|
5744921065 | ||
|
4c93258cd2 | ||
|
bf140890d8 | ||
|
29a31d4c40 | ||
|
dbd403a2f6 | ||
|
cb353ec6a8 | ||
|
2beabfced6 | ||
|
7bdee5c13e | ||
|
c07784566a | ||
|
2e1fe2933c | ||
|
d378ca6fe0 | ||
|
446f4113d9 | ||
|
5963c67376 | ||
|
f5f18ca57c | ||
|
d8c840d126 | ||
|
65483743b4 | ||
|
4651883454 | ||
|
470bae6268 | ||
|
75ba13b834 | ||
|
057695361d | ||
|
0e1aeb0a76 | ||
|
f2dae651b3 | ||
|
9f414938ba | ||
|
4f7b07f922 | ||
|
69cc56b589 | ||
|
1028b24333 | ||
|
4d1c4f6a22 | ||
|
83ab128a01 | ||
|
b0b4a92240 | ||
|
c06d24aa19 | ||
|
4f4df2de8d | ||
|
b5f36902c5 | ||
|
0bcbf6708a | ||
|
2e665a56b4 | ||
|
1e8ef2e1d9 | ||
|
e6ecde4346 | ||
|
2d42e6197a | ||
|
5605fdd9b2 | ||
|
ab7082d4c3 | ||
|
f9ac627e0e | ||
|
cc2066ea89 | ||
|
aa380171b4 | ||
|
a43abb37e0 | ||
|
787b513715 | ||
|
362af5e6a0 | ||
|
8dbd797429 | ||
|
57ac26cbe7 | ||
|
010cbe6e18 | ||
|
8e68f2a0a7 | ||
|
52aab9d564 | ||
|
06f563bb73 | ||
|
78077a6ec1 | ||
|
c45ef6c540 | ||
|
42dc03d77b | ||
|
9254723719 | ||
|
26ad966ab6 | ||
|
a2723a3c90 | ||
|
2f8ea7e4ea | ||
|
45c21d7f42 | ||
|
f442c21fa4 | ||
|
ee9d08a2be | ||
|
7606585672 | ||
|
81d0f338cf | ||
|
951f257654 | ||
|
f461faf0b6 | ||
|
a8d57abda6 | ||
|
982ddfdeff | ||
|
da4331ac78 | ||
|
07d43275f8 | ||
|
ec38444b7d | ||
|
ee9010dc23 | ||
|
74210c5b5c | ||
|
d14d1d9994 | ||
|
5b19d0fc5e | ||
|
2baee24f30 | ||
|
85d6164956 | ||
|
a5b2b66bb6 | ||
|
3faa389860 | ||
|
bc5d532735 | ||
|
4f3d19ce7a | ||
|
45fed9cf1b | ||
|
e19489144c | ||
|
83707297de | ||
|
2f40261b8d | ||
|
b1202011f6 | ||
|
65d51fd8bd | ||
|
1a271d977b | ||
|
9c6294d95b | ||
|
c7bba14e9a | ||
|
667f896cfb | ||
|
cc1df0a708 | ||
|
d1ee383d9b | ||
|
a794fb3d31 | ||
|
50e41179fc | ||
|
e3138cd56a | ||
|
ab6720ad32 | ||
|
5f154a5859 | ||
|
99dd7cefc7 | ||
|
85997e2445 | ||
|
1bd4b9a6e3 | ||
|
5d4f1d98d7 | ||
|
1637257772 | ||
|
a904b101a3 | ||
|
917a975baa | ||
|
0568b36a90 | ||
|
6d122462bb | ||
|
ffaf5e4400 | ||
|
2a4ecd4850 | ||
|
3f4479fe12 | ||
|
34032964c0 | ||
|
6c57751579 | ||
|
9a3ac908a9 | ||
|
80fdd00541 | ||
|
aeec2bbe06 | ||
|
5033a0ba35 | ||
|
d2a00852a8 | ||
|
7e131ac88d | ||
|
e374b9128f | ||
|
e605234d38 | ||
|
272d5be776 | ||
|
0e7d797400 | ||
|
fcc06e82bf | ||
|
60a21d34bf | ||
|
74f939fd53 | ||
|
7e131143f9 | ||
|
ac15a6da25 | ||
|
c0a7714f33 | ||
|
0c864694b6 | ||
|
4b246d34c8 | ||
|
87454ac37f | ||
|
e3837c603d | ||
|
e84832b7f4 | ||
|
b209726b1c | ||
|
51a669f93c | ||
|
c8a1119556 | ||
|
24a37be1a9 | ||
|
5659275c0c | ||
|
7760d6b30d | ||
|
4515c8547e | ||
|
2766bbc83a | ||
|
c4980a90f9 | ||
|
61d2024eb5 | ||
|
af142306aa | ||
|
7437a9686b | ||
|
82f90e618c | ||
|
55a77b0ef0 | ||
|
2e68626165 | ||
|
06b7eb8504 | ||
|
ccbabaa3b4 | ||
|
0da36eab44 | ||
|
7f5bef1203 | ||
|
625d62c448 | ||
|
f06418c99b | ||
|
1958f2de5b | ||
|
c19c88c9e0 | ||
|
716f47edae | ||
|
cd83ee21bb | ||
|
d98ae59535 | ||
|
a2e99ac533 | ||
|
da5d3ea8d9 | ||
|
fed64e4850 | ||
|
9959581b7a | ||
|
c9f4685e5d | ||
|
f96c334f48 | ||
|
04dc39bb16 | ||
|
0a6f263106 | ||
|
697a0a3a36 | ||
|
7559151ab5 | ||
|
2c62730301 | ||
|
62c27ab140 | ||
|
17c872ed61 | ||
|
6714feea8f | ||
|
b6b0ef704a | ||
|
39998a9c88 | ||
|
cf38d4cf25 | ||
|
0467c075f7 | ||
|
c1c55bf59f | ||
|
0a58c05049 | ||
|
81f2fad3ad | ||
|
0a0f2e294b | ||
|
cd4653ecc0 | ||
|
7ea428913a | ||
|
5c86029029 | ||
|
acd101af9b | ||
|
d811a71da0 | ||
|
4050555801 | ||
|
e4b0e7ea7a | ||
|
a79c2cb7a8 | ||
|
dbaf795a79 | ||
|
4b937de415 | ||
|
2fe02d8154 | ||
|
691b9a8312 | ||
|
5243192296 | ||
|
8131887204 | ||
|
36550440bf | ||
|
9d980ab17c | ||
|
72c214a8e4 | ||
|
0f7a1c4882 | ||
|
0abc2ba250 | ||
|
f24d15f6ca | ||
|
386eccdda2 | ||
|
5d43faecaf | ||
|
acbaf634dc | ||
|
c379946c95 | ||
|
711c954d1a | ||
|
ffb779eb74 | ||
|
8db73ae9bd | ||
|
208ebb2724 | ||
|
951ec51968 | ||
|
8a3d506a53 | ||
|
c839f74c63 | ||
|
8389b39c00 | ||
|
4908bacc4b | ||
|
38fa3b485c | ||
|
2d473cf693 | ||
|
4193ff3874 | ||
|
68a0752849 | ||
|
815b5b429d | ||
|
76bdb3657a | ||
|
234373df11 | ||
|
a486eed2e3 | ||
|
795d92da8d | ||
|
58e2e7e238 | ||
|
25d27263cb | ||
|
bbadcc838e | ||
|
b1a0904c07 | ||
|
399b680b41 | ||
|
7c0636c5f9 | ||
|
f58f870457 | ||
|
a7c8dc04e3 | ||
|
90ee56eb35 | ||
|
1032737600 | ||
|
24639713f1 | ||
|
fff7e926c9 | ||
|
7d7ecc06f5 | ||
|
5992304b47 | ||
|
5c52e7564f | ||
|
0b7fbdc1cb | ||
|
0bfdeae147 | ||
|
89b2999a80 | ||
|
2080d6d422 | ||
|
bc5e23b143 | ||
|
64fae71a37 | ||
|
594543916d | ||
|
aa9286eaba | ||
|
ce762f41ac | ||
|
529b12af20 | ||
|
f98b6fb0a1 | ||
|
30cd4cf1f9 | ||
|
1356131ec1 | ||
|
9731ce38ec | ||
|
07892271e5 | ||
|
066c574709 | ||
|
4786e0a2ae | ||
|
358c89b332 | ||
|
fa6f9a8a66 | ||
|
091aea088e | ||
|
7ecdca1b8c | ||
|
9ee3125913 | ||
|
279eba95b7 | ||
|
b462e969a1 | ||
|
5d323456ee | ||
|
6e130c336e | ||
|
457b614d96 | ||
|
415d96082e | ||
|
5eddf5ce8e | ||
|
006c3cc50f | ||
|
2b24390a8a | ||
|
69f04bb8b0 | ||
|
c96bf7c7d5 | ||
|
15a8876d06 | ||
|
89e991aae9 | ||
|
064b26ccd3 | ||
|
5bf35bddc1 | ||
|
2b69823949 | ||
|
1f01ecae30 | ||
|
58fd5d4060 | ||
|
a2a1ff6ae8 | ||
|
326aae4edb | ||
|
2a30eb2fd3 | ||
|
0b3ad5e569 | ||
|
aa302c237d | ||
|
9526f0448f | ||
|
3399f2b9a5 | ||
|
803b3671d0 | ||
|
c3fada29a8 | ||
|
86e5f6d317 | ||
|
3122a0479d | ||
|
956e56cd37 | ||
|
b19a5fc3dc | ||
|
0ecbbd8e71 |
44
.travis.yml
44
.travis.yml
@ -1,17 +1,41 @@
|
||||
language: c
|
||||
sudo: required
|
||||
|
||||
addons:
|
||||
postgresql: 9.3
|
||||
env:
|
||||
global:
|
||||
- PGUSER=postgres
|
||||
- PGDATABASE=postgres
|
||||
- PGOPTIONS='-c client_min_messages=NOTICE'
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
#- sudo apt-get install -q postgresql-9.3-postgis-2.1
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install -q postgresql-server-dev-9.3
|
||||
- sudo apt-get install -q postgresql-plpython-9.3
|
||||
jobs:
|
||||
include:
|
||||
- env: POSTGRESQL_VERSION="11" POSTGIS_VERSION="2.5"
|
||||
dist: xenial
|
||||
- env: POSTGRESQL_VERSION="12" POSTGIS_VERSION="2.5"
|
||||
dist: bionic
|
||||
- env: POSTGRESQL_VERSION="12" POSTGIS_VERSION="3"
|
||||
dist: bionic
|
||||
- env: POSTGRESQL_VERSION="13" POSTGIS_VERSION="3"
|
||||
dist: bionic
|
||||
|
||||
script:
|
||||
- sudo apt-get remove postgresql* -y
|
||||
- sudo apt-get install -y --allow-unauthenticated --no-install-recommends --no-install-suggests postgresql-$POSTGRESQL_VERSION postgresql-client-$POSTGRESQL_VERSION postgresql-server-dev-$POSTGRESQL_VERSION postgresql-common
|
||||
- if [[ $POSTGRESQL_VERSION == '9.6' ]]; then sudo apt-get install -y postgresql-contrib-9.6; fi;
|
||||
- sudo apt-get install -y --allow-unauthenticated postgresql-$POSTGRESQL_VERSION-postgis-$POSTGIS_VERSION postgresql-$POSTGRESQL_VERSION-postgis-$POSTGIS_VERSION-scripts postgis
|
||||
# For pre12, install plpython2. For PG12 install plpython3
|
||||
- if [[ $POSTGRESQL_VERSION == '11' ]]; then sudo apt-get install -y postgresql-plpython-$POSTGRESQL_VERSION python python-redis; else sudo apt-get install -y postgresql-plpython3-$POSTGRESQL_VERSION python3 python3-redis; fi;
|
||||
- for i in $(pg_lsclusters | tail -n +2 | awk '{print $1}'); do sudo pg_dropcluster --stop $i main; done;
|
||||
- sudo rm -rf /etc/postgresql/$POSTGRESQL_VERSION /var/lib/postgresql/$POSTGRESQL_VERSION /var/ramfs/postgresql/$POSTGRESQL_VERSION
|
||||
- sudo pg_createcluster -u postgres $POSTGRESQL_VERSION main --start -- --auth-local trust --auth-host password
|
||||
- export PGPORT=$(pg_lsclusters | grep $POSTGRESQL_VERSION | awk '{print $3}')
|
||||
- make
|
||||
- sudo make install
|
||||
- PGOPTIONS='-c client_min_messages=NOTICE' PGUSER=postgres make installcheck ||
|
||||
{ cat regression.diffs; false; }
|
||||
- make installcheck
|
||||
|
||||
after_failure:
|
||||
- pg_lsclusters
|
||||
- cat regression.out
|
||||
- cat regression.diffs
|
||||
- echo $PGPORT
|
||||
- sudo cat /var/log/postgresql/postgresql-$POSTGRESQL_VERSION-main.log
|
||||
|
@ -22,13 +22,18 @@ and upgrade of the objects. This means using CREATE OR REPLACE for
|
||||
the functions, and whatever it takes to check existence of any previous
|
||||
version of objects in other cases.
|
||||
|
||||
When used as an extension (probably always from version 0.2.0 onwards)
|
||||
all the objects will be installed in a "cartodb" schema. Take this into
|
||||
account to fully-qualify internal calls to avoid (possibly dangerous)
|
||||
name clashes.
|
||||
When adding a new function or modifying an exiting one make sure that the
|
||||
[VOLATILITY](https://www.postgresql.org/docs/current/static/xfunc-volatility.html) and [PARALLEL](https://www.postgresql.org/docs/9.6/static/parallel-safety.html) categories are updated accordingly.
|
||||
|
||||
|
||||
Although the extension will usually be installed in the "cartodb" schema, please
|
||||
use @extschema@ to fully-qualify internal calls to avoid name clashes.
|
||||
When you use postgis functions or types, please fully-qualify them by using
|
||||
@postgisschema@ (it's changed to "public" by the install script) to avoid
|
||||
pg_upgrade issues.
|
||||
|
||||
Every new feature (as well as bugfixes) should come with a test case,
|
||||
see next section.
|
||||
see the 'Writing testcases' section.
|
||||
|
||||
Writing testcases
|
||||
-----------------
|
||||
@ -62,3 +67,7 @@ A useful query:
|
||||
```sql
|
||||
SELECT * FROM pg_extension_update_paths('cartodb') WHERE path IS NOT NULL AND source = cdb_version();
|
||||
```
|
||||
|
||||
## Submitting Contributions
|
||||
|
||||
* You will need to sign a Contributor License Agreement (CLA) before making a submission. [Learn more here](https://carto.com/contributions).
|
||||
|
122
Makefile
122
Makefile
@ -1,9 +1,10 @@
|
||||
# cartodb/Makefile
|
||||
|
||||
EXTENSION = cartodb
|
||||
EXTVERSION = 0.16.3
|
||||
EXTVERSION = 0.37.1
|
||||
|
||||
SED = sed
|
||||
AWK = awk
|
||||
|
||||
CDBSCRIPTS = \
|
||||
scripts-enabled/*.sql \
|
||||
@ -70,6 +71,45 @@ UPGRADABLE = \
|
||||
0.16.1 \
|
||||
0.16.2 \
|
||||
0.16.3 \
|
||||
0.16.4 \
|
||||
0.17.0 \
|
||||
0.17.1 \
|
||||
0.18.0 \
|
||||
0.18.1 \
|
||||
0.18.2 \
|
||||
0.18.3 \
|
||||
0.18.4 \
|
||||
0.18.5 \
|
||||
0.19.0 \
|
||||
0.19.1 \
|
||||
0.19.2 \
|
||||
0.20.0 \
|
||||
0.21.0 \
|
||||
0.22.0 \
|
||||
0.22.1 \
|
||||
0.22.2 \
|
||||
0.23.0 \
|
||||
0.23.1 \
|
||||
0.23.2 \
|
||||
0.24.0 \
|
||||
0.24.1 \
|
||||
0.25.0 \
|
||||
0.26.0 \
|
||||
0.26.1 \
|
||||
0.27.0 \
|
||||
0.27.1 \
|
||||
0.27.2 \
|
||||
0.28.0 \
|
||||
0.28.1 \
|
||||
0.29.0 \
|
||||
0.30.0 \
|
||||
0.31.0 \
|
||||
0.32.0 \
|
||||
0.33.0 \
|
||||
0.34.0 \
|
||||
0.35.0 \
|
||||
0.36.0 \
|
||||
0.37.0 \
|
||||
$(EXTVERSION)dev \
|
||||
$(EXTVERSION)next \
|
||||
$(END)
|
||||
@ -80,8 +120,6 @@ UPGRADES = \
|
||||
$(SED) 's/$$/--$(EXTVERSION).sql/' | \
|
||||
$(SED) 's/ /--$(EXTVERSION).sql $(EXTENSION)--/g')
|
||||
|
||||
GITDIR=$(shell test -d .git && echo '.git' || cat .git | $(SED) 's/^gitdir: //')
|
||||
|
||||
DATA_built = \
|
||||
$(EXTENSION)--$(EXTVERSION).sql \
|
||||
$(EXTENSION)--$(EXTVERSION)--$(EXTVERSION)next.sql \
|
||||
@ -93,18 +131,27 @@ EXTRA_CLEAN = cartodb_version.sql
|
||||
DOCS = README.md
|
||||
REGRESS_OLD = $(wildcard test/*.sql)
|
||||
REGRESS_LEGACY = $(REGRESS_OLD:.sql=)
|
||||
REGRESS = test_setup $(REGRESS_LEGACY)
|
||||
REGRESS = test/test_setup $(REGRESS_LEGACY)
|
||||
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
|
||||
$(EXTENSION)--$(EXTVERSION).sql: $(CDBSCRIPTS) cartodb_version.sql Makefile
|
||||
PG_VERSION := $(shell $(PG_CONFIG) --version | $(AWK) '{split($$2,a,"."); print a[1]}')
|
||||
PG_12_GE := $(shell [ $(PG_VERSION) -ge 12 ] && echo true)
|
||||
PLPYTHONU := plpythonu
|
||||
ifeq ($(PG_12_GE), true)
|
||||
PLPYTHONU := plpython3u
|
||||
endif
|
||||
PGPORT ?= '5432'
|
||||
PGUSER ?= 'postgres'
|
||||
|
||||
$(EXTENSION)--$(EXTVERSION).sql: $(CDBSCRIPTS) cartodb_version.sql Makefile
|
||||
echo '\echo Use "CREATE EXTENSION $(EXTENSION)" to load this file. \quit' > $@
|
||||
cat $(CDBSCRIPTS) | \
|
||||
$(SED) -e 's/public\./cartodb./g' \
|
||||
-e 's/:DATABASE_USERNAME/cdb_org_admin/g' \
|
||||
-e "s/''public''/''cartodb''/g" >> $@
|
||||
$(SED) -e 's/@extschema@/cartodb/g' \
|
||||
-e 's/@postgisschema@/public/g' \
|
||||
-e 's/@@plpythonu@@/$(PLPYTHONU)/g' >> $@
|
||||
echo "GRANT USAGE ON SCHEMA cartodb TO public;" >> $@
|
||||
cat cartodb_version.sql >> $@
|
||||
|
||||
@ -118,10 +165,10 @@ $(EXTENSION)--$(EXTVERSION)--$(EXTVERSION)next.sql: $(EXTENSION)--$(EXTVERSION).
|
||||
cp $< $@
|
||||
|
||||
$(EXTENSION).control: $(EXTENSION).control.in Makefile
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' $< > $@
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/g' -e 's/@@plpythonu@@/$(PLPYTHONU)/g' $< > $@
|
||||
|
||||
cartodb_version.sql: cartodb_version.sql.in Makefile $(GITDIR)/index
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' $< > $@
|
||||
cartodb_version.sql: cartodb_version.sql.in Makefile
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' -e 's/@extschema@/cartodb/g' -e "s/@postgisschema@/public/g" -e 's/@@plpythonu@@/$(PLPYTHONU)/g' $< > $@
|
||||
|
||||
# Needed for consistent `echo` results with backslashes
|
||||
SHELL = bash
|
||||
@ -130,19 +177,36 @@ legacy_regress: $(REGRESS_OLD) Makefile
|
||||
mkdir -p sql/test/
|
||||
mkdir -p expected/test/
|
||||
mkdir -p results/test/
|
||||
cat sql/test_setup.sql | \
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' -e 's/@extschema@/cartodb/g' -e "s/@postgisschema@/public/g" -e 's/@@plpythonu@@/$(PLPYTHONU)/g' \
|
||||
> sql/test/test_setup.sql
|
||||
cp sql/test_setup_expect expected/test/test_setup.out
|
||||
for f in $(REGRESS_OLD); do \
|
||||
tn=`basename $${f} .sql`; \
|
||||
of=sql/test/$${tn}.sql; \
|
||||
echo '\set ECHO none' > $${of}; \
|
||||
echo '\a' >> $${of}; \
|
||||
echo '\t' >> $${of}; \
|
||||
echo '\set QUIET off' >> $${of}; \
|
||||
cat $${f} | \
|
||||
$(SED) -e 's/public\./cartodb./g' >> $${of}; \
|
||||
exp=expected/test/$${tn}.out; \
|
||||
echo '\set ECHO none' > $${exp}; \
|
||||
cat test/$${tn}_expect >> $${exp}; \
|
||||
done
|
||||
tn=`basename $${f} .sql`; \
|
||||
of=sql/test/$${tn}.sql; \
|
||||
echo '\set ECHO none' > $${of}; \
|
||||
echo '\a' >> $${of}; \
|
||||
echo '\t' >> $${of}; \
|
||||
echo '\set QUIET off' >> $${of}; \
|
||||
cat $${f} | \
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' \
|
||||
-e 's/@extschema@/cartodb/g' \
|
||||
-e "s/@postgisschema@/public/g" \
|
||||
-e 's/@@plpythonu@@/$(PLPYTHONU)/g' \
|
||||
-e 's/@@PGPORT@@/$(PGPORT)/g' \
|
||||
-e 's/@@PGUSER@@/$(PGUSER)/g' \
|
||||
>> $${of}; \
|
||||
exp=expected/test/$${tn}.out; \
|
||||
echo '\set ECHO none' > $${exp}; \
|
||||
cat test/$${tn}_expect | \
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' \
|
||||
-e 's/@extschema@/cartodb/g' \
|
||||
-e "s/@postgisschema@/public/g" \
|
||||
-e 's/@@plpythonu@@/$(PLPYTHONU)/g' \
|
||||
-e 's/@@PGPORT@@/$(PGPORT)/g' \
|
||||
-e 's/@@PGUSER@@/$(PGUSER)/g' \
|
||||
>> $${exp}; \
|
||||
done
|
||||
|
||||
test_organization:
|
||||
bash test/organization/test.sh
|
||||
@ -150,7 +214,15 @@ test_organization:
|
||||
test_extension_new:
|
||||
bash test/extension/test.sh
|
||||
|
||||
legacy_tests: legacy_regress
|
||||
legacy_tests: legacy_regress $(EXTENSION)--unpackaged--$(EXTVERSION).sql
|
||||
|
||||
installcheck: legacy_tests test_extension_new test_organization
|
||||
PGREGRESS := $(shell dirname `$(PG_CONFIG) --pgxs`)/../../src/test/regress/pg_regress
|
||||
PGBINDIR := $(shell $(PG_CONFIG) --bindir)
|
||||
PGREGRESSDATABASE = 'contrib_regression'
|
||||
regress: legacy_tests
|
||||
PGUSER=$(PGUSER) \
|
||||
PGPORT=$(PGPORT) \
|
||||
$(PGREGRESS) --inputdir=./ --bindir='$(PGBINDIR)' --dbname=$(PGREGRESSDATABASE) $(REGRESS)
|
||||
|
||||
installcheck: test_extension_new test_organization
|
||||
$(MAKE) -C . regress
|
||||
|
190
NEWS.md
190
NEWS.md
@ -1,3 +1,193 @@
|
||||
0.37.1 (2020-12-02)
|
||||
* Change `__CDB_RegenerateTable_Get_Commands` to use the caller timeout or '1min' if not set.
|
||||
|
||||
0.37.0 (2020-11-26)
|
||||
* Raised minimum PG version to 11.
|
||||
* Add `CDB_RegenerateTable` function to regenerate a table.
|
||||
* Add `CDB_GetTableQueries` to get the queries of a table (constraints, indices, triggers...).
|
||||
* Add `CDB_ApplyQueriesSafe` to apply the queries of `CDB_GetTableQueries` discarding any exceptions.
|
||||
* Deprecate creation of new overview tables.
|
||||
* _cdb_has_usable_geom_record: Check only the extension schema.
|
||||
|
||||
0.36.0 (2020-02-13)
|
||||
* Make `_CDB_Group_API_Auth` python3 compatible by passing bytes representation instead of a string.
|
||||
* Make `_CDB_Group_API_Request` python3 compatible by adapting the function signature of `HTTPConnection`.
|
||||
|
||||
0.35.0 (2019-12-30)
|
||||
* Reapply the changes in 0.33.0 (the issue we were looking for was unrelated)
|
||||
* Reapply `Make PG12 depend on plpython3u instead of plpythonu`
|
||||
* Fix identifier quotation in `CDB_UserDataSize`
|
||||
|
||||
0.34.0 (2019-12-23)
|
||||
* Revert changes done in 0.33.0, keeping function signature to drop them
|
||||
|
||||
0.33.0 (2019-12-20)
|
||||
* Revert `Make PG12 depend on plpython3u instead of plpythonu`.
|
||||
* Add functions to manage Federated Tables (Foreign Data Wrapper)
|
||||
|
||||
0.32.0 (2019-11-08)
|
||||
* Fix oAuth ownership re-assignation for functions
|
||||
* Some fixes for PG12.
|
||||
* Make PG12 depend on plpython3u instead of plpythonu
|
||||
* CDB_UserDataSize is now compatible with postgis 3 without postgis_raster.
|
||||
* Makefile: Add regress target (checks regress tests without needing to install the extension)
|
||||
|
||||
0.31.0 (2019-10-08)
|
||||
* Ghost tables: Add missing tags (#370)
|
||||
* Set search_path in security definer functions.
|
||||
|
||||
0.30.0 (2019-07-17)
|
||||
* Added new admin functions to connect CARTO with user FDW's (#369)
|
||||
|
||||
0.29.0 (2019-07-15)
|
||||
* Added new function CDB_OAuth:
|
||||
* Install event trigger to check for table/view/sequence/function creation
|
||||
* Reassign the ownership of new objects to a defined role in the cdb_conf
|
||||
* Changed MakeFile to support different expects for differents PG versions
|
||||
|
||||
0.28.1 (2019-07-04)
|
||||
* Avoid temporary tables creation in CDB_SyncTable (#366)
|
||||
* Make CDB_Get_Foreign_Updated_At robust to missing CDB_TableMetadata (#362)
|
||||
|
||||
0.28.0 (2019-07-01)
|
||||
* New function CDB_SyncTable (#355)
|
||||
|
||||
0.27.2 (2019-06-21)
|
||||
* Improvements and fixes in Ghost tables functions (#360)
|
||||
|
||||
0.27.1 (2019-06-03)
|
||||
* Add some qualifications that were left in the previous release.
|
||||
|
||||
0.27.0 (2019-06-03)
|
||||
* Fully qualify function calls
|
||||
* Several improvements to bash tests.
|
||||
* Avoid dropping publicuser in tests.
|
||||
* Raise minimum requirement to PostgreSQL 9.6.
|
||||
|
||||
0.26.1 (2019-03-19)
|
||||
* Remove default TIS values from Ghost tables functions
|
||||
|
||||
0.26.0 (2019-03-11)
|
||||
* Use `ST_ShiftLongitude` instead of `ST_Shift_Longitude`.
|
||||
* Add Ghost tables functions to install triggers and enqueue the linking process
|
||||
|
||||
0.25.0 (2019-02-22)
|
||||
* Add `CDB_Username` to get the cartodb username from the current PostgreSQL user
|
||||
|
||||
0.24.1 (2019-01-02)
|
||||
* Drop functions removed in 0.12 (#341)
|
||||
* Travis: Test with PostgreSQL 9.5, 10 and 11.
|
||||
|
||||
0.24.0 (2018-09-13)
|
||||
* Travis: Test with PostgreSQL 9.5 and 10.
|
||||
* _cdb_estimated_extent: Fix bug with ST_EstimatedExtent interaction.
|
||||
* Improvements in `CDB_JenksBins`.
|
||||
* Now it ignores NULLs.
|
||||
* No longer puts the same value in multiple categories.
|
||||
* Removes all limits related to size.
|
||||
* If not set, the number of iterations done is based now on the size of the array.
|
||||
* Fixed multiple bugs.
|
||||
* The internal function `CDB_JenksBinsIteration` has changed its signature.
|
||||
|
||||
0.23.2 (2018-07-19)
|
||||
* Fix `CDB_QueryTablesText` with parenthesized queries (#335)
|
||||
|
||||
0.23.1 (2018-07-19)
|
||||
* Fix `CDB_EstimateRowCount` parallelizability #333
|
||||
|
||||
0.23.0 (2018-07-03)
|
||||
* Add a new helper function `_CDB_Table_Exists(table_name_with_optional_schema TEXT)` #332
|
||||
|
||||
0.22.2 (2018-05-29)
|
||||
* Fix: Fix hyphenates usernames in 0.22.1 fix (#331)
|
||||
|
||||
0.22.1 (2018-05-29)
|
||||
* Fix: Correctly grant permission to all sequences related with table (#330)
|
||||
|
||||
0.22.0 (2018-03-22)
|
||||
* Fix: allow older ogr2ogr to work in -append mode (#319,#325)
|
||||
* Refactors CDB_QuantileBins to rely on PostgreSQL function `percentile_disc` #316
|
||||
|
||||
0.21.0 (2018-02-15)
|
||||
* Add optional parameter to limit the number of cells in grid-generation functions #322
|
||||
* Fix: grant usage on cartodb_id sequence when sharing read write #323
|
||||
* Fix: Change sed in-place for tmpfiles 524319
|
||||
|
||||
0.20.0 (2017-11-08)
|
||||
* Added VOLATILITY and PARALLEL categories to all functions
|
||||
|
||||
0.19.2 (2017-06-30)
|
||||
* Improved functions to generate unique identifiers #305
|
||||
|
||||
0.19.1 (2017-06-05)
|
||||
|
||||
* Fixed a deadlock problem when trying to regenarate overviews #302
|
||||
|
||||
0.19.0 (2017-04-11)
|
||||
|
||||
* Add new function `CDB_EstimateRowCount` #295
|
||||
|
||||
0.18.5 (2016-11-30)
|
||||
|
||||
* Add to new overview creation strategies #290
|
||||
* Fix tests: race condition with publicuser #157
|
||||
* Fix: CDB_Stats divisions by zero #181
|
||||
* Better implementation of `CDB_EqualIntervalBins` #244
|
||||
* New tests for binning functions #249
|
||||
|
||||
0.18.4 (2016-11-04)
|
||||
|
||||
* No functional changes; fixes the migration from previous versions #288
|
||||
|
||||
0.18.3 (2016-11-03)
|
||||
|
||||
* Exclude analysis cache tables from the quota #281
|
||||
|
||||
0.18.2 (2016-10-20)
|
||||
-------------------
|
||||
|
||||
* Fix: cleanup inconsistent position of `username` column in analysis catalog after upgrades
|
||||
[#285](https://github.com/cartodb/cartodb-postgresql/pull/285)
|
||||
|
||||
0.18.1 (2016-10-19)
|
||||
-------------------
|
||||
|
||||
* Increase analysis limit factor to 2 [#284](https://github.com/CartoDB/cartodb-postgresql/pull/284)
|
||||
|
||||
0.18.0 (2016-10-17)
|
||||
-------------------
|
||||
|
||||
* Fix: exclude NULL geometries when creating Overviews #269
|
||||
* Function to check analysis tables limits #279
|
||||
|
||||
0.17.1 (2016-08-16)
|
||||
-------------------
|
||||
|
||||
* Add cache_tables column to cdb_analysis_catalog table #274.
|
||||
|
||||
|
||||
0.17.0 (2016-07-04)
|
||||
-------------------
|
||||
|
||||
* Add export config for cdb_analysis_catalog table #268.
|
||||
* Add some extra fields to cdb_analysis_catalog table. Track user, error_message for failures, and last entity modifying the node #267.
|
||||
* Exclude overviews from user data size #262.
|
||||
|
||||
|
||||
0.16.4 (2016-05-27)
|
||||
-------------------
|
||||
|
||||
* Change CDB_ZoomFromScale() to use a formula and raise
|
||||
maximum overview level from 23 to 29.
|
||||
[#259](https://github.com/CartoDB/cartodb-postgresql/pull/259)
|
||||
|
||||
* Fix bug in overview creating causing it to fail when `x` or
|
||||
`y` columns exist with non-integer type. Prevent also
|
||||
potential integer overflows limiting maximum overview level
|
||||
to 23.
|
||||
[#258](https://github.com/CartoDB/cartodb-postgresql/pull/258)
|
||||
|
||||
|
||||
0.16.3 (2016-05-09)
|
||||
-------------------
|
||||
|
||||
|
28
README.md
28
README.md
@ -1,8 +1,7 @@
|
||||
cartodb-postgresql
|
||||
==================
|
||||
|
||||
[![Build Status](http://api.travis-ci.org/CartoDB/cartodb-postgresql.svg?branch=master)]
|
||||
(http://travis-ci.org/CartoDB/cartodb-postgresql)
|
||||
[![Build Status](http://api.travis-ci.org/CartoDB/cartodb-postgresql.svg?branch=master)](http://travis-ci.org/CartoDB/cartodb-postgresql)
|
||||
|
||||
PostgreSQL extension for CartoDB
|
||||
|
||||
@ -11,8 +10,9 @@ See [the cartodb-postgresql wiki](https://github.com/CartoDB/cartodb-postgresql/
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
* PostgreSQL 9.3+ (with plpythonu extension and xml support)
|
||||
* [PostGIS extension](http://postgis.net)
|
||||
* PostgreSQL 11+ (with plpythonu extension). For PostgreSQL 12+ plpython3u is required instead. Older versions might still work but they aren't actively tested or supported.
|
||||
* [PostGIS extension](http://postgis.net)
|
||||
* Python with [Redis module](https://pypi.org/project/redis/)
|
||||
|
||||
Install
|
||||
-------
|
||||
@ -30,7 +30,7 @@ make installcheck
|
||||
|
||||
NOTE: you need to run the installcheck as a superuser, use PGUSER
|
||||
env variable if needed, like: PGUSER=postgres make installcheck
|
||||
|
||||
|
||||
NOTE: the tests need to run against a **clean postgres instance**, if you have some roles already created test will likely fail due `publicuser` not being dropped.
|
||||
|
||||
Enable database
|
||||
@ -39,23 +39,7 @@ Enable database
|
||||
In a database that needs to be turned into a "cartodb" user database, run:
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION cartodb;
|
||||
```
|
||||
|
||||
Migrate existing cartodb database
|
||||
---------------------------------
|
||||
|
||||
When upgrading an existing cartodb user database, the cartodb extension
|
||||
can be migrated from the "unpackaged" version. The procedure will copy
|
||||
the data from ``public.CDB_TableMetada`` to ``cartodb.CDB_TableMetadata``,
|
||||
re-cartodbfy all tables using old functions in triggers and drop the
|
||||
cartodb functions from the 'public' schema. All new cartodb objects will
|
||||
be in the "cartodb" schema.
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION postgis FROM unpackaged;
|
||||
CREATE EXTENSION cartodb FROM unpackaged;
|
||||
CREATE EXTENSION cartodb CASCADE;
|
||||
```
|
||||
|
||||
Update cartodb extension
|
||||
|
11
carto-package.json
Normal file
11
carto-package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "carto_postgresql_ext",
|
||||
"current_version": {
|
||||
"requires": {
|
||||
"postgresql": ">=10.0.0",
|
||||
"postgis": ">=2.4.0.0"
|
||||
},
|
||||
"works_with": {
|
||||
}
|
||||
}
|
||||
}
|
@ -3,4 +3,4 @@ comment = 'Turn a database into a cartodb user database.'
|
||||
superuser = true
|
||||
relocatable = false
|
||||
schema = cartodb
|
||||
requires = 'plpythonu, postgis'
|
||||
requires = '@@plpythonu@@, postgis'
|
||||
|
@ -1,6 +1,6 @@
|
||||
DO $$ BEGIN IF EXISTS (SELECT * FROM pg_proc p, pg_namespace n WHERE p.proname = 'cdb_transformtowebmercator' AND p.pronamespace = n.oid AND n.nspname = 'public') THEN RAISE EXCEPTION 'Use CREATE EXTENSION cartodb FROM unpackaged'; END IF; END; $$ LANGUAGE 'plpgsql'; -- forbid duplicated extension
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_version()
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_version()
|
||||
RETURNS text AS $$
|
||||
SELECT '@@VERSION@@'::text;
|
||||
$$ language 'sql' IMMUTABLE STRICT;
|
||||
|
25
doc/CDB_EstimateRowCount.md
Normal file
25
doc/CDB_EstimateRowCount.md
Normal file
@ -0,0 +1,25 @@
|
||||
Estimate the number of rows of a query.
|
||||
|
||||
|
||||
#### Using the function
|
||||
|
||||
```sql
|
||||
SELECT CDB_EstimateRowCount($$
|
||||
UPDATE addresses SET the_geom = cdb_geocode_street_point(addr, city, state, 'US');
|
||||
$$) AS row_count;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
row_count
|
||||
-----------
|
||||
5
|
||||
(1 row)
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
CDB_EstimateRowCount(query)
|
||||
|
||||
* **query** text: the SQL query to estimate the row count for.
|
@ -1,3 +1,8 @@
|
||||
|
||||
============================ WARNING ===================================
|
||||
Creating overviews is no longer supported.
|
||||
============================ WARNING ===================================
|
||||
|
||||
Overviews are tables that represent a *reduced* version of a dataset intended
|
||||
for efficient rendering at certain zoom levels while preserving the
|
||||
general visual appearance of the complete dataset.
|
||||
|
56
doc/CDB_SyncTable.md
Normal file
56
doc/CDB_SyncTable.md
Normal file
@ -0,0 +1,56 @@
|
||||
Synchronize two tables. This function will synchronize a *destination* table with a *source* table.
|
||||
The idea is that the *destination* is a replica of *source* and *source* has been subject to
|
||||
modifications that are to be applied to *destination*.
|
||||
|
||||
This will be achieved by deleting the rows in the destination not present
|
||||
in the source, inserting rows of the source not in the destination and updating modified rows.
|
||||
If the destination table does not exist it will be created and all the rows of the source inserted into it.
|
||||
|
||||
Both tables must have a consistent `cartodb_id` primary key column which will be used to match
|
||||
the source and destination rows.
|
||||
|
||||
Note that both tables do not necessarily become identical after the synchronization, since additional columns
|
||||
may have been added to the destination; those columns will not be altered by the synchronization.
|
||||
|
||||
In addition some source columns may be skipped by listing them in the optional last argument; such columns
|
||||
will not be updated in the destination, so if they are present in it their values won't be altered.
|
||||
|
||||
|
||||
#### Using the function
|
||||
|
||||
Import some data using COPY FROM into a temporary table, then synchronize a table with the data and
|
||||
finally delete the temporary table. This could be used import and update some data periodically while
|
||||
allowing to add columns to the data that will be preserved across updates.
|
||||
|
||||
```sql
|
||||
CREATE tmp_pois(cartodb_id int, name text, type text, longitude double precision, latitude double precision, rank int);
|
||||
COPY tmp_pois FROM '/tmp/pois.csv';
|
||||
SELECT CDB_SyncTable('tmp_pois', 'public', 'pois');
|
||||
DROP TABLE tmp_pois;
|
||||
```
|
||||
|
||||
Now we could perform some changes to the `pois` to maintain our own ranking:
|
||||
|
||||
```sql
|
||||
UPDATE pois SET rank = random()*4 + 1;
|
||||
```
|
||||
|
||||
Then, if the source were updated at `/tmp/pois.csv` we could synchronize with it while preserving our `rank` values with:
|
||||
|
||||
```sql
|
||||
CREATE tmp_pois(cartodb_id int, name text, type text, longitude double precision, latitude double precision, rank int);
|
||||
COPY tmp_pois FROM '/tmp/pois.csv';
|
||||
SELECT CDB_SyncTable('tmp_pois', 'public', 'pois', '{rank}');
|
||||
DROP TABLE tmp_pois;
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
```
|
||||
CDB_SyncTable(src_table, dst_schema, dst_table, skip_cols)
|
||||
```
|
||||
|
||||
* **src_table** REGCLASS the source data for the synchronization
|
||||
* **dst_scgena** REGNAMESPACE the destination schema
|
||||
* **dst_table** NAME the destination table to be updated
|
||||
* **skip_cols** NAME[] an array of column names, empty by default, which will be skipped
|
@ -3,7 +3,11 @@ List the name of available tables (only the usable ones)
|
||||
#### Using the function
|
||||
|
||||
```sql
|
||||
--- Returns a row for each table having given permission with the table name
|
||||
--- Returns a row for each table having given permission with the table name.
|
||||
--- It also returns tables from others users if you've permission to see them. For example, consider the following scenario:
|
||||
--- User X and User Y at account C.
|
||||
--- User X has a public table T.
|
||||
--- User Y will see table T.
|
||||
--- Currently accepted permissions are: 'public', 'private' or 'all'
|
||||
SELECT CDB_UserTables(perms)
|
||||
```
|
||||
|
@ -1,6 +1,5 @@
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION plpythonu;
|
||||
CREATE EXTENSION cartodb;
|
||||
SET client_min_messages TO error;
|
||||
CREATE EXTENSION cartodb CASCADE;
|
||||
CREATE FUNCTION public.cdb_invalidate_varnish(table_name text)
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
|
@ -1,6 +1,6 @@
|
||||
-- Table to register analysis nodes from https://github.com/cartodb/camshaft
|
||||
CREATE TABLE IF NOT EXISTS
|
||||
cartodb.cdb_analysis_catalog (
|
||||
@extschema@.cdb_analysis_catalog (
|
||||
-- md5 hex hash
|
||||
node_id char(40) CONSTRAINT cdb_analysis_catalog_pkey PRIMARY KEY,
|
||||
-- being json allows to do queries like analysis_def->>'type' = 'buffer'
|
||||
@ -20,5 +20,76 @@ cartodb.cdb_analysis_catalog (
|
||||
-- should register the number of times the node was used
|
||||
hits NUMERIC DEFAULT 0,
|
||||
-- should register what was the last node using current node
|
||||
last_used_from char(40)
|
||||
last_used_from char(40),
|
||||
-- last job modifying the node
|
||||
last_modified_by uuid,
|
||||
-- store error message for failures
|
||||
last_error_message text,
|
||||
-- cached tables involved in the analysis
|
||||
cache_tables regclass[] NOT NULL DEFAULT '{}',
|
||||
-- useful for multi account deployments
|
||||
username text
|
||||
);
|
||||
|
||||
-- This can only be called from an SQL script executed by CREATE EXTENSION
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
BEGIN
|
||||
PERFORM pg_catalog.pg_extension_config_dump('@extschema@.cdb_analysis_catalog', '');
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Migrations to add new columns from old versions.
|
||||
-- IMPORTANT: Those columns will be added in order of creation. To be consistent
|
||||
-- in column order, ensure that new columns are added at the end and in the same order.
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN last_modified_by uuid;
|
||||
EXCEPTION
|
||||
WHEN duplicate_column THEN END;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN last_error_message text;
|
||||
EXCEPTION
|
||||
WHEN duplicate_column THEN END;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN cache_tables regclass[] NOT NULL DEFAULT '{}';
|
||||
EXCEPTION
|
||||
WHEN duplicate_column THEN END;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
BEGIN
|
||||
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN username text;
|
||||
EXCEPTION
|
||||
WHEN duplicate_column THEN END;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- We want the "username" column to be moved to the last position if it was on a position from other versions
|
||||
-- see https://github.com/CartoDB/cartodb-postgresql/issues/276
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
DECLARE
|
||||
column_index int;
|
||||
BEGIN
|
||||
SELECT ordinal_position FROM information_schema.columns WHERE table_name='cdb_analysis_catalog' AND table_schema='@extschema@' AND column_name='username' INTO column_index;
|
||||
IF column_index = 1 OR column_index = 10 THEN
|
||||
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN username_final text;
|
||||
UPDATE @extschema@.cdb_analysis_catalog SET username_final = username;
|
||||
ALTER TABLE @extschema@.cdb_analysis_catalog DROP COLUMN username;
|
||||
ALTER TABLE @extschema@.cdb_analysis_catalog RENAME COLUMN username_final TO username;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
65
scripts-available/CDB_AnalysisCheck.sql
Normal file
65
scripts-available/CDB_AnalysisCheck.sql
Normal file
@ -0,0 +1,65 @@
|
||||
-- Read configuration parameter analysis_quota_factor, making it
|
||||
-- accessible to regular users (which don't have access to cdb_conf)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_GetConfAnalysisQuotaFactor()
|
||||
RETURNS float8 AS
|
||||
$$
|
||||
BEGIN
|
||||
RETURN @extschema@.CDB_Conf_GetConf('analysis_quota_factor')::text::float8;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql'
|
||||
STABLE
|
||||
PARALLEL SAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
|
||||
-- Get the factor (fraction of the quota) for Camshaft cached analysis tables
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_AnalysisQuotaFactor()
|
||||
RETURNS float8 AS
|
||||
$$
|
||||
DECLARE
|
||||
factor float8;
|
||||
BEGIN
|
||||
-- We use a floating point cdb_conf parameter
|
||||
factor := @extschema@._CDB_GetConfAnalysisQuotaFactor();
|
||||
-- With a default value
|
||||
IF factor IS NULL THEN
|
||||
factor := 2;
|
||||
END IF;
|
||||
RETURN factor;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' STABLE PARALLEL SAFE;
|
||||
|
||||
-- This checks the space used up by Camshaft cached analysis tables.
|
||||
-- An exception will be raised if the limits are exceeded.
|
||||
-- The name of an analysis table is passed; this, in addition to the
|
||||
-- db role that executes this function is used to determined which
|
||||
-- analysis tables will be considered.
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_CheckAnalysisQuota(table_name TEXT)
|
||||
RETURNS void AS
|
||||
$$
|
||||
DECLARE
|
||||
schema_name TEXT;
|
||||
user_name TEXT;
|
||||
nominal_quota int8;
|
||||
cache_size float8;
|
||||
BEGIN
|
||||
-- We rely on the search_path to determine the user's schema and
|
||||
-- check for all analysis tables in that schema.
|
||||
-- An alternative would be to use cdb_analysis_catalog to
|
||||
-- select analysis tables (cache_tables) from the same user, analysis or node.
|
||||
-- For example:
|
||||
-- SELECT unnest(cache_tables) FROM cdb_analysis_catalog
|
||||
-- WHERE username IN (SELECT username FROM cdb_analysis_catalog
|
||||
-- WHERE table_name::regclass = ANY (cache_tables));
|
||||
-- At the moment we're not using the provided table_name.
|
||||
|
||||
SELECT current_schema() INTO schema_name;
|
||||
EXECUTE FORMAT('SELECT %I._CDB_UserQuotaInBytes();', schema_name) INTO nominal_quota;
|
||||
IF nominal_quota * @extschema@._CDB_AnalysisQuotaFactor() < @extschema@._CDB_AnalysisDataSize(schema_name) THEN
|
||||
-- The limit is defined by a factor applied to the total space quota for the user
|
||||
RAISE EXCEPTION 'Analysis cache space limits exceeded';
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
55
scripts-available/CDB_AnalysisSupport.sql
Normal file
55
scripts-available/CDB_AnalysisSupport.sql
Normal file
@ -0,0 +1,55 @@
|
||||
-- Internal auxiliar functions to deal with [Camshaft](https://github.com/cartodb/camshaft) cached analysis tables.
|
||||
|
||||
-- This function returns TRUE if a given table name corresponds to a Camshaft cached analysis table
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_IsAnalysisTableName(table_name TEXT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN table_name SIMILAR TO '\Aanalysis_[0-9a-f]{10}_[0-9a-f]{40}\Z';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- This function returns a relation of Camshaft cached analysis tables in the given schema.
|
||||
-- If the schema name parameter is NULL, then tables from all schemas
|
||||
-- that may contain user tables are returned.
|
||||
-- For each table, the regclass, schema name and table name are returned.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_AnalysisTablesInSchema(schema_name text DEFAULT NULL)
|
||||
RETURNS TABLE(table_regclass REGCLASS, schema_name TEXT, table_name TEXT)
|
||||
AS $$
|
||||
SELECT * FROM @extschema@._CDB_UserTablesInSchema(schema_name) WHERE @extschema@._CDB_IsAnalysisTableName(table_name);
|
||||
$$ LANGUAGE 'sql' STABLE PARALLEL SAFE;
|
||||
|
||||
-- This function returns a relation user tables excluding analysis tables
|
||||
-- If the schema name parameter is NULL, then tables from all schemas
|
||||
-- that may contain user tables are returned.
|
||||
-- For each table, the regclass, schema name and table name are returned.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_NonAnalysisTablesInSchema(schema_name text DEFAULT NULL)
|
||||
RETURNS TABLE(table_regclass REGCLASS, schema_name TEXT, table_name TEXT)
|
||||
AS $$
|
||||
SELECT * FROM @extschema@._CDB_UserTablesInSchema(schema_name) WHERE Not @extschema@._CDB_IsAnalysisTableName(table_name);
|
||||
$$ LANGUAGE 'sql' STABLE PARALLEL SAFE;
|
||||
|
||||
-- Total spaced used up by Camshaft cached analysis tables in the given schema.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_AnalysisDataSize(schema_name TEXT DEFAULT NULL)
|
||||
RETURNS bigint AS
|
||||
$$
|
||||
DECLARE
|
||||
total_size bigint;
|
||||
BEGIN
|
||||
WITH analysis_tables AS (
|
||||
SELECT t.schema_name, t.table_name FROM @extschema@._CDB_AnalysisTablesInSchema(schema_name) t
|
||||
)
|
||||
SELECT COALESCE(INT8(SUM(@extschema@._CDB_total_relation_size(analysis_tables.schema_name, analysis_tables.table_name))))::int8
|
||||
INTO total_size FROM analysis_tables;
|
||||
IF total_size IS NOT NULL THEN
|
||||
RETURN total_size;
|
||||
ELSE
|
||||
RETURN 0;
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
-- Function returning the column names of a table
|
||||
CREATE OR REPLACE FUNCTION CDB_ColumnNames(REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_ColumnNames(REGCLASS)
|
||||
RETURNS SETOF information_schema.sql_identifier
|
||||
AS $$
|
||||
SELECT
|
||||
@ -9,8 +9,8 @@ AS $$
|
||||
WHERE c.oid = $1::oid
|
||||
AND a.attstattarget < 0 -- exclude system columns
|
||||
ORDER BY a.attnum;
|
||||
$$ LANGUAGE SQL;
|
||||
$$ LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- This is to migrate from pre-0.2.0 version
|
||||
-- See http://github.com/CartoDB/cartodb-postgresql/issues/36
|
||||
GRANT EXECUTE ON FUNCTION CDB_ColumnNames(REGCLASS) TO PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION @extschema@.CDB_ColumnNames(REGCLASS) TO PUBLIC;
|
||||
|
@ -1,5 +1,5 @@
|
||||
-- Function returning the type of a column
|
||||
CREATE OR REPLACE FUNCTION CDB_ColumnType(REGCLASS, TEXT)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_ColumnType(REGCLASS, TEXT)
|
||||
RETURNS information_schema.character_data
|
||||
AS $$
|
||||
SELECT
|
||||
@ -9,8 +9,8 @@ AS $$
|
||||
WHERE c.oid = $1::oid
|
||||
AND a.attname = $2
|
||||
AND a.attstattarget < 0; -- exclude system columns
|
||||
$$ LANGUAGE SQL;
|
||||
$$ LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- This is to migrate from pre-0.2.0 version
|
||||
-- See http://github.com/CartoDB/cartodb-postgresql/issues/36
|
||||
GRANT EXECUTE ON FUNCTION CDB_ColumnType(REGCLASS, TEXT) TO public;
|
||||
GRANT EXECUTE ON FUNCTION @extschema@.CDB_ColumnType(REGCLASS, TEXT) TO public;
|
||||
|
@ -5,44 +5,44 @@
|
||||
-- Functions needing reading configuration should use SECURITY DEFINER.
|
||||
----------------------------------
|
||||
|
||||
-- This will trigger NOTICE if cartodb.CDB_CONF already exists
|
||||
-- This will trigger NOTICE if @extschema@.CDB_CONF already exists
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
BEGIN
|
||||
CREATE TABLE IF NOT EXISTS cartodb.CDB_CONF ( KEY TEXT PRIMARY KEY, VALUE JSON NOT NULL );
|
||||
CREATE TABLE IF NOT EXISTS @extschema@.CDB_CONF ( KEY TEXT PRIMARY KEY, VALUE JSON NOT NULL );
|
||||
END
|
||||
$$;
|
||||
|
||||
-- This can only be called from an SQL script executed by CREATE EXTENSION
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
BEGIN
|
||||
PERFORM pg_catalog.pg_extension_config_dump('cartodb.CDB_CONF', '');
|
||||
PERFORM pg_catalog.pg_extension_config_dump('@extschema@.CDB_CONF', '');
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Conf_SetConf(key text, value JSON)
|
||||
FUNCTION @extschema@.CDB_Conf_SetConf(key text, value JSON)
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
PERFORM cartodb.CDB_Conf_RemoveConf(key);
|
||||
EXECUTE 'INSERT INTO cartodb.CDB_CONF (KEY, VALUE) VALUES ($1, $2);' USING key, value;
|
||||
PERFORM @extschema@.CDB_Conf_RemoveConf(key);
|
||||
EXECUTE 'INSERT INTO @extschema@.CDB_CONF (KEY, VALUE) VALUES ($1, $2);' USING key, value;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Conf_RemoveConf(key text)
|
||||
FUNCTION @extschema@.CDB_Conf_RemoveConf(key text)
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
EXECUTE 'DELETE FROM cartodb.CDB_CONF WHERE KEY = $1;' USING key;
|
||||
EXECUTE 'DELETE FROM @extschema@.CDB_CONF WHERE KEY = $1;' USING key;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Conf_GetConf(key text)
|
||||
FUNCTION @extschema@.CDB_Conf_GetConf(key text)
|
||||
RETURNS JSON AS $$
|
||||
DECLARE
|
||||
value JSON;
|
||||
BEGIN
|
||||
EXECUTE 'SELECT VALUE FROM cartodb.CDB_CONF WHERE KEY = $1;' INTO value USING key;
|
||||
EXECUTE 'SELECT VALUE FROM @extschema@.CDB_CONF WHERE KEY = $1;' INTO value USING key;
|
||||
RETURN value;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
$$ LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
|
||||
|
14
scripts-available/CDB_DDLTriggers.sql
Normal file
14
scripts-available/CDB_DDLTriggers.sql
Normal file
@ -0,0 +1,14 @@
|
||||
--
|
||||
-- Legacy file
|
||||
-- Introduced again to make sure that updates do not leave dangling functions
|
||||
--
|
||||
|
||||
DROP FUNCTION IF EXISTS @extschema@.cdb_handle_create_table();
|
||||
DROP FUNCTION IF EXISTS @extschema@.cdb_handle_drop_table();
|
||||
DROP FUNCTION IF EXISTS @extschema@.cdb_handle_alter_column();
|
||||
DROP FUNCTION IF EXISTS @extschema@.cdb_handle_drop_column();
|
||||
DROP FUNCTION IF EXISTS @extschema@.cdb_handle_add_column();
|
||||
DROP FUNCTION IF EXISTS @extschema@.cdb_disable_ddl_hooks();
|
||||
DROP FUNCTION IF EXISTS @extschema@.cdb_enable_ddl_hooks();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
-- Convert timestamp to double precision
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_DateToNumber(input timestamp)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DateToNumber(input timestamp)
|
||||
RETURNS double precision AS $$
|
||||
DECLARE output double precision;
|
||||
BEGIN
|
||||
@ -12,11 +12,11 @@ BEGIN
|
||||
RETURN output;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' STABLE STRICT;
|
||||
LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL UNSAFE;
|
||||
|
||||
-- Convert timestamp with time zone to double precision
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_DateToNumber(input timestamp with time zone)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DateToNumber(input timestamp with time zone)
|
||||
RETURNS double precision AS $$
|
||||
DECLARE output double precision;
|
||||
BEGIN
|
||||
@ -28,4 +28,4 @@ BEGIN
|
||||
RETURN output;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' STABLE STRICT;
|
||||
LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL UNSAFE;
|
||||
|
@ -1,5 +1,5 @@
|
||||
-- Find thousand and decimal digits separators
|
||||
CREATE OR REPLACE FUNCTION CDB_DigitSeparator (rel REGCLASS, fld TEXT, OUT t CHAR, OUT d CHAR)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DigitSeparator (rel REGCLASS, fld TEXT, OUT t CHAR, OUT d CHAR)
|
||||
as $$
|
||||
DECLARE
|
||||
sql TEXT;
|
||||
@ -50,4 +50,4 @@ BEGIN
|
||||
|
||||
END
|
||||
$$
|
||||
LANGUAGE 'plpgsql' STABLE STRICT;
|
||||
LANGUAGE 'plpgsql' STABLE STRICT PARALLEL SAFE;
|
||||
|
@ -10,7 +10,7 @@
|
||||
-- 1. width_bucket/histograms: http://tapoueh.org/blog/2014/02/21-PostgreSQL-histogram
|
||||
-- 2. R implementation: https://github.com/cran/agrmt
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_DistType ( in_array NUMERIC[] ) RETURNS text as $$
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DistType ( in_array NUMERIC[] ) RETURNS text as $$
|
||||
DECLARE
|
||||
element_count INT4;
|
||||
minv numeric;
|
||||
@ -60,16 +60,16 @@ BEGIN
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
|
||||
signature = _CDB_DistTypeClassify(ajus);
|
||||
signature = @extschema@._CDB_DistTypeClassify(ajus);
|
||||
END IF;
|
||||
|
||||
RETURN signature;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
$$ language plpgsql IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- Classify data into AJUSFL
|
||||
|
||||
CREATE OR REPLACE FUNCTION _CDB_DistTypeClassify ( in_array INT[] ) RETURNS text as $$
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_DistTypeClassify ( in_array INT[] ) RETURNS text as $$
|
||||
DECLARE
|
||||
element_count INT4;
|
||||
maxv numeric;
|
||||
@ -119,4 +119,4 @@ BEGIN
|
||||
|
||||
RETURN type;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
$$ language plpgsql IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
@ -5,7 +5,7 @@
|
||||
--
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_DistinctMeasure ( in_array text[], threshold numeric DEFAULT null ) RETURNS numeric as $$
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DistinctMeasure ( in_array text[], threshold numeric DEFAULT null ) RETURNS numeric as $$
|
||||
DECLARE
|
||||
element_count INT4;
|
||||
maxval numeric;
|
||||
@ -43,4 +43,4 @@ BEGIN
|
||||
|
||||
RETURN passes;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
$$ language plpgsql IMMUTABLE PARALLEL SAFE;
|
||||
|
@ -1,8 +1,8 @@
|
||||
--
|
||||
-- Calculate the equal interval bins for a given column
|
||||
--
|
||||
-- @param in_array A numeric array of numbers to determine the best
|
||||
-- to determine the bin boundary
|
||||
-- @param in_array An array of numbers to determine the best
|
||||
-- bin boundary
|
||||
--
|
||||
-- @param breaks The number of bins you want to find.
|
||||
--
|
||||
@ -11,27 +11,14 @@
|
||||
--
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_EqualIntervalBins ( in_array NUMERIC[], breaks INT ) RETURNS NUMERIC[] as $$
|
||||
DECLARE
|
||||
diff numeric;
|
||||
min_val numeric;
|
||||
max_val numeric;
|
||||
tmp_val numeric;
|
||||
i INT := 1;
|
||||
reply numeric[];
|
||||
BEGIN
|
||||
SELECT min(e), max(e) INTO min_val, max_val FROM ( SELECT unnest(in_array) e ) x WHERE e IS NOT NULL;
|
||||
diff = (max_val - min_val) / breaks::numeric;
|
||||
LOOP
|
||||
IF i < breaks THEN
|
||||
tmp_val = min_val + i::numeric * diff;
|
||||
reply = array_append(reply, tmp_val);
|
||||
i := i+1;
|
||||
ELSE
|
||||
reply = array_append(reply, max_val);
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
RETURN reply;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_EqualIntervalBins ( in_array anyarray, breaks INT ) RETURNS anyarray as $$
|
||||
WITH stats AS (
|
||||
SELECT min(e), (max(e)-min(e))/breaks AS del
|
||||
FROM (SELECT unnest(in_array) e) AS p)
|
||||
SELECT array_agg(bins)
|
||||
FROM (
|
||||
SELECT min + generate_series(1,breaks)*del AS bins
|
||||
FROM stats) q;
|
||||
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_EqualIntervalBins( numeric[], integer);
|
||||
|
36
scripts-available/CDB_EstimateRowCount.sql
Normal file
36
scripts-available/CDB_EstimateRowCount.sql
Normal file
@ -0,0 +1,36 @@
|
||||
-- Internal function to generate stats for a table if they don't exist
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_GenerateStats(reloid REGCLASS)
|
||||
RETURNS VOID
|
||||
AS $$
|
||||
DECLARE
|
||||
has_stats BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS (
|
||||
SELECT * FROM pg_catalog.pg_statistic WHERE starelid = reloid
|
||||
) INTO has_stats;
|
||||
IF NOT has_stats THEN
|
||||
EXECUTE Format('ANALYZE %s;', reloid);
|
||||
END IF;
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql'
|
||||
VOLATILE
|
||||
STRICT
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
-- Return a row count estimate of the result of a query using statistics
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_EstimateRowCount(query text)
|
||||
RETURNS Numeric
|
||||
AS $$
|
||||
DECLARE
|
||||
plan JSON;
|
||||
BEGIN
|
||||
-- Make sure statistics exist for all the tables of the query
|
||||
PERFORM @extschema@._CDB_GenerateStats(tabname) FROM unnest(@extschema@.CDB_QueryTablesText(query)) AS tabname;
|
||||
|
||||
-- Use the query planner to obtain an estimate of the number of result rows
|
||||
EXECUTE 'EXPLAIN (FORMAT JSON) ' || query INTO STRICT plan;
|
||||
RETURN plan->0->'Plan'->'Plan Rows';
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE STRICT PARALLEL UNSAFE;
|
@ -1,2 +1,2 @@
|
||||
SELECT pg_catalog.pg_extension_config_dump('cartodb.cdb_tablemetadata','');
|
||||
SELECT pg_catalog.pg_extension_config_dump('@extschema@.cdb_tablemetadata','');
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_extension_reload() RETURNS void
|
||||
CREATE OR REPLACE FUNCTION @extschema@.cdb_extension_reload() RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
ver TEXT;
|
||||
sql TEXT;
|
||||
BEGIN
|
||||
ver := split_part(cartodb.cdb_version(), ' ', 1);
|
||||
ver := split_part(@extschema@.cdb_version(), ' ', 1);
|
||||
sql := 'ALTER EXTENSION cartodb UPDATE TO ''' || ver || 'next''';
|
||||
EXECUTE sql;
|
||||
sql := 'ALTER EXTENSION cartodb UPDATE TO ''' || ver || '''';
|
||||
EXECUTE sql;
|
||||
END;
|
||||
$$ language 'plpgsql' VOLATILE;
|
||||
$$ language 'plpgsql' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.schema_exists(schema_name text)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.schema_exists(schema_name text)
|
||||
RETURNS boolean AS
|
||||
$$
|
||||
SELECT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = schema_name::text);
|
||||
$$
|
||||
language sql VOLATILE;
|
||||
language sql STABLE PARALLEL SAFE;
|
||||
|
437
scripts-available/CDB_FederatedServer.sql
Normal file
437
scripts-available/CDB_FederatedServer.sql
Normal file
@ -0,0 +1,437 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- This function is just a placement to store and use the pattern for
|
||||
-- foreign object names
|
||||
-- Servers: cdb_fs_$(server_name)
|
||||
-- View schema: cdb_fs_$(server_name)
|
||||
-- > This is where all views created when importing tables are placed
|
||||
-- > One server has only one view schema
|
||||
-- Import Schemas: cdb_fs_schema_$(md5sum(server_name || remote_schema_name))
|
||||
-- > This is where the foreign tables are placed
|
||||
-- > One server has one import schema per remote schema plus auxiliar ones used
|
||||
-- to access the remote catalog (pg_catalog, information_schema...)
|
||||
-- Owner role: cdb_fs_$(md5sum(current_database() || server_name)
|
||||
-- > This is the role than owns all schemas and tables related to the server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Name_Pattern()
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
SELECT 'cdb_fs_'::text;
|
||||
$$
|
||||
LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
--
|
||||
-- Produce a valid DB name for servers generated for the Federated Server
|
||||
-- If check_existence is true, it'll throw if the server doesn't exists
|
||||
-- This name is also used as the schema to store views
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Generate_Server_Name(input_name TEXT, check_existence BOOL)
|
||||
RETURNS NAME
|
||||
AS $$
|
||||
DECLARE
|
||||
internal_server_name text := format('%s%s', @extschema@.__CDB_FS_Name_Pattern(), input_name);
|
||||
BEGIN
|
||||
IF input_name IS NULL OR char_length(input_name) = 0 THEN
|
||||
RAISE EXCEPTION 'Server name cannot be NULL';
|
||||
END IF;
|
||||
|
||||
-- We discard anything that would be truncated
|
||||
IF (char_length(internal_server_name) >= 64) THEN
|
||||
RAISE EXCEPTION 'Server name (%) is too long to be used as identifier', input_name;
|
||||
END IF;
|
||||
|
||||
IF (check_existence AND (NOT EXISTS (SELECT * FROM pg_foreign_server WHERE srvname = internal_server_name))) THEN
|
||||
RAISE EXCEPTION 'Server "%" does not exist', input_name;
|
||||
END IF;
|
||||
|
||||
RETURN internal_server_name::name;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
--
|
||||
-- Given the internal name for a remote server, it returns the name used by the user
|
||||
-- Reverses __CDB_FS_Generate_Server_Name
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Extract_Server_Name(internal_server_name NAME)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
SELECT right(internal_server_name,
|
||||
char_length(internal_server_name::TEXT) - char_length(@extschema@.__CDB_FS_Name_Pattern()))::TEXT;
|
||||
$$
|
||||
LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
--
|
||||
-- Produce a valid name for a schema generated for the Federated Server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Generate_Schema_Name(internal_server_name NAME, schema_name TEXT)
|
||||
RETURNS NAME
|
||||
AS $$
|
||||
DECLARE
|
||||
hash_value text := md5(internal_server_name::text || '__' || schema_name::text);
|
||||
BEGIN
|
||||
IF schema_name IS NULL THEN
|
||||
RAISE EXCEPTION 'Schema name cannot be NULL';
|
||||
END IF;
|
||||
RETURN format('%s%s%s', @extschema@.__CDB_FS_Name_Pattern(), 'schema_', hash_value)::name;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
--
|
||||
-- Produce a valid name for a role generated for the Federated Server
|
||||
-- This needs to include the current database in its hash to avoid collisions in clusters with more than one database
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Generate_Server_Role_Name(internal_server_name NAME)
|
||||
RETURNS NAME
|
||||
AS $$
|
||||
DECLARE
|
||||
hash_value text := md5(current_database()::text || '__' || internal_server_name::text);
|
||||
role_name text := format('%s%s%s', @extschema@.__CDB_FS_Name_Pattern(), 'role_', hash_value);
|
||||
BEGIN
|
||||
RETURN role_name::name;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
|
||||
|
||||
--
|
||||
-- Creates (if not exist) a schema to place the objects for a remote schema
|
||||
-- The schema is with the same AUTHORIZATION as the server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Create_Schema(internal_server_name NAME, schema_name TEXT)
|
||||
RETURNS NAME
|
||||
AS $$
|
||||
DECLARE
|
||||
schema_name name := @extschema@.__CDB_FS_Generate_Schema_Name(internal_server_name, schema_name);
|
||||
role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(internal_server_name);
|
||||
BEGIN
|
||||
-- By changing the local role to the owner of the server we have an
|
||||
-- easy way to check for permissions and keep all objects under the same owner
|
||||
BEGIN
|
||||
EXECUTE 'SET LOCAL ROLE ' || quote_ident(role_name);
|
||||
EXCEPTION
|
||||
WHEN invalid_parameter_value THEN
|
||||
RAISE EXCEPTION 'Server "%" does not exist',
|
||||
@extschema@.__CDB_FS_Extract_Server_Name(internal_server_name);
|
||||
WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Not enough permissions to access the server "%"',
|
||||
@extschema@.__CDB_FS_Extract_Server_Name(internal_server_name);
|
||||
END;
|
||||
|
||||
IF NOT EXISTS (SELECT oid FROM pg_namespace WHERE nspname = schema_name) THEN
|
||||
EXECUTE 'CREATE SCHEMA ' || quote_ident(schema_name) || ' AUTHORIZATION ' || quote_ident(role_name);
|
||||
END IF;
|
||||
RETURN schema_name;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Returns the type of a server by internal name
|
||||
-- Currently all of them should be postgres_fdw
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_server_type(internal_server_name NAME)
|
||||
RETURNS name
|
||||
AS $$
|
||||
SELECT f.fdwname
|
||||
FROM pg_foreign_server s
|
||||
JOIN pg_foreign_data_wrapper f ON s.srvfdw = f.oid
|
||||
WHERE s.srvname = internal_server_name;
|
||||
$$
|
||||
LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Take a config jsonb and transform it to an input suitable for _CDB_SetUp_User_PG_FDW_Server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_credentials_to_user_mapping(input_config JSONB)
|
||||
RETURNS jsonb
|
||||
AS $$
|
||||
DECLARE
|
||||
mapping jsonb := '{}'::jsonb;
|
||||
BEGIN
|
||||
IF NOT (input_config ? 'credentials') THEN
|
||||
RAISE EXCEPTION 'Credentials are mandatory';
|
||||
END IF;
|
||||
|
||||
-- For now, allow not passing username or password
|
||||
IF input_config->'credentials'->'username' IS NOT NULL THEN
|
||||
mapping := jsonb_build_object('user', input_config->'credentials'->'username');
|
||||
END IF;
|
||||
IF input_config->'credentials'->'password' IS NOT NULL THEN
|
||||
mapping := mapping || jsonb_build_object('password', input_config->'credentials'->'password');
|
||||
END IF;
|
||||
|
||||
RETURN (input_config - 'credentials')::jsonb || jsonb_build_object('user_mapping', mapping);
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Take a config jsonb as input and return it augmented with default
|
||||
-- options
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_add_default_options(input_config jsonb)
|
||||
RETURNS jsonb
|
||||
AS $$
|
||||
DECLARE
|
||||
default_options jsonb := '{
|
||||
"extensions": "postgis",
|
||||
"updatable": "false",
|
||||
"use_remote_estimate": "true",
|
||||
"fetch_size": "1000"
|
||||
}';
|
||||
server_config jsonb;
|
||||
BEGIN
|
||||
IF NOT (input_config ? 'server') THEN
|
||||
RAISE EXCEPTION 'Server information is mandatory';
|
||||
END IF;
|
||||
server_config := default_options || to_jsonb(input_config->'server');
|
||||
RETURN jsonb_set(input_config, '{server}'::text[], server_config);
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Given an server name, returns the username used in the configuration if the caller has rights to access it
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_get_usermapping_username(internal_server_name NAME)
|
||||
RETURNS text
|
||||
AS $$
|
||||
DECLARE
|
||||
role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(internal_server_name);
|
||||
username text;
|
||||
BEGIN
|
||||
BEGIN
|
||||
EXECUTE 'SET LOCAL ROLE ' || quote_ident(role_name);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RETURN NULL;
|
||||
END;
|
||||
|
||||
SELECT (SELECT option_value FROM pg_options_to_table(u.umoptions) WHERE option_name LIKE 'user') as name INTO username
|
||||
FROM pg_foreign_server s
|
||||
LEFT JOIN pg_user_mappings u
|
||||
ON u.srvid = s.oid
|
||||
WHERE s.srvname = internal_server_name
|
||||
ORDER BY 1;
|
||||
|
||||
RESET ROLE;
|
||||
|
||||
RETURN username;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Public functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--
|
||||
-- Registers a new PG server
|
||||
--
|
||||
-- Example config: '{
|
||||
-- "server": {
|
||||
-- "dbname": "fdw_target",
|
||||
-- "host": "localhost",
|
||||
-- "port": 5432,
|
||||
-- "extensions": "postgis",
|
||||
-- "updatable": "false",
|
||||
-- "use_remote_estimate": "true",
|
||||
-- "fetch_size": "1000"
|
||||
-- },
|
||||
-- "credentials": {
|
||||
-- "username": "fdw_user",
|
||||
-- "password": "foobarino"
|
||||
-- }
|
||||
-- }'
|
||||
--
|
||||
-- The configuration from __CDB_FS_add_default_options will be appended
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_Register_PG(server TEXT, config JSONB)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => false);
|
||||
final_config json := @extschema@.__CDB_FS_credentials_to_user_mapping(@extschema@.__CDB_FS_add_default_options(config));
|
||||
role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(server_internal);
|
||||
row record;
|
||||
option record;
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT * FROM pg_extension WHERE extname = 'postgres_fdw') THEN
|
||||
RAISE EXCEPTION 'postgres_fdw extension is not installed'
|
||||
USING HINT = 'Please install it with `CREATE EXTENSION postgres_fdw`';
|
||||
END IF;
|
||||
|
||||
-- We only create server and roles if the server didn't exist before
|
||||
IF NOT EXISTS (SELECT * FROM pg_foreign_server WHERE srvname = server_internal) THEN
|
||||
BEGIN
|
||||
EXECUTE FORMAT('CREATE SERVER %I FOREIGN DATA WRAPPER postgres_fdw', server_internal);
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = role_name) THEN
|
||||
EXECUTE FORMAT('CREATE ROLE %I NOLOGIN', role_name);
|
||||
END IF;
|
||||
EXECUTE FORMAT('GRANT ALL PRIVILEGES ON DATABASE %I TO %I', current_database(), role_name);
|
||||
|
||||
-- These grants over `@extschema@` and `@postgisschema@` are necessary for the cases
|
||||
-- where the schemas aren't accessible to PUBLIC, which is what happens in a CARTO database
|
||||
EXECUTE FORMAT('GRANT USAGE ON SCHEMA %I TO %I', '@extschema@', role_name);
|
||||
EXECUTE FORMAT('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA %I TO %I', '@extschema@', role_name);
|
||||
EXECUTE FORMAT('GRANT USAGE ON SCHEMA %I TO %I', '@postgisschema@', role_name);
|
||||
EXECUTE FORMAT('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA %I TO %I', '@postgisschema@', role_name);
|
||||
EXECUTE FORMAT('GRANT SELECT ON ALL TABLES IN SCHEMA %I TO %I', '@postgisschema@', role_name);
|
||||
|
||||
EXECUTE FORMAT('GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO %I', role_name);
|
||||
EXECUTE FORMAT('GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO %I', role_name);
|
||||
EXECUTE FORMAT('GRANT USAGE ON FOREIGN SERVER %I TO %I', server_internal, role_name);
|
||||
EXECUTE FORMAT('ALTER SERVER %I OWNER TO %I', server_internal, role_name);
|
||||
EXECUTE FORMAT ('CREATE USER MAPPING FOR %I SERVER %I', role_name, server_internal);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Could not create server %: %', server, SQLERRM
|
||||
USING HINT = 'Please clean the left over objects';
|
||||
END;
|
||||
END IF;
|
||||
|
||||
-- Add new options
|
||||
FOR row IN SELECT p.key, p.value from lateral json_each_text(final_config->'server') p
|
||||
LOOP
|
||||
IF NOT EXISTS (
|
||||
WITH a AS (
|
||||
SELECT split_part(unnest(srvoptions), '=', 1) AS options FROM pg_foreign_server WHERE srvname=server_internal
|
||||
) SELECT * from a where options = row.key)
|
||||
THEN
|
||||
EXECUTE FORMAT('ALTER SERVER %I OPTIONS (ADD %I %L)', server_internal, row.key, row.value);
|
||||
ELSE
|
||||
EXECUTE FORMAT('ALTER SERVER %I OPTIONS (SET %I %L)', server_internal, row.key, row.value);
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- Update user mapping settings
|
||||
FOR option IN SELECT o.key, o.value from lateral json_each_text(final_config->'user_mapping') o
|
||||
LOOP
|
||||
IF NOT EXISTS (
|
||||
WITH a AS (
|
||||
SELECT split_part(unnest(umoptions), '=', 1) as options from pg_user_mappings WHERE srvname = server_internal AND usename = role_name
|
||||
) SELECT * from a where options = option.key)
|
||||
THEN
|
||||
EXECUTE FORMAT('ALTER USER MAPPING FOR %I SERVER %I OPTIONS (ADD %I %L)', role_name, server_internal, option.key, option.value);
|
||||
ELSE
|
||||
EXECUTE FORMAT('ALTER USER MAPPING FOR %I SERVER %I OPTIONS (SET %I %L)', role_name, server_internal, option.key, option.value);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Drops a registered server and all the objects associated with it
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_Unregister(server TEXT)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true);
|
||||
role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(server_internal);
|
||||
BEGIN
|
||||
SET client_min_messages = ERROR;
|
||||
BEGIN
|
||||
EXECUTE FORMAT ('DROP USER MAPPING FOR %I SERVER %I', role_name, server_internal);
|
||||
EXECUTE FORMAT ('DROP OWNED BY %I', role_name);
|
||||
EXECUTE FORMAT ('DROP ROLE %I', role_name);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Not enough permissions to drop the server "%"', server;
|
||||
END;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- List registered servers
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_List_Servers(server TEXT DEFAULT '%')
|
||||
RETURNS TABLE (
|
||||
name text,
|
||||
driver text,
|
||||
host text,
|
||||
port text,
|
||||
dbname text,
|
||||
readmode text,
|
||||
username text
|
||||
)
|
||||
AS $$
|
||||
DECLARE
|
||||
server_name text := concat(@extschema@.__CDB_FS_Name_Pattern(), server);
|
||||
BEGIN
|
||||
RETURN QUERY SELECT
|
||||
-- Name as shown to the user
|
||||
@extschema@.__CDB_FS_Extract_Server_Name(s.srvname) AS "Name",
|
||||
|
||||
-- Which driver are we using (postgres_fdw, odbc_fdw...)
|
||||
@extschema@.__CDB_FS_server_type(s.srvname)::text AS "Driver",
|
||||
|
||||
-- Read options from pg_foreign_server
|
||||
(SELECT option_value FROM pg_options_to_table(s.srvoptions) WHERE option_name LIKE 'host') AS "Host",
|
||||
(SELECT option_value FROM pg_options_to_table(s.srvoptions) WHERE option_name LIKE 'port') AS "Port",
|
||||
(SELECT option_value FROM pg_options_to_table(s.srvoptions) WHERE option_name LIKE 'dbname') AS "DBName",
|
||||
CASE WHEN (SELECT NOT option_value::boolean FROM pg_options_to_table(s.srvoptions) WHERE option_name LIKE 'updatable') THEN 'read-only' ELSE 'read-write' END AS "ReadMode",
|
||||
|
||||
@extschema@.__CDB_FS_get_usermapping_username(s.srvname)::text AS "Username"
|
||||
FROM pg_foreign_server s
|
||||
LEFT JOIN pg_user_mappings u
|
||||
ON u.srvid = s.oid
|
||||
WHERE s.srvname ILIKE server_name
|
||||
ORDER BY 1;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL SAFE;
|
||||
|
||||
|
||||
--
|
||||
-- Grant access to a server
|
||||
-- In the future we might consider adding the server's view schema to the role search_path
|
||||
-- to make it easier to access the created views
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_Grant_Access(server TEXT, db_role NAME)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true);
|
||||
server_role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(server_internal);
|
||||
BEGIN
|
||||
IF (db_role IS NULL) THEN
|
||||
RAISE EXCEPTION 'User role "%" cannot be NULL', username;
|
||||
END IF;
|
||||
BEGIN
|
||||
EXECUTE format('GRANT %I TO %I', server_role_name, db_role);
|
||||
EXCEPTION
|
||||
WHEN insufficient_privilege THEN
|
||||
RAISE EXCEPTION 'You do not have rights to grant access on "%"', server;
|
||||
WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Could not grant access on "%" to "%": %', server, db_role, SQLERRM;
|
||||
END;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Revoke access to a server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_Revoke_Access(server TEXT, db_role NAME)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true);
|
||||
server_role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(server_internal);
|
||||
BEGIN
|
||||
IF (db_role IS NULL) THEN
|
||||
RAISE EXCEPTION 'User role "%" cannot be NULL', username;
|
||||
END IF;
|
||||
BEGIN
|
||||
EXECUTE format('REVOKE %I FROM %I', server_role_name, db_role);
|
||||
EXCEPTION
|
||||
WHEN insufficient_privilege THEN
|
||||
RAISE EXCEPTION 'You do not have rights to revoke access on "%"', server;
|
||||
WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Could not revoke access on "%" to "%": %', server, db_role, SQLERRM;
|
||||
END;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
243
scripts-available/CDB_FederatedServerDiagnostics.sql
Normal file
243
scripts-available/CDB_FederatedServerDiagnostics.sql
Normal file
@ -0,0 +1,243 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Import a foreign table if it does not exist
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Import_If_Not_Exists(server_internal name, remote_schema name, remote_table name)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM pg_class
|
||||
WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = local_schema)
|
||||
AND relname = remote_table
|
||||
) THEN
|
||||
EXECUTE format('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I',
|
||||
remote_schema, remote_table, server_internal, local_schema);
|
||||
END IF;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Get the version of a remote PG server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_Server_Version_PG(server_internal name)
|
||||
RETURNS text
|
||||
AS $$
|
||||
DECLARE
|
||||
remote_schema name := 'pg_catalog';
|
||||
remote_table name := 'pg_settings';
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
||||
remote_server_version text;
|
||||
BEGIN
|
||||
PERFORM @extschema@.__CDB_FS_Import_If_Not_Exists(server_internal, remote_schema, remote_table);
|
||||
|
||||
BEGIN
|
||||
EXECUTE format('
|
||||
SELECT setting FROM %I.%I WHERE name = ''server_version'';
|
||||
', local_schema, remote_table) INTO remote_server_version;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Not enough permissions to access the server "%"',
|
||||
@extschema@.__CDB_FS_Extract_Server_Name(server_internal);
|
||||
END;
|
||||
|
||||
RETURN remote_server_version;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--
|
||||
-- Get the PostGIS extension version of a remote PG server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_PostGIS_Version_PG(server_internal name)
|
||||
RETURNS text
|
||||
AS $$
|
||||
DECLARE
|
||||
remote_schema name := 'pg_catalog';
|
||||
remote_table name := 'pg_extension';
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
||||
remote_postgis_version text;
|
||||
BEGIN
|
||||
PERFORM @extschema@.__CDB_FS_Import_If_Not_Exists(server_internal, remote_schema, remote_table);
|
||||
|
||||
BEGIN
|
||||
EXECUTE format('
|
||||
SELECT extversion FROM %I.%I WHERE extname = ''postgis'';
|
||||
', local_schema, remote_table) INTO remote_postgis_version;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Not enough permissions to access the server "%"',
|
||||
@extschema@.__CDB_FS_Extract_Server_Name(server_internal);
|
||||
END;
|
||||
|
||||
RETURN remote_postgis_version;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--
|
||||
-- Get the foreign server options of a remote PG server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_Server_Options_PG(server_internal name)
|
||||
RETURNS jsonb
|
||||
AS $$
|
||||
-- See https://www.postgresql.org/docs/current/catalog-pg-foreign-server.html
|
||||
-- See https://www.postgresql.org/docs/current/functions-info.html
|
||||
SELECT jsonb_object_agg(opt.option_name, opt.option_value) FROM (
|
||||
SELECT (pg_options_to_table(srvoptions)).*
|
||||
FROM pg_foreign_server
|
||||
WHERE srvname = server_internal
|
||||
) AS opt;
|
||||
$$
|
||||
LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--
|
||||
-- Get a foreign PG server hostname from the catalog
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_Server_Host_PG(server_internal name)
|
||||
RETURNS text
|
||||
AS $$
|
||||
SELECT option_value FROM (
|
||||
SELECT (pg_options_to_table(srvoptions)).*
|
||||
FROM pg_foreign_server WHERE srvname = server_internal
|
||||
) AS opt WHERE opt.option_name = 'host';
|
||||
$$
|
||||
LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--
|
||||
-- Get a foreign PG server port from the catalog
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_Server_Port_PG(server_internal name)
|
||||
RETURNS integer
|
||||
AS $$
|
||||
SELECT option_value::integer FROM (
|
||||
SELECT (pg_options_to_table(srvoptions)).*
|
||||
FROM pg_foreign_server WHERE srvname = server_internal
|
||||
) AS opt WHERE opt.option_name = 'port';
|
||||
$$
|
||||
LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Get one measure of network latency in ms to a remote TCP server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_TCP_Foreign_Server_Latency(
|
||||
server_internal name,
|
||||
timeout_seconds float DEFAULT 5.0,
|
||||
n_samples integer DEFAULT 10
|
||||
)
|
||||
RETURNS jsonb
|
||||
AS $$
|
||||
import socket
|
||||
import json
|
||||
import math
|
||||
from timeit import default_timer as timer
|
||||
|
||||
plan = plpy.prepare("SELECT @extschema@.__CDB_FS_Foreign_Server_Host_PG($1) AS host", ['name'])
|
||||
rv = plpy.execute(plan, [server_internal], 1)
|
||||
host = rv[0]['host']
|
||||
|
||||
plan = plpy.prepare("SELECT @extschema@.__CDB_FS_Foreign_Server_Port_PG($1) AS port", ['name'])
|
||||
rv = plpy.execute(plan, [server_internal], 1)
|
||||
port = rv[0]['port'] or 5432
|
||||
|
||||
n_errors = 0
|
||||
samples = []
|
||||
|
||||
for i in range(n_samples):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(timeout_seconds)
|
||||
|
||||
t_start = timer()
|
||||
|
||||
try:
|
||||
s.connect((host, int(port)))
|
||||
t_stop = timer()
|
||||
s.shutdown(socket.SHUT_RD)
|
||||
except (socket.timeout, OSError, socket.error) as ex:
|
||||
plpy.warning('could not connect to server %s:%d, %s' % (host, port, str(ex)))
|
||||
n_errors += 1
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
t_connect = (t_stop - t_start) * 1000.0
|
||||
plpy.debug('TCP connection %s:%d time=%.2f ms' % (host, port, t_connect))
|
||||
samples.append(t_connect)
|
||||
|
||||
stats = {
|
||||
'n_samples': n_samples,
|
||||
'n_errors': n_errors,
|
||||
}
|
||||
n = len(samples)
|
||||
if n >= 1:
|
||||
mean = sum(samples) / n
|
||||
stats.update({
|
||||
'avg': round(mean, 3),
|
||||
'min': round(min(samples), 3),
|
||||
'max': round(max(samples), 3)
|
||||
})
|
||||
if n >= 2:
|
||||
var = sum([ (x - mean)**2 for x in samples ]) / (n-1)
|
||||
stdev = math.sqrt(var)
|
||||
stats.update({
|
||||
'stdev': round(stdev, 3)
|
||||
})
|
||||
return json.dumps(stats)
|
||||
$$
|
||||
LANGUAGE @@plpythonu@@ VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--
|
||||
-- Collect and return diagnostics info from a remote PG into a jsonb
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Server_Diagnostics_PG(server_internal name)
|
||||
RETURNS jsonb
|
||||
AS $$
|
||||
DECLARE
|
||||
remote_server_version text := @extschema@.__CDB_FS_Foreign_Server_Version_PG(server_internal);
|
||||
remote_postgis_version text := @extschema@.__CDB_FS_Foreign_PostGIS_Version_PG(server_internal);
|
||||
remote_server_options jsonb := @extschema@.__CDB_FS_Foreign_Server_Options_PG(server_internal);
|
||||
remote_server_latency_ms jsonb := @extschema@.__CDB_FS_TCP_Foreign_Server_Latency(server_internal);
|
||||
BEGIN
|
||||
RETURN jsonb_build_object(
|
||||
'server_version', remote_server_version,
|
||||
'postgis_version', remote_postgis_version,
|
||||
'server_options', remote_server_options,
|
||||
'server_latency_ms', remote_server_latency_ms
|
||||
);
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Public functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Collect and return diagnostics info from a remote PG into a jsonb
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_Diagnostics(server TEXT)
|
||||
RETURNS jsonb
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true);
|
||||
server_type name := @extschema@.__CDB_FS_server_type(server_internal);
|
||||
BEGIN
|
||||
CASE server_type
|
||||
WHEN 'postgres_fdw' THEN
|
||||
RETURN @extschema@.__CDB_FS_Server_Diagnostics_PG(server_internal);
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Not implemented server type % for remote server %', server_type, server;
|
||||
END CASE;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
298
scripts-available/CDB_FederatedServerListRemote.sql
Normal file
298
scripts-available/CDB_FederatedServerListRemote.sql
Normal file
@ -0,0 +1,298 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- List the schemas of a remote PG server
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_List_Foreign_Schemas_PG(server_internal name)
|
||||
RETURNS TABLE(remote_schema name)
|
||||
AS $$
|
||||
DECLARE
|
||||
-- Import schemata from the information schema
|
||||
--
|
||||
-- "The view schemata contains all schemas in the current database
|
||||
-- that the current user has access to (by way of being the owner
|
||||
-- or having some privilege)."
|
||||
-- See https://www.postgresql.org/docs/11/infoschema-schemata.html
|
||||
--
|
||||
-- "The information schema is defined in the SQL standard and can
|
||||
-- therefore be expected to be portable and remain stable"
|
||||
-- See https://www.postgresql.org/docs/11/information-schema.html
|
||||
inf_schema name := 'information_schema';
|
||||
remote_table name := 'schemata';
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, inf_schema);
|
||||
BEGIN
|
||||
-- Import the foreign schemata table
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM pg_class
|
||||
WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = local_schema)
|
||||
AND relname = remote_table
|
||||
) THEN
|
||||
EXECUTE format('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I',
|
||||
inf_schema, remote_table, server_internal, local_schema);
|
||||
END IF;
|
||||
|
||||
-- Return the result we're interested in. Exclude toast and temp schemas
|
||||
BEGIN
|
||||
RETURN QUERY EXECUTE format('
|
||||
SELECT schema_name::name AS remote_schema FROM %I.%I
|
||||
WHERE schema_name NOT LIKE %s
|
||||
ORDER BY remote_schema
|
||||
', local_schema, remote_table, '''pg_%''');
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Not enough permissions to access the server "%"',
|
||||
@extschema@.__CDB_FS_Extract_Server_Name(server_internal);
|
||||
END;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- List the names of the tables in a remote PG schema
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_List_Foreign_Tables_PG(server_internal name, remote_schema name)
|
||||
RETURNS TABLE(remote_table name)
|
||||
AS $func$
|
||||
DECLARE
|
||||
-- Import `tables` from the information schema
|
||||
--
|
||||
-- "The view tables contains all tables and views defined in the
|
||||
-- current database. Only those tables and views are shown that
|
||||
-- the current user has access to (by way of being the owner or
|
||||
-- having some privilege)."
|
||||
-- https://www.postgresql.org/docs/11/infoschema-tables.html
|
||||
|
||||
-- Create local target schema if it does not exists
|
||||
inf_schema name := 'information_schema';
|
||||
remote_table name := 'tables';
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, inf_schema);
|
||||
BEGIN
|
||||
-- Import the foreign `tables` if not done
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM pg_class
|
||||
WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = local_schema)
|
||||
AND relname = remote_table
|
||||
) THEN
|
||||
EXECUTE format('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I',
|
||||
inf_schema, remote_table, server_internal, local_schema);
|
||||
END IF;
|
||||
|
||||
-- Note: in this context, schema names are not to be quoted
|
||||
RETURN QUERY EXECUTE format($q$
|
||||
SELECT table_name::name AS remote_table FROM %I.%I WHERE table_schema = '%s' ORDER BY table_name
|
||||
$q$, local_schema, remote_table, remote_schema);
|
||||
END
|
||||
$func$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--
|
||||
-- List the columns in a remote PG schema
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_List_Foreign_Columns_PG(server_internal name, remote_schema name)
|
||||
RETURNS TABLE(table_name name, column_name name, column_type text)
|
||||
AS $func$
|
||||
DECLARE
|
||||
-- Import `columns` from the information schema
|
||||
--
|
||||
-- "The view columns contains information about all table columns (or view columns)
|
||||
-- in the database. System columns (oid, etc.) are not included. Only those columns
|
||||
-- are shown that the current user has access to (by way of being the owner or having some privilege)."
|
||||
-- https://www.postgresql.org/docs/11/infoschema-columns.html
|
||||
|
||||
-- Create local target schema if it does not exists
|
||||
inf_schema name := 'information_schema';
|
||||
remote_col_table name := 'columns';
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, inf_schema);
|
||||
BEGIN
|
||||
-- Import the foreign `columns` if not done
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM pg_class
|
||||
WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = local_schema)
|
||||
AND relname = remote_col_table
|
||||
) THEN
|
||||
EXECUTE format('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I',
|
||||
inf_schema, remote_col_table, server_internal, local_schema);
|
||||
END IF;
|
||||
|
||||
-- Note: in this context, schema names are not to be quoted
|
||||
-- We join with the geometry columns to change the type `USER-DEFINED`
|
||||
-- by its appropiate geometry and srid
|
||||
RETURN QUERY EXECUTE format($q$
|
||||
SELECT
|
||||
a.table_name::name,
|
||||
a.column_name::name,
|
||||
COALESCE(b.column_type, a.data_type)::TEXT as column_type
|
||||
FROM
|
||||
%I.%I a
|
||||
LEFT JOIN
|
||||
@extschema@.__CDB_FS_List_Foreign_Geometry_Columns_PG('%s', '%s') b
|
||||
ON a.table_name = b.table_name AND a.column_name = b.column_name
|
||||
WHERE table_schema = '%s'
|
||||
ORDER BY a.table_name, a.column_name $q$,
|
||||
local_schema, remote_col_table,
|
||||
server_internal, remote_schema,
|
||||
remote_schema);
|
||||
END
|
||||
$func$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- List the geometry columns in a remote PG schema
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_List_Foreign_Geometry_Columns_PG(server_internal name, remote_schema name, postgis_schema name DEFAULT 'public')
|
||||
RETURNS TABLE(table_name name, column_name name, column_type text)
|
||||
AS $func$
|
||||
DECLARE
|
||||
-- Import `geometry_columns` and `geography_columns` from the postgis schema
|
||||
-- We assume that postgis is installed in the public schema
|
||||
|
||||
-- Create local target schema if it does not exists
|
||||
remote_geometry_view name := 'geometry_columns';
|
||||
remote_geography_view name := 'geography_columns';
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, postgis_schema);
|
||||
BEGIN
|
||||
-- Import the foreign `geometry_columns` and `geography_columns` if not done
|
||||
IF NOT EXISTS (
|
||||
SELECT * FROM pg_class
|
||||
WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = local_schema)
|
||||
AND relname = remote_geometry_view
|
||||
) THEN
|
||||
EXECUTE format('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I, %I) FROM SERVER %I INTO %I',
|
||||
postgis_schema, remote_geometry_view, remote_geography_view, server_internal, local_schema);
|
||||
END IF;
|
||||
|
||||
BEGIN
|
||||
-- Note: We return both the type and srid as the type
|
||||
RETURN QUERY EXECUTE format($q$
|
||||
SELECT
|
||||
f_table_name::NAME as table_name,
|
||||
f_geometry_column::NAME as column_name,
|
||||
type::TEXT || ',' || srid::TEXT as column_type
|
||||
FROM
|
||||
(
|
||||
SELECT * FROM %I.%I UNION ALL SELECT * FROM %I.%I
|
||||
) _geo_views
|
||||
WHERE f_table_schema = '%s'
|
||||
$q$,
|
||||
local_schema, remote_geometry_view,
|
||||
local_schema, remote_geography_view,
|
||||
remote_schema);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE INFO 'Could not find Postgis installation in the remote "%" schema in server "%"',
|
||||
postgis_schema, @extschema@.__CDB_FS_Extract_Server_Name(server_internal);
|
||||
RETURN;
|
||||
END;
|
||||
END
|
||||
$func$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Public functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- List remote schemas in a federated server that the current user has access to.
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_List_Remote_Schemas(server TEXT)
|
||||
RETURNS TABLE(remote_schema name)
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true);
|
||||
server_type name := @extschema@.__CDB_FS_server_type(server_internal);
|
||||
BEGIN
|
||||
CASE server_type
|
||||
WHEN 'postgres_fdw' THEN
|
||||
RETURN QUERY SELECT @extschema@.__CDB_FS_List_Foreign_Schemas_PG(server_internal);
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Not implemented server type % for remote server %', server_type, server;
|
||||
END CASE;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- List remote tables in a federated server that the current user has access to.
|
||||
-- For registered tables it returns also the associated configuration
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_List_Remote_Tables(server TEXT, remote_schema TEXT)
|
||||
RETURNS TABLE(
|
||||
registered boolean,
|
||||
remote_table TEXT,
|
||||
local_qualified_name TEXT,
|
||||
id_column_name TEXT,
|
||||
geom_column_name TEXT,
|
||||
webmercator_column_name TEXT,
|
||||
columns JSON
|
||||
)
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true);
|
||||
server_type name := @extschema@.__CDB_FS_server_type(server_internal);
|
||||
BEGIN
|
||||
CASE server_type
|
||||
WHEN 'postgres_fdw' THEN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
coalesce(registered_tables.registered, false)::boolean as registered,
|
||||
foreign_tables.remote_table::text as remote_table,
|
||||
registered_tables.local_qualified_name as local_qualified_name,
|
||||
registered_tables.id_column_name as id_column_name,
|
||||
registered_tables.geom_column_name as geom_column_name,
|
||||
registered_tables.webmercator_column_name as webmercator_column_name,
|
||||
remote_columns.columns as columns
|
||||
FROM
|
||||
@extschema@.__CDB_FS_List_Foreign_Tables_PG(server_internal, remote_schema) foreign_tables
|
||||
LEFT JOIN
|
||||
@extschema@.__CDB_FS_List_Registered_Tables(server_internal, remote_schema) registered_tables
|
||||
ON foreign_tables.remote_table = registered_tables.remote_table
|
||||
LEFT JOIN
|
||||
( -- Extract and group columns with their remote table
|
||||
SELECT table_name,
|
||||
json_agg(json_build_object('Name', column_name, 'Type', column_type)) as columns
|
||||
FROM @extschema@.__CDB_FS_List_Foreign_Columns_PG(server_internal, remote_schema)
|
||||
GROUP BY table_name
|
||||
) remote_columns
|
||||
ON foreign_tables.remote_table = remote_columns.table_name
|
||||
ORDER BY foreign_tables.remote_table;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Not implemented server type % for remote server %', server_type, remote_server;
|
||||
END CASE;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- List the columns of a remote table in a federated server that the current user has access to.
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_List_Remote_Columns(
|
||||
server TEXT,
|
||||
remote_schema TEXT,
|
||||
remote_table TEXT)
|
||||
RETURNS TABLE(column_n name, column_t text)
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true);
|
||||
server_type name := @extschema@.__CDB_FS_server_type(server_internal);
|
||||
BEGIN
|
||||
IF remote_table IS NULL THEN
|
||||
RAISE EXCEPTION 'Remote table name cannot be NULL';
|
||||
END IF;
|
||||
|
||||
CASE server_type
|
||||
WHEN 'postgres_fdw' THEN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
column_name,
|
||||
column_type
|
||||
FROM @extschema@.__CDB_FS_List_Foreign_Columns_PG(server_internal, remote_schema)
|
||||
WHERE table_name = remote_table
|
||||
ORDER BY column_name;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Not implemented server type % for remote server %', server_type, remote_server;
|
||||
END CASE;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
345
scripts-available/CDB_FederatedServerTables.sql
Normal file
345
scripts-available/CDB_FederatedServerTables.sql
Normal file
@ -0,0 +1,345 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Checks if a column is of integer type
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Column_Is_Integer(input_table REGCLASS, colname NAME)
|
||||
RETURNS boolean
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM atttypid FROM pg_catalog.pg_attribute
|
||||
WHERE attrelid = input_table
|
||||
AND attname = colname
|
||||
AND atttypid IN (SELECT oid FROM pg_type
|
||||
WHERE typname IN
|
||||
('smallint', 'integer', 'bigint', 'int2', 'int4', 'int8'));
|
||||
RETURN FOUND;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL STABLE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Checks if a column is of geometry type
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Column_Is_Geometry(input_table REGCLASS, colname NAME)
|
||||
RETURNS boolean
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM atttypid FROM pg_catalog.pg_attribute
|
||||
WHERE attrelid = input_table
|
||||
AND attname = colname
|
||||
AND atttypid = '@postgisschema@.geometry'::regtype;
|
||||
RETURN FOUND;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL STABLE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Returns the name of all the columns from a table
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_GetColumns(input_table REGCLASS)
|
||||
RETURNS SETOF NAME
|
||||
AS $$
|
||||
SELECT
|
||||
a.attname as "colname"
|
||||
FROM pg_catalog.pg_attribute a
|
||||
WHERE
|
||||
a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
AND a.attrelid = (
|
||||
SELECT c.oid
|
||||
FROM pg_catalog.pg_class c
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.oid = input_table::oid
|
||||
)
|
||||
ORDER BY a.attnum;
|
||||
$$ LANGUAGE SQL STABLE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Returns the id column from a view definition
|
||||
-- Note: The id is always of one of this forms:
|
||||
-- SELECT t.cartodb_id,
|
||||
-- SELECT t.my_id as cartodb_id,
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Get_View_id_column(view_def TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
WITH column_definitions AS
|
||||
(
|
||||
SELECT regexp_split_to_array(trim(leading from regexp_split_to_table(view_def, '\n')), ' ') AS col_def
|
||||
)
|
||||
SELECT trim(trailing ',' FROM split_part(col_def[2], '.', 2))
|
||||
FROM column_definitions
|
||||
WHERE trim(trailing ',' FROM col_def[array_length(col_def, 1)]) IN ('t.cartodb_id', 'cartodb_id')
|
||||
LIMIT 1;
|
||||
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
--
|
||||
-- Returns the geom column from a view definition
|
||||
--
|
||||
-- Note: The the_geom is always of one of this forms:
|
||||
-- t.the_geom,
|
||||
-- t.my_geom as the_geom,
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Get_View_geom_column(view_def TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
WITH column_definitions AS
|
||||
(
|
||||
SELECT regexp_split_to_array(trim(leading from regexp_split_to_table(view_def, '\n')), ' ') AS col_def
|
||||
)
|
||||
SELECT trim(trailing ',' FROM split_part(col_def[1], '.', 2))
|
||||
FROM column_definitions
|
||||
WHERE trim(trailing ',' FROM col_def[array_length(col_def, 1)]) IN ('t.the_geom', 'the_geom')
|
||||
LIMIT 1;
|
||||
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
--
|
||||
-- Returns the webmercatorcolumn from a view definition
|
||||
-- Note: The the_geom_webmercator is always of one of this forms:
|
||||
-- t.the_geom_webmercator,
|
||||
-- t.my_geom as the_geom_webmercator,
|
||||
-- Or without the trailing comma:
|
||||
-- t.the_geom_webmercator
|
||||
-- t.my_geom as the_geom_webmercator
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Get_View_webmercator_column(view_def TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
WITH column_definitions AS
|
||||
(
|
||||
SELECT regexp_split_to_array(trim(leading from regexp_split_to_table(view_def, '\n')), ' ') AS col_def
|
||||
)
|
||||
SELECT trim(trailing ',' FROM split_part(col_def[1], '.', 2))
|
||||
FROM column_definitions
|
||||
WHERE trim(trailing ',' FROM col_def[array_length(col_def, 1)]) IN ('t.the_geom_webmercator', 'the_geom_webmercator')
|
||||
LIMIT 1;
|
||||
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
|
||||
--
|
||||
-- List all registered tables in a server + schema
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_List_Registered_Tables(
|
||||
server_internal NAME,
|
||||
remote_schema TEXT
|
||||
)
|
||||
RETURNS TABLE(
|
||||
registered boolean,
|
||||
remote_table TEXT,
|
||||
local_qualified_name TEXT,
|
||||
id_column_name TEXT,
|
||||
geom_column_name TEXT,
|
||||
webmercator_column_name TEXT
|
||||
)
|
||||
AS $$
|
||||
DECLARE
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
||||
BEGIN
|
||||
RETURN QUERY SELECT
|
||||
true as registered,
|
||||
source_table::text as remote_table,
|
||||
format('%I.%I', dependent_schema, dependent_view)::text as local_qualified_name,
|
||||
@extschema@.__CDB_FS_Get_View_id_column(view_definition) as id_column_name,
|
||||
@extschema@.__CDB_FS_Get_View_geom_column(view_definition) as geom_column_name,
|
||||
@extschema@.__CDB_FS_Get_View_webmercator_column(view_definition) as webmercator_column_name
|
||||
FROM
|
||||
(
|
||||
SELECT DISTINCT
|
||||
dependent_ns.nspname as dependent_schema,
|
||||
dependent_view.relname as dependent_view,
|
||||
source_table.relname as source_table,
|
||||
pg_get_viewdef(dependent_view.oid) as view_definition
|
||||
FROM pg_depend
|
||||
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
|
||||
JOIN pg_class as dependent_view ON pg_rewrite.ev_class = dependent_view.oid
|
||||
JOIN pg_class as source_table ON pg_depend.refobjid = source_table.oid
|
||||
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
|
||||
JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
|
||||
WHERE
|
||||
source_ns.nspname = local_schema
|
||||
ORDER BY 1,2
|
||||
) _aux;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Public functions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Sets up a Federated Table
|
||||
--
|
||||
-- Precondition: the federated server has to be set up via
|
||||
-- CDB_Federated_Server_Register_PG
|
||||
--
|
||||
-- Postcondition: it generates a view in the schema of the user that
|
||||
-- can be used through SQL and Maps API's.
|
||||
-- If the table was already exported, it will be dropped and re-imported
|
||||
--
|
||||
-- The view is placed under the server's view schema (cdb_fs_$(server_name))
|
||||
--
|
||||
-- E.g:
|
||||
-- SELECT cartodb.CDB_SetUp_PG_Federated_Table(
|
||||
-- 'amazon', -- mandatory, name of the federated server
|
||||
-- 'my_remote_schema', -- mandatory, schema name
|
||||
-- 'my_remote_table', -- mandatory, table name
|
||||
-- 'id', -- mandatory, name of the id column
|
||||
-- 'geom', -- optional, name of the geom column, preferably in 4326
|
||||
-- 'webmercator' -- optional, should be in 3857 if present
|
||||
-- 'local_name' -- optional, name of the local view (uses the remote_name if not declared)
|
||||
-- );
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Table_Register(
|
||||
server TEXT,
|
||||
remote_schema TEXT,
|
||||
remote_table TEXT,
|
||||
id_column TEXT,
|
||||
geom_column TEXT DEFAULT NULL,
|
||||
webmercator_column TEXT DEFAULT NULL,
|
||||
local_name NAME DEFAULT NULL
|
||||
)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => false);
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
||||
role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(server_internal);
|
||||
|
||||
src_table REGCLASS; -- import_schema.remote_table - import_schema is local_schema
|
||||
local_view text; -- view_schema.local_name - view_schema is server_internal
|
||||
|
||||
rest_of_cols TEXT[];
|
||||
geom_expression TEXT;
|
||||
webmercator_expression TEXT;
|
||||
carto_columns_expression TEXT[];
|
||||
BEGIN
|
||||
IF remote_table IS NULL THEN
|
||||
RAISE EXCEPTION 'Remote table name cannot be NULL';
|
||||
END IF;
|
||||
|
||||
-- Make do with whatever columns are provided
|
||||
IF webmercator_column IS NULL THEN
|
||||
webmercator_column := geom_column;
|
||||
ELSIF geom_column IS NULL THEN
|
||||
geom_column := webmercator_column;
|
||||
END IF;
|
||||
|
||||
IF local_name IS NULL THEN
|
||||
local_name := remote_table;
|
||||
END IF;
|
||||
|
||||
-- Import the foreign table
|
||||
-- Drop the old view / table if there was one
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = local_schema AND table_name = remote_table) THEN
|
||||
EXECUTE @extschema@.CDB_Federated_Table_Unregister(server, remote_schema, remote_table);
|
||||
END IF;
|
||||
BEGIN
|
||||
EXECUTE FORMAT('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I;',
|
||||
remote_schema, remote_table, server_internal, local_schema);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Could not import schema "%" of server "%": %', remote_schema, server, SQLERRM;
|
||||
END;
|
||||
|
||||
BEGIN
|
||||
src_table := format('%I.%I', local_schema, remote_table);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Could not import table "%.%" of server "%"', remote_schema, remote_table, server;
|
||||
END;
|
||||
|
||||
-- Check id_column is numeric
|
||||
IF NOT @extschema@.__CDB_FS_Column_Is_Integer(src_table, id_column) THEN
|
||||
RAISE EXCEPTION 'non integer id_column "%"', id_column;
|
||||
END IF;
|
||||
|
||||
-- Check if the geom and mercator columns have a geometry type (if provided)
|
||||
IF geom_column IS NOT NULL AND NOT @extschema@.__CDB_FS_Column_Is_Geometry(src_table, geom_column) THEN
|
||||
RAISE EXCEPTION 'non geometry column "%"', geom_column;
|
||||
END IF;
|
||||
IF webmercator_column IS NOT NULL AND NOT @extschema@.__CDB_FS_Column_Is_Geometry(src_table, webmercator_column) THEN
|
||||
RAISE EXCEPTION 'non geometry column "%"', webmercator_column;
|
||||
END IF;
|
||||
|
||||
-- Get a list of columns excluding the id, geom and the_geom_webmercator
|
||||
SELECT ARRAY(
|
||||
SELECT quote_ident(c) FROM @extschema@.__CDB_FS_GetColumns(src_table) AS c
|
||||
WHERE c NOT IN (SELECT * FROM (SELECT unnest(ARRAY[id_column, geom_column, webmercator_column, 'cartodb_id', 'the_geom', 'the_geom_webmercator']) col) carto WHERE carto.col IS NOT NULL)
|
||||
) INTO rest_of_cols;
|
||||
|
||||
IF geom_column IS NULL
|
||||
THEN
|
||||
geom_expression := 'NULL AS the_geom';
|
||||
ELSIF @postgisschema@.Find_SRID(local_schema::varchar, remote_table::varchar, geom_column::varchar) = 4326
|
||||
THEN
|
||||
geom_expression := format('t.%I AS the_geom', geom_column);
|
||||
ELSE
|
||||
-- It needs an ST_Transform to 4326
|
||||
geom_expression := format('@postgisschema@.ST_Transform(t.%I,4326) AS the_geom', geom_column);
|
||||
END IF;
|
||||
|
||||
IF webmercator_column IS NULL
|
||||
THEN
|
||||
webmercator_expression := 'NULL AS the_geom_webmercator';
|
||||
ELSIF @postgisschema@.Find_SRID(local_schema::varchar, remote_table::varchar, webmercator_column::varchar) = 3857
|
||||
THEN
|
||||
webmercator_expression := format('t.%I AS the_geom_webmercator', webmercator_column);
|
||||
ELSE
|
||||
-- It needs an ST_Transform to 3857
|
||||
webmercator_expression := format('@postgisschema@.ST_Transform(t.%I,3857) AS the_geom_webmercator', webmercator_column);
|
||||
END IF;
|
||||
|
||||
-- CARTO columns expressions
|
||||
carto_columns_expression := ARRAY[
|
||||
format('t.%1$I AS cartodb_id', id_column),
|
||||
geom_expression,
|
||||
webmercator_expression
|
||||
];
|
||||
|
||||
-- Create view schema if it doesn't exist
|
||||
IF NOT EXISTS (SELECT oid FROM pg_namespace WHERE nspname = server_internal) THEN
|
||||
EXECUTE 'CREATE SCHEMA ' || quote_ident(server_internal) || ' AUTHORIZATION ' || quote_ident(role_name);
|
||||
END IF;
|
||||
|
||||
-- Create a view with homogeneous CDB fields
|
||||
BEGIN
|
||||
EXECUTE format(
|
||||
'CREATE OR REPLACE VIEW %1$I.%2$I AS
|
||||
SELECT %3s
|
||||
FROM %4$s t',
|
||||
server_internal, local_name,
|
||||
array_to_string(carto_columns_expression || rest_of_cols, ','),
|
||||
src_table
|
||||
);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
IF EXISTS (SELECT to_regclass(format('%1$I.%2$I', server_internal, local_name))) THEN
|
||||
RAISE EXCEPTION 'Could not import table "%" as "%.%" already exists', remote_table, server_internal, local_name;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Could not import table "%" as "%": %', remote_table, local_name, SQLERRM;
|
||||
END IF;
|
||||
END;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
--
|
||||
-- Unregisters a remote table. Any dependent object will be dropped
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Table_Unregister(
|
||||
server TEXT,
|
||||
remote_schema TEXT,
|
||||
remote_table TEXT
|
||||
)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => false);
|
||||
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
||||
BEGIN
|
||||
EXECUTE FORMAT ('DROP FOREIGN TABLE %I.%I CASCADE;', local_schema, remote_table);
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
@ -4,7 +4,7 @@
|
||||
-- All the FDW settings are read from the `cdb_conf.fdws` entry json file.
|
||||
---------------------------
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Setup_FDW(fdw_name text, config json)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Setup_FDW(fdw_name text, config json)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -58,58 +58,58 @@ BEGIN
|
||||
END IF;
|
||||
|
||||
-- Give the organization role usage permisions over the schema
|
||||
SELECT cartodb.CDB_Organization_Member_Group_Role_Member_Name() INTO org_role;
|
||||
SELECT @extschema@.CDB_Organization_Member_Group_Role_Member_Name() INTO org_role;
|
||||
EXECUTE FORMAT ('GRANT USAGE ON SCHEMA %I TO %I', fdw_name, org_role);
|
||||
|
||||
-- Bring here the remote cdb_tablemetadata
|
||||
IF NOT EXISTS ( SELECT * FROM PG_CLASS WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname=fdw_name) and relname='cdb_tablemetadata') THEN
|
||||
EXECUTE FORMAT ('CREATE FOREIGN TABLE %I.cdb_tablemetadata (tabname text, updated_at timestamp with time zone) SERVER %I OPTIONS (table_name ''cdb_tablemetadata_text'', schema_name ''public'', updatable ''false'')', fdw_name, fdw_name);
|
||||
EXECUTE FORMAT ('CREATE FOREIGN TABLE %I.cdb_tablemetadata (tabname text, updated_at timestamp with time zone) SERVER %I OPTIONS (table_name ''cdb_tablemetadata_text'', schema_name ''@extschema@'', updatable ''false'')', fdw_name, fdw_name);
|
||||
END IF;
|
||||
EXECUTE FORMAT ('GRANT SELECT ON %I.cdb_tablemetadata TO %I', fdw_name, org_role);
|
||||
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL;
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Setup_FDWS()
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Setup_FDWS()
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
DECLARE
|
||||
row record;
|
||||
BEGIN
|
||||
FOR row IN SELECT p.key, p.value from lateral json_each(cartodb.CDB_Conf_GetConf('fdws')) p LOOP
|
||||
EXECUTE 'SELECT cartodb._CDB_Setup_FDW($1, $2)' USING row.key, row.value;
|
||||
FOR row IN SELECT p.key, p.value from lateral json_each(@extschema@.CDB_Conf_GetConf('fdws')) p LOOP
|
||||
EXECUTE 'SELECT @extschema@._CDB_Setup_FDW($1, $2)' USING row.key, row.value;
|
||||
END LOOP;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL;
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Setup_FDW(fdw_name text)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Setup_FDW(fdw_name text)
|
||||
RETURNS void AS
|
||||
$BODY$
|
||||
DECLARE
|
||||
config json;
|
||||
BEGIN
|
||||
SELECT p.value FROM LATERAL json_each(cartodb.CDB_Conf_GetConf('fdws')) p WHERE p.key = fdw_name INTO config;
|
||||
EXECUTE 'SELECT cartodb._CDB_Setup_FDW($1, $2)' USING fdw_name, config;
|
||||
SELECT p.value FROM LATERAL json_each(@extschema@.CDB_Conf_GetConf('fdws')) p WHERE p.key = fdw_name INTO config;
|
||||
EXECUTE 'SELECT @extschema@._CDB_Setup_FDW($1, $2)' USING fdw_name, config;
|
||||
END
|
||||
$BODY$
|
||||
LANGUAGE plpgsql VOLATILE;
|
||||
LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_Add_Remote_Table(source text, table_name text)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Add_Remote_Table(source text, table_name text)
|
||||
RETURNS void AS
|
||||
$$
|
||||
BEGIN
|
||||
PERFORM cartodb._CDB_Setup_FDW(source);
|
||||
PERFORM @extschema@._CDB_Setup_FDW(source);
|
||||
EXECUTE FORMAT ('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I;', source, table_name, source, source);
|
||||
--- Grant SELECT to publicuser
|
||||
EXECUTE FORMAT ('GRANT SELECT ON %I.%I TO publicuser;', source, table_name);
|
||||
END
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_Get_Foreign_Updated_At(foreign_table regclass)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Get_Foreign_Updated_At(foreign_table regclass)
|
||||
RETURNS timestamp with time zone AS
|
||||
$$
|
||||
DECLARE
|
||||
@ -125,14 +125,21 @@ BEGIN
|
||||
|
||||
-- We assume that the remote cdb_tablemetadata is called cdb_tablemetadata and is on the same schema as the queried table.
|
||||
SELECT nspname FROM pg_class c, pg_namespace n WHERE c.oid=foreign_table AND c.relnamespace = n.oid INTO fdw_schema_name;
|
||||
EXECUTE FORMAT('SELECT updated_at FROM %I.cdb_tablemetadata WHERE tabname=%L ORDER BY updated_at DESC LIMIT 1', fdw_schema_name, remote_table_name) INTO time;
|
||||
BEGIN
|
||||
EXECUTE FORMAT('SELECT updated_at FROM %I.cdb_tablemetadata WHERE tabname=%L ORDER BY updated_at DESC LIMIT 1', fdw_schema_name, remote_table_name) INTO time;
|
||||
EXCEPTION
|
||||
WHEN undefined_table THEN
|
||||
-- If you add a GET STACKED DIAGNOSTICS text_var = RETURNED_SQLSTATE
|
||||
-- you get a code 42P01 which corresponds to undefined_table
|
||||
RAISE NOTICE 'CDB_Get_Foreign_Updated_At: could not find %.cdb_tablemetadata while checking % updated_at, returning NULL timestamp', fdw_schema_name, foreign_table;
|
||||
END;
|
||||
RETURN time;
|
||||
END
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._cdb_dbname_of_foreign_table(reloid oid)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._cdb_dbname_of_foreign_table(reloid oid)
|
||||
RETURNS TEXT AS $$
|
||||
SELECT option_value FROM pg_options_to_table((
|
||||
|
||||
@ -142,25 +149,25 @@ RETURNS TEXT AS $$
|
||||
WHERE ft.ftrelid = reloid
|
||||
|
||||
)) WHERE option_name='dbname';
|
||||
$$ LANGUAGE SQL;
|
||||
$$ LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
-- Return a set of (dbname, schema_name, table_name, updated_at)
|
||||
-- It is aware of foreign tables
|
||||
-- It assumes the local (schema_name, table_name) map to the remote ones with the same name
|
||||
-- Note: dbname is never quoted whereas schema and table names are when needed.
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_QueryTables_Updated_At(query text)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_QueryTables_Updated_At(query text)
|
||||
RETURNS TABLE(dbname text, schema_name text, table_name text, updated_at timestamptz)
|
||||
AS $$
|
||||
WITH query_tables AS (
|
||||
SELECT unnest(CDB_QueryTablesText(query)) schema_table_name
|
||||
SELECT unnest(@extschema@.CDB_QueryTablesText(query)) schema_table_name
|
||||
), query_tables_oid AS (
|
||||
SELECT schema_table_name, schema_table_name::regclass::oid AS reloid
|
||||
FROM query_tables
|
||||
),
|
||||
fqtn AS (
|
||||
SELECT
|
||||
(CASE WHEN c.relkind = 'f' THEN cartodb._cdb_dbname_of_foreign_table(query_tables_oid.reloid)
|
||||
(CASE WHEN c.relkind = 'f' THEN @extschema@._cdb_dbname_of_foreign_table(query_tables_oid.reloid)
|
||||
ELSE current_database()
|
||||
END)::text AS dbname,
|
||||
quote_ident(n.nspname::text) schema_name,
|
||||
@ -172,17 +179,17 @@ AS $$
|
||||
WHERE c.oid = query_tables_oid.reloid
|
||||
)
|
||||
SELECT fqtn.dbname, fqtn.schema_name, fqtn.table_name,
|
||||
(CASE WHEN relkind = 'f' THEN cartodb.CDB_Get_Foreign_Updated_At(reloid)
|
||||
ELSE (SELECT md.updated_at FROM CDB_TableMetadata md WHERE md.tabname = reloid)
|
||||
(CASE WHEN relkind = 'f' THEN @extschema@.CDB_Get_Foreign_Updated_At(reloid)
|
||||
ELSE (SELECT md.updated_at FROM @extschema@.CDB_TableMetadata md WHERE md.tabname = reloid)
|
||||
END) AS updated_at
|
||||
FROM fqtn;
|
||||
$$ LANGUAGE SQL;
|
||||
$$ LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
-- Return the last updated time of a set of tables
|
||||
-- It is aware of foreign tables
|
||||
-- It assumes the local (schema_name, table_name) map to the remote ones with the same name
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_Last_Updated_Time(tables text[])
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Last_Updated_Time(tables text[])
|
||||
RETURNS timestamptz AS $$
|
||||
WITH t AS (
|
||||
SELECT unnest(tables) AS schema_table_name
|
||||
@ -190,10 +197,19 @@ RETURNS timestamptz AS $$
|
||||
SELECT (t.schema_table_name)::regclass::oid as reloid FROM t
|
||||
), t_updated_at AS (
|
||||
SELECT
|
||||
(CASE WHEN relkind = 'f' THEN cartodb.CDB_Get_Foreign_Updated_At(reloid)
|
||||
ELSE (SELECT md.updated_at FROM CDB_TableMetadata md WHERE md.tabname = reloid)
|
||||
(CASE WHEN relkind = 'f' THEN @extschema@.CDB_Get_Foreign_Updated_At(reloid)
|
||||
ELSE (SELECT md.updated_at FROM @extschema@.CDB_TableMetadata md WHERE md.tabname = reloid)
|
||||
END) AS updated_at
|
||||
FROM t_oid
|
||||
LEFT JOIN pg_catalog.pg_class c ON c.oid = reloid
|
||||
) SELECT max(updated_at) FROM t_updated_at;
|
||||
$$ LANGUAGE SQL;
|
||||
$$ LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Deprecated
|
||||
--------------------------------------------------------------------------------
|
||||
DROP FUNCTION IF EXISTS @extschema@.__CDB_User_FDW_Object_Names(name);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_SetUp_User_PG_FDW_Server(name, json);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Drop_User_PG_FDW_Server(name, boolean);
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_SetUp_User_PG_FDW_Table(name, name, name);
|
||||
|
159
scripts-available/CDB_GhostTables.sql
Normal file
159
scripts-available/CDB_GhostTables.sql
Normal file
@ -0,0 +1,159 @@
|
||||
-- Enqueues a job to run Ghost tables linking process for the provided username
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_LinkGhostTables(username text, db_name text, event_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
if not username:
|
||||
return
|
||||
|
||||
if 'json' not in GD:
|
||||
import json
|
||||
GD['json'] = json
|
||||
else:
|
||||
json = GD['json']
|
||||
|
||||
tis_config = plpy.execute("select @extschema@.CDB_Conf_GetConf('invalidation_service');")[0]['cdb_conf_getconf']
|
||||
if not tis_config:
|
||||
plpy.warning('Invalidation service configuration not found. Skipping Ghost Tables linking.')
|
||||
return
|
||||
|
||||
tis_config_dict = json.loads(tis_config)
|
||||
tis_host = tis_config_dict.get('host')
|
||||
tis_port = tis_config_dict.get('port')
|
||||
tis_timeout = tis_config_dict.get('timeout', 5)
|
||||
tis_retry = tis_config_dict.get('retry', 5)
|
||||
|
||||
client = GD.get('invalidation', None)
|
||||
|
||||
while True:
|
||||
|
||||
if not client:
|
||||
try:
|
||||
import redis
|
||||
client = redis.Redis(host=tis_host, port=tis_port, socket_timeout=tis_timeout)
|
||||
GD['invalidation'] = client
|
||||
except Exception as err:
|
||||
# NOTE: no retries on connection error
|
||||
plpy.warning('Error trying to connect to Invalidation Service to link Ghost Tables: ' + str(err))
|
||||
break
|
||||
|
||||
try:
|
||||
client.execute_command('DBSCH', db_name, username, event_name)
|
||||
break
|
||||
except Exception as err:
|
||||
client = GD['invalidation'] = None # force reconnect
|
||||
if not tis_retry:
|
||||
plpy.warning('Error calling Invalidation Service to link Ghost Tables: ' + str(err))
|
||||
break
|
||||
tis_retry -= 1 # try reconnecting
|
||||
$$ LANGUAGE '@@plpythonu@@' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Enqueues a job to run Ghost tables linking process for the current user
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_LinkGhostTables(event_name text DEFAULT 'USER')
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
username TEXT;
|
||||
db_name TEXT;
|
||||
BEGIN
|
||||
EXECUTE 'SELECT @extschema@.CDB_Username();' INTO username;
|
||||
EXECUTE 'SELECT current_database();' INTO db_name;
|
||||
|
||||
PERFORM @extschema@._CDB_LinkGhostTables(username, db_name, event_name);
|
||||
RAISE INFO '_CDB_LinkGhostTables() called with username=%, event_name=%', username, event_name;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
-- Trigger function to call CDB_LinkGhostTables()
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_LinkGhostTablesTrigger()
|
||||
RETURNS trigger
|
||||
AS $$
|
||||
DECLARE
|
||||
ddl_tag TEXT;
|
||||
BEGIN
|
||||
EXECUTE 'DELETE FROM @extschema@.cdb_ddl_execution WHERE txid = txid_current() RETURNING tag;' INTO ddl_tag;
|
||||
PERFORM @extschema@.CDB_LinkGhostTables(ddl_tag);
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
-- Event trigger to save the current transaction in @extschema@.cdb_ddl_execution
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_SaveDDLTransaction()
|
||||
RETURNS event_trigger
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO @extschema@.cdb_ddl_execution VALUES (txid_current(), tg_tag) ON CONFLICT ON CONSTRAINT cdb_ddl_execution_pkey DO NOTHING;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
-- Creates the trigger on DDL events to link ghost tables
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_EnableGhostTablesTrigger()
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
DROP EVENT TRIGGER IF EXISTS link_ghost_tables;
|
||||
DROP TRIGGER IF EXISTS check_ddl_update ON @extschema@.cdb_ddl_execution;
|
||||
|
||||
-- Table to store the transaction id from DDL events to avoid multiple executions
|
||||
CREATE TABLE IF NOT EXISTS @extschema@.cdb_ddl_execution(txid bigint PRIMARY KEY, tag text);
|
||||
|
||||
CREATE CONSTRAINT TRIGGER check_ddl_update
|
||||
AFTER INSERT ON @extschema@.cdb_ddl_execution
|
||||
INITIALLY DEFERRED
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE @extschema@._CDB_LinkGhostTablesTrigger();
|
||||
|
||||
CREATE EVENT TRIGGER link_ghost_tables
|
||||
ON ddl_command_end
|
||||
WHEN TAG IN ('CREATE TABLE',
|
||||
'SELECT INTO',
|
||||
'DROP TABLE',
|
||||
'ALTER TABLE',
|
||||
|
||||
'CREATE TRIGGER',
|
||||
'DROP TRIGGER',
|
||||
'ALTER TRIGGER',
|
||||
|
||||
'CREATE VIEW',
|
||||
'DROP VIEW',
|
||||
'ALTER VIEW',
|
||||
|
||||
'CREATE FOREIGN TABLE',
|
||||
'ALTER FOREIGN TABLE',
|
||||
'DROP FOREIGN TABLE',
|
||||
|
||||
'ALTER MATERIALIZED VIEW',
|
||||
'CREATE MATERIALIZED VIEW',
|
||||
'DROP MATERIALIZED VIEW',
|
||||
|
||||
'IMPORT FOREIGN SCHEMA',
|
||||
|
||||
'DROP EXTENSION',
|
||||
'DROP SCHEMA',
|
||||
'DROP SERVER',
|
||||
'DROP TYPE')
|
||||
EXECUTE PROCEDURE @extschema@.CDB_SaveDDLTransaction();
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Drops the trigger on DDL events to link ghost tables
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DisableGhostTablesTrigger()
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
DROP EVENT TRIGGER IF EXISTS link_ghost_tables;
|
||||
DROP TRIGGER IF EXISTS check_ddl_update ON @extschema@.cdb_ddl_execution;
|
||||
DROP TABLE IF EXISTS @extschema@.cdb_ddl_execution;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
@ -1,26 +1,26 @@
|
||||
-- Great circle point-to-point routes, based on:
|
||||
-- http://blog.cartodb.com/jets-and-datelines/
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_GreatCircle(start_point geometry, end_point geometry, max_segment_length NUMERIC DEFAULT 100000)
|
||||
RETURNS geometry AS $$
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_GreatCircle(start_point @postgisschema@.geometry, end_point @postgisschema@.geometry, max_segment_length NUMERIC DEFAULT 100000)
|
||||
RETURNS @postgisschema@.geometry AS $$
|
||||
DECLARE
|
||||
line geometry;
|
||||
line @postgisschema@.geometry;
|
||||
BEGIN
|
||||
line = ST_Segmentize(
|
||||
ST_Makeline(
|
||||
line = @postgisschema@.ST_Segmentize(
|
||||
@postgisschema@.ST_Makeline(
|
||||
start_point,
|
||||
end_point
|
||||
)::geography,
|
||||
max_segment_length
|
||||
)::geometry;
|
||||
|
||||
IF ST_XMax(line) - ST_XMin(line) > 180 THEN
|
||||
line = ST_Difference(
|
||||
ST_Shift_Longitude(line),
|
||||
ST_Buffer(ST_GeomFromText('LINESTRING(180 90, 180 -90)', 4326), 0.00001)
|
||||
IF @postgisschema@.ST_XMax(line) - @postgisschema@.ST_XMin(line) > 180 THEN
|
||||
line = @postgisschema@.ST_Difference(
|
||||
@postgisschema@.ST_ShiftLongitude(line),
|
||||
@postgisschema@.ST_Buffer(@postgisschema@.ST_GeomFromText('LINESTRING(180 90, 180 -90)', 4326), 0.00001)
|
||||
);
|
||||
END IF;
|
||||
RETURN line;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql';
|
||||
LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
@ -6,16 +6,16 @@
|
||||
|
||||
-- Creates a new group
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_CreateGroup(group_name text)
|
||||
FUNCTION @extschema@.CDB_Group_CreateGroup(group_name text)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
EXECUTE format('CREATE ROLE %I NOLOGIN;', group_role);
|
||||
PERFORM cartodb._CDB_Group_CreateGroup_API(group_name, group_role);
|
||||
PERFORM @extschema@._CDB_Group_CreateGroup_API(group_name, group_role);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Drops group and everything that role owns
|
||||
-- TODO: LIMITATION: in order to drop a role all its owned objects must be dropped before.
|
||||
@ -23,74 +23,74 @@ $$ LANGUAGE PLPGSQL VOLATILE;
|
||||
-- Not even the role creator can drop the role and the objects it owns.
|
||||
-- All group owned objects by the group are permissions.
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_DropGroup(group_name text)
|
||||
FUNCTION @extschema@.CDB_Group_DropGroup(group_name text)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
EXECUTE format('DROP OWNED BY %I', group_role);
|
||||
EXECUTE format('DROP ROLE IF EXISTS %I', group_role);
|
||||
PERFORM cartodb._CDB_Group_DropGroup_API(group_name);
|
||||
PERFORM @extschema@._CDB_Group_DropGroup_API(group_name);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Renames a group
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_RenameGroup(old_group_name text, new_group_name text)
|
||||
FUNCTION @extschema@.CDB_Group_RenameGroup(old_group_name text, new_group_name text)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
old_group_role TEXT;
|
||||
new_group_role TEXT;
|
||||
BEGIN
|
||||
old_group_role = cartodb._CDB_Group_GroupRole(old_group_name);
|
||||
new_group_role = cartodb._CDB_Group_GroupRole(new_group_name);
|
||||
old_group_role = @extschema@._CDB_Group_GroupRole(old_group_name);
|
||||
new_group_role = @extschema@._CDB_Group_GroupRole(new_group_name);
|
||||
EXECUTE format('ALTER ROLE %I RENAME TO %I', old_group_role, new_group_role);
|
||||
PERFORM cartodb._CDB_Group_RenameGroup_API(old_group_name, new_group_name, new_group_role);
|
||||
PERFORM @extschema@._CDB_Group_RenameGroup_API(old_group_name, new_group_name, new_group_role);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Adds users to a group
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_AddUsers(group_name text, usernames text[])
|
||||
FUNCTION @extschema@.CDB_Group_AddUsers(group_name text, usernames text[])
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
user_role TEXT;
|
||||
username TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
foreach username in array usernames
|
||||
loop
|
||||
user_role := cartodb._CDB_User_RoleFromUsername(username);
|
||||
user_role := @extschema@._CDB_User_RoleFromUsername(username);
|
||||
IF(group_role IS NULL OR user_role IS NULL)
|
||||
THEN
|
||||
RAISE EXCEPTION 'Group role (%) and user role (%) must be already existing', group_role, user_role;
|
||||
END IF;
|
||||
EXECUTE format('GRANT %I TO %I', group_role, user_role);
|
||||
end loop;
|
||||
PERFORM cartodb._CDB_Group_AddUsers_API(group_name, usernames);
|
||||
PERFORM @extschema@._CDB_Group_AddUsers_API(group_name, usernames);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Removes users from a group
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_RemoveUsers(group_name text, usernames text[])
|
||||
FUNCTION @extschema@.CDB_Group_RemoveUsers(group_name text, usernames text[])
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
user_role TEXT;
|
||||
username TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
foreach username in array usernames
|
||||
loop
|
||||
user_role := cartodb._CDB_User_RoleFromUsername(username);
|
||||
user_role := @extschema@._CDB_User_RoleFromUsername(username);
|
||||
EXECUTE format('REVOKE %I FROM %I', group_role, user_role);
|
||||
end loop;
|
||||
PERFORM cartodb._CDB_Group_RemoveUsers_API(group_name, usernames);
|
||||
PERFORM @extschema@._CDB_Group_RemoveUsers_API(group_name, usernames);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
----------------------------------
|
||||
-- TABLE MANAGEMENT FUNCTIONS
|
||||
@ -100,67 +100,67 @@ $$ LANGUAGE PLPGSQL VOLATILE;
|
||||
|
||||
-- Grants table read permission to a group
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_Table_GrantRead(group_name text, username text, table_name text)
|
||||
FUNCTION @extschema@.CDB_Group_Table_GrantRead(group_name text, username text, table_name text)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
PERFORM cartodb._CDB_Group_Table_GrantRead(group_name, username, table_name, true);
|
||||
PERFORM @extschema@._CDB_Group_Table_GrantRead(group_name, username, table_name, true);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_Table_GrantRead(group_name text, username text, table_name text, sync boolean)
|
||||
FUNCTION @extschema@._CDB_Group_Table_GrantRead(group_name text, username text, table_name text, sync boolean)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
EXECUTE format('GRANT USAGE ON SCHEMA %I TO %I', username, group_role);
|
||||
EXECUTE format('GRANT SELECT ON TABLE %I.%I TO %I', username, table_name, group_role );
|
||||
IF(sync) THEN
|
||||
PERFORM cartodb._CDB_Group_Table_GrantPermission_API(group_name, username, table_name, 'r');
|
||||
PERFORM @extschema@._CDB_Group_Table_GrantPermission_API(group_name, username, table_name, 'r');
|
||||
END IF;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Grants table write permission to a group
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_Table_GrantReadWrite(group_name text, username text, table_name text)
|
||||
FUNCTION @extschema@.CDB_Group_Table_GrantReadWrite(group_name text, username text, table_name text)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
PERFORM cartodb._CDB_Group_Table_GrantReadWrite(group_name, username, table_name, true);
|
||||
PERFORM @extschema@._CDB_Group_Table_GrantReadWrite(group_name, username, table_name, true);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_Table_GrantReadWrite(group_name text, username text, table_name text, sync boolean)
|
||||
FUNCTION @extschema@._CDB_Group_Table_GrantReadWrite(group_name text, username text, table_name text, sync boolean)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
EXECUTE format('GRANT USAGE ON SCHEMA %I TO %I', username, group_role);
|
||||
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.%I TO %I', username, table_name, group_role);
|
||||
PERFORM cartodb._CDB_Group_TableSequences_Permission(group_name, username, table_name, true);
|
||||
PERFORM @extschema@._CDB_Group_TableSequences_Permission(group_name, username, table_name, true);
|
||||
IF(sync) THEN
|
||||
PERFORM cartodb._CDB_Group_Table_GrantPermission_API(group_name, username, table_name, 'w');
|
||||
PERFORM @extschema@._CDB_Group_Table_GrantPermission_API(group_name, username, table_name, 'w');
|
||||
END IF;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Granting and revoking permissions on sequences
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_TableSequences_Permission(group_name text, username text, table_name text, do_grant bool)
|
||||
FUNCTION @extschema@._CDB_Group_TableSequences_Permission(group_name text, username text, table_name text, do_grant bool)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
column_name TEXT;
|
||||
sequence_name TEXT;
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
FOR column_name IN EXECUTE 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG = current_database() AND TABLE_SCHEMA = $1 AND TABLE_NAME = $2 AND COLUMN_DEFAULT LIKE ''nextval%''' USING username, table_name
|
||||
LOOP
|
||||
EXECUTE format('SELECT PG_GET_SERIAL_SEQUENCE(''%I.%I'', ''%I'')', username, table_name, column_name) INTO sequence_name;
|
||||
@ -175,47 +175,47 @@ BEGIN
|
||||
END LOOP;
|
||||
RETURN;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Revokes all permissions on a table from a group
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Group_Table_RevokeAll(group_name text, username text, table_name text)
|
||||
FUNCTION @extschema@.CDB_Group_Table_RevokeAll(group_name text, username text, table_name text)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
PERFORM cartodb._CDB_Group_Table_RevokeAll(group_name, username, table_name, true);
|
||||
PERFORM @extschema@._CDB_Group_Table_RevokeAll(group_name, username, table_name, true);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_Table_RevokeAll(group_name text, username text, table_name text, sync boolean)
|
||||
FUNCTION @extschema@._CDB_Group_Table_RevokeAll(group_name text, username text, table_name text, sync boolean)
|
||||
RETURNS VOID AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
BEGIN
|
||||
group_role := cartodb._CDB_Group_GroupRole(group_name);
|
||||
group_role := @extschema@._CDB_Group_GroupRole(group_name);
|
||||
EXECUTE format('REVOKE ALL ON TABLE %I.%I FROM %I', username, table_name, group_role);
|
||||
PERFORM cartodb._CDB_Group_TableSequences_Permission(group_name, username, table_name, false);
|
||||
PERFORM @extschema@._CDB_Group_TableSequences_Permission(group_name, username, table_name, false);
|
||||
IF(sync) THEN
|
||||
PERFORM cartodb._CDB_Group_Table_RevokeAllPermission_API(group_name, username, table_name);
|
||||
PERFORM @extschema@._CDB_Group_Table_RevokeAllPermission_API(group_name, username, table_name);
|
||||
END IF;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-----------------------
|
||||
-- Helper functions
|
||||
-----------------------
|
||||
-- Given a group name returns a role. group_name must be a valid PostgreSQL idenfifier. See http://www.postgresql.org/docs/9.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_GroupRole(group_name text)
|
||||
FUNCTION @extschema@._CDB_Group_GroupRole(group_name text)
|
||||
RETURNS TEXT AS $$
|
||||
DECLARE
|
||||
group_role TEXT;
|
||||
prefix TEXT;
|
||||
max_length constant INTEGER := 63;
|
||||
BEGIN
|
||||
prefix = format('%s_g_', cartodb._CDB_Group_ShortDatabaseName());
|
||||
prefix = format('%s_g_', @extschema@._CDB_Group_ShortDatabaseName());
|
||||
group_role := format('%s%s', prefix, group_name);
|
||||
IF LENGTH(group_role) > max_length
|
||||
THEN
|
||||
@ -223,11 +223,11 @@ BEGIN
|
||||
END IF;
|
||||
RETURN group_role;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- Returns the first owner of the schema matching username. Organization user schemas must have one only owner.
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_User_RoleFromUsername(username text)
|
||||
FUNCTION @extschema@._CDB_User_RoleFromUsername(username text)
|
||||
RETURNS TEXT AS $$
|
||||
DECLARE
|
||||
user_role TEXT;
|
||||
@ -237,11 +237,11 @@ BEGIN
|
||||
SELECT pg_get_userbyid(nspowner) FROM pg_namespace WHERE nspname = username INTO user_role;
|
||||
RETURN user_role;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- Database names are too long, we need a shorter version for composing role names
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_ShortDatabaseName()
|
||||
FUNCTION @extschema@._CDB_Group_ShortDatabaseName()
|
||||
RETURNS TEXT AS $$
|
||||
DECLARE
|
||||
short_database_name TEXT;
|
||||
@ -249,4 +249,4 @@ BEGIN
|
||||
SELECT md5(current_database()) INTO short_database_name;
|
||||
RETURN short_database_name;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
|
||||
|
@ -2,130 +2,175 @@
|
||||
-- GROUP METADATA API FUNCTIONS
|
||||
--
|
||||
-- Meant to be used by CDB_Group_* functions to sync data with the editor.
|
||||
-- Requires configuration parameter. Example: SELECT cartodb.CDB_Conf_SetConf('groups_api', '{ "host": "127.0.0.1", "port": 3000, "timeout": 10, "username": "extension", "password": "elephant" }');
|
||||
-- Requires configuration parameter. Example: SELECT @extschema@.CDB_Conf_SetConf('groups_api', '{ "host": "127.0.0.1", "port": 3000, "timeout": 10, "username": "extension", "password": "elephant" }');
|
||||
----------------------------------
|
||||
|
||||
-- TODO: delete this development cleanup before final merge
|
||||
DROP FUNCTION IF EXISTS cartodb.CDB_Group_AddMember(group_name text, username text);
|
||||
DROP FUNCTION IF EXISTS cartodb.CDB_Group_RemoveMember(group_name text, username text);
|
||||
DROP FUNCTION IF EXISTS cartodb._CDB_Group_AddMember_API(group_name text, username text);
|
||||
DROP FUNCTION IF EXISTS cartodb._CDB_Group_RemoveMember_API(group_name text, username text);
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_Group_AddMember(group_name text, username text);
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_Group_RemoveMember(group_name text, username text);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Group_AddMember_API(group_name text, username text);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Group_RemoveMember_API(group_name text, username text);
|
||||
|
||||
-- Sends the create group request
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_CreateGroup_API(group_name text, group_role text)
|
||||
FUNCTION @extschema@._CDB_Group_CreateGroup_API(group_name text, group_role text)
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
import string
|
||||
|
||||
url = '/api/v1/databases/{0}/groups'
|
||||
body = '{ "name": "%s", "database_role": "%s" }' % (group_name, group_role)
|
||||
query = "select cartodb._CDB_Group_API_Request('POST', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
query = "select @extschema@._CDB_Group_API_Request('POST', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
plpy.execute(query)
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE '@@plpythonu@@'
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_DropGroup_API(group_name text)
|
||||
FUNCTION @extschema@._CDB_Group_DropGroup_API(group_name text)
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
import string
|
||||
import urllib
|
||||
try:
|
||||
from urllib import pathname2url
|
||||
except:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
url = '/api/v1/databases/{0}/groups/%s' % (urllib.pathname2url(group_name))
|
||||
url = '/api/v1/databases/{0}/groups/%s' % (pathname2url(group_name))
|
||||
|
||||
query = "select cartodb._CDB_Group_API_Request('DELETE', '%s', '', '{204, 404}') as response_status" % url
|
||||
query = "select @extschema@._CDB_Group_API_Request('DELETE', '%s', '', '{204, 404}') as response_status" % url
|
||||
plpy.execute(query)
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE '@@plpythonu@@'
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_RenameGroup_API(old_group_name text, new_group_name text, new_group_role text)
|
||||
FUNCTION @extschema@._CDB_Group_RenameGroup_API(old_group_name text, new_group_name text, new_group_role text)
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
import string
|
||||
import urllib
|
||||
try:
|
||||
from urllib import pathname2url
|
||||
except:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
url = '/api/v1/databases/{0}/groups/%s' % (urllib.pathname2url(old_group_name))
|
||||
url = '/api/v1/databases/{0}/groups/%s' % (pathname2url(old_group_name))
|
||||
body = '{ "name": "%s", "database_role": "%s" }' % (new_group_name, new_group_role)
|
||||
query = "select cartodb._CDB_Group_API_Request('PUT', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
query = "select @extschema@._CDB_Group_API_Request('PUT', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
plpy.execute(query)
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE '@@plpythonu@@'
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_AddUsers_API(group_name text, usernames text[])
|
||||
FUNCTION @extschema@._CDB_Group_AddUsers_API(group_name text, usernames text[])
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
import string
|
||||
import urllib
|
||||
try:
|
||||
from urllib import pathname2url
|
||||
except:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
url = '/api/v1/databases/{0}/groups/%s/users' % (urllib.pathname2url(group_name))
|
||||
url = '/api/v1/databases/{0}/groups/%s/users' % (pathname2url(group_name))
|
||||
body = "{ \"users\": [\"%s\"] }" % "\",\"".join(usernames)
|
||||
query = "select cartodb._CDB_Group_API_Request('POST', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
query = "select @extschema@._CDB_Group_API_Request('POST', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
plpy.execute(query)
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE '@@plpythonu@@'
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_RemoveUsers_API(group_name text, usernames text[])
|
||||
FUNCTION @extschema@._CDB_Group_RemoveUsers_API(group_name text, usernames text[])
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
import string
|
||||
import urllib
|
||||
try:
|
||||
from urllib import pathname2url
|
||||
except:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
url = '/api/v1/databases/{0}/groups/%s/users' % (urllib.pathname2url(group_name))
|
||||
url = '/api/v1/databases/{0}/groups/%s/users' % (pathname2url(group_name))
|
||||
body = "{ \"users\": [\"%s\"] }" % "\",\"".join(usernames)
|
||||
query = "select cartodb._CDB_Group_API_Request('DELETE', '%s', '%s', '{200, 404}') as response_status" % (url, body)
|
||||
query = "select @extschema@._CDB_Group_API_Request('DELETE', '%s', '%s', '{200, 404}') as response_status" % (url, body)
|
||||
plpy.execute(query)
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE '@@plpythonu@@'
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
BEGIN
|
||||
-- Needed for dropping type
|
||||
DROP FUNCTION IF EXISTS cartodb._CDB_Group_API_Conf();
|
||||
DROP TYPE IF EXISTS _CDB_Group_API_Params;
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Group_API_Conf();
|
||||
DROP TYPE IF EXISTS @extschema@._CDB_Group_API_Params;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_Table_GrantPermission_API(group_name text, username text, table_name text, access text)
|
||||
FUNCTION @extschema@._CDB_Group_Table_GrantPermission_API(group_name text, username text, table_name text, access text)
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
import string
|
||||
import urllib
|
||||
try:
|
||||
from urllib import pathname2url
|
||||
except:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
url = '/api/v1/databases/{0}/groups/%s/permission/%s/tables/%s' % (urllib.pathname2url(group_name), username, table_name)
|
||||
url = '/api/v1/databases/{0}/groups/%s/permission/%s/tables/%s' % (pathname2url(group_name), username, table_name)
|
||||
body = '{ "access": "%s" }' % access
|
||||
query = "select cartodb._CDB_Group_API_Request('PUT', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
query = "select @extschema@._CDB_Group_API_Request('PUT', '%s', '%s', '{200, 409}') as response_status" % (url, body)
|
||||
plpy.execute(query)
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE '@@plpythonu@@'
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
BEGIN
|
||||
-- Needed for dropping type
|
||||
DROP FUNCTION IF EXISTS cartodb._CDB_Group_API_Conf();
|
||||
DROP TYPE IF EXISTS _CDB_Group_API_Params;
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Group_API_Conf();
|
||||
DROP TYPE IF EXISTS @extschema@._CDB_Group_API_Params;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_Table_RevokeAllPermission_API(group_name text, username text, table_name text)
|
||||
FUNCTION @extschema@._CDB_Group_Table_RevokeAllPermission_API(group_name text, username text, table_name text)
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
import string
|
||||
import urllib
|
||||
try:
|
||||
from urllib import pathname2url
|
||||
except:
|
||||
from urllib.request import pathname2url
|
||||
|
||||
url = '/api/v1/databases/{0}/groups/%s/permission/%s/tables/%s' % (urllib.pathname2url(group_name), username, table_name)
|
||||
query = "select cartodb._CDB_Group_API_Request('DELETE', '%s', '', '{200, 404}') as response_status" % url
|
||||
url = '/api/v1/databases/{0}/groups/%s/permission/%s/tables/%s' % (pathname2url(group_name), username, table_name)
|
||||
query = "select @extschema@._CDB_Group_API_Request('DELETE', '%s', '', '{200, 404}') as response_status" % url
|
||||
plpy.execute(query)
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE '@@plpythonu@@'
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
BEGIN
|
||||
-- Needed for dropping type
|
||||
DROP FUNCTION IF EXISTS cartodb._CDB_Group_API_Conf();
|
||||
DROP TYPE IF EXISTS _CDB_Group_API_Params;
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Group_API_Conf();
|
||||
DROP TYPE IF EXISTS @extschema@._CDB_Group_API_Params;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE TYPE _CDB_Group_API_Params AS (
|
||||
CREATE TYPE @extschema@._CDB_Group_API_Params AS (
|
||||
host text,
|
||||
port int,
|
||||
timeout int,
|
||||
@ -135,35 +180,49 @@ CREATE TYPE _CDB_Group_API_Params AS (
|
||||
-- This must be explicitally extracted because "composite types are currently not supported".
|
||||
-- See http://www.postgresql.org/docs/9.3/static/plpython-database.html.
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_API_Conf()
|
||||
RETURNS _CDB_Group_API_Params AS
|
||||
FUNCTION @extschema@._CDB_Group_API_Conf()
|
||||
RETURNS @extschema@._CDB_Group_API_Params AS
|
||||
$$
|
||||
conf = plpy.execute("SELECT cartodb.CDB_Conf_GetConf('groups_api') conf")[0]['conf']
|
||||
conf = plpy.execute("SELECT @extschema@.CDB_Conf_GetConf('groups_api') conf")[0]['conf']
|
||||
if conf is None:
|
||||
return None
|
||||
else:
|
||||
import json
|
||||
params = json.loads(conf)
|
||||
auth = 'Basic %s' % plpy.execute("SELECT cartodb._CDB_Group_API_Auth('%s', '%s') as auth" % (params['username'], params['password']))[0]['auth']
|
||||
auth = 'Basic %s' % plpy.execute("SELECT @extschema@._CDB_Group_API_Auth('%s', '%s') as auth" % (params['username'], params['password']))[0]['auth']
|
||||
return { "host": params['host'], "port": params['port'], 'timeout': params['timeout'], 'auth': auth }
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE;
|
||||
$$ LANGUAGE '@@plpythonu@@' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_API_Auth(username text, password text)
|
||||
FUNCTION @extschema@._CDB_Group_API_Auth(username text, password text)
|
||||
RETURNS TEXT AS
|
||||
$$
|
||||
import sys
|
||||
import base64
|
||||
return base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE;
|
||||
|
||||
data_to_encode = '%s:%s' % (username, password)
|
||||
if sys.version_info[0] < 3:
|
||||
data_encoded = base64.encodestring(data_to_encode)
|
||||
else:
|
||||
data_encoded = base64.b64encode(data_to_encode.encode()).decode()
|
||||
|
||||
data_encoded = data_encoded.replace('\n', '')
|
||||
return data_encoded
|
||||
$$ LANGUAGE '@@plpythonu@@' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- url must contain a '%s' placeholder that will be replaced by current_database, for security reasons.
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Group_API_Request(method text, url text, body text, valid_return_codes int[])
|
||||
FUNCTION @extschema@._CDB_Group_API_Request(method text, url text, body text, valid_return_codes int[])
|
||||
RETURNS int AS
|
||||
$$
|
||||
import httplib
|
||||
python_v2 = True
|
||||
try:
|
||||
import httplib as client
|
||||
except:
|
||||
from http import client
|
||||
python_v2 = False
|
||||
|
||||
params = plpy.execute("select c.host, c.port, c.timeout, c.auth from cartodb._CDB_Group_API_Conf() c;")[0]
|
||||
params = plpy.execute("select c.host, c.port, c.timeout, c.auth from @extschema@._CDB_Group_API_Conf() c;")[0]
|
||||
if params['host'] is None:
|
||||
return None
|
||||
|
||||
@ -174,22 +233,25 @@ $$
|
||||
last_err = None
|
||||
while retry > 0:
|
||||
try:
|
||||
client = SD['groups_api_client'] = httplib.HTTPConnection(params['host'], params['port'], False, params['timeout'])
|
||||
if python_v2:
|
||||
conn = SD['groups_api_client'] = client.HTTPConnection(params['host'], params['port'], False, params['timeout'])
|
||||
else:
|
||||
conn = SD['groups_api_client'] = client.HTTPConnection(params['host'], port=params['port'], timeout=params['timeout'])
|
||||
database_name = plpy.execute("select current_database();")[0]['current_database']
|
||||
client.request(method, url.format(database_name), body, headers)
|
||||
response = client.getresponse()
|
||||
conn.request(method, url.format(database_name), body, headers)
|
||||
response = conn.getresponse()
|
||||
assert response.status in valid_return_codes
|
||||
return response.status
|
||||
except Exception as err:
|
||||
retry -= 1
|
||||
last_err = err
|
||||
plpy.warning('Retrying after: ' + str(err))
|
||||
client = SD['groups_api_client'] = None
|
||||
conn = SD['groups_api_client'] = None
|
||||
|
||||
if last_err is not None:
|
||||
plpy.error('Fatal Group API error: ' + str(last_err))
|
||||
raise last_err
|
||||
|
||||
return None
|
||||
$$ LANGUAGE 'plpythonu' VOLATILE;
|
||||
revoke all on function cartodb._CDB_Group_API_Request(text, text, text, int[]) from public;
|
||||
$$ LANGUAGE '@@plpythonu@@' VOLATILE PARALLEL UNSAFE;
|
||||
revoke all on function @extschema@._CDB_Group_API_Request(text, text, text, int[]) from public;
|
||||
|
@ -8,7 +8,7 @@
|
||||
--
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_HeadsTailsBins ( in_array NUMERIC[], breaks INT) RETURNS NUMERIC[] as $$
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_HeadsTailsBins ( in_array NUMERIC[], breaks INT) RETURNS NUMERIC[] as $$
|
||||
DECLARE
|
||||
element_count INT4;
|
||||
arr_mean numeric;
|
||||
@ -43,4 +43,4 @@ BEGIN
|
||||
END LOOP;
|
||||
RETURN reply;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
$$ language plpgsql IMMUTABLE PARALLEL SAFE;
|
||||
|
@ -1,7 +1,11 @@
|
||||
-- Create a sequence that belongs to the schema of the extension.
|
||||
-- It will be used to generate unique identifiers within the
|
||||
|
||||
|
||||
-- UTF8 safe and length aware. Find a unique identifier with a given prefix
|
||||
-- and/or suffix and withing a schema. If a schema is not specified, the identifier
|
||||
-- is guaranteed to be unique for all schemas.
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Unique_Identifier(prefix TEXT, relname TEXT, suffix TEXT, schema TEXT DEFAULT NULL)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Unique_Identifier(prefix TEXT, relname TEXT, suffix TEXT, schema TEXT DEFAULT NULL)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -15,15 +19,15 @@ DECLARE
|
||||
|
||||
i INTEGER;
|
||||
BEGIN
|
||||
-- Accounts for the _XX incremental suffix in case the identifier is taken
|
||||
usedspace := 3;
|
||||
-- Accounts for the XXXX incremental suffix in case the identifier is taken
|
||||
usedspace := 4;
|
||||
usedspace := usedspace + coalesce(octet_length(prefix), 0);
|
||||
usedspace := usedspace + coalesce(octet_length(suffix), 0);
|
||||
|
||||
candrelname := _CDB_Octet_Truncate(relname, maxlen - usedspace);
|
||||
candrelname := @extschema@._CDB_Octet_Truncate(relname, maxlen - usedspace);
|
||||
|
||||
IF candrelname = '' THEN
|
||||
PERFORM _CDB_Error('prefixes are to long to generate a valid identifier', '_CDB_Unique_Identifier');
|
||||
PERFORM @extschema@._CDB_Error('prefixes are to long to generate a valid identifier', '_CDB_Unique_Identifier');
|
||||
END IF;
|
||||
|
||||
ident := coalesce(prefix, '') || candrelname || coalesce(suffix, '');
|
||||
@ -31,7 +35,7 @@ BEGIN
|
||||
i := 0;
|
||||
origident := ident;
|
||||
|
||||
WHILE i < 100 LOOP
|
||||
WHILE i < 10000 LOOP
|
||||
IF schema IS NOT NULL THEN
|
||||
SELECT c.relname, n.nspname
|
||||
INTO rec
|
||||
@ -51,18 +55,18 @@ BEGIN
|
||||
RETURN ident;
|
||||
END IF;
|
||||
|
||||
ident := origident || '_' || i;
|
||||
ident := origident || i;
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
|
||||
PERFORM _CDB_Error('looping too far', '_CDB_Unique_Identifier');
|
||||
PERFORM @extschema@._CDB_Error('looping too far', '_CDB_Unique_Identifier');
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
-- UTF8 safe and length aware. Find a unique identifier for a column with a given prefix
|
||||
-- and/or suffix based on colname and within a relation specified via reloid.
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Unique_Column_Identifier(prefix TEXT, colname TEXT, suffix TEXT, reloid REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Unique_Column_Identifier(prefix TEXT, colname TEXT, suffix TEXT, reloid REGCLASS)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -76,15 +80,15 @@ DECLARE
|
||||
|
||||
i INTEGER;
|
||||
BEGIN
|
||||
-- Accounts for the _XX incremental suffix in case the identifier is taken
|
||||
usedspace := 3;
|
||||
-- Accounts for the XXXX incremental suffix in case the identifier is taken
|
||||
usedspace := 4;
|
||||
usedspace := usedspace + coalesce(octet_length(prefix), 0);
|
||||
usedspace := usedspace + coalesce(octet_length(suffix), 0);
|
||||
|
||||
candcolname := _CDB_Octet_Truncate(colname, maxlen - usedspace);
|
||||
candcolname := @extschema@._CDB_Octet_Truncate(colname, maxlen - usedspace);
|
||||
|
||||
IF candcolname = '' THEN
|
||||
PERFORM _CDB_Error('prefixes are to long to generate a valid identifier', '_CDB_Unique_Column_Identifier');
|
||||
PERFORM @extschema@._CDB_Error('prefixes are to long to generate a valid identifier', '_CDB_Unique_Column_Identifier');
|
||||
END IF;
|
||||
|
||||
ident := coalesce(prefix, '') || candcolname || coalesce(suffix, '');
|
||||
@ -92,7 +96,7 @@ BEGIN
|
||||
i := 0;
|
||||
origident := ident;
|
||||
|
||||
WHILE i < 100 LOOP
|
||||
WHILE i < 10000 LOOP
|
||||
SELECT a.attname
|
||||
INTO rec
|
||||
FROM pg_class c
|
||||
@ -106,18 +110,18 @@ BEGIN
|
||||
RETURN ident;
|
||||
END IF;
|
||||
|
||||
ident := origident || '_' || i;
|
||||
ident := origident || i;
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
|
||||
PERFORM _CDB_Error('looping too far', '_CDB_Unique_Column_Identifier');
|
||||
PERFORM @extschema@._CDB_Error('looping too far', '_CDB_Unique_Column_Identifier');
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE PARALLEL SAFE;
|
||||
|
||||
|
||||
-- Truncates a given string to a max_octets octets taking care
|
||||
-- not to leave characters in half. UTF8 safe.
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Octet_Truncate(string TEXT, max_octets INTEGER)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Octet_Truncate(string TEXT, max_octets INTEGER)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -153,4 +157,21 @@ BEGIN
|
||||
|
||||
RETURN left(string, (i - 1));
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
|
||||
-- Checks if a given text representing a qualified or unqualified table name (relation)
|
||||
-- actually exists in the database. It is meant to be used as a guard for other function/queries.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Table_Exists(table_name_with_optional_schema TEXT)
|
||||
RETURNS bool
|
||||
AS $$
|
||||
DECLARE
|
||||
table_exists bool := false;
|
||||
BEGIN
|
||||
table_exists := EXISTS(SELECT * FROM pg_class WHERE table_name_with_optional_schema::regclass::oid = oid AND relkind = 'r');
|
||||
RETURN table_exists;
|
||||
EXCEPTION
|
||||
WHEN invalid_schema_name OR undefined_table THEN
|
||||
RETURN false;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
@ -1,14 +1,18 @@
|
||||
-- Return an Hexagon with given center and side (or maximal radius)
|
||||
CREATE OR REPLACE FUNCTION CDB_MakeHexagon(center GEOMETRY, radius FLOAT8)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_MakeHexagon(center GEOMETRY, radius FLOAT8)
|
||||
RETURNS GEOMETRY
|
||||
AS $$
|
||||
SELECT ST_MakePolygon(ST_MakeLine(geom))
|
||||
SELECT @postgisschema@.ST_MakePolygon(@postgisschema@.ST_MakeLine(geom))
|
||||
FROM
|
||||
(
|
||||
SELECT (ST_DumpPoints(ST_ExteriorRing(ST_Buffer($1, $2, 3)))).*
|
||||
SELECT (@postgisschema@.ST_DumpPoints(@postgisschema@.ST_ExteriorRing(@postgisschema@.ST_Buffer($1, $2, 3)))).*
|
||||
) as points
|
||||
WHERE path[1] % 2 != 0
|
||||
$$ LANGUAGE 'sql' IMMUTABLE STRICT;
|
||||
$$ LANGUAGE 'sql' IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
|
||||
-- In older versions of the extension, CDB_HexagonGrid had a different signature
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_HexagonGrid(GEOMETRY, FLOAT8, GEOMETRY);
|
||||
|
||||
--
|
||||
-- Fill given extent with an hexagonal coverage
|
||||
@ -25,10 +29,13 @@ $$ LANGUAGE 'sql' IMMUTABLE STRICT;
|
||||
-- If omitted the origin will be 0,0.
|
||||
-- The parameter is checked for having the same SRID
|
||||
-- as the extent.
|
||||
--
|
||||
--
|
||||
-- @param maxcells Optional maximum number of grid cells to generate;
|
||||
-- if the grid requires more cells to cover the extent
|
||||
-- and exception will occur.
|
||||
----
|
||||
-- DROP FUNCTION IF EXISTS CDB_HexagonGrid(ext GEOMETRY, side FLOAT8);
|
||||
CREATE OR REPLACE FUNCTION CDB_HexagonGrid(ext GEOMETRY, side FLOAT8, origin GEOMETRY DEFAULT NULL)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_HexagonGrid(ext GEOMETRY, side FLOAT8, origin GEOMETRY DEFAULT NULL, maxcells INTEGER DEFAULT 512*512)
|
||||
RETURNS SETOF GEOMETRY
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -73,11 +80,11 @@ BEGIN
|
||||
yoff := 0;
|
||||
|
||||
IF origin IS NOT NULL THEN
|
||||
IF ST_SRID(origin) != srid THEN
|
||||
IF @postgisschema@.ST_SRID(origin) != srid THEN
|
||||
RAISE EXCEPTION 'SRID mismatch between extent (%) and origin (%)', srid, ST_SRID(origin);
|
||||
END IF;
|
||||
xoff := ST_X(origin);
|
||||
yoff := ST_Y(origin);
|
||||
xoff := @postgisschema@.ST_X(origin);
|
||||
yoff := @postgisschema@.ST_Y(origin);
|
||||
END IF;
|
||||
|
||||
RAISE DEBUG 'X offset: %', xoff;
|
||||
@ -89,47 +96,53 @@ BEGIN
|
||||
RAISE DEBUG 'Y grid size: %', ygrd;
|
||||
|
||||
-- Tweak horizontal start on hstep*2 grid from origin
|
||||
hskip := ceil((ST_XMin(ext)-xoff)/hstep);
|
||||
hskip := ceil((@postgisschema@.ST_XMin(ext)-xoff)/hstep);
|
||||
RAISE DEBUG 'hskip: %', hskip;
|
||||
hstart := xoff + hskip*hstep;
|
||||
RAISE DEBUG 'hstart: %', hstart;
|
||||
|
||||
-- Tweak vertical start on hstep grid from origin
|
||||
vstart := yoff + ceil((ST_Ymin(ext)-yoff)/vstep)*vstep;
|
||||
vstart := yoff + ceil((@postgisschema@.ST_Ymin(ext)-yoff)/vstep)*vstep;
|
||||
RAISE DEBUG 'vstart: %', vstart;
|
||||
|
||||
hend := ST_XMax(ext);
|
||||
vend := ST_YMax(ext);
|
||||
hend := @postgisschema@.ST_XMax(ext);
|
||||
vend := @postgisschema@.ST_YMax(ext);
|
||||
|
||||
IF vstart - (vstep/2.0) < ST_YMin(ext) THEN
|
||||
IF vstart - (vstep/2.0) < @postgisschema@.ST_YMin(ext) THEN
|
||||
vstartary := ARRAY[ vstart + (vstep/2.0), vstart ];
|
||||
ELSE
|
||||
vstartary := ARRAY[ vstart - (vstep/2.0), vstart ];
|
||||
END IF;
|
||||
|
||||
If maxcells IS NOT NULL AND maxcells > 0 THEN
|
||||
IF CEIL((CEIL((vend-vstart)/(vstep/2.0)) * CEIL((hend-hstart)/(hstep*2.0/3.0)))/3.0)::integer > maxcells THEN
|
||||
RAISE EXCEPTION 'The requested grid is too big to be rendered';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
vstartidx := abs(hskip)%2;
|
||||
|
||||
RAISE DEBUG 'vstartary: % : %', vstartary[1], vstartary[2];
|
||||
RAISE DEBUG 'vstartidx: %', vstartidx;
|
||||
|
||||
c := ST_SetSRID(ST_MakePoint(hstart, vstartary[vstartidx+1]), srid);
|
||||
h := ST_SnapToGrid(CDB_MakeHexagon(c, side), xoff, yoff, xgrd, ygrd);
|
||||
c := @postgisschema@.ST_SetSRID(@postgisschema@.ST_MakePoint(hstart, vstartary[vstartidx+1]), srid);
|
||||
h := @postgisschema@.ST_SnapToGrid(@extschema@.CDB_MakeHexagon(c, side), xoff, yoff, xgrd, ygrd);
|
||||
vstartidx := (vstartidx + 1) % 2;
|
||||
WHILE ST_X(c) < hend LOOP -- over X
|
||||
WHILE @postgisschema@.ST_X(c) < hend LOOP -- over X
|
||||
--RAISE DEBUG 'X loop starts, center point: %', ST_AsText(c);
|
||||
WHILE ST_Y(c) < vend LOOP -- over Y
|
||||
WHILE @postgisschema@.ST_Y(c) < vend LOOP -- over Y
|
||||
--RAISE DEBUG 'Center: %', ST_AsText(c);
|
||||
--h := ST_SnapToGrid(CDB_MakeHexagon(c, side), xoff, yoff, xgrd, ygrd);
|
||||
RETURN NEXT h;
|
||||
h := ST_SnapToGrid(ST_Translate(h, 0, vstep), xoff, yoff, xgrd, ygrd);
|
||||
c := ST_Translate(c, 0, vstep); -- TODO: drop ?
|
||||
h := @postgisschema@.ST_SnapToGrid(ST_Translate(h, 0, vstep), xoff, yoff, xgrd, ygrd);
|
||||
c := @postgisschema@.ST_Translate(c, 0, vstep); -- TODO: drop ?
|
||||
END LOOP;
|
||||
-- TODO: translate h direcly ...
|
||||
c := ST_SetSRID(ST_MakePoint(ST_X(c)+hstep, vstartary[vstartidx+1]), srid);
|
||||
h := ST_SnapToGrid(CDB_MakeHexagon(c, side), xoff, yoff, xgrd, ygrd);
|
||||
c := @postgisschema@.ST_SetSRID(@postgisschema@.ST_MakePoint(ST_X(c)+hstep, vstartary[vstartidx+1]), srid);
|
||||
h := @postgisschema@.ST_SnapToGrid(@extschema@.CDB_MakeHexagon(c, side), xoff, yoff, xgrd, ygrd);
|
||||
vstartidx := (vstartidx + 1) % 2;
|
||||
END LOOP;
|
||||
|
||||
RETURN;
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE PARALLEL SAFE;
|
||||
|
@ -10,212 +10,337 @@
|
||||
--
|
||||
-- @param invert Optional wheter to return the top of each bin (default)
|
||||
-- or the bottom. BOOLEAN, default=FALSE.
|
||||
--
|
||||
--
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_JenksBins(in_array NUMERIC[], breaks INT, iterations INT DEFAULT 0, invert BOOLEAN DEFAULT FALSE)
|
||||
RETURNS NUMERIC[] as
|
||||
$$
|
||||
DECLARE
|
||||
in_matrix NUMERIC[][];
|
||||
in_unique_count BIGINT;
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_JenksBins ( in_array NUMERIC[], breaks INT, iterations INT DEFAULT 5, invert BOOLEAN DEFAULT FALSE) RETURNS NUMERIC[] as $$
|
||||
DECLARE
|
||||
element_count INT4;
|
||||
shuffles INT;
|
||||
arr_mean NUMERIC;
|
||||
sdam NUMERIC;
|
||||
|
||||
i INT;
|
||||
bot INT;
|
||||
top INT;
|
||||
|
||||
tops INT[];
|
||||
classes INT[][];
|
||||
i INT := 1; j INT := 1;
|
||||
j INT := 1;
|
||||
curr_result NUMERIC[];
|
||||
best_result NUMERIC[];
|
||||
seedtarget TEXT;
|
||||
quant NUMERIC[];
|
||||
shuffles INT;
|
||||
|
||||
BEGIN
|
||||
-- get the total size of our row
|
||||
element_count := array_length(in_array, 1); --array_upper(in_array, 1) - array_lower(in_array, 1);
|
||||
-- ensure the ordering of in_array
|
||||
SELECT array_agg(e) INTO in_array FROM (SELECT unnest(in_array) e ORDER BY e) x;
|
||||
-- stop if no rows
|
||||
IF element_count IS NULL THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
-- stop if our breaks are more than our input array size
|
||||
IF element_count < breaks THEN
|
||||
RETURN in_array;
|
||||
END IF;
|
||||
-- We clean the input array (remove NULLs) and create 2 arrays
|
||||
-- [1] contains the unique values in in_array
|
||||
-- [2] contains the number of appearances of those unique values
|
||||
SELECT ARRAY[array_agg(value), array_agg(count)] FROM
|
||||
(
|
||||
SELECT value, count(1)::numeric as count
|
||||
FROM unnest(in_array) AS value
|
||||
WHERE value is NOT NULL
|
||||
GROUP BY value
|
||||
ORDER BY value
|
||||
) __clean_array_q INTO in_matrix;
|
||||
|
||||
shuffles := LEAST(GREATEST(floor(2500000.0/(element_count::float*iterations::float)), 1), 750)::int;
|
||||
-- get our mean value
|
||||
SELECT avg(v) INTO arr_mean FROM ( SELECT unnest(in_array) as v ) x;
|
||||
-- Get the number of unique values
|
||||
in_unique_count := array_length(in_matrix[1:1], 2);
|
||||
|
||||
-- assume best is actually Quantile
|
||||
SELECT CDB_QuantileBins(in_array, breaks) INTO quant;
|
||||
|
||||
-- if data is very very large, just return quant and be done
|
||||
IF element_count > 5000000 THEN
|
||||
RETURN quant;
|
||||
IF in_unique_count IS NULL THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
-- change quant into bottom, top markers
|
||||
LOOP
|
||||
IF i = 1 THEN
|
||||
bot = 1;
|
||||
ELSE
|
||||
-- use last top to find this bot
|
||||
bot = top+1;
|
||||
END IF;
|
||||
IF i = breaks THEN
|
||||
top = element_count;
|
||||
IF in_unique_count <= breaks THEN
|
||||
-- There isn't enough distinct values for the requested breaks
|
||||
RETURN ARRAY(Select unnest(in_matrix[1:1])) _a;
|
||||
END IF;
|
||||
|
||||
-- If not declated explicitly we iterate based on the length of the array
|
||||
IF iterations < 1 THEN
|
||||
-- This is based on a 'looks fine' heuristic
|
||||
iterations := log(in_unique_count)::integer + 1;
|
||||
END IF;
|
||||
|
||||
-- We set the number of shuffles per iteration as the number of unique values but
|
||||
-- this is just another 'looks fine' heuristic
|
||||
shuffles := in_unique_count;
|
||||
|
||||
-- Get the mean value of the whole vector (already ignores NULLs)
|
||||
SELECT avg(v) INTO arr_mean FROM ( SELECT unnest(in_array) as v ) x;
|
||||
|
||||
-- Calculate the sum of squared deviations from the array mean (SDAM).
|
||||
SELECT sum(((arr_mean - v)^2) * w) INTO sdam FROM (
|
||||
SELECT unnest(in_matrix[1:1]) as v, unnest(in_matrix[2:2]) as w
|
||||
) x;
|
||||
|
||||
-- To start, we create ranges with approximately the same amount of different values
|
||||
top := 0;
|
||||
i := 1;
|
||||
LOOP
|
||||
bot := top + 1;
|
||||
top := ROUND(i * in_unique_count::numeric / breaks::NUMERIC);
|
||||
|
||||
IF i = 1 THEN
|
||||
classes = ARRAY[ARRAY[bot,top]];
|
||||
ELSE
|
||||
SELECT count(*) INTO top FROM ( SELECT unnest(in_array) as v) x WHERE v <= quant[i];
|
||||
END IF;
|
||||
IF i = 1 THEN
|
||||
classes = ARRAY[ARRAY[bot,top]];
|
||||
ELSE
|
||||
classes = ARRAY_CAT(classes,ARRAY[bot,top]);
|
||||
classes = ARRAY_CAT(classes, ARRAY[bot,top]);
|
||||
END IF;
|
||||
|
||||
i := i + 1;
|
||||
IF i > breaks THEN EXIT; END IF;
|
||||
i = i+1;
|
||||
END LOOP;
|
||||
|
||||
best_result = CDB_JenksBinsIteration( in_array, breaks, classes, invert, element_count, arr_mean, shuffles);
|
||||
best_result = @extschema@.CDB_JenksBinsIteration(in_matrix, breaks, classes, invert, sdam, shuffles);
|
||||
|
||||
--set the seed so we can ensure the same results
|
||||
SELECT setseed(0.4567) INTO seedtarget;
|
||||
--loop through random starting positions
|
||||
LOOP
|
||||
IF j > iterations-1 THEN EXIT; END IF;
|
||||
IF j > iterations-1 THEN EXIT; END IF;
|
||||
i = 1;
|
||||
tops = ARRAY[element_count];
|
||||
tops = ARRAY[in_unique_count];
|
||||
LOOP
|
||||
IF i = breaks THEN EXIT; END IF;
|
||||
SELECT array_agg(distinct e) INTO tops FROM (SELECT unnest(array_cat(tops, ARRAY[floor(random()*element_count::float)::int])) as e ORDER BY e) x WHERE e != 1;
|
||||
i = array_length(tops, 1);
|
||||
END LOOP;
|
||||
IF i = breaks THEN EXIT; END IF;
|
||||
SELECT array_agg(distinct e) INTO tops FROM (
|
||||
SELECT unnest(array_cat(tops, ARRAY[trunc(random() * in_unique_count::float8)::int + 1])) as e ORDER BY e
|
||||
) x;
|
||||
i = array_length(tops, 1);
|
||||
END LOOP;
|
||||
top := 0;
|
||||
i = 1;
|
||||
LOOP
|
||||
IF i > breaks THEN EXIT; END IF;
|
||||
IF i = 1 THEN
|
||||
bot = 1;
|
||||
ELSE
|
||||
bot = top+1;
|
||||
END IF;
|
||||
LOOP
|
||||
bot := top + 1;
|
||||
top = tops[i];
|
||||
IF i = 1 THEN
|
||||
classes = ARRAY[ARRAY[bot,top]];
|
||||
ELSE
|
||||
classes = ARRAY_CAT(classes,ARRAY[bot,top]);
|
||||
IF i = 1 THEN
|
||||
classes = ARRAY[ARRAY[bot,top]];
|
||||
ELSE
|
||||
classes = ARRAY_CAT(classes, ARRAY[bot,top]);
|
||||
END IF;
|
||||
i := i+1;
|
||||
END LOOP;
|
||||
curr_result = CDB_JenksBinsIteration( in_array, breaks, classes, invert, element_count, arr_mean, shuffles);
|
||||
|
||||
i := i+1;
|
||||
IF i > breaks THEN EXIT; END IF;
|
||||
END LOOP;
|
||||
|
||||
curr_result = @extschema@.CDB_JenksBinsIteration(in_matrix, breaks, classes, invert, sdam, shuffles);
|
||||
|
||||
IF curr_result[1] > best_result[1] THEN
|
||||
best_result = curr_result;
|
||||
j = j-1; -- if we found a better result, add one more search
|
||||
END IF;
|
||||
|
||||
j = j+1;
|
||||
END LOOP;
|
||||
|
||||
RETURN (best_result)[2:array_upper(best_result, 1)];
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL RESTRICTED;
|
||||
|
||||
|
||||
--
|
||||
-- Perform a single iteration of the Jenks classification
|
||||
--
|
||||
-- Returns an array with:
|
||||
-- - First element: gvf
|
||||
-- - Second to 2+n: Category limits
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_JenksBinsIteration ( in_matrix NUMERIC[], breaks INT, classes INT[], invert BOOLEAN, element_count INT4, arr_mean NUMERIC, max_search INT); -- Old signature
|
||||
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_JenksBinsIteration ( in_matrix NUMERIC[], breaks INT, classes INT[], invert BOOLEAN, sdam NUMERIC, max_search INT DEFAULT 50) RETURNS NUMERIC[] as $$
|
||||
DECLARE
|
||||
i INT;
|
||||
iterations INT = 0;
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_JenksBinsIteration ( in_array NUMERIC[], breaks INT, classes INT[][], invert BOOLEAN, element_count INT4, arr_mean NUMERIC, max_search INT DEFAULT 50) RETURNS NUMERIC[] as $$
|
||||
DECLARE
|
||||
tmp_val numeric;
|
||||
new_classes int[][];
|
||||
tmp_class int[];
|
||||
i INT := 1;
|
||||
j INT := 1;
|
||||
side INT := 2;
|
||||
sdam numeric;
|
||||
gvf numeric := 0.0;
|
||||
new_gvf numeric;
|
||||
arr_gvf numeric[];
|
||||
class_avg numeric;
|
||||
class_max_i INT;
|
||||
class_min_i INT;
|
||||
class_max numeric;
|
||||
class_min numeric;
|
||||
reply numeric[];
|
||||
BEGIN
|
||||
|
||||
-- Calculate the sum of squared deviations from the array mean (SDAM).
|
||||
SELECT sum((arr_mean - e)^2) INTO sdam FROM ( SELECT unnest(in_array) as e ) x;
|
||||
--Identify the breaks for the lowest GVF
|
||||
LOOP
|
||||
i = 1;
|
||||
LOOP
|
||||
-- get our mean
|
||||
SELECT avg(e) INTO class_avg FROM ( SELECT unnest(in_array[classes[i][1]:classes[i][2]]) as e) x;
|
||||
-- find the deviation
|
||||
SELECT sum((class_avg-e)^2) INTO tmp_val FROM ( SELECT unnest(in_array[classes[i][1]:classes[i][2]]) as e ) x;
|
||||
IF i = 1 THEN
|
||||
arr_gvf = ARRAY[tmp_val];
|
||||
-- init our min/max map for later
|
||||
class_max = arr_gvf[i];
|
||||
class_min = arr_gvf[i];
|
||||
class_min_i = 1;
|
||||
class_max_i = 1;
|
||||
ELSE
|
||||
arr_gvf = array_append(arr_gvf, tmp_val);
|
||||
END IF;
|
||||
i := i+1;
|
||||
IF i > breaks THEN EXIT; END IF;
|
||||
END LOOP;
|
||||
-- calculate our new GVF
|
||||
SELECT sdam-sum(e) INTO new_gvf FROM ( SELECT unnest(arr_gvf) as e ) x;
|
||||
-- if no improvement was made, exit
|
||||
IF new_gvf < gvf THEN EXIT; END IF;
|
||||
gvf = new_gvf;
|
||||
IF j > max_search THEN EXIT; END IF;
|
||||
j = j+1;
|
||||
i = 1;
|
||||
LOOP
|
||||
--establish directionality (uppward through classes or downward)
|
||||
IF arr_gvf[i] < class_min THEN
|
||||
class_min = arr_gvf[i];
|
||||
class_min_i = i;
|
||||
END IF;
|
||||
IF arr_gvf[i] > class_max THEN
|
||||
class_max = arr_gvf[i];
|
||||
class_max_i = i;
|
||||
END IF;
|
||||
i := i+1;
|
||||
IF i > breaks THEN EXIT; END IF;
|
||||
END LOOP;
|
||||
IF class_max_i > class_min_i THEN
|
||||
class_min_i = class_max_i - 1;
|
||||
gvf numeric := 0.0;
|
||||
new_gvf numeric;
|
||||
arr_gvf numeric[];
|
||||
arr_avg numeric[];
|
||||
class_avg numeric;
|
||||
class_dev numeric;
|
||||
|
||||
class_max_i INT = 0;
|
||||
class_min_i INT = 0;
|
||||
dev_max numeric;
|
||||
dev_min numeric;
|
||||
|
||||
best_classes INT[] = classes;
|
||||
best_gvf numeric[];
|
||||
best_avg numeric[];
|
||||
move_elements INT = 1;
|
||||
|
||||
reply numeric[];
|
||||
|
||||
BEGIN
|
||||
|
||||
-- We fill the arrays with the initial values
|
||||
i = 0;
|
||||
LOOP
|
||||
IF i = breaks THEN EXIT; END IF;
|
||||
i = i + 1;
|
||||
|
||||
-- Get class mean
|
||||
SELECT (sum(v * w) / sum(w)) INTO class_avg FROM (
|
||||
SELECT unnest(in_matrix[1:1][classes[i][1]:classes[i][2]]) as v,
|
||||
unnest(in_matrix[2:2][classes[i][1]:classes[i][2]]) as w
|
||||
) x;
|
||||
|
||||
-- Get class deviation
|
||||
SELECT sum((class_avg - v)^2 * w) INTO class_dev FROM (
|
||||
SELECT unnest(in_matrix[1:1][classes[i][1]:classes[i][2]]) as v,
|
||||
unnest(in_matrix[2:2][classes[i][1]:classes[i][2]]) as w
|
||||
) x;
|
||||
|
||||
|
||||
IF i = 1 THEN
|
||||
arr_avg = ARRAY[class_avg];
|
||||
arr_gvf = ARRAY[class_dev];
|
||||
ELSE
|
||||
class_min_i = class_max_i + 1;
|
||||
arr_avg = array_append(arr_avg, class_avg);
|
||||
arr_gvf = array_append(arr_gvf, class_dev);
|
||||
END IF;
|
||||
--Move from higher class to a lower gid order
|
||||
IF class_max_i > class_min_i THEN
|
||||
classes[class_max_i][1] = classes[class_max_i][1] + 1;
|
||||
classes[class_min_i][2] = classes[class_min_i][2] + 1;
|
||||
ELSE -- Move from lower class UP into a higher class by gid
|
||||
classes[class_max_i][2] = classes[class_max_i][2] - 1;
|
||||
classes[class_min_i][1] = classes[class_min_i][1] - 1;
|
||||
END LOOP;
|
||||
|
||||
-- We copy the values to avoid recalculation when a failure happens
|
||||
best_avg = arr_avg;
|
||||
best_gvf = arr_gvf;
|
||||
|
||||
iterations = 0;
|
||||
LOOP
|
||||
IF iterations = max_search THEN EXIT; END IF;
|
||||
iterations = iterations + 1;
|
||||
|
||||
-- calculate our new GVF
|
||||
SELECT sdam - sum(e) INTO new_gvf FROM ( SELECT unnest(arr_gvf) as e ) x;
|
||||
|
||||
-- Check if any improvement was made
|
||||
IF new_gvf <= gvf THEN
|
||||
-- If we were moving too many elements, go back and move less
|
||||
IF move_elements <= 2 OR class_max_i = class_min_i THEN
|
||||
EXIT;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
move_elements = GREATEST(move_elements / 8, 1);
|
||||
|
||||
-- Rollback from saved statuses
|
||||
classes = best_classes;
|
||||
new_gvf = gvf;
|
||||
|
||||
i = class_min_i;
|
||||
LOOP
|
||||
arr_avg[i] = best_avg[i];
|
||||
arr_gvf[i] = best_gvf[i];
|
||||
|
||||
IF i = class_max_i THEN EXIT; END IF;
|
||||
i = i + 1;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- We search for the classes with the min and max deviation
|
||||
i = 1;
|
||||
class_min_i = 1;
|
||||
class_max_i = 1;
|
||||
dev_max = arr_gvf[1];
|
||||
dev_min = arr_gvf[1];
|
||||
LOOP
|
||||
IF i = breaks THEN EXIT; END IF;
|
||||
i = i + 1;
|
||||
|
||||
IF arr_gvf[i] < dev_min THEN
|
||||
dev_min = arr_gvf[i];
|
||||
class_min_i = i;
|
||||
ELSE
|
||||
IF arr_gvf[i] > dev_max THEN
|
||||
dev_max = arr_gvf[i];
|
||||
class_max_i = i;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
|
||||
-- Save best values for comparison and output
|
||||
gvf = new_gvf;
|
||||
best_classes = classes;
|
||||
|
||||
-- Limit the moved elements as to not remove everything from class_max_i
|
||||
move_elements = LEAST(move_elements, classes[class_max_i][2] - classes[class_max_i][1]);
|
||||
|
||||
-- Move `move_elements` from class_max_i to class_min_i
|
||||
IF class_min_i < class_max_i THEN
|
||||
i := class_min_i;
|
||||
LOOP
|
||||
IF i = class_max_i THEN EXIT; END IF;
|
||||
classes[i][2] = classes[i][2] + move_elements;
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
|
||||
i := class_max_i;
|
||||
LOOP
|
||||
IF i = class_min_i THEN EXIT; END IF;
|
||||
classes[i][1] = classes[i][1] + move_elements;
|
||||
i := i - 1;
|
||||
END LOOP;
|
||||
ELSE
|
||||
i := class_min_i;
|
||||
LOOP
|
||||
IF i = class_max_i THEN EXIT; END IF;
|
||||
classes[i][1] = classes[i][1] - move_elements;
|
||||
i := i - 1;
|
||||
END LOOP;
|
||||
|
||||
i := class_max_i;
|
||||
LOOP
|
||||
IF i = class_min_i THEN EXIT; END IF;
|
||||
classes[i][2] = classes[i][2] - move_elements;
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
END IF;
|
||||
|
||||
-- Recalculate avg and deviation ONLY for the affected classes
|
||||
i = LEAST(class_min_i, class_max_i);
|
||||
class_max_i = GREATEST(class_min_i, class_max_i);
|
||||
class_min_i = i;
|
||||
LOOP
|
||||
SELECT (sum(v * w) / sum(w)) INTO class_avg FROM (
|
||||
SELECT unnest(in_matrix[1:1][classes[i][1]:classes[i][2]]) as v,
|
||||
unnest(in_matrix[2:2][classes[i][1]:classes[i][2]]) as w
|
||||
) x;
|
||||
|
||||
SELECT sum((class_avg - v)^2 * w) INTO class_dev FROM (
|
||||
SELECT unnest(in_matrix[1:1][classes[i][1]:classes[i][2]]) as v,
|
||||
unnest(in_matrix[2:2][classes[i][1]:classes[i][2]]) as w
|
||||
) x;
|
||||
|
||||
-- Save status (in case it's needed for rollback) and store the new one
|
||||
best_avg[i] = arr_avg[i];
|
||||
arr_avg[i] = class_avg;
|
||||
|
||||
best_gvf[i] = arr_gvf[i];
|
||||
arr_gvf[i] = class_dev;
|
||||
|
||||
IF i = class_max_i THEN EXIT; END IF;
|
||||
i = i + 1;
|
||||
END LOOP;
|
||||
|
||||
move_elements = move_elements * 2;
|
||||
|
||||
END LOOP;
|
||||
|
||||
i = 1;
|
||||
LOOP
|
||||
LOOP
|
||||
IF invert = TRUE THEN
|
||||
side = 1; --default returns bottom side of breaks, invert returns top side
|
||||
END IF;
|
||||
reply = array_append(reply, in_array[classes[i][side]]);
|
||||
i = i+1;
|
||||
IF i > breaks THEN EXIT; END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN array_prepend(gvf, reply);
|
||||
reply = array_append(reply, unnest(in_matrix[1:1][best_classes[i][side]:best_classes[i][side]]));
|
||||
i = i+1;
|
||||
IF i > breaks THEN EXIT; END IF;
|
||||
END LOOP;
|
||||
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
reply = array_prepend(gvf, reply);
|
||||
RETURN reply;
|
||||
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
@ -7,13 +7,11 @@
|
||||
--
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_LatLng (lat NUMERIC, lng NUMERIC) RETURNS geometry as $$
|
||||
-- this function is silly
|
||||
SELECT ST_SetSRID(ST_MakePoint(lng,lat),4326);
|
||||
$$ language SQL IMMUTABLE;
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_LatLng (lat NUMERIC, lng NUMERIC) RETURNS @postgisschema@.geometry as $$
|
||||
SELECT @postgisschema@.ST_SetSRID(@postgisschema@.ST_MakePoint(lng,lat), 4326);
|
||||
$$ language SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_LatLng (lat FLOAT8, lng FLOAT8) RETURNS geometry as $$
|
||||
-- this function is silly
|
||||
SELECT ST_SetSRID(ST_MakePoint(lng,lat),4326);
|
||||
$$ language SQL IMMUTABLE;
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_LatLng (lat FLOAT8, lng FLOAT8) RETURNS @postgisschema@.geometry as $$
|
||||
SELECT @postgisschema@.ST_SetSRID(@postgisschema@.ST_MakePoint(lng,lat), 4326);
|
||||
$$ language SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
-- Mode
|
||||
-- https://wiki.postgresql.org/wiki/Aggregate_Mode
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Math_final_mode(anyarray)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Math_final_mode(anyarray)
|
||||
RETURNS anyelement AS
|
||||
$BODY$
|
||||
SELECT a
|
||||
@ -13,14 +13,15 @@ $BODY$
|
||||
ORDER BY COUNT(1) DESC, 1
|
||||
LIMIT 1;
|
||||
$BODY$
|
||||
LANGUAGE 'sql' IMMUTABLE;
|
||||
LANGUAGE 'sql' IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
DROP AGGREGATE IF EXISTS cartodb.CDB_Math_Mode(anyelement);
|
||||
DROP AGGREGATE IF EXISTS @extschema@.CDB_Math_Mode(anyelement);
|
||||
|
||||
CREATE AGGREGATE cartodb.CDB_Math_Mode(anyelement) (
|
||||
CREATE AGGREGATE @extschema@.CDB_Math_Mode(anyelement) (
|
||||
SFUNC=array_append,
|
||||
STYPE=anyarray,
|
||||
FINALFUNC=_CDB_Math_final_mode,
|
||||
FINALFUNC=@extschema@._CDB_Math_final_mode,
|
||||
PARALLEL = SAFE,
|
||||
INITCOND='{}'
|
||||
);
|
||||
|
||||
|
65
scripts-available/CDB_OAuth.sql
Normal file
65
scripts-available/CDB_OAuth.sql
Normal file
@ -0,0 +1,65 @@
|
||||
-- Function that reassign the owner of a table to their ownership_role
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_OAuthReassignTableOwnerOnCreation()
|
||||
RETURNS event_trigger
|
||||
AS $$
|
||||
DECLARE
|
||||
obj record;
|
||||
owner_role text;
|
||||
creator_role text;
|
||||
BEGIN
|
||||
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
|
||||
LOOP
|
||||
RAISE DEBUG '% ddl object: % % % %',
|
||||
tg_tag,
|
||||
obj.command_tag,
|
||||
obj.object_type,
|
||||
obj.schema_name,
|
||||
obj.object_identity;
|
||||
IF obj.object_type = 'function' THEN
|
||||
SELECT rolname FROM pg_proc JOIN pg_roles ON proowner = pg_roles.oid WHERE pg_proc.oid = obj.objid INTO creator_role;
|
||||
ELSE
|
||||
SELECT rolname FROM pg_class JOIN pg_roles ON relowner = pg_roles.oid WHERE pg_class.oid = obj.objid INTO creator_role;
|
||||
END IF;
|
||||
SELECT value->>'ownership_role_name' from @extschema@.CDB_Conf_GetConf('api_keys_' || quote_ident(creator_role)) value INTO owner_role;
|
||||
IF owner_role IS NULL OR owner_role = '' THEN
|
||||
RAISE DEBUG 'owner_role not found';
|
||||
CONTINUE;
|
||||
ELSE
|
||||
EXECUTE 'ALTER ' || obj.object_type || ' ' || obj.object_identity || ' OWNER TO ' || quote_ident(owner_role);
|
||||
IF obj.object_type = 'function' THEN
|
||||
EXECUTE 'GRANT ALL ON FUNCTION ' || obj.object_identity || ' TO ' || QUOTE_IDENT(creator_role);
|
||||
ELSE
|
||||
EXECUTE 'GRANT ALL ON ' || obj.object_identity || ' TO ' || QUOTE_IDENT(creator_role);
|
||||
END IF;
|
||||
RAISE DEBUG 'Changing ownership from % to %', creator_role, owner_role;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
-- Creates the trigger on DDL events in order to reassign the owner
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_EnableOAuthReassignTablesTrigger()
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
DROP EVENT TRIGGER IF EXISTS oauth_reassign_tables_trigger;
|
||||
|
||||
CREATE EVENT TRIGGER oauth_reassign_tables_trigger
|
||||
ON ddl_command_end
|
||||
WHEN TAG IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO', 'CREATE VIEW', 'CREATE FOREIGN TABLE', 'CREATE MATERIALIZED VIEW', 'CREATE SEQUENCE', 'CREATE FUNCTION')
|
||||
EXECUTE PROCEDURE @extschema@.CDB_OAuthReassignTableOwnerOnCreation();
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Deletes the trigger on DDL events in order to reassign the owner
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DisableOAuthReassignTablesTrigger()
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
DROP EVENT TRIGGER IF EXISTS oauth_reassign_tables_trigger;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
@ -1,16 +1,23 @@
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Member_Group_Role_Member_Name()
|
||||
FUNCTION @extschema@.CDB_Organization_Member_Group_Role_Member_Name()
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
SELECT 'cdb_org_member'::text || '_' || md5(current_database());
|
||||
$$
|
||||
LANGUAGE SQL IMMUTABLE;
|
||||
LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
|
||||
----- ########################## WARNING ##########################
|
||||
----- The code below creates a new role for the organization but
|
||||
----- only when the extension is INSTALLED in a database, i.e. it
|
||||
----- won't work if you clone a database that has it installed.
|
||||
----- If you do, you need to update the extension to next and back
|
||||
----- ########################## WARNING ##########################
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
DECLARE
|
||||
cdb_org_member_role_name TEXT;
|
||||
BEGIN
|
||||
cdb_org_member_role_name := cartodb.CDB_Organization_Member_Group_Role_Member_Name();
|
||||
cdb_org_member_role_name := @extschema@.CDB_Organization_Member_Group_Role_Member_Name();
|
||||
IF NOT EXISTS ( SELECT * FROM pg_roles WHERE rolname= cdb_org_member_role_name )
|
||||
THEN
|
||||
EXECUTE 'CREATE ROLE "' || cdb_org_member_role_name || '" NOLOGIN;';
|
||||
@ -19,31 +26,37 @@ END
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Create_Member(role_name text)
|
||||
FUNCTION @extschema@.CDB_Organization_Create_Member(role_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
EXECUTE 'GRANT "' || cartodb.CDB_Organization_Member_Group_Role_Member_Name() || '" TO "' || role_name || '"';
|
||||
EXECUTE 'GRANT "' || @extschema@.CDB_Organization_Member_Group_Role_Member_Name() || '" TO "' || role_name || '"';
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Administrator
|
||||
-------------------------------------------------------------------------------
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb._CDB_Organization_Admin_Role_Name()
|
||||
FUNCTION @extschema@._CDB_Organization_Admin_Role_Name()
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
SELECT current_database() || '_a'::text;
|
||||
$$
|
||||
LANGUAGE SQL IMMUTABLE;
|
||||
LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
----- ########################## WARNING ##########################
|
||||
----- The code below creates a new role for the organization but
|
||||
----- only when the extension is INSTALLED in a database, i.e. it
|
||||
----- won't work if you clone a database that has it installed.
|
||||
----- If you do, you need to update the extension to next and back
|
||||
----- ########################## WARNING ##########################
|
||||
-- Administrator role creation on extension install
|
||||
DO LANGUAGE 'plpgsql' $$
|
||||
DECLARE
|
||||
cdb_org_admin_role_name TEXT;
|
||||
BEGIN
|
||||
cdb_org_admin_role_name := cartodb._CDB_Organization_Admin_Role_Name();
|
||||
cdb_org_admin_role_name := @extschema@._CDB_Organization_Admin_Role_Name();
|
||||
IF NOT EXISTS ( SELECT * FROM pg_roles WHERE rolname= cdb_org_admin_role_name )
|
||||
THEN
|
||||
EXECUTE format('CREATE ROLE %I CREATEROLE NOLOGIN;', cdb_org_admin_role_name);
|
||||
@ -52,80 +65,105 @@ END
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_AddAdmin(username text)
|
||||
FUNCTION @extschema@.CDB_Organization_AddAdmin(username text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
cdb_user_role TEXT;
|
||||
cdb_admin_role TEXT;
|
||||
BEGIN
|
||||
cdb_admin_role := cartodb._CDB_Organization_Admin_Role_Name();
|
||||
cdb_user_role := cartodb._CDB_User_RoleFromUsername(username);
|
||||
cdb_admin_role := @extschema@._CDB_Organization_Admin_Role_Name();
|
||||
cdb_user_role := @extschema@._CDB_User_RoleFromUsername(username);
|
||||
EXECUTE format('GRANT %I TO %I WITH ADMIN OPTION', cdb_admin_role, cdb_user_role);
|
||||
-- CREATEROLE is not inherited, and is needed for user creation
|
||||
EXECUTE format('ALTER ROLE %I CREATEROLE', cdb_user_role);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_RemoveAdmin(username text)
|
||||
FUNCTION @extschema@.CDB_Organization_RemoveAdmin(username text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
cdb_user_role TEXT;
|
||||
cdb_admin_role TEXT;
|
||||
BEGIN
|
||||
cdb_admin_role := cartodb._CDB_Organization_Admin_Role_Name();
|
||||
cdb_user_role := cartodb._CDB_User_RoleFromUsername(username);
|
||||
cdb_admin_role := @extschema@._CDB_Organization_Admin_Role_Name();
|
||||
cdb_user_role := @extschema@._CDB_User_RoleFromUsername(username);
|
||||
EXECUTE format('ALTER ROLE %I NOCREATEROLE', cdb_user_role);
|
||||
EXECUTE format('REVOKE %I FROM %I', cdb_admin_role, cdb_user_role);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Sharing tables
|
||||
-------------------------------------------------------------------------------
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Add_Table_Read_Permission(from_schema text, table_name text, to_role_name text)
|
||||
FUNCTION @extschema@.CDB_Organization_Add_Table_Read_Permission(from_schema text, table_name text, to_role_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
EXECUTE 'GRANT USAGE ON SCHEMA "' || from_schema || '" TO "' || to_role_name || '"';
|
||||
EXECUTE 'GRANT SELECT ON "' || from_schema || '"."' || table_name || '" TO "' || to_role_name || '"';
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Add_Table_Organization_Read_Permission(from_schema text, table_name text)
|
||||
FUNCTION @extschema@.CDB_Organization_Add_Table_Organization_Read_Permission(from_schema text, table_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
EXECUTE 'SELECT cartodb.CDB_Organization_Add_Table_Read_Permission(''' || from_schema || ''', ''' || table_name || ''', ''' || cartodb.CDB_Organization_Member_Group_Role_Member_Name() || ''');';
|
||||
EXECUTE 'SELECT @extschema@.CDB_Organization_Add_Table_Read_Permission(''' || from_schema || ''', ''' || table_name || ''', ''' || @extschema@.CDB_Organization_Member_Group_Role_Member_Name() || ''');';
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Add_Table_Read_Write_Permission(from_schema text, table_name text, to_role_name text)
|
||||
FUNCTION @extschema@._CDB_Organization_Get_Table_Sequences(from_schema text, table_name text)
|
||||
RETURNS SETOF TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY EXECUTE 'SELECT
|
||||
quote_ident(n.nspname) || ''.'' || quote_ident(c.relname)
|
||||
FROM
|
||||
pg_depend d
|
||||
JOIN pg_class c ON d.objid = c.oid
|
||||
JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE
|
||||
d.refobjsubid > 0 AND
|
||||
d.classid = ''pg_class''::regclass AND
|
||||
c.relkind = ''S''::"char" AND
|
||||
d.refobjid = (''' || quote_ident(from_schema) || '.' || quote_ident(table_name) ||''')::regclass';
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION @extschema@.CDB_Organization_Add_Table_Read_Write_Permission(from_schema text, table_name text, to_role_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
sequence_name TEXT;
|
||||
BEGIN
|
||||
EXECUTE 'GRANT USAGE ON SCHEMA "' || from_schema || '" TO "' || to_role_name || '"';
|
||||
EXECUTE 'GRANT SELECT, INSERT, UPDATE, DELETE ON "' || from_schema || '"."' || table_name || '" TO "' || to_role_name || '"';
|
||||
|
||||
FOR sequence_name IN SELECT * FROM @extschema@._CDB_Organization_Get_Table_Sequences(from_schema, table_name) LOOP
|
||||
EXECUTE 'GRANT USAGE, SELECT ON SEQUENCE ' || sequence_name || ' TO "' || to_role_name || '"';
|
||||
END LOOP;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Add_Table_Organization_Read_Write_Permission(from_schema text, table_name text)
|
||||
FUNCTION @extschema@.CDB_Organization_Add_Table_Organization_Read_Write_Permission(from_schema text, table_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
EXECUTE 'SELECT cartodb.CDB_Organization_Add_Table_Read_Write_Permission(''' || from_schema || ''', ''' || table_name || ''', ''' || cartodb.CDB_Organization_Member_Group_Role_Member_Name() || ''');';
|
||||
EXECUTE 'SELECT @extschema@.CDB_Organization_Add_Table_Read_Write_Permission(''' || from_schema || ''', ''' || table_name || ''', ''' || @extschema@.CDB_Organization_Member_Group_Role_Member_Name() || ''');';
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Remove_Access_Permission(from_schema text, table_name text, to_role_name text)
|
||||
FUNCTION @extschema@.CDB_Organization_Remove_Access_Permission(from_schema text, table_name text, to_role_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
@ -134,13 +172,20 @@ BEGIN
|
||||
-- We need to revoke usage on schema only if we are revoking privileges from the last table where to_role_name has
|
||||
-- any permission granted within the schema from_schema
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
CREATE OR REPLACE
|
||||
FUNCTION cartodb.CDB_Organization_Remove_Organization_Access_Permission(from_schema text, table_name text)
|
||||
FUNCTION @extschema@.CDB_Organization_Remove_Organization_Access_Permission(from_schema text, table_name text)
|
||||
RETURNS void
|
||||
AS $$
|
||||
BEGIN
|
||||
EXECUTE 'SELECT cartodb.CDB_Organization_Remove_Access_Permission(''' || from_schema || ''', ''' || table_name || ''', ''' || cartodb.CDB_Organization_Member_Group_Role_Member_Name() || ''');';
|
||||
EXECUTE 'SELECT @extschema@.CDB_Organization_Remove_Access_Permission(''' || from_schema || ''', ''' || table_name || ''', ''' || @extschema@.CDB_Organization_Member_Group_Role_Member_Name() || ''');';
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Deprecated
|
||||
--------------------------------------------------------------------------------
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_Organization_Grant_Role(name);
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_Organization_Revoke_Role(name);
|
||||
|
@ -1,154 +1,8 @@
|
||||
-- Information about tables in a schema.
|
||||
-- If the schema name parameter is NULL, then tables from all schemas
|
||||
-- that may contain user tables are returned.
|
||||
-- For each table, the regclass, schema name and table name are returned.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_UserTablesInSchema(schema_name text DEFAULT NULL)
|
||||
RETURNS TABLE(table_regclass REGCLASS, schema_name TEXT, table_name TEXT)
|
||||
AS $$
|
||||
SELECT
|
||||
c.oid::regclass AS table_regclass,
|
||||
n.nspname::text AS schema_name,
|
||||
c.relname::text AS table_relname
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind = 'r'
|
||||
AND c.relname NOT IN ('cdb_tablemetadata', 'spatial_ref_sys')
|
||||
AND CASE WHEN schema_name IS NULL
|
||||
THEN n.nspname NOT IN ('pg_catalog', 'information_schema', 'topology', 'cartodb')
|
||||
ELSE n.nspname = schema_name
|
||||
END;
|
||||
$$ LANGUAGE 'sql';
|
||||
|
||||
-- Pattern that can be used to detect overview tables and Extract
|
||||
-- the intended zoom level from the table name.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTableDiscriminator()
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN '\A_vovw_(\d+)_';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
-- substring(tablename from _CDB_OverviewTableDiscriminator())
|
||||
|
||||
|
||||
-- Pattern matched by the overview tables of a given base table name.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTablePattern(base_table TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN _CDB_OverviewTableDiscriminator() || base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
-- tablename SIMILAR TO _CDB_OverviewTablePattern(base_table)
|
||||
|
||||
-- Name of an overview table, given the base table name and the Z level
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTableName(base_table TEXT, z INTEGER)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN '_vovw_' || z::text || '_' || base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Condition to check if a tabla is an overview table of some base table
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_IsOverviewTableOf(base_table TEXT, otable TEXT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN otable SIMILAR TO _CDB_OverviewTablePattern(base_table);
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Extract the Z level from an overview table name
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTableZ(otable TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN substring(otable from _CDB_OverviewTableDiscriminator())::integer;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Name of the base table corresponding to an overview table
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewBaseTableName(overview_table TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
IF _CDB_OverviewTableZ(overview_table) IS NULL THEN
|
||||
RETURN overview_table;
|
||||
ELSE
|
||||
RETURN regexp_replace(overview_table, _CDB_OverviewTableDiscriminator(), '');
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewBaseTable(overview_table REGCLASS)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name TEXT;
|
||||
schema_name TEXT;
|
||||
base_name TEXT;
|
||||
base_table REGCLASS;
|
||||
BEGIN
|
||||
SELECT * FROM _cdb_split_table_name(overview_table) INTO schema_name, table_name;
|
||||
base_name := _CDB_OverviewBaseTableName(table_name);
|
||||
IF base_name != table_name THEN
|
||||
base_table := Format('%I.%I', schema_name, base_name)::regclass;
|
||||
ELSE
|
||||
base_table := overview_table;
|
||||
END IF;
|
||||
RETURN base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Schema and relation names of a table given its reloid
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the table.
|
||||
-- Return (schema_name, table_name)
|
||||
-- note that returned names will be quoted if necessary
|
||||
CREATE OR REPLACE FUNCTION _cdb_split_table_name(reloid REGCLASS, OUT schema_name TEXT, OUT table_name TEXT)
|
||||
AS $$
|
||||
BEGIN
|
||||
SELECT n.nspname, c.relname
|
||||
INTO STRICT schema_name, table_name
|
||||
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.oid = reloid;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Schema and relation names of a table given its reloid
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the table.
|
||||
-- Return (schema_name, table_name)
|
||||
-- note that returned names will be quoted if necessary
|
||||
CREATE OR REPLACE FUNCTION _cdb_schema_name(reloid REGCLASS)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
schema_name TEXT;
|
||||
BEGIN
|
||||
SELECT n.nspname
|
||||
INTO STRICT schema_name
|
||||
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.oid = reloid;
|
||||
RETURN schema_name;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Remove a dataset's existing overview tables.
|
||||
-- Scope: public
|
||||
-- Parameters:
|
||||
-- reloid: oid of the table.
|
||||
CREATE OR REPLACE FUNCTION CDB_DropOverviews(reloid REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_DropOverviews(reloid REGCLASS)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -156,15 +10,15 @@ DECLARE
|
||||
schema_name TEXT;
|
||||
table_name TEXT;
|
||||
BEGIN
|
||||
SELECT * FROM _cdb_split_table_name(reloid) INTO schema_name, table_name;
|
||||
SELECT * FROM @extschema@._cdb_split_table_name(reloid) INTO schema_name, table_name;
|
||||
FOR row IN
|
||||
SELECT * FROM CDB_Overviews(reloid)
|
||||
SELECT * FROM @extschema@.CDB_Overviews(reloid)
|
||||
LOOP
|
||||
EXECUTE Format('DROP TABLE %s;', row.overview_table);
|
||||
RAISE NOTICE 'Dropped overview for level %: %', row.z, row.overview_table;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
|
||||
@ -175,23 +29,23 @@ $$ LANGUAGE PLPGSQL VOLATILE;
|
||||
-- Return relation of overviews for the table with
|
||||
-- the base table oid,
|
||||
-- z level of the overview and overview table oid, ordered by z.
|
||||
CREATE OR REPLACE FUNCTION CDB_Overviews(reloid REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Overviews(reloid REGCLASS)
|
||||
RETURNS TABLE(base_table REGCLASS, z integer, overview_table REGCLASS)
|
||||
AS $$
|
||||
DECLARE
|
||||
schema_name TEXT;
|
||||
base_table_name TEXT;
|
||||
BEGIN
|
||||
SELECT * FROM _cdb_split_table_name(reloid) INTO schema_name, base_table_name;
|
||||
SELECT * FROM @extschema@._cdb_split_table_name(reloid) INTO schema_name, base_table_name;
|
||||
RETURN QUERY SELECT
|
||||
reloid AS base_table,
|
||||
_CDB_OverviewTableZ(table_name) AS z,
|
||||
@extschema@._CDB_OverviewTableZ(table_name) AS z,
|
||||
table_regclass AS overview_table
|
||||
FROM _CDB_UserTablesInSchema(schema_name)
|
||||
WHERE _CDB_IsOverviewTableOf((SELECT relname FROM pg_class WHERE oid=reloid), table_name)
|
||||
FROM @extschema@._CDB_UserTablesInSchema(schema_name)
|
||||
WHERE @extschema@._CDB_IsOverviewTableOf((SELECT relname FROM pg_class WHERE oid=reloid), table_name)
|
||||
ORDER BY z;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL STABLE PARALLEL RESTRICTED;
|
||||
|
||||
-- Return existing overviews (if any) for multiple dataset tables.
|
||||
-- Scope: public
|
||||
@ -202,31 +56,31 @@ $$ LANGUAGE PLPGSQL;
|
||||
-- z level of the overview and overview table oid, ordered by z.
|
||||
-- Note: CDB_Overviews can be applied to the result of CDB_QueryTablesText
|
||||
-- to obtain the overviews applicable to a query.
|
||||
CREATE OR REPLACE FUNCTION CDB_Overviews(tables regclass[])
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Overviews(tables regclass[])
|
||||
RETURNS TABLE(base_table REGCLASS, z integer, overview_table REGCLASS)
|
||||
AS $$
|
||||
SELECT
|
||||
base_table::regclass AS base_table,
|
||||
_CDB_OverviewTableZ(table_name) AS z,
|
||||
@extschema@._CDB_OverviewTableZ(table_name) AS z,
|
||||
table_regclass AS overview_table
|
||||
FROM
|
||||
_CDB_UserTablesInSchema(), unnest(tables) base_table
|
||||
@extschema@._CDB_UserTablesInSchema(), unnest(tables) base_table
|
||||
WHERE
|
||||
schema_name = _cdb_schema_name(base_table)
|
||||
AND _CDB_IsOverviewTableOf((SELECT relname FROM pg_class WHERE oid=base_table), table_name)
|
||||
schema_name = @extschema@._cdb_schema_name(base_table)
|
||||
AND @extschema@._CDB_IsOverviewTableOf((SELECT relname FROM pg_class WHERE oid=base_table), table_name)
|
||||
ORDER BY base_table, z;
|
||||
$$ LANGUAGE SQL;
|
||||
$$ LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- Calculate the estimated extent of a cartodbfy'ed table.
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table.
|
||||
-- Return value A box2d extent in 3857.
|
||||
CREATE OR REPLACE FUNCTION _cdb_estimated_extent(reloid REGCLASS)
|
||||
RETURNS box2d
|
||||
CREATE OR REPLACE FUNCTION @extschema@._cdb_estimated_extent(reloid REGCLASS)
|
||||
RETURNS @postgisschema@.box2d
|
||||
AS $$
|
||||
DECLARE
|
||||
ext box2d;
|
||||
ext @postgisschema@.box2d;
|
||||
ext_query text;
|
||||
table_id record;
|
||||
BEGIN
|
||||
@ -235,29 +89,26 @@ AS $$
|
||||
FROM pg_class c JOIN pg_namespace n on n.oid = c.relnamespace WHERE c.oid = reloid::oid;
|
||||
|
||||
ext_query = format(
|
||||
'SELECT ST_EstimatedExtent(''%1$s'', ''%2$s'', ''%3$s'');',
|
||||
'SELECT @postgisschema@.ST_EstimatedExtent(''%1$s'', ''%2$s'', ''%3$s'');',
|
||||
table_id.schema_name, table_id.table_name, 'the_geom_webmercator'
|
||||
);
|
||||
|
||||
BEGIN
|
||||
EXECUTE ext_query INTO ext;
|
||||
EXCEPTION
|
||||
-- This is the typical ERROR: stats for "mytable" do not exist
|
||||
WHEN internal_error THEN
|
||||
EXECUTE ext_query INTO ext;
|
||||
IF ext IS NULL THEN
|
||||
-- Get stats and execute again
|
||||
EXECUTE format('ANALYZE %1$s', reloid);
|
||||
|
||||
-- We check the geometry type in case the error is due to empty geometries
|
||||
IF _CDB_GeometryTypes(reloid) IS NULL THEN
|
||||
IF @extschema@._CDB_GeometryTypes(reloid) IS NULL THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
EXECUTE ext_query INTO ext;
|
||||
END;
|
||||
END IF;
|
||||
|
||||
RETURN ext;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Determine the max feature density of a given dataset.
|
||||
-- Scope: private.
|
||||
@ -265,7 +116,7 @@ $$ LANGUAGE PLPGSQL VOLATILE;
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- nz: number of zoom levels to consider from z0 upward.
|
||||
-- Return value: feature density (num_features / webmercator_squared_meters).
|
||||
CREATE OR REPLACE FUNCTION _CDB_Feature_Density(reloid REGCLASS, nz integer)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Feature_Density(reloid REGCLASS, nz integer)
|
||||
RETURNS FLOAT8
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -284,7 +135,7 @@ AS $$
|
||||
-- the area of tiles at level Z: c*c*power(2, -2*z)
|
||||
-- with c = CDB_XYZ_Resolution(-8) (earth circumference)
|
||||
min_features = '500';
|
||||
SELECT CDB_XYZ_Resolution(-8) INTO c;
|
||||
SELECT @extschema@.CDB_XYZ_Resolution(-8) INTO c;
|
||||
|
||||
-- We first compute a set of *seed* tiles, of the minimum Z level, z0, such that
|
||||
-- they cover the extent of the table and we have at least n of them in each
|
||||
@ -295,23 +146,27 @@ AS $$
|
||||
-- considered tiles.
|
||||
EXECUTE Format('
|
||||
WITH RECURSIVE t(x, y, z, e) AS (
|
||||
WITH ext AS (SELECT _cdb_estimated_extent(%6$s) as g),
|
||||
WITH ext AS (SELECT @extschema@._cdb_estimated_extent(%6$s) as g),
|
||||
base AS (
|
||||
SELECT (-floor(log(2, (greatest(ST_XMax(ext.g)-ST_XMin(ext.g), ST_YMax(ext.g)-ST_YMin(ext.g))/(%4$s*%5$s))::numeric)))::integer z
|
||||
SELECT
|
||||
least(
|
||||
-floor(log(2, (greatest(@postgisschema@.ST_XMax(ext.g)-@postgisschema@.ST_XMin(ext.g), @postgisschema@.ST_YMax(ext.g)-@postgisschema@.ST_YMin(ext.g))/(%4$s*%5$s))::numeric)),
|
||||
@extschema@._CDB_MaxOverviewLevel()+1
|
||||
)::integer z
|
||||
FROM ext
|
||||
),
|
||||
lim AS (
|
||||
SELECT
|
||||
FLOOR((ST_XMin(ext.g)+CDB_XYZ_Resolution(0)*128)/(CDB_XYZ_Resolution(base.z)*256))::integer x0,
|
||||
FLOOR((ST_XMax(ext.g)+CDB_XYZ_Resolution(0)*128)/(CDB_XYZ_Resolution(base.z)*256))::integer x1,
|
||||
FLOOR((CDB_XYZ_Resolution(0)*128-ST_YMin(ext.g))/(CDB_XYZ_Resolution(base.z)*256))::integer y1,
|
||||
FLOOR((CDB_XYZ_Resolution(0)*128-ST_YMax(ext.g))/(CDB_XYZ_Resolution(base.z)*256))::integer y0
|
||||
FLOOR((@postgisschema@.ST_XMin(ext.g)+@extschema@.CDB_XYZ_Resolution(0)*128)/(@extschema@.CDB_XYZ_Resolution(base.z)*256))::integer x0,
|
||||
FLOOR((@postgisschema@.ST_XMax(ext.g)+@extschema@.CDB_XYZ_Resolution(0)*128)/(@extschema@.CDB_XYZ_Resolution(base.z)*256))::integer x1,
|
||||
FLOOR((@extschema@.CDB_XYZ_Resolution(0)*128-@postgisschema@.ST_YMin(ext.g))/(@extschema@.CDB_XYZ_Resolution(base.z)*256))::integer y1,
|
||||
FLOOR((@extschema@.CDB_XYZ_Resolution(0)*128-@postgisschema@.ST_YMax(ext.g))/(@extschema@.CDB_XYZ_Resolution(base.z)*256))::integer y0
|
||||
FROM ext, base
|
||||
),
|
||||
seed AS (
|
||||
SELECT xt, yt, base.z, (
|
||||
SELECT count(*) FROM %1$s
|
||||
WHERE the_geom_webmercator && CDB_XYZ_Extent(xt, yt, base.z)
|
||||
WHERE the_geom_webmercator && @extschema@.CDB_XYZ_Extent(xt, yt, base.z)
|
||||
) e
|
||||
FROM base, lim, generate_series(lim.x0, lim.x1) xt, generate_series(lim.y0, lim.y1) yt
|
||||
)
|
||||
@ -319,17 +174,17 @@ AS $$
|
||||
UNION ALL
|
||||
SELECT x*2 + xx, y*2 + yy, t.z+1, (
|
||||
SELECT count(*) FROM %1$s
|
||||
WHERE the_geom_webmercator && CDB_XYZ_Extent(x*2 + xx, y*2 + yy, t.z+1)
|
||||
WHERE the_geom_webmercator && @extschema@.CDB_XYZ_Extent(t.x*2 + c.xx, t.y*2 + c.yy, t.z+1)
|
||||
)
|
||||
FROM t, base, (VALUES (0, 0), (0, 1), (1, 1), (1, 0)) AS c(xx, yy)
|
||||
WHERE t.e > %2$s AND t.z < (base.z + %3$s)
|
||||
WHERE t.e > %2$s AND t.z < least(base.z + %3$s, _CDB_MaxZoomLevel())
|
||||
)
|
||||
SELECT MAX(e/ST_Area(CDB_XYZ_Extent(x,y,z))) FROM t where e > 0;
|
||||
SELECT MAX(e/@postgisschema@.ST_Area(@extschema@.CDB_XYZ_Extent(x,y,z))) FROM t where e > 0;
|
||||
', reloid::text, min_features, nz, n, c, reloid::oid)
|
||||
INTO fd;
|
||||
RETURN fd;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Experimental default strategy to assign a reference base Z level
|
||||
-- to a cartodbfied table. The resulting Z level represents the
|
||||
@ -338,7 +193,7 @@ $$ LANGUAGE PLPGSQL STABLE;
|
||||
-- Parameters:
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: Z level as an integer
|
||||
CREATE OR REPLACE FUNCTION _CDB_Feature_Density_Ref_Z_Strategy(reloid REGCLASS, tolerance_px FLOAT8 DEFAULT NULL)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Feature_Density_Ref_Z_Strategy(reloid REGCLASS, tolerance_px FLOAT8 DEFAULT NULL)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -355,17 +210,17 @@ AS $$
|
||||
|
||||
-- Compute fd as an estimation of the (maximum) number
|
||||
-- of features per unit of tile area (in webmercator squared meters)
|
||||
SELECT _CDB_Feature_Density(reloid, nz) INTO fd;
|
||||
SELECT @extschema@._CDB_Feature_Density(reloid, nz) INTO fd;
|
||||
-- lim maximum number of (desiderable) features per tile
|
||||
-- we have c = 2*Pi*R = CDB_XYZ_Resolution(-8) (earth circumference)
|
||||
-- ta(z): tile area = power(c*power(2,-z), 2) = c*c*power(2,-2*z)
|
||||
-- => fd*ta(z) is the average number of features per tile at level z
|
||||
-- find minimum z so that fd*ta(z) <= lim
|
||||
-- compute a rough 'feature density' value
|
||||
SELECT CDB_XYZ_Resolution(-8) INTO c;
|
||||
RETURN ceil(log(2.0, (c*c*fd/lim)::numeric)/2);
|
||||
SELECT @extschema@.CDB_XYZ_Resolution(-8) INTO c;
|
||||
RETURN least(@extschema@._CDB_MaxOverviewLevel()+1, ceil(log(2.0, (c*c*fd/lim)::numeric)/2));
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Overview table name for a given Z level and base dataset or overview table
|
||||
-- Scope: private.
|
||||
@ -376,7 +231,7 @@ $$ LANGUAGE PLPGSQL STABLE;
|
||||
-- overview_z Z level of the overview to be named, must be smaller than ref_z
|
||||
-- Return value: the name to be used for the overview. The name is always
|
||||
-- unqualified (does not include a schema name).
|
||||
CREATE OR REPLACE FUNCTION _CDB_Overview_Name(ref REGCLASS, ref_z INTEGER, overview_z INTEGER)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Overview_Name(ref REGCLASS, ref_z INTEGER, overview_z INTEGER)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -385,11 +240,11 @@ AS $$
|
||||
suffix TEXT;
|
||||
is_overview BOOLEAN;
|
||||
BEGIN
|
||||
SELECT * FROM _cdb_split_table_name(ref) INTO schema_name, base;
|
||||
SELECT _CDB_OverviewBaseTableName(base) INTO base;
|
||||
RETURN _CDB_OverviewTableName(base, overview_z);
|
||||
SELECT * FROM @extschema@._cdb_split_table_name(ref) INTO schema_name, base;
|
||||
SELECT @extschema@._CDB_OverviewBaseTableName(base) INTO base;
|
||||
RETURN @extschema@._CDB_OverviewTableName(base, overview_z);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Sampling reduction method.
|
||||
-- Valid for any kind of geometry.
|
||||
@ -399,7 +254,7 @@ $$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
-- ref_z Z level assigned to the original table
|
||||
-- overview_z Z level of the overview to be generated, must be smaller than ref_z
|
||||
-- Return value: Name of the generated overview table
|
||||
CREATE OR REPLACE FUNCTION _CDB_Sampling_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, tolerance_px FLOAT8 DEFAULT NULL)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Sampling_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, tolerance_px FLOAT8 DEFAULT NULL, has_overview_created BOOLEAN DEFAULT FALSE)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -410,39 +265,12 @@ AS $$
|
||||
num_samples INTEGER;
|
||||
schema_name TEXT;
|
||||
table_name TEXT;
|
||||
overview_table_name TEXT;
|
||||
creation_clause TEXT;
|
||||
BEGIN
|
||||
overview_rel := _CDB_Overview_Name(reloid, ref_z, overview_z);
|
||||
-- TODO: compute fraction from tolerance_px if not NULL
|
||||
fraction := power(2, 2*(overview_z - ref_z));
|
||||
|
||||
SELECT * FROM _cdb_split_table_name(reloid) INTO schema_name, table_name;
|
||||
|
||||
EXECUTE Format('DROP TABLE IF EXISTS %I.%I CASCADE;', schema_name, overview_rel);
|
||||
|
||||
-- Estimate number of rows
|
||||
SELECT reltuples, relpages FROM pg_class INTO STRICT class_info
|
||||
WHERE oid = reloid::oid;
|
||||
|
||||
IF class_info.relpages < 2 OR fraction > 0.5 THEN
|
||||
-- We'll avoid possible CDB_RandomTids problems
|
||||
EXECUTE Format('
|
||||
CREATE TABLE %I AS SELECT * FROM %s WHERE random() < %s;
|
||||
', overview_rel, reloid, fraction);
|
||||
ELSE
|
||||
num_samples := ceil(class_info.reltuples*fraction);
|
||||
EXECUTE Format('
|
||||
CREATE TABLE %4$I.%1$I AS SELECT * FROM %2$s
|
||||
WHERE ctid = ANY (
|
||||
ARRAY[
|
||||
(SELECT CDB_RandomTids(''%2$s'', %3$s))
|
||||
]
|
||||
);
|
||||
', overview_rel, reloid, num_samples, schema_name);
|
||||
END IF;
|
||||
|
||||
RETURN Format('%I.%I', schema_name, overview_rel)::regclass;
|
||||
RAISE EXCEPTION 'Creating overviews is deprecated';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Register new overview table (post-creation chores)
|
||||
-- Scope: private
|
||||
@ -453,9 +281,9 @@ $$ LANGUAGE PLPGSQL;
|
||||
-- This function is declared SECURITY DEFINER so it executes with the privileges
|
||||
-- of the function creator to have a chance to alter the privileges of the
|
||||
-- overview table to match those of the dataset. It will only perform any change
|
||||
-- if the overview table belgons to the same scheme as the dataset and it
|
||||
-- if the overview table belongs to the same scheme as the dataset and it
|
||||
-- matches the scheme naming for overview tables.
|
||||
CREATE OR REPLACE FUNCTION _CDB_Register_Overview(dataset REGCLASS, overview_table REGCLASS, overview_z INTEGER)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Register_Overview(dataset REGCLASS, overview_table REGCLASS, overview_z INTEGER)
|
||||
RETURNS VOID
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -466,38 +294,13 @@ AS $$
|
||||
overview_scheme TEXT;
|
||||
overview_name TEXT;
|
||||
BEGIN
|
||||
-- This function will only register a table as an overview table if it matches
|
||||
-- the overviews naming scheme for the dataset and z level and the table belongs
|
||||
-- to the same scheme as the the dataset
|
||||
SELECT * FROM _cdb_split_table_name(dataset) INTO dataset_scheme, dataset_name;
|
||||
SELECT * FROM _cdb_split_table_name(overview_table) INTO overview_scheme, overview_name;
|
||||
IF dataset_scheme = overview_scheme AND
|
||||
overview_name = _CDB_OverviewTableName(dataset_name, overview_z) THEN
|
||||
|
||||
-- preserve the owner of the base table
|
||||
SELECT u.usename
|
||||
FROM pg_catalog.pg_class c
|
||||
JOIN pg_catalog.pg_user u ON (c.relowner=u.usesysid)
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relname = dataset_name::text AND n.nspname = dataset_scheme
|
||||
INTO table_owner;
|
||||
|
||||
EXECUTE Format('ALTER TABLE IF EXISTS %s OWNER TO %I;', overview_table::text, table_owner);
|
||||
|
||||
-- preserve the table privileges
|
||||
UPDATE pg_class c_to
|
||||
SET relacl = c_from.relacl
|
||||
FROM pg_class c_from
|
||||
WHERE c_from.oid = dataset
|
||||
AND c_to.oid = overview_table;
|
||||
|
||||
PERFORM _CDB_Add_Indexes(overview_table);
|
||||
|
||||
-- TODO: If metadata about existing overviews is to be stored
|
||||
-- it should be done here (CDB_Overviews would consume such metadata)
|
||||
END IF;
|
||||
RAISE EXCEPTION 'Creating overviews is deprecated';
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL SECURITY DEFINER;
|
||||
$$ LANGUAGE PLPGSQL
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
-- Dataset attributes (column names other than the
|
||||
-- CartoDB primary key and geometry columns) which should be aggregated
|
||||
@ -506,14 +309,14 @@ $$ LANGUAGE PLPGSQL SECURITY DEFINER;
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: set of attribute names
|
||||
CREATE OR REPLACE FUNCTION _CDB_Aggregable_Attributes(reloid REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Aggregable_Attributes(reloid REGCLASS)
|
||||
RETURNS SETOF information_schema.sql_identifier
|
||||
AS $$
|
||||
SELECT c FROM CDB_ColumnNames(reloid) c, _CDB_Columns() cdb
|
||||
SELECT c FROM @extschema@.CDB_ColumnNames(reloid) c, @extschema@._CDB_Columns() cdb
|
||||
WHERE c NOT IN (
|
||||
cdb.pkey, cdb.geomcol, cdb.mercgeomcol
|
||||
)
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
$$ LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- List of dataset attributes to be aggregated in aggregated overview
|
||||
-- as a comma-separated SQL expression.
|
||||
@ -521,22 +324,22 @@ $$ LANGUAGE SQL STABLE;
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: SQL subexpression as text
|
||||
CREATE OR REPLACE FUNCTION _CDB_Aggregable_Attributes_Expression(reloid REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Aggregable_Attributes_Expression(reloid REGCLASS)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
attr_list TEXT;
|
||||
BEGIN
|
||||
SELECT string_agg(s.c, ',') FROM (
|
||||
SELECT * FROM _CDB_Aggregable_Attributes(reloid) c
|
||||
SELECT @extschema@._CDB_Aggregable_Attributes(reloid)::text c
|
||||
) AS s INTO attr_list;
|
||||
|
||||
RETURN attr_list;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
$$ LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- Check if a column of a table is of an unlimited-length text type
|
||||
CREATE OR REPLACE FUNCTION _cdb_unlimited_text_column(reloid REGCLASS, col_name TEXT)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._cdb_unlimited_text_column(reloid REGCLASS, col_name TEXT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
SELECT EXISTS (
|
||||
@ -549,9 +352,9 @@ AS $$
|
||||
AND format_type(a.atttypid, NULL) IN ('text', 'character varying', 'character')
|
||||
AND format_type(a.atttypid, NULL) = format_type(a.atttypid, a.atttypmod)
|
||||
);
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
$$ LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION _cdb_categorical_column(reloid REGCLASS, col_name TEXT)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._cdb_categorical_column(reloid REGCLASS, col_name TEXT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -578,9 +381,9 @@ BEGIN
|
||||
INTO categorical;
|
||||
RETURN categorical;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL RESTRICTED;
|
||||
|
||||
CREATE OR REPLACE FUNCTION _cdb_mode_of_array(anyarray)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._cdb_mode_of_array(anyarray)
|
||||
RETURNS anyelement AS
|
||||
$$
|
||||
SELECT a
|
||||
@ -589,13 +392,14 @@ $$
|
||||
ORDER BY COUNT(1) DESC, 1
|
||||
LIMIT 1;
|
||||
$$
|
||||
LANGUAGE SQL IMMUTABLE;
|
||||
LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
DROP AGGREGATE IF EXISTS _cdb_mode(anyelement);
|
||||
CREATE AGGREGATE _cdb_mode(anyelement) (
|
||||
DROP AGGREGATE IF EXISTS @extschema@._cdb_mode(anyelement);
|
||||
CREATE AGGREGATE @extschema@._cdb_mode(anyelement) (
|
||||
SFUNC=array_append,
|
||||
STYPE=anyarray,
|
||||
FINALFUNC=_cdb_mode_of_array,
|
||||
FINALFUNC=@extschema@._cdb_mode_of_array,
|
||||
PARALLEL = SAFE,
|
||||
INITCOND='{}'
|
||||
);
|
||||
|
||||
@ -607,7 +411,7 @@ CREATE AGGREGATE _cdb_mode(anyelement) (
|
||||
-- table_alias: (optional) table qualifier for the column to be aggregated
|
||||
-- Return SQL subexpression as text with aggregated attribute aliased
|
||||
-- with its original name.
|
||||
CREATE OR REPLACE FUNCTION _CDB_Attribute_Aggregation_Expression(reloid REGCLASS, column_name TEXT, table_alias TEXT DEFAULT '')
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Attribute_Aggregation_Expression(reloid REGCLASS, column_name TEXT, table_alias TEXT DEFAULT '')
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -624,10 +428,10 @@ BEGIN
|
||||
qualified_column := Format('%I', column_name);
|
||||
END IF;
|
||||
|
||||
column_type := CDB_ColumnType(reloid, column_name);
|
||||
column_type := @extschema@.CDB_ColumnType(reloid, column_name);
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT * FROM CDB_ColumnNames(reloid) as colname WHERE colname = '_feature_count'
|
||||
SELECT * FROM @extschema@.CDB_ColumnNames(reloid) as colname WHERE colname = '_feature_count'
|
||||
) INTO has_counter_column;
|
||||
IF has_counter_column THEN
|
||||
feature_count := '_feature_count';
|
||||
@ -637,24 +441,24 @@ BEGIN
|
||||
total_feature_count := 'count(*)';
|
||||
END IF;
|
||||
|
||||
base_table := _CDB_OverviewBaseTable(reloid);
|
||||
base_table := @extschema@._CDB_OverviewBaseTable(reloid);
|
||||
|
||||
CASE column_type
|
||||
WHEN 'double precision', 'real', 'integer', 'bigint', 'numeric' THEN
|
||||
IF column_name = '_feature_count' THEN
|
||||
RETURN 'SUM(_feature_count)';
|
||||
ELSE
|
||||
IF column_type = 'integer' AND _cdb_categorical_column(base_table, column_name) THEN
|
||||
IF column_type = 'integer' AND @extschema@._cdb_categorical_column(base_table, column_name) THEN
|
||||
RETURN Format('CDB_Math_Mode(%s)::', qualified_column) || column_type;
|
||||
ELSE
|
||||
RETURN Format('SUM(%s*%s)/%s::' || column_type, qualified_column, feature_count, total_feature_count);
|
||||
END IF;
|
||||
END IF;
|
||||
WHEN 'text', 'character varying', 'character' THEN
|
||||
IF _cdb_categorical_column(base_table, column_name) THEN
|
||||
IF @extschema@._cdb_categorical_column(base_table, column_name) THEN
|
||||
RETURN Format('_cdb_mode(%s)::', qualified_column) || column_type;
|
||||
ELSE
|
||||
IF _cdb_unlimited_text_column(base_table, column_name) THEN
|
||||
IF @extschema@._cdb_unlimited_text_column(base_table, column_name) THEN
|
||||
-- TODO: this should not be applied to columns containing largish text;
|
||||
-- it is intended only to short names/identifiers
|
||||
RETURN 'CASE WHEN count(distinct ' || qualified_column || ') = 1 THEN MIN(' || qualified_column || ') WHEN ' || total_feature_count || ' < 5 THEN string_agg(distinct ' || qualified_column || ','' / '') ELSE ''*'' END::' || column_type;
|
||||
@ -668,7 +472,7 @@ BEGIN
|
||||
RETURN 'CASE count(*) WHEN 1 THEN MIN(' || qualified_column || ') ELSE NULL END::' || column_type;
|
||||
END CASE;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL RESTRICTED;
|
||||
|
||||
-- List of dataset aggregated attributes as a comma-separated SQL expression.
|
||||
-- Scope: private.
|
||||
@ -676,34 +480,34 @@ $$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- table_alias: (optional) table qualifier for the columns to be aggregated
|
||||
-- Return value: SQL subexpression as text
|
||||
CREATE OR REPLACE FUNCTION _CDB_Aggregated_Attributes_Expression(reloid REGCLASS, table_alias TEXT DEFAULT '')
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_Aggregated_Attributes_Expression(reloid REGCLASS, table_alias TEXT DEFAULT '')
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
attr_list TEXT;
|
||||
BEGIN
|
||||
SELECT string_agg(_CDB_Attribute_Aggregation_Expression(reloid, s.c, table_alias) || Format(' AS %s', s.c), ',')
|
||||
SELECT string_agg(@extschema@._CDB_Attribute_Aggregation_Expression(reloid, s.c, table_alias) || Format(' AS %s', s.c), ',')
|
||||
FROM (
|
||||
SELECT * FROM _CDB_Aggregable_Attributes(reloid) c
|
||||
SELECT @extschema@._CDB_Aggregable_Attributes(reloid)::text c
|
||||
) AS s INTO attr_list;
|
||||
|
||||
RETURN attr_list;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL RESTRICTED;
|
||||
|
||||
-- Array of geometry types detected in a cartodbfied table
|
||||
-- For effciency only look at a limited number of rwos.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: array of geometry type names
|
||||
CREATE OR REPLACE FUNCTION _CDB_GeometryTypes(reloid REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_GeometryTypes(reloid REGCLASS)
|
||||
RETURNS TEXT[]
|
||||
AS $$
|
||||
DECLARE
|
||||
gtypes TEXT[];
|
||||
BEGIN
|
||||
EXECUTE Format('
|
||||
SELECT array_agg(DISTINCT ST_GeometryType(the_geom)) FROM (
|
||||
SELECT array_agg(DISTINCT @postgisschema@.ST_GeometryType(the_geom)) FROM (
|
||||
SELECT the_geom FROM %s
|
||||
WHERE (the_geom is not null) LIMIT 10
|
||||
) as geom_types
|
||||
@ -711,7 +515,7 @@ BEGIN
|
||||
INTO gtypes;
|
||||
RETURN gtypes;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
$$ LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- Experimental Overview reduction method for point datasets.
|
||||
-- It clusters the points using a grid, then aggregates the point in each
|
||||
@ -723,7 +527,7 @@ $$ LANGUAGE PLPGSQL STABLE;
|
||||
-- ref_z Z level assigned to the original table
|
||||
-- overview_z Z level of the overview to be generated, must be smaller than ref_z
|
||||
-- Return value: Name of the generated overview table
|
||||
CREATE OR REPLACE FUNCTION _CDB_GridCluster_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, grid_px FLOAT8 DEFAULT NULL)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_GridCluster_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, grid_px FLOAT8 DEFAULT NULL, has_overview_created BOOLEAN DEFAULT FALSE)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -744,8 +548,39 @@ AS $$
|
||||
schema_name TEXT;
|
||||
table_name TEXT;
|
||||
point_geom TEXT;
|
||||
overview_table_name TEXT;
|
||||
creation_clause TEXT;
|
||||
BEGIN
|
||||
SELECT _CDB_GeometryTypes(reloid) INTO gtypes;
|
||||
RAISE EXCEPTION 'Creating overviews is deprecated';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- This strategy places the aggregation of each cluster at the centroid of the cluster members.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_GridClusterCentroid_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, grid_px FLOAT8 DEFAULT NULL, has_overview_created BOOLEAN DEFAULT FALSE)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
overview_rel TEXT;
|
||||
reduction FLOAT8;
|
||||
base_name TEXT;
|
||||
pixel_m FLOAT8;
|
||||
grid_m FLOAT8;
|
||||
offset_m FLOAT8;
|
||||
offset_x TEXT;
|
||||
offset_y TEXT;
|
||||
cell_x TEXT;
|
||||
cell_y TEXT;
|
||||
aggr_attributes TEXT;
|
||||
attributes TEXT;
|
||||
columns TEXT;
|
||||
gtypes TEXT[];
|
||||
schema_name TEXT;
|
||||
table_name TEXT;
|
||||
point_geom TEXT;
|
||||
overview_table_name TEXT;
|
||||
creation_clause TEXT;
|
||||
BEGIN
|
||||
SELECT @extschema@._CDB_GeometryTypes(reloid) INTO gtypes;
|
||||
IF gtypes IS NULL OR array_upper(gtypes, 1) <> 1 OR gtypes[1] <> 'ST_Point' THEN
|
||||
-- This strategy only supports datasets with point geomety
|
||||
RETURN NULL;
|
||||
@ -753,22 +588,22 @@ AS $$
|
||||
|
||||
--TODO: check applicability: geometry type, minimum number of points...
|
||||
|
||||
overview_rel := _CDB_Overview_Name(reloid, ref_z, overview_z);
|
||||
overview_rel := @extschema@._CDB_Overview_Name(reloid, ref_z, overview_z);
|
||||
|
||||
-- Grid size in pixels at Z level overview_z
|
||||
IF grid_px IS NULL THEN
|
||||
grid_px := 1.0;
|
||||
END IF;
|
||||
|
||||
SELECT * FROM _cdb_split_table_name(reloid) INTO schema_name, table_name;
|
||||
SELECT * FROM @extschema@._cdb_split_table_name(reloid) INTO schema_name, table_name;
|
||||
|
||||
-- pixel_m: size of a pixel in webmercator units (meters)
|
||||
SELECT CDB_XYZ_Resolution(overview_z) INTO pixel_m;
|
||||
SELECT @extschema@.CDB_XYZ_Resolution(overview_z) INTO pixel_m;
|
||||
-- grid size in meters
|
||||
grid_m = grid_px * pixel_m;
|
||||
|
||||
attributes := _CDB_Aggregable_Attributes_Expression(reloid);
|
||||
aggr_attributes := _CDB_Aggregated_Attributes_Expression(reloid);
|
||||
attributes := @extschema@._CDB_Aggregable_Attributes_Expression(reloid);
|
||||
aggr_attributes := @extschema@._CDB_Aggregated_Attributes_Expression(reloid);
|
||||
IF attributes <> '' THEN
|
||||
attributes := ', ' || attributes;
|
||||
END IF;
|
||||
@ -790,7 +625,7 @@ AS $$
|
||||
offset_y := Format('%2$s/2 - MOD((%1$s)::numeric, (%2$s)::numeric)::float8', cell_y, pixel_m);
|
||||
END IF;
|
||||
|
||||
point_geom := Format('ST_SetSRID(ST_MakePoint(%1$s + %3$s, %2$s + %4$s), 3857)', cell_x, cell_y, offset_x, offset_y);
|
||||
point_geom := Format('@postgisschema@.ST_SetSRID(@postgisschema@.ST_MakePoint(%1$s + %3$s, %2$s + %4$s), 3857)', cell_x, cell_y, offset_x, offset_y);
|
||||
|
||||
-- compute the resulting columns in the same order as in the base table
|
||||
WITH cols AS (
|
||||
@ -798,10 +633,10 @@ AS $$
|
||||
CASE c
|
||||
WHEN 'cartodb_id' THEN 'cartodb_id'
|
||||
WHEN 'the_geom' THEN
|
||||
Format('ST_Transform(%s, 4326) AS the_geom', point_geom)
|
||||
'@postgisschema@.ST_Transform(@postgisschema@.ST_SetSRID(@postgisschema@.ST_MakePoint(_sum_of_x/n, _sum_of_y/n), 3857), 4326) AS the_geom'
|
||||
WHEN 'the_geom_webmercator' THEN
|
||||
Format('%s AS the_geom_webmercator', point_geom)
|
||||
ELSE c
|
||||
'@postgisschema@.ST_SetSRID(@postgisschema@.ST_MakePoint(_sum_of_x/n, _sum_of_y/n), 3857) AS the_geom_webmercator'
|
||||
ELSE c::text
|
||||
END AS column
|
||||
FROM CDB_ColumnNames(reloid) c
|
||||
)
|
||||
@ -813,30 +648,69 @@ AS $$
|
||||
columns := columns || ', n AS _feature_count';
|
||||
END IF;
|
||||
|
||||
EXECUTE Format('DROP TABLE IF EXISTS %I.%I CASCADE;', schema_name, overview_rel);
|
||||
overview_table_name := Format('%I.%I', schema_name, overview_rel);
|
||||
IF has_overview_created THEN
|
||||
RAISE NOTICE 'Grid cluster centroid strategy deleting and inserting because % has overviews', overview_table_name;
|
||||
EXECUTE Format('DELETE FROM %s;', overview_table_name);
|
||||
creation_clause := Format('INSERT INTO %s', overview_table_name);
|
||||
ELSE
|
||||
RAISE NOTICE 'Grid cluster centroid strategy creating a new table overview %', overview_table_name;
|
||||
creation_clause := Format('CREATE TABLE %s AS', overview_table_name);
|
||||
END IF;
|
||||
|
||||
-- Now we cluster the data using a grid of size grid_m
|
||||
-- and selecte the centroid (average coordinates) of each cluster.
|
||||
-- If we had a selected numeric attribute of interest we could use it
|
||||
-- as a weight for the average coordinates.
|
||||
EXECUTE Format('
|
||||
CREATE TABLE %7$I.%3$I AS
|
||||
%3$s
|
||||
WITH clusters AS (
|
||||
SELECT
|
||||
%5$s
|
||||
count(*) AS n,
|
||||
Floor(ST_X(f.the_geom_webmercator)/%2$s)::int AS gx,
|
||||
Floor(ST_Y(f.the_geom_webmercator)/%2$s)::int AS gy,
|
||||
SUM(@postgisschema@.ST_X(f.the_geom_webmercator)) AS _sum_of_x,
|
||||
SUM(@postgisschema@.ST_Y(f.the_geom_webmercator)) AS _sum_of_y,
|
||||
Floor(@postgisschema@.ST_Y(f.the_geom_webmercator)/%2$s)::int AS gy,
|
||||
Floor(@postgisschema@.ST_X(f.the_geom_webmercator)/%2$s)::int AS gx,
|
||||
MIN(cartodb_id) AS cartodb_id
|
||||
FROM %1$s f
|
||||
GROUP BY gx, gy
|
||||
)
|
||||
SELECT %6$s FROM clusters
|
||||
', reloid::text, grid_m, overview_rel, attributes, aggr_attributes, columns, schema_name);
|
||||
', reloid::text, grid_m, creation_clause, attributes, aggr_attributes, columns);
|
||||
|
||||
RETURN Format('%I.%I', schema_name, overview_rel)::regclass;
|
||||
RETURN Format('%s', overview_table_name)::regclass;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- This strategy places the aggregation of each cluster at the position of one of the cluster members.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_GridClusterSample_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, grid_px FLOAT8 DEFAULT NULL, has_overview_created BOOLEAN DEFAULT FALSE)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
overview_rel TEXT;
|
||||
reduction FLOAT8;
|
||||
base_name TEXT;
|
||||
pixel_m FLOAT8;
|
||||
grid_m FLOAT8;
|
||||
offset_m FLOAT8;
|
||||
offset_x TEXT;
|
||||
offset_y TEXT;
|
||||
cell_x TEXT;
|
||||
cell_y TEXT;
|
||||
aggr_attributes TEXT;
|
||||
attributes TEXT;
|
||||
columns TEXT;
|
||||
gtypes TEXT[];
|
||||
schema_name TEXT;
|
||||
table_name TEXT;
|
||||
point_geom TEXT;
|
||||
overview_table_name TEXT;
|
||||
creation_clause TEXT;
|
||||
BEGIN
|
||||
RAISE EXCEPTION 'Creating overviews is deprecated';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Create overview tables for a dataset.
|
||||
-- Scope: public
|
||||
@ -849,20 +723,18 @@ $$ LANGUAGE PLPGSQL;
|
||||
-- created by the strategy must have the same columns
|
||||
-- as the base table and in the same order.
|
||||
-- Return value: Array with the names of the generated overview tables
|
||||
CREATE OR REPLACE FUNCTION CDB_CreateOverviews(reloid REGCLASS, refscale_strategy regproc DEFAULT '_CDB_Feature_Density_Ref_Z_Strategy(REGCLASS,FLOAT8)'::regprocedure, reduce_strategy regproc DEFAULT '_CDB_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8)'::regprocedure)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_CreateOverviews(reloid REGCLASS, refscale_strategy regproc DEFAULT '@extschema@._CDB_Feature_Density_Ref_Z_Strategy(REGCLASS,FLOAT8)'::regprocedure, reduce_strategy regproc DEFAULT '@extschema@._CDB_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8,BOOLEAN)'::regprocedure)
|
||||
RETURNS text[]
|
||||
AS $$
|
||||
DECLARE
|
||||
tolerance_px FLOAT8;
|
||||
BEGIN
|
||||
-- Use the default tolerance
|
||||
tolerance_px := 1.0;
|
||||
RETURN CDB_CreateOverviewsWithToleranceInPixels(reloid, tolerance_px, refscale_strategy, reduce_strategy);
|
||||
RAISE EXCEPTION 'Creating overviews is deprecated';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Create overviews with additional parameter to define the desired detail/tolerance in pixels
|
||||
CREATE OR REPLACE FUNCTION CDB_CreateOverviewsWithToleranceInPixels(reloid REGCLASS, tolerance_px FLOAT8, refscale_strategy regproc DEFAULT '_CDB_Feature_Density_Ref_Z_Strategy(REGCLASS,FLOAT8)'::regprocedure, reduce_strategy regproc DEFAULT '_CDB_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8)'::regprocedure)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_CreateOverviewsWithToleranceInPixels(reloid REGCLASS, tolerance_px FLOAT8, refscale_strategy regproc DEFAULT '@extschema@._CDB_Feature_Density_Ref_Z_Strategy(REGCLASS,FLOAT8)'::regprocedure, reduce_strategy regproc DEFAULT '@extschema@._CDB_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8,BOOLEAN)'::regprocedure)
|
||||
RETURNS text[]
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -874,53 +746,19 @@ DECLARE
|
||||
overview_tables REGCLASS[];
|
||||
overviews_step integer := 1;
|
||||
has_counter_column boolean;
|
||||
has_overviews_for_z boolean;
|
||||
BEGIN
|
||||
-- Determine the referece zoom level
|
||||
EXECUTE 'SELECT ' || quote_ident(refscale_strategy::text) || Format('(''%s'', %s);', reloid, tolerance_px) INTO ref_z;
|
||||
|
||||
IF ref_z < 0 OR ref_z IS NULL THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
-- Determine overlay zoom levels
|
||||
-- TODO: should be handled by the refscale_strategy?
|
||||
overview_z := ref_z - 1;
|
||||
WHILE overview_z >= 0 LOOP
|
||||
SELECT array_append(overviews_z, overview_z) INTO overviews_z;
|
||||
overview_z := overview_z - overviews_step;
|
||||
END LOOP;
|
||||
|
||||
-- Create overlay tables
|
||||
base_z := ref_z;
|
||||
base_rel := reloid;
|
||||
FOREACH overview_z IN ARRAY overviews_z LOOP
|
||||
EXECUTE 'SELECT ' || quote_ident(reduce_strategy::text) || Format('(''%s'', %s, %s, %s);', base_rel, base_z, overview_z, tolerance_px) INTO base_rel;
|
||||
IF base_rel IS NULL THEN
|
||||
EXIT;
|
||||
END IF;
|
||||
base_z := overview_z;
|
||||
PERFORM _CDB_Register_Overview(reloid, base_rel, base_z);
|
||||
SELECT array_append(overview_tables, base_rel) INTO overview_tables;
|
||||
END LOOP;
|
||||
|
||||
IF overview_tables IS NOT NULL AND array_length(overview_tables, 1) > 0 THEN
|
||||
SELECT EXISTS (
|
||||
SELECT * FROM CDB_ColumnNames(reloid) as colname WHERE colname = '_feature_count'
|
||||
) INTO has_counter_column;
|
||||
IF NOT has_counter_column THEN
|
||||
EXECUTE Format('
|
||||
ALTER TABLE %s ADD COLUMN _feature_count integer DEFAULT 1;
|
||||
', reloid);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN overview_tables;
|
||||
RAISE EXCEPTION 'Creating overviews is deprecated';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Here are some older signatures of these functions, no longar in use.
|
||||
-- Here are some older signatures of these functions, no longer in use.
|
||||
-- They must be droped here, after the (new) definition of the function `CDB_CreateOverviews`
|
||||
-- because that function used to contain references to them in the default argument values.
|
||||
DROP FUNCTION IF EXISTS _CDB_Feature_Density_Ref_Z_Strategy(REGCLASS);
|
||||
DROP FUNCTION IF EXISTS _CDB_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER);
|
||||
DROP FUNCTION IF EXISTS _CDB_Sampling_Reduce_Strategy(REGCLASS,INTEGER,INTEGER);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Feature_Density_Ref_Z_Strategy(REGCLASS);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_GridClusterCentroid_Reduce_Strategy(REGCLASS, INTEGER, INTEGER, FLOAT8);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_GridClusterSample_Reduce_Strategy(REGCLASS, INTEGER, INTEGER, FLOAT8);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Sampling_Reduce_Strategy(REGCLASS,INTEGER,INTEGER);
|
||||
DROP FUNCTION IF EXISTS @extschema@._CDB_Sampling_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8);
|
||||
|
173
scripts-available/CDB_OverviewsSupport.sql
Normal file
173
scripts-available/CDB_OverviewsSupport.sql
Normal file
@ -0,0 +1,173 @@
|
||||
-- Auxiliary overviews FUNCTIONS
|
||||
|
||||
-- Maximum zoom level for which overviews may be created
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_MaxOverviewLevel()
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Zoom level will be limited so that both tile coordinates
|
||||
-- and gridding coordinates within a tile up to 1px
|
||||
-- (i.e. tile coordinates / 256)
|
||||
-- can be stored in a 32-bit signed integer.
|
||||
-- We have 31 bits por positive numbers
|
||||
-- For zoom level Z coordinates range from 0 to 2^Z-1, so they
|
||||
-- need Z bits, and need 8 bits more to address pixels within a tile
|
||||
-- (gridding), so we'll limit Z to a maximum of 31 - 8
|
||||
RETURN 23;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Maximum zoom level usable with integer coordinates
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_MaxZoomLevel()
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN 31;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Information about tables in a schema.
|
||||
-- If the schema name parameter is NULL, then tables from all schemas
|
||||
-- that may contain user tables are returned.
|
||||
-- For each table, the regclass, schema name and table name are returned.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_UserTablesInSchema(schema_name text DEFAULT NULL)
|
||||
RETURNS TABLE(table_regclass REGCLASS, schema_name TEXT, table_name TEXT)
|
||||
AS $$
|
||||
SELECT
|
||||
c.oid::regclass AS table_regclass,
|
||||
n.nspname::text AS schema_name,
|
||||
c.relname::text AS table_relname
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind = 'r'
|
||||
AND c.relname NOT IN ('cdb_tablemetadata', 'cdb_analysis_catalog', 'cdb_conf', 'spatial_ref_sys')
|
||||
AND CASE WHEN schema_name IS NULL
|
||||
THEN n.nspname NOT IN ('pg_catalog', 'information_schema', 'topology', '@extschema@')
|
||||
ELSE n.nspname = schema_name
|
||||
END;
|
||||
$$ LANGUAGE 'sql' STABLE PARALLEL SAFE;
|
||||
|
||||
-- Pattern that can be used to detect overview tables and Extract
|
||||
-- the intended zoom level from the table name.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTableDiscriminator()
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN '\A_vovw_(\d+)_';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
-- substring(tablename from _CDB_OverviewTableDiscriminator())
|
||||
|
||||
|
||||
-- Pattern matched by the overview tables of a given base table name.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTablePattern(base_table TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN @extschema@._CDB_OverviewTableDiscriminator() || base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
-- tablename SIMILAR TO _CDB_OverviewTablePattern(base_table)
|
||||
|
||||
-- Name of an overview table, given the base table name and the Z level
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTableName(base_table TEXT, z INTEGER)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN '_vovw_' || z::text || '_' || base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Condition to check if a tabla is an overview table of some base table
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_IsOverviewTableOf(base_table TEXT, otable TEXT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN otable SIMILAR TO @extschema@._CDB_OverviewTablePattern(base_table);
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Extract the Z level from an overview table name
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTableZ(otable TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN substring(otable from @extschema@._CDB_OverviewTableDiscriminator())::integer;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Name of the base table corresponding to an overview table
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewBaseTableName(overview_table TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
IF @extschema@._CDB_OverviewTableZ(overview_table) IS NULL THEN
|
||||
RETURN overview_table;
|
||||
ELSE
|
||||
RETURN regexp_replace(overview_table, @extschema@._CDB_OverviewTableDiscriminator(), '');
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewBaseTable(overview_table REGCLASS)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
table_name TEXT;
|
||||
schema_name TEXT;
|
||||
base_name TEXT;
|
||||
base_table REGCLASS;
|
||||
BEGIN
|
||||
SELECT * FROM @extschema@._cdb_split_table_name(overview_table) INTO schema_name, table_name;
|
||||
base_name := @extschema@._CDB_OverviewBaseTableName(table_name);
|
||||
IF base_name != table_name THEN
|
||||
base_table := Format('%I.%I', schema_name, base_name)::regclass;
|
||||
ELSE
|
||||
base_table := overview_table;
|
||||
END IF;
|
||||
RETURN base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Schema and relation names of a table given its reloid
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the table.
|
||||
-- Return (schema_name, table_name)
|
||||
-- note that returned names will be quoted if necessary
|
||||
CREATE OR REPLACE FUNCTION @extschema@._cdb_split_table_name(reloid REGCLASS, OUT schema_name TEXT, OUT table_name TEXT)
|
||||
AS $$
|
||||
BEGIN
|
||||
SELECT n.nspname, c.relname
|
||||
INTO STRICT schema_name, table_name
|
||||
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.oid = reloid;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
-- Schema and relation names of a table given its reloid
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the table.
|
||||
-- Return (schema_name, table_name)
|
||||
-- note that returned names will be quoted if necessary
|
||||
CREATE OR REPLACE FUNCTION @extschema@._cdb_schema_name(reloid REGCLASS)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
schema_name TEXT;
|
||||
BEGIN
|
||||
SELECT n.nspname
|
||||
INTO STRICT schema_name
|
||||
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.oid = reloid;
|
||||
RETURN schema_name;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
|
@ -5,39 +5,14 @@
|
||||
-- bins based on the Quantile method.
|
||||
--
|
||||
-- @param breaks The number of bins you want to find.
|
||||
--
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_QuantileBins ( in_array NUMERIC[], breaks INT) RETURNS NUMERIC[] as $$
|
||||
DECLARE
|
||||
element_count INT4;
|
||||
break_size numeric;
|
||||
tmp_val numeric;
|
||||
i INT := 1;
|
||||
reply numeric[];
|
||||
BEGIN
|
||||
-- sort our values
|
||||
SELECT array_agg(e) INTO in_array FROM (SELECT unnest(in_array) e ORDER BY e ASC) x;
|
||||
-- get the total size of our data
|
||||
element_count := array_length(in_array, 1);
|
||||
break_size := element_count::numeric / breaks;
|
||||
-- slice our bread
|
||||
LOOP
|
||||
IF i < breaks THEN
|
||||
IF break_size * i % 1 > 0 THEN
|
||||
SELECT e INTO tmp_val FROM ( SELECT unnest(in_array) e LIMIT 1 OFFSET ceil(break_size * i) - 1) x;
|
||||
ELSE
|
||||
SELECT avg(e) INTO tmp_val FROM ( SELECT unnest(in_array) e LIMIT 2 OFFSET ceil(break_size * i) - 1 ) x;
|
||||
END IF;
|
||||
ELSIF i = breaks THEN
|
||||
-- select the last value
|
||||
SELECT max(e) INTO tmp_val FROM ( SELECT unnest(in_array) e ) x;
|
||||
ELSE
|
||||
EXIT;
|
||||
END IF;
|
||||
|
||||
reply = array_append(reply, tmp_val);
|
||||
i := i+1;
|
||||
END LOOP;
|
||||
RETURN reply;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_QuantileBins(in_array numeric[], breaks int)
|
||||
RETURNS numeric[]
|
||||
AS $$
|
||||
SELECT
|
||||
percentile_disc(Array(SELECT generate_series(1, breaks) / breaks::numeric))
|
||||
WITHIN GROUP (ORDER BY x ASC) AS p
|
||||
FROM
|
||||
unnest(in_array) AS x;
|
||||
$$ language SQL IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
@ -3,7 +3,7 @@
|
||||
-- Regexp curtesy of Hubert Lubaczewski (depesz)
|
||||
-- Implemented in plpython for performance reasons
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_QueryStatements(query text)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_QueryStatements(query text)
|
||||
RETURNS SETOF TEXT AS $$
|
||||
import re
|
||||
pat = re.compile( r'''((?:[^'"$;]+|"[^"]*"|'[^']*'|(\$[^$]*\$).*?\2)+)''', re.DOTALL )
|
||||
@ -11,4 +11,4 @@ RETURNS SETOF TEXT AS $$
|
||||
cleaned = match[0].strip()
|
||||
if ( cleaned ):
|
||||
yield cleaned
|
||||
$$ language 'plpythonu' IMMUTABLE STRICT;
|
||||
$$ language '@@plpythonu@@' IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
@ -2,7 +2,7 @@
|
||||
--
|
||||
-- Requires PostgreSQL 9.x+
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_QueryTablesText(query text)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_QueryTablesText(query text)
|
||||
RETURNS text[]
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -11,19 +11,16 @@ DECLARE
|
||||
rec RECORD;
|
||||
rec2 RECORD;
|
||||
BEGIN
|
||||
|
||||
|
||||
tables := '{}';
|
||||
|
||||
FOR rec IN SELECT CDB_QueryStatements(query) q LOOP
|
||||
|
||||
IF NOT ( rec.q ilike 'select%' or rec.q ilike 'with%' ) THEN
|
||||
--RAISE WARNING 'Skipping %', rec.q;
|
||||
CONTINUE;
|
||||
END IF;
|
||||
|
||||
FOR rec IN SELECT @extschema@.CDB_QueryStatements(query) q LOOP
|
||||
BEGIN
|
||||
EXECUTE 'EXPLAIN (FORMAT XML, VERBOSE) ' || rec.q INTO STRICT exp;
|
||||
EXCEPTION WHEN others THEN
|
||||
EXCEPTION WHEN syntax_error THEN
|
||||
-- We can get a syntax error if the user tries to EXPLAIN a DDL
|
||||
CONTINUE;
|
||||
WHEN others THEN
|
||||
-- TODO: if error is 'relation "xxxxxx" does not exist', take xxxxxx as
|
||||
-- the affected table ?
|
||||
RAISE WARNING 'CDB_QueryTables cannot explain query: % (%: %)', rec.q, SQLSTATE, SQLERRM;
|
||||
@ -64,15 +61,15 @@ BEGIN
|
||||
|
||||
return tables;
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE STRICT PARALLEL UNSAFE;
|
||||
|
||||
|
||||
-- Keep CDB_QueryTables with same signature for backwards compatibility.
|
||||
-- It should probably be removed in the future.
|
||||
CREATE OR REPLACE FUNCTION CDB_QueryTables(query text)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_QueryTables(query text)
|
||||
RETURNS name[]
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN CDB_QueryTablesText(query)::name[];
|
||||
RETURN @extschema@.CDB_QueryTablesText(query)::name[];
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE STRICT PARALLEL UNSAFE;
|
||||
|
@ -1,4 +1,4 @@
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_total_relation_size(_schema_name TEXT, _table_name TEXT)
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_total_relation_size(_schema_name TEXT, _table_name TEXT)
|
||||
RETURNS bigint AS
|
||||
$$
|
||||
DECLARE relation_size bigint := 0;
|
||||
@ -7,47 +7,63 @@ BEGIN
|
||||
SELECT pg_total_relation_size(format('"%s"."%s"', _schema_name, _table_name)) INTO relation_size;
|
||||
EXCEPTION
|
||||
WHEN undefined_table OR OTHERS THEN
|
||||
RAISE NOTICE 'cartodb._CDB_total_relation_size(''%'', ''%'') caught error: % (%)', _schema_name, _table_name, SQLERRM, SQLSTATE;
|
||||
RAISE NOTICE '@extschema@._CDB_total_relation_size(''%'', ''%'') caught error: % (%)', _schema_name, _table_name, SQLERRM, SQLSTATE;
|
||||
END;
|
||||
RETURN relation_size;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' VOLATILE;
|
||||
LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Return the estimated size of user data. Used for quota checking.
|
||||
CREATE OR REPLACE FUNCTION CDB_UserDataSize(schema_name TEXT)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_UserDataSize(schema_name TEXT)
|
||||
RETURNS bigint AS
|
||||
$$
|
||||
DECLARE
|
||||
total_size INT8;
|
||||
raster_available BOOLEAN;
|
||||
raster_read_query TEXT;
|
||||
BEGIN
|
||||
-- Postgis 3+ might not install raster
|
||||
raster_available := EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_views
|
||||
WHERE schemaname = '@postgisschema@'
|
||||
AND viewname = 'raster_overviews'
|
||||
);
|
||||
|
||||
IF raster_available THEN
|
||||
raster_read_query := Format('SELECT o_table_name, r_table_name FROM @postgisschema@.raster_overviews
|
||||
WHERE o_table_schema = %L AND o_table_catalog = current_database()', schema_name);
|
||||
ELSE
|
||||
raster_read_query := 'SELECT NULL::text AS o_table_name, NULL::text AS r_table_name';
|
||||
END IF;
|
||||
EXECUTE Format('
|
||||
WITH raster_tables AS (
|
||||
SELECT o_table_name, r_table_name FROM raster_overviews
|
||||
WHERE o_table_schema = schema_name AND o_table_catalog = current_database()
|
||||
%s
|
||||
),
|
||||
user_tables AS (
|
||||
SELECT table_name FROM information_schema.tables
|
||||
WHERE table_catalog = current_database() AND table_schema = schema_name
|
||||
AND table_name != 'spatial_ref_sys'
|
||||
AND table_name != 'cdb_tablemetadata'
|
||||
AND table_type = 'BASE TABLE'
|
||||
SELECT table_name FROM @extschema@._CDB_NonAnalysisTablesInSchema(%L)
|
||||
),
|
||||
table_cat AS (
|
||||
SELECT
|
||||
table_name,
|
||||
EXISTS(select * from raster_tables where o_table_name = table_name) AS is_overview,
|
||||
(
|
||||
EXISTS(select * from raster_tables where o_table_name = table_name)
|
||||
OR table_name SIMILAR TO @extschema@._CDB_OverviewTableDiscriminator() || ''[\w\d]*''
|
||||
) AS is_overview,
|
||||
EXISTS(SELECT * FROM raster_tables WHERE r_table_name = table_name) AS is_raster
|
||||
FROM user_tables
|
||||
),
|
||||
sizes AS (
|
||||
SELECT COALESCE(INT8(SUM(cartodb._CDB_total_relation_size(schema_name, table_name)))) table_size,
|
||||
SELECT COALESCE(INT8(SUM(@extschema@._CDB_total_relation_size(%L, table_name)))) table_size,
|
||||
CASE
|
||||
WHEN is_overview THEN 0
|
||||
WHEN is_raster THEN 1
|
||||
ELSE 0.5 -- Division by 2 is for not counting the_geom_webmercator
|
||||
END AS multiplier FROM table_cat GROUP BY is_overview, is_raster
|
||||
)
|
||||
SELECT sum(table_size*multiplier)::int8 INTO total_size FROM sizes;
|
||||
SELECT sum(table_size*multiplier)::int8 FROM sizes
|
||||
', raster_read_query, schema_name, schema_name) INTO total_size;
|
||||
|
||||
IF total_size IS NOT NULL THEN
|
||||
RETURN total_size;
|
||||
@ -56,20 +72,20 @@ BEGIN
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' VOLATILE;
|
||||
LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
-- Return the estimated size of user data. Used for quota checking.
|
||||
-- Implicit schema version for backwards compatibility
|
||||
CREATE OR REPLACE FUNCTION CDB_UserDataSize()
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_UserDataSize()
|
||||
RETURNS bigint AS
|
||||
$$
|
||||
SELECT public.CDB_UserDataSize('public');
|
||||
SELECT @extschema@.CDB_UserDataSize('public');
|
||||
$$
|
||||
LANGUAGE 'sql' VOLATILE;
|
||||
LANGUAGE 'sql' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Triggers cannot have declared arguments: pbfact float8, qmax int8, schema_name text
|
||||
CREATE OR REPLACE FUNCTION CDB_CheckQuota()
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_CheckQuota()
|
||||
RETURNS trigger AS
|
||||
$$
|
||||
DECLARE
|
||||
@ -81,7 +97,7 @@ DECLARE
|
||||
BEGIN
|
||||
IF TG_NARGS = 3 THEN
|
||||
schema_name := TG_ARGV[2];
|
||||
IF cartodb.schema_exists(schema_name) = false THEN
|
||||
IF @extschema@.schema_exists(schema_name) = false THEN
|
||||
RAISE EXCEPTION 'Invalid schema name "%"', schema_name;
|
||||
END IF;
|
||||
ELSE
|
||||
@ -112,7 +128,7 @@ BEGIN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT public.CDB_UserDataSize(schema_name) INTO quota;
|
||||
SELECT @extschema@.CDB_UserDataSize(schema_name) INTO quota;
|
||||
IF quota > qmax THEN
|
||||
RAISE EXCEPTION 'Quota exceeded by %KB', (quota-qmax)/1024;
|
||||
ELSE RAISE DEBUG 'User quota in bytes: % < % (max allowed)', quota, qmax;
|
||||
@ -122,16 +138,16 @@ BEGIN
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' VOLATILE;
|
||||
LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_SetUserQuotaInBytes(schema_name text, bytes int8)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_SetUserQuotaInBytes(schema_name text, bytes int8)
|
||||
RETURNS int8 AS
|
||||
$$
|
||||
DECLARE
|
||||
sql text;
|
||||
BEGIN
|
||||
IF cartodb.schema_exists(schema_name::text) = false THEN
|
||||
IF @extschema@.schema_exists(schema_name::text) = false THEN
|
||||
RAISE EXCEPTION 'Invalid schema name "%"', schema_name::text;
|
||||
END IF;
|
||||
|
||||
@ -143,14 +159,14 @@ BEGIN
|
||||
return bytes;
|
||||
END
|
||||
$$
|
||||
LANGUAGE 'plpgsql' VOLATILE STRICT;
|
||||
LANGUAGE 'plpgsql' VOLATILE STRICT PARALLEL UNSAFE;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_SetUserQuotaInBytes(bytes int8)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_SetUserQuotaInBytes(bytes int8)
|
||||
RETURNS int8 AS
|
||||
$$
|
||||
BEGIN
|
||||
return public.CDB_SetUserQuotaInBytes('public', bytes);
|
||||
return @extschema@.CDB_SetUserQuotaInBytes('public', bytes);
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' VOLATILE STRICT;
|
||||
LANGUAGE 'plpgsql' VOLATILE STRICT PARALLEL UNSAFE;
|
||||
|
@ -15,7 +15,7 @@
|
||||
--
|
||||
--
|
||||
-- }{
|
||||
CREATE OR REPLACE FUNCTION CDB_RandomTids(in_table regclass, in_nsamples integer)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_RandomTids(in_table regclass, in_nsamples integer)
|
||||
RETURNS tid[]
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -64,6 +64,6 @@ BEGIN
|
||||
RETURN tidlist;
|
||||
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' STABLE STRICT;
|
||||
$$ LANGUAGE 'plpgsql' STABLE STRICT PARALLEL SAFE;
|
||||
-- }
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
-- In older versions of the extension, CDB_RectangleGrid had a different signature
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_RectangleGrid(GEOMETRY, FLOAT8, FLOAT8, GEOMETRY);
|
||||
|
||||
--
|
||||
-- Fill given extent with a rectangular coverage
|
||||
--
|
||||
@ -6,7 +9,7 @@
|
||||
-- be emitted. The returned hexagons will have the same SRID
|
||||
-- as this extent.
|
||||
--
|
||||
-- @param width With of each rectangle
|
||||
-- @param width Width of each rectangle
|
||||
--
|
||||
-- @param height Height of each rectangle
|
||||
--
|
||||
@ -14,9 +17,12 @@
|
||||
-- If omitted the origin will be 0,0.
|
||||
-- The parameter is checked for having the same SRID
|
||||
-- as the extent.
|
||||
--
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_RectangleGrid(ext GEOMETRY, width FLOAT8, height FLOAT8, origin GEOMETRY DEFAULT NULL)
|
||||
-- @param maxcells Optional maximum number of grid cells to generate;
|
||||
-- if the grid requires more cells to cover the extent
|
||||
-- and exception will occur.
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_RectangleGrid(ext GEOMETRY, width FLOAT8, height FLOAT8, origin GEOMETRY DEFAULT NULL, maxcells INTEGER DEFAULT 512*512)
|
||||
RETURNS SETOF GEOMETRY
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -38,17 +44,17 @@ DECLARE
|
||||
srid INTEGER;
|
||||
BEGIN
|
||||
|
||||
srid := ST_SRID(ext);
|
||||
srid := @postgisschema@.ST_SRID(ext);
|
||||
|
||||
xoff := 0;
|
||||
yoff := 0;
|
||||
|
||||
IF origin IS NOT NULL THEN
|
||||
IF ST_SRID(origin) != srid THEN
|
||||
IF @postgisschema@.ST_SRID(origin) != srid THEN
|
||||
RAISE EXCEPTION 'SRID mismatch between extent (%) and origin (%)', srid, ST_SRID(origin);
|
||||
END IF;
|
||||
xoff := ST_X(origin);
|
||||
yoff := ST_Y(origin);
|
||||
xoff := @postgisschema@.ST_X(origin);
|
||||
yoff := @postgisschema@.ST_Y(origin);
|
||||
END IF;
|
||||
|
||||
--RAISE DEBUG 'X offset: %', xoff;
|
||||
@ -66,11 +72,11 @@ BEGIN
|
||||
vstep := height;
|
||||
|
||||
-- Tweak horizontal start on hstep grid from origin
|
||||
hstart := xoff + ceil((ST_XMin(ext)-xoff)/hstep)*hstep;
|
||||
hstart := xoff + ceil((@postgisschema@.ST_XMin(ext)-xoff)/hstep)*hstep;
|
||||
--RAISE DEBUG 'hstart: %', hstart;
|
||||
|
||||
-- Tweak vertical start on vstep grid from origin
|
||||
vstart := yoff + ceil((ST_Ymin(ext)-yoff)/vstep)*vstep;
|
||||
vstart := yoff + ceil((@postgisschema@.ST_Ymin(ext)-yoff)/vstep)*vstep;
|
||||
--RAISE DEBUG 'vstart: %', vstart;
|
||||
|
||||
hend := ST_XMax(ext);
|
||||
@ -79,13 +85,19 @@ BEGIN
|
||||
--RAISE DEBUG 'hend: %', hend;
|
||||
--RAISE DEBUG 'vend: %', vend;
|
||||
|
||||
If maxcells IS NOT NULL AND maxcells > 0 THEN
|
||||
IF ((hend - hstart)/hstep * (vend - vstart)/vstep)::integer > maxcells THEN
|
||||
RAISE EXCEPTION 'The requested grid is too big to be rendered';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
x := hstart;
|
||||
WHILE x < hend LOOP -- over X
|
||||
y := vstart;
|
||||
h := ST_MakeEnvelope(x-hw, y-hh, x+hw, y+hh, srid);
|
||||
h := @postgisschema@.ST_MakeEnvelope(x-hw, y-hh, x+hw, y+hh, srid);
|
||||
WHILE y < vend LOOP -- over Y
|
||||
RETURN NEXT h;
|
||||
h := ST_Translate(h, 0, vstep);
|
||||
h := @postgisschema@.ST_Translate(h, 0, vstep);
|
||||
y := yoff + round(((y + vstep)-yoff)/ygrd)*ygrd; -- round to grid
|
||||
END LOOP;
|
||||
x := xoff + round(((x + hstep)-xoff)/xgrd)*xgrd; -- round to grid
|
||||
@ -93,4 +105,4 @@ BEGIN
|
||||
|
||||
RETURN;
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE PARALLEL SAFE;
|
||||
|
153
scripts-available/CDB_RegenerateTable.sql
Normal file
153
scripts-available/CDB_RegenerateTable.sql
Normal file
@ -0,0 +1,153 @@
|
||||
--
|
||||
-- Given a table, returns a series of queries that can be used to recreate it
|
||||
-- It does not include data
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_RegenerateTable_Get_Commands(tableoid OID)
|
||||
RETURNS text[]
|
||||
AS $$
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
query = "SELECT current_setting('statement_timeout') as t"
|
||||
rv = plpy.execute(query, 1)
|
||||
timeout_string = str(rv[0]['t'])
|
||||
if timeout_string == "0":
|
||||
timeout_string = "1min"
|
||||
|
||||
query = "SELECT current_database()::text as dname"
|
||||
rv = plpy.execute(query, 1)
|
||||
database_name_string = str(rv[0]['dname'])
|
||||
|
||||
query = """SELECT concat(quote_ident(nspname), '.', quote_ident(relname)) as quoted_name
|
||||
FROM pg_catalog.pg_class AS c
|
||||
JOIN pg_catalog.pg_namespace AS ns
|
||||
ON c.relnamespace = ns.oid
|
||||
WHERE c.oid = '%s'""" % (tableoid)
|
||||
rv = plpy.execute(query, 1)
|
||||
full_tablename_string = str(rv[0]['quoted_name'])
|
||||
|
||||
# NOTE: We always use -s so data is never dumped!
|
||||
# That would be a security issue that we would need to deal with (and we currently do not need it)
|
||||
process_parameters = ["pg_dump", "-s", "--lock-wait-timeout", timeout_string, "-t", full_tablename_string, database_name_string]
|
||||
|
||||
proc = subprocess.Popen(process_parameters, stdout=subprocess.PIPE, shell=False)
|
||||
(out, err) = proc.communicate()
|
||||
if (err or not out):
|
||||
plpy.error('Could not get table properties')
|
||||
|
||||
line = out.decode("utf-8")
|
||||
lines = line.rsplit(";\n", -1)
|
||||
clean_lines = []
|
||||
for i in range(0, len(lines)):
|
||||
line = lines[i]
|
||||
sublines = line.splitlines()
|
||||
sublines = [line.rstrip() for line in sublines]
|
||||
sublines = [line for line in sublines if line]
|
||||
sublines = [line for line in sublines if not line.startswith('--')]
|
||||
sublines = [line for line in sublines if not line.lower().startswith('set ')]
|
||||
sublines = [line for line in sublines if line.lower().find('pg_catalog.set_config(') == -1]
|
||||
|
||||
if len(sublines):
|
||||
clean_lines.append("".join(sublines))
|
||||
|
||||
return clean_lines
|
||||
$$
|
||||
LANGUAGE @@plpythonu@@ VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Returns a list of queries that can be used to regenerate the structure of a table
|
||||
-- The query to create the table is not included
|
||||
-- The optional parameter **ignore_cartodbfication** will remove queries related to the cartodbfication of the table
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_GetTableQueries(tableoid OID, ignore_cartodbfication BOOL DEFAULT false)
|
||||
RETURNS text[]
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
children INTEGER;
|
||||
queries TEXT[];
|
||||
BEGIN
|
||||
EXECUTE FORMAT ('SELECT count(*) FROM pg_catalog.pg_inherits WHERE inhparent = %L', tableoid)
|
||||
INTO children;
|
||||
IF children > 0 THEN
|
||||
RAISE EXCEPTION 'CDB_GetTableQueries does not support the parent of partitioned tables';
|
||||
END IF;
|
||||
IF NOT ignore_cartodbfication THEN
|
||||
EXECUTE FORMAT('
|
||||
SELECT array_agg(a)
|
||||
FROM unnest(@extschema@.__CDB_RegenerateTable_Get_Commands(%L)) a
|
||||
WHERE a NOT SIMILAR TO ''CREATE TABLE%%'';', tableoid) INTO queries;
|
||||
ELSE
|
||||
EXECUTE FORMAT('
|
||||
SELECT array_agg(a)
|
||||
FROM unnest(@extschema@.__CDB_RegenerateTable_Get_Commands(%L)) a
|
||||
WHERE a NOT SIMILAR TO ''CREATE TABLE%%'' AND
|
||||
a NOT SIMILAR TO (''%%PRIMARY KEY \(cartodb_id\)%%'') AND
|
||||
a NOT SIMILAR TO (''%%cartodb_id_seq%%'') AND
|
||||
a NOT SIMILAR TO (''%%track_updates%%'') AND
|
||||
a NOT SIMILAR TO (''%%update_the_geom_webmercator_trigger%%'') AND
|
||||
a NOT SIMILAR TO (''%%test_quota%%'') AND
|
||||
a NOT SIMILAR TO (''%%test_quota_per_row%%'') AND
|
||||
a NOT SIMILAR TO (''%%gist \(the_geom\)%%'') AND
|
||||
a NOT SIMILAR TO (''%%gist \(the_geom_webmercator\)%%'');', tableoid) INTO queries;
|
||||
END IF;
|
||||
RETURN queries;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Helper function to apply the result of CDB_GetTableQueries catching and discarding any exceptions
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_ApplyQueriesSafe(queries TEXT[])
|
||||
RETURNS void
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
i INTEGER;
|
||||
BEGIN
|
||||
IF array_length(queries, 1) > 0 THEN
|
||||
FOR i IN 1 .. array_upper(queries, 1)
|
||||
LOOP
|
||||
BEGIN
|
||||
EXECUTE queries[i];
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
CONTINUE;
|
||||
END;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL STRICT VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
-- Regenerates a table
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_RegenerateTable(tableoid OID)
|
||||
RETURNS void
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
temp_name TEXT := 'temp_' || encode(sha224(random()::text::bytea), 'hex');
|
||||
table_name TEXT;
|
||||
queries TEXT[] := @extschema@.__CDB_RegenerateTable_Get_Commands(tableoid);
|
||||
i INTEGER;
|
||||
children INTEGER;
|
||||
BEGIN
|
||||
EXECUTE FORMAT ('SELECT count(*) FROM pg_catalog.pg_inherits WHERE inhparent = %L', tableoid)
|
||||
INTO children;
|
||||
IF children > 0 THEN
|
||||
RAISE EXCEPTION 'CDB_RegenerateTable does not support the parent of partitioned tables';
|
||||
END IF;
|
||||
|
||||
EXECUTE FORMAT('SELECT concat(quote_ident(nspname), ''.'', quote_ident(relname)) as quoted_name
|
||||
FROM pg_catalog.pg_class AS c
|
||||
JOIN pg_catalog.pg_namespace AS ns
|
||||
ON c.relnamespace = ns.oid
|
||||
WHERE c.oid = %L', tableoid) INTO table_name;
|
||||
|
||||
EXECUTE FORMAT('CREATE TEMPORARY TABLE %s ON COMMIT DROP AS SELECT * FROM %s', temp_name, table_name);
|
||||
EXECUTE FORMAT('DROP TABLE %s', table_name);
|
||||
|
||||
FOR i IN 1 .. array_upper(queries, 1)
|
||||
LOOP
|
||||
EXECUTE queries[i];
|
||||
END LOOP;
|
||||
|
||||
EXECUTE FORMAT('INSERT INTO %s SELECT * FROM %I', table_name, temp_name);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
@ -1,4 +1,4 @@
|
||||
---- Make sure 'cartodb' is in database search path
|
||||
---- Make sure '@extschema@' is in database search path
|
||||
DO
|
||||
$$
|
||||
DECLARE
|
||||
@ -8,13 +8,13 @@ BEGIN
|
||||
SELECT reset_val INTO var_cur_search_path
|
||||
FROM pg_settings WHERE name = 'search_path';
|
||||
|
||||
IF var_cur_search_path LIKE '%cartodb%' THEN
|
||||
RAISE DEBUG '"cartodb" already in database search_path';
|
||||
IF var_cur_search_path LIKE '%@extschema@%' THEN
|
||||
RAISE DEBUG '"@extschema@" already in database search_path';
|
||||
ELSE
|
||||
var_cur_search_path := var_cur_search_path || ', "cartodb"';
|
||||
var_cur_search_path := var_cur_search_path || ', "@extschema@"';
|
||||
EXECUTE 'ALTER DATABASE ' || quote_ident(current_database()) ||
|
||||
' SET search_path = ' || var_cur_search_path;
|
||||
RAISE DEBUG '"cartodb" has been added to end of database search_path';
|
||||
RAISE DEBUG '"@extschema@" has been added to end of database search_path';
|
||||
END IF;
|
||||
|
||||
-- Reset search_path
|
||||
|
@ -4,44 +4,50 @@
|
||||
-- @param in_array A numeric array of numbers
|
||||
--
|
||||
-- Returns: statistical quantity chosen
|
||||
--
|
||||
--
|
||||
-- References: http://www.itl.nist.gov/div898/handbook/eda/section3/eda35b.htm
|
||||
--
|
||||
|
||||
-- Calculate kurtosis
|
||||
CREATE OR REPLACE FUNCTION CDB_Kurtosis ( in_array NUMERIC[] ) RETURNS NUMERIC as $$
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Kurtosis ( in_array NUMERIC[] ) RETURNS NUMERIC as $$
|
||||
DECLARE
|
||||
a numeric;
|
||||
c numeric;
|
||||
s numeric;
|
||||
k numeric;
|
||||
BEGIN
|
||||
SELECT AVG(e), COUNT(e)::numeric, stddev(e) INTO a, c, s FROM ( SELECT unnest(in_array) e ) x;
|
||||
SELECT AVG(e), COUNT(e)::numeric * power(stddev(e),4) INTO a, c FROM ( SELECT unnest(in_array) e ) x;
|
||||
|
||||
EXECUTE 'SELECT sum(power($1 - e, 4)) / ( $2 * power($3, 4)) - 3
|
||||
FROM (SELECT unnest($4) e ) x'
|
||||
INTO k
|
||||
USING a, c, s, in_array;
|
||||
IF c=0 THEN
|
||||
RETURN 0;
|
||||
ELSE
|
||||
|
||||
RETURN k;
|
||||
EXECUTE 'SELECT sum(power($1 - e, 4)) / ($2 ) - 3
|
||||
FROM (SELECT unnest($3) e ) x'
|
||||
INTO k
|
||||
USING a, c, in_array;
|
||||
|
||||
RETURN k;
|
||||
END IF;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
$$ language plpgsql IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
||||
-- Calculate skewness
|
||||
CREATE OR REPLACE FUNCTION CDB_Skewness ( in_array NUMERIC[] ) RETURNS NUMERIC as $$
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Skewness ( in_array NUMERIC[] ) RETURNS NUMERIC as $$
|
||||
DECLARE
|
||||
a numeric;
|
||||
c numeric;
|
||||
s numeric;
|
||||
sk numeric;
|
||||
BEGIN
|
||||
SELECT AVG(e), COUNT(e)::numeric, stddev(e) INTO a, c, s FROM ( SELECT unnest(in_array) e ) x;
|
||||
SELECT AVG(e), COUNT(e)::numeric * power(stddev(e),3) INTO a, c FROM ( SELECT unnest(in_array) e ) x;
|
||||
IF c=0 THEN
|
||||
RETURN 0;
|
||||
ELSE
|
||||
EXECUTE 'SELECT sum(power($1 - e, 3)) / ( $2 )
|
||||
FROM (SELECT unnest($3) e ) x'
|
||||
INTO sk
|
||||
USING a, c, in_array;
|
||||
|
||||
EXECUTE 'SELECT sum(power($1 - e, 3)) / ( $2 * power($3, 3))
|
||||
FROM (SELECT unnest($4) e ) x'
|
||||
INTO sk
|
||||
USING a, c, s, in_array;
|
||||
|
||||
RETURN sk;
|
||||
RETURN sk;
|
||||
END IF;
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
$$ language plpgsql IMMUTABLE STRICT PARALLEL SAFE;
|
||||
|
@ -1,7 +1,7 @@
|
||||
-- Convert string to date
|
||||
--
|
||||
DROP FUNCTION IF EXISTS CDB_StringToDate(character varying);
|
||||
CREATE OR REPLACE FUNCTION CDB_StringToDate(input character varying)
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_StringToDate(character varying);
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_StringToDate(input character varying)
|
||||
RETURNS TIMESTAMP AS $$
|
||||
DECLARE output TIMESTAMP;
|
||||
BEGIN
|
||||
@ -17,4 +17,4 @@ BEGIN
|
||||
RETURN output;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' STABLE STRICT;
|
||||
LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL UNSAFE;
|
||||
|
167
scripts-available/CDB_SyncTable.sql
Normal file
167
scripts-available/CDB_SyncTable.sql
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
Gets the column names of a given table.
|
||||
|
||||
Sample usage:
|
||||
|
||||
SELECT @extschema@._CDB_GetColumns('public.films');
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_GetColumns(src_table REGCLASS)
|
||||
RETURNS SETOF NAME
|
||||
AS $$
|
||||
SELECT
|
||||
a.attname as "colname"
|
||||
FROM
|
||||
pg_catalog.pg_attribute a
|
||||
WHERE
|
||||
a.attnum > 0
|
||||
AND NOT a.attisdropped
|
||||
AND a.attrelid = (
|
||||
SELECT c.oid
|
||||
FROM pg_catalog.pg_class c
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.oid = src_table::oid
|
||||
AND pg_catalog.pg_table_is_visible(c.oid)
|
||||
)
|
||||
ORDER BY a.attnum;
|
||||
$$ LANGUAGE sql STABLE PARALLEL UNSAFE;
|
||||
|
||||
|
||||
/*
|
||||
Given an array of quoted column names, it generates an UPDATE SET
|
||||
clause with the following form:
|
||||
|
||||
the_geom = changed.the_geom,
|
||||
id = changed.id,
|
||||
elevation = changed.elevation
|
||||
|
||||
Example of usage:
|
||||
|
||||
SELECT @extschema@.__CDB_GetUpdateSetClause('{the_geom, id, elevation}', 'changed');
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_GetUpdateSetClause(colnames TEXT[], update_source TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
set_clause_list TEXT[];
|
||||
col TEXT;
|
||||
BEGIN
|
||||
FOREACH col IN ARRAY colnames
|
||||
LOOP
|
||||
set_clause_list := array_append(set_clause_list, format('%1$s = %2$s.%1$s', col, update_source));
|
||||
END lOOP;
|
||||
RETURN array_to_string(set_clause_list, ', ');
|
||||
END;
|
||||
$$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
|
||||
/*
|
||||
Given a prefix, generate a safe unique NAME for a temp table.
|
||||
|
||||
Example of usage:
|
||||
|
||||
SELECT @extschema@.__CDB_GenerateUniqueName('src_sync'); --> src_sync_718794_120106
|
||||
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_GenerateUniqueName(prefix TEXT)
|
||||
RETURNS NAME
|
||||
AS $$
|
||||
SELECT format('%s_%s_%s', prefix, txid_current(), (random()*1000000)::int)::NAME;
|
||||
$$ LANGUAGE sql VOLATILE PARALLEL UNSAFE;
|
||||
|
||||
/*
|
||||
Given a table name and an array of column names,
|
||||
return array of column names qualified with the table name and quoted when necessary
|
||||
tablename and colnames should be properly quoted, and for this reason the type NAME is not
|
||||
used for them (with quotes they could exceed the maximum identifier length)
|
||||
|
||||
Example of usage:
|
||||
|
||||
SELECT @extschema@.__CDB_QualifyColumns('t', ARRAY['a','"b-1"']); --> ARRAY['t.a','t."b-1"']
|
||||
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION @extschema@.__CDB_QualifyColumns(tablename NAME, colnames NAME[]) RETURNS TEXT[] AS
|
||||
$$
|
||||
SELECT array_agg(tablename || '.' || _colname) from unnest(colnames) _colname;
|
||||
$$ LANGUAGE sql IMMUTABLE PARALLEL SAFE;
|
||||
|
||||
/*
|
||||
A Table Syncer
|
||||
|
||||
Assumptions:
|
||||
- Both tables contain a consistent cartodb_id column
|
||||
- Destination table has all columns of the source or does not exist
|
||||
|
||||
Sample usage:
|
||||
|
||||
SELECT CDB_SyncTable('radar_stations', 'public', 'syncdest');
|
||||
SELECT CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest', '{the_geom, the_geom_webmercator}');
|
||||
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_SyncTable(src_table REGCLASS, dst_schema REGNAMESPACE, dst_table NAME, skip_cols NAME[] = '{}')
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
fq_dest_table TEXT;
|
||||
|
||||
colnames TEXT[];
|
||||
dst_colnames TEXT;
|
||||
src_colnames TEXT;
|
||||
|
||||
update_set_clause TEXT;
|
||||
|
||||
num_rows BIGINT;
|
||||
err_context text;
|
||||
|
||||
t timestamptz;
|
||||
BEGIN
|
||||
-- If the destination table does not exist, just copy the source table
|
||||
fq_dest_table := format('%s.%I', dst_schema, dst_table);
|
||||
EXECUTE format('CREATE TABLE IF NOT EXISTS %s as TABLE %s', fq_dest_table, src_table);
|
||||
GET DIAGNOSTICS num_rows = ROW_COUNT;
|
||||
IF num_rows > 0 THEN
|
||||
RAISE NOTICE 'INSERTED % row(s)', num_rows;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
skip_cols := skip_cols || '{cartodb_id}';
|
||||
|
||||
-- Get the list of columns from the source table, excluding skip_cols
|
||||
SELECT ARRAY(SELECT quote_ident(c) FROM @extschema@._CDB_GetColumns(src_table) as c EXCEPT SELECT unnest(skip_cols)) INTO colnames;
|
||||
|
||||
-- Deal with deleted rows: ids in dest but not in source
|
||||
t := clock_timestamp();
|
||||
EXECUTE format(
|
||||
'DELETE FROM %1$s _dst WHERE NOT EXISTS (SELECT * FROM %2$s _src WHERE _src.cartodb_id=_dst.cartodb_id)',
|
||||
fq_dest_table, src_table);
|
||||
GET DIAGNOSTICS num_rows = ROW_COUNT;
|
||||
RAISE NOTICE 'DELETED % row(s)', num_rows;
|
||||
RAISE DEBUG 'DELETE time (s): %', clock_timestamp() - t;
|
||||
|
||||
-- Deal with inserted rows: ids in source but not in dest
|
||||
t := clock_timestamp();
|
||||
EXECUTE format('
|
||||
INSERT INTO %1$s(cartodb_id, %2$s)
|
||||
SELECT cartodb_id, %2$s FROM %3$s _src WHERE NOT EXISTS (SELECT * FROM %1$s _dst WHERE _src.cartodb_id=_dst.cartodb_id)
|
||||
', fq_dest_table, array_to_string(colnames, ','), src_table);
|
||||
GET DIAGNOSTICS num_rows = ROW_COUNT;
|
||||
RAISE NOTICE 'INSERTED % row(s)', num_rows;
|
||||
RAISE DEBUG 'INSERT time (s): %', clock_timestamp() - t;
|
||||
|
||||
-- Deal with modified rows: ids in source and dest but different hashes
|
||||
t := clock_timestamp();
|
||||
update_set_clause := @extschema@.__CDB_GetUpdateSetClause(colnames, '_changed');
|
||||
dst_colnames := array_to_string(@extschema@.__CDB_QualifyColumns('_dst', colnames), ',');
|
||||
src_colnames := array_to_string(@extschema@.__CDB_QualifyColumns('_src', colnames), ',');
|
||||
EXECUTE format('
|
||||
UPDATE %1$s _update SET %2$s
|
||||
FROM (
|
||||
SELECT _src.* FROM %3$s _src JOIN %1$s _dst ON (_dst.cartodb_id = _src.cartodb_id)
|
||||
WHERE md5(ROW(%4$s)::text) <> md5(ROW(%5$s)::text)
|
||||
) _changed
|
||||
WHERE _update.cartodb_id = _changed.cartodb_id;
|
||||
', fq_dest_table, update_set_clause, src_table, dst_colnames, src_colnames);
|
||||
GET DIAGNOSTICS num_rows = ROW_COUNT;
|
||||
RAISE NOTICE 'MODIFIED % row(s)', num_rows;
|
||||
RAISE DEBUG 'UPDATE time (s): %', clock_timestamp() - t;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
|
@ -1,5 +1,5 @@
|
||||
-- Function returning indexes for a table
|
||||
CREATE OR REPLACE FUNCTION CDB_TableIndexes(REGCLASS)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_TableIndexes(REGCLASS)
|
||||
RETURNS TABLE(index_name name, index_unique bool, index_primary bool, index_keys text array)
|
||||
AS $$
|
||||
|
||||
@ -16,12 +16,12 @@ AS $$
|
||||
JOIN pg_class
|
||||
ON pg_class.oid = idx.indexrelid
|
||||
WHERE pg_indexes.tablename = '' || $1 || ''
|
||||
AND '' || $1 || '' IN (SELECT CDB_UserTables())
|
||||
AND '' || $1 || '' IN (SELECT @extschema@.CDB_UserTables())
|
||||
AND pg_class.relname=pg_indexes.indexname
|
||||
;
|
||||
|
||||
$$ LANGUAGE SQL;
|
||||
$$ LANGUAGE SQL STABLE PARALLEL SAFE;
|
||||
|
||||
-- This is to migrate from pre-0.2.0 version
|
||||
-- See http://github.com/CartoDB/cartodb-postgresql/issues/36
|
||||
GRANT EXECUTE ON FUNCTION CDB_TableIndexes(REGCLASS) TO public;
|
||||
GRANT EXECUTE ON FUNCTION @extschema@.CDB_TableIndexes(REGCLASS) TO public;
|
||||
|
@ -1,18 +1,18 @@
|
||||
|
||||
CREATE TABLE IF NOT EXISTS
|
||||
public.CDB_TableMetadata (
|
||||
@extschema@.CDB_TableMetadata (
|
||||
tabname regclass not null primary key,
|
||||
updated_at timestamp with time zone not null default now()
|
||||
);
|
||||
|
||||
CREATE OR REPLACE VIEW public.CDB_TableMetadata_Text AS
|
||||
CREATE OR REPLACE VIEW @extschema@.CDB_TableMetadata_Text AS
|
||||
SELECT FORMAT('%I.%I', n.nspname::text, c.relname::text) tabname, updated_at
|
||||
FROM public.CDB_TableMetadata, pg_catalog.pg_class c
|
||||
FROM @extschema@.CDB_TableMetadata m JOIN pg_catalog.pg_class c ON m.tabname::oid = c.oid
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid;
|
||||
|
||||
-- No one can see this
|
||||
-- Updates are only possible trough the security definer trigger
|
||||
-- GRANT SELECT ON public.CDB_TableMetadata TO public;
|
||||
-- GRANT SELECT ON @extschema@.CDB_TableMetadata TO public;
|
||||
|
||||
--
|
||||
-- Trigger logging updated_at in the CDB_TableMetadata
|
||||
@ -27,42 +27,45 @@ CREATE OR REPLACE VIEW public.CDB_TableMetadata_Text AS
|
||||
--
|
||||
-- NOTE: _never_ attach to CDB_TableMetadata ...
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_TableMetadata_Trigger()
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_TableMetadata_Trigger()
|
||||
RETURNS trigger AS
|
||||
$$
|
||||
BEGIN
|
||||
-- Guard against infinite loop
|
||||
IF TG_RELID = 'public.CDB_TableMetadata'::regclass::oid THEN
|
||||
IF TG_RELID = '@extschema@.CDB_TableMetadata'::regclass::oid THEN
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
|
||||
-- Cleanup stale entries
|
||||
DELETE FROM public.CDB_TableMetadata
|
||||
DELETE FROM @extschema@.CDB_TableMetadata
|
||||
WHERE NOT EXISTS (
|
||||
SELECT oid FROM pg_class WHERE oid = tabname
|
||||
);
|
||||
|
||||
WITH nv as (
|
||||
SELECT TG_RELID as tabname, NOW() as t
|
||||
SELECT TG_RELID as tabname, now() as t
|
||||
), updated as (
|
||||
UPDATE public.CDB_TableMetadata x SET updated_at = nv.t
|
||||
UPDATE @extschema@.CDB_TableMetadata x SET updated_at = nv.t
|
||||
FROM nv WHERE x.tabname = nv.tabname
|
||||
RETURNING x.tabname
|
||||
)
|
||||
INSERT INTO public.CDB_TableMetadata SELECT nv.*
|
||||
INSERT INTO @extschema@.CDB_TableMetadata SELECT nv.*
|
||||
FROM nv LEFT JOIN updated USING(tabname)
|
||||
WHERE updated.tabname IS NULL;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
--
|
||||
-- Trigger invalidating varnish whenever CDB_TableMetadata
|
||||
-- record change.
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION _CDB_TableMetadata_Updated()
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_TableMetadata_Updated()
|
||||
RETURNS trigger AS
|
||||
$$
|
||||
DECLARE
|
||||
@ -93,14 +96,14 @@ BEGIN
|
||||
--
|
||||
|
||||
-- Call the first varnish invalidation function owned
|
||||
-- by a superuser found in cartodb or public schema
|
||||
-- by a superuser found in @extschema@ or public schema
|
||||
-- (in that order)
|
||||
found := false;
|
||||
FOR rec IN SELECT u.usesuper, u.usename, n.nspname, p.proname
|
||||
FROM pg_proc p, pg_namespace n, pg_user u
|
||||
WHERE p.proname = 'cdb_invalidate_varnish'
|
||||
AND p.pronamespace = n.oid
|
||||
AND n.nspname IN ('public', 'cartodb')
|
||||
AND n.nspname IN ('public', '@extschema@')
|
||||
AND u.usesysid = p.proowner
|
||||
AND u.usesuper
|
||||
ORDER BY n.nspname
|
||||
@ -116,31 +119,34 @@ BEGIN
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
||||
$$ LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
PARALLEL UNSAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
||||
|
||||
DROP TRIGGER IF EXISTS table_modified ON public.CDB_TableMetadata;
|
||||
DROP TRIGGER IF EXISTS table_modified ON @extschema@.CDB_TableMetadata;
|
||||
-- NOTE: on DELETE we would be unable to convert the table
|
||||
-- oid (regclass) to its name
|
||||
CREATE TRIGGER table_modified AFTER INSERT OR UPDATE
|
||||
ON public.CDB_TableMetadata FOR EACH ROW EXECUTE PROCEDURE
|
||||
_CDB_TableMetadata_Updated();
|
||||
ON @extschema@.CDB_TableMetadata FOR EACH ROW EXECUTE PROCEDURE
|
||||
@extschema@._CDB_TableMetadata_Updated();
|
||||
|
||||
|
||||
-- similar to TOUCH(1) in unix filesystems but for table in cdb_tablemetadata
|
||||
CREATE OR REPLACE FUNCTION public.CDB_TableMetadataTouch(tablename regclass)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_TableMetadataTouch(tablename regclass)
|
||||
RETURNS void AS
|
||||
$$
|
||||
BEGIN
|
||||
WITH upsert AS (
|
||||
UPDATE public.cdb_tablemetadata
|
||||
UPDATE @extschema@.cdb_tablemetadata
|
||||
SET updated_at = NOW()
|
||||
WHERE tabname = tablename
|
||||
RETURNING *
|
||||
)
|
||||
INSERT INTO public.cdb_tablemetadata (tabname, updated_at)
|
||||
INSERT INTO @extschema@.cdb_tablemetadata (tabname, updated_at)
|
||||
SELECT tablename, NOW()
|
||||
WHERE NOT EXISTS (SELECT * FROM upsert);
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql' VOLATILE STRICT;
|
||||
LANGUAGE 'plpgsql' VOLATILE STRICT PARALLEL UNSAFE;
|
||||
|
@ -5,19 +5,19 @@
|
||||
-- for web mercator by "clipping" anything outside to the valid
|
||||
-- range.
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_TransformToWebmercator(geom geometry)
|
||||
RETURNS geometry
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_TransformToWebmercator(geom @postgisschema@.geometry)
|
||||
RETURNS @postgisschema@.geometry
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
valid_extent GEOMETRY;
|
||||
latlon_input GEOMETRY;
|
||||
clipped_input GEOMETRY;
|
||||
to_webmercator GEOMETRY;
|
||||
ret GEOMETRY;
|
||||
valid_extent @postgisschema@.GEOMETRY;
|
||||
latlon_input @postgisschema@.GEOMETRY;
|
||||
clipped_input @postgisschema@.GEOMETRY;
|
||||
to_webmercator @postgisschema@.GEOMETRY;
|
||||
ret @postgisschema@.GEOMETRY;
|
||||
BEGIN
|
||||
|
||||
IF ST_Srid(geom) = 3857 THEN
|
||||
IF @postgisschema@.ST_Srid(geom) = 3857 THEN
|
||||
RETURN geom;
|
||||
END IF;
|
||||
|
||||
@ -27,52 +27,56 @@ BEGIN
|
||||
-- to -85.0511 to 85.0511 but as long as proj
|
||||
-- does not complain we are happy
|
||||
--
|
||||
valid_extent := ST_MakeEnvelope(-180, -89, 180, 89, 4326);
|
||||
valid_extent := @postgisschema@.ST_MakeEnvelope(-180, -89, 180, 89, 4326);
|
||||
|
||||
-- Then we transform to WGS84 latlon, which is
|
||||
-- where we have known coordinates for the clipping
|
||||
--
|
||||
latlon_input := ST_Transform(geom, 4326);
|
||||
--
|
||||
latlon_input := @postgisschema@.ST_Transform(geom, 4326);
|
||||
|
||||
-- Don't bother clipping if the geometry boundary doesn't
|
||||
-- go outside the valid extent.
|
||||
IF latlon_input @ valid_extent THEN
|
||||
RETURN ST_Transform(latlon_input, 3857);
|
||||
IF @postgisschema@.geometry_within(latlon_input, valid_extent) THEN
|
||||
BEGIN
|
||||
RETURN @postgisschema@.ST_Transform(latlon_input, 3857);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RETURN NULL;
|
||||
END;
|
||||
END IF;
|
||||
|
||||
-- Since we're going to use ST_Intersection on input
|
||||
-- we'd better ensure the input is valid
|
||||
-- TODO: only do this if the first ST_Intersection fails ?
|
||||
IF ST_Dimension(geom) != 0 AND
|
||||
IF @postgisschema@.ST_Dimension(geom) != 0 AND
|
||||
-- See http://trac.osgeo.org/postgis/ticket/1719
|
||||
GeometryType(geom) != 'GEOMETRYCOLLECTION'
|
||||
@postgisschema@.GeometryType(geom) != 'GEOMETRYCOLLECTION'
|
||||
THEN
|
||||
BEGIN
|
||||
latlon_input := ST_MakeValid(latlon_input);
|
||||
latlon_input := @postgisschema@.ST_MakeValid(latlon_input);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
-- See http://github.com/Vizzuality/cartodb/issues/931
|
||||
RAISE WARNING 'Could not clean input geometry: %', SQLERRM;
|
||||
RETURN NULL;
|
||||
RETURN NULL;
|
||||
END;
|
||||
latlon_input := ST_CollectionExtract(latlon_input, ST_Dimension(geom)+1);
|
||||
latlon_input := @postgisschema@.ST_CollectionExtract(latlon_input, ST_Dimension(geom)+1);
|
||||
END IF;
|
||||
|
||||
-- Then we clip, trying to retain the input type
|
||||
-- TODO: catch exceptions here too ?
|
||||
clipped_input := ST_Intersection(latlon_input, valid_extent);
|
||||
clipped_input := @postgisschema@.ST_Intersection(latlon_input, valid_extent);
|
||||
|
||||
-- We transform to web mercator
|
||||
to_webmercator := ST_Transform(clipped_input, 3857);
|
||||
to_webmercator := @postgisschema@.ST_Transform(clipped_input, 3857);
|
||||
|
||||
-- Finally we convert EMPTY to NULL
|
||||
-- Finally we convert EMPTY to NULL
|
||||
-- See https://github.com/Vizzuality/cartodb/issues/706
|
||||
-- And retain "multi" status
|
||||
ret := CASE WHEN ST_IsEmpty(to_webmercator) THEN NULL::geometry
|
||||
WHEN GeometryType(geom) LIKE 'MULTI%' THEN ST_Multi(to_webmercator)
|
||||
ret := CASE WHEN @postgisschema@.ST_IsEmpty(to_webmercator) THEN NULL::@postgisschema@.geometry
|
||||
WHEN @postgisschema@.GeometryType(geom) LIKE 'MULTI%' THEN @postgisschema@.ST_Multi(to_webmercator)
|
||||
ELSE to_webmercator
|
||||
END;
|
||||
|
||||
RETURN ret;
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL UNSAFE;
|
||||
|
@ -5,8 +5,8 @@
|
||||
--
|
||||
-- Currently accepted permissions are: 'public', 'private' or 'all'
|
||||
--
|
||||
DROP FUNCTION IF EXISTS CDB_UserTables(text);
|
||||
CREATE OR REPLACE FUNCTION CDB_UserTables(perm text DEFAULT 'all')
|
||||
DROP FUNCTION IF EXISTS @extschema@.CDB_UserTables(text);
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_UserTables(perm text DEFAULT 'all')
|
||||
RETURNS SETOF name
|
||||
AS $$
|
||||
|
||||
@ -14,15 +14,15 @@ SELECT c.relname
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relkind = 'r'
|
||||
AND c.relname NOT IN ('cdb_tablemetadata', 'spatial_ref_sys')
|
||||
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'topology', 'cartodb')
|
||||
AND c.relname NOT IN ('cdb_tablemetadata', 'cdb_analysis_catalog', 'cdb_conf', 'spatial_ref_sys')
|
||||
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'topology', '@extschema@')
|
||||
AND CASE WHEN perm = 'public' THEN has_table_privilege('publicuser', c.oid, 'SELECT')
|
||||
WHEN perm = 'private' THEN has_table_privilege(current_user, c.oid, 'SELECT') AND NOT has_table_privilege('publicuser', c.oid, 'SELECT')
|
||||
WHEN perm = 'all' THEN has_table_privilege(current_user, c.oid, 'SELECT') OR has_table_privilege('publicuser', c.oid, 'SELECT')
|
||||
ELSE false END;
|
||||
|
||||
$$ LANGUAGE 'sql';
|
||||
$$ LANGUAGE 'sql' STABLE PARALLEL SAFE;
|
||||
|
||||
-- This is to migrate from pre-0.2.0 version
|
||||
-- See http://github.com/CartoDB/cartodb-postgresql/issues/36
|
||||
GRANT EXECUTE ON FUNCTION CDB_UserTables(text) TO public;
|
||||
GRANT EXECUTE ON FUNCTION @extschema@.CDB_UserTables(text) TO public;
|
||||
|
10
scripts-available/CDB_Username.sql
Normal file
10
scripts-available/CDB_Username.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- Returns the cartodb username of the current PostgreSQL session
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_Username()
|
||||
RETURNS text
|
||||
AS $$
|
||||
SELECT @extschema@.CDB_Conf_GetConf(concat('api_keys_', session_user))->>'username';
|
||||
$$ LANGUAGE SQL
|
||||
STABLE
|
||||
PARALLEL SAFE
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_temp;
|
@ -1,13 +1,12 @@
|
||||
|
||||
-- {
|
||||
-- Return pixel resolution at the given zoom level
|
||||
-- }{
|
||||
CREATE OR REPLACE FUNCTION CDB_XYZ_Resolution(z INTEGER)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_XYZ_Resolution(z INTEGER)
|
||||
RETURNS FLOAT8
|
||||
AS $$
|
||||
-- circumference divided by 256 is z0 resolution, then divide by 2^z
|
||||
SELECT 6378137.0*2.0*pi() / 256.0 / power(2.0, z);
|
||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
||||
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE STRICT;
|
||||
-- }
|
||||
|
||||
-- {
|
||||
@ -16,7 +15,7 @@ $$ LANGUAGE SQL IMMUTABLE STRICT;
|
||||
-- SRID of the returned polygon is forceably 3857
|
||||
--
|
||||
-- }{
|
||||
CREATE OR REPLACE FUNCTION CDB_XYZ_Extent(x INTEGER, y INTEGER, z INTEGER)
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_XYZ_Extent(x INTEGER, y INTEGER, z INTEGER)
|
||||
RETURNS GEOMETRY
|
||||
AS $$
|
||||
DECLARE
|
||||
@ -35,7 +34,7 @@ BEGIN
|
||||
-- Size of each tile in pixels (1:1 aspect ratio)
|
||||
tile_size := 256;
|
||||
|
||||
initial_resolution := CDB_XYZ_Resolution(0);
|
||||
initial_resolution := @extschema@.CDB_XYZ_Resolution(0);
|
||||
--RAISE DEBUG 'Initial resolution: %', initial_resolution;
|
||||
|
||||
origin_shift := (initial_resolution * tile_size) / 2.0;
|
||||
@ -57,8 +56,7 @@ BEGIN
|
||||
--RAISE DEBUG 'ymin: %', ymin;
|
||||
--RAISE DEBUG 'ymax: %', ymax;
|
||||
|
||||
RETURN ST_MakeEnvelope(xmin, ymin, xmax, ymax, 3857);
|
||||
|
||||
RETURN @postgisschema@.ST_MakeEnvelope(xmin, ymin, xmax, ymax, 3857);
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL SAFE;
|
||||
-- }
|
||||
|
@ -1,30 +1,36 @@
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_ZoomFromScale(scaleDenominator numeric) RETURNS int AS $$
|
||||
BEGIN
|
||||
CASE
|
||||
WHEN scaleDenominator > 500000000 THEN RETURN 0;
|
||||
WHEN scaleDenominator <= 500000000 AND scaleDenominator > 200000000 THEN RETURN 1;
|
||||
WHEN scaleDenominator <= 200000000 AND scaleDenominator > 100000000 THEN RETURN 2;
|
||||
WHEN scaleDenominator <= 100000000 AND scaleDenominator > 50000000 THEN RETURN 3;
|
||||
WHEN scaleDenominator <= 50000000 AND scaleDenominator > 25000000 THEN RETURN 4;
|
||||
WHEN scaleDenominator <= 25000000 AND scaleDenominator > 12500000 THEN RETURN 5;
|
||||
WHEN scaleDenominator <= 12500000 AND scaleDenominator > 6500000 THEN RETURN 6;
|
||||
WHEN scaleDenominator <= 6500000 AND scaleDenominator > 3000000 THEN RETURN 7;
|
||||
WHEN scaleDenominator <= 3000000 AND scaleDenominator > 1500000 THEN RETURN 8;
|
||||
WHEN scaleDenominator <= 1500000 AND scaleDenominator > 750000 THEN RETURN 9;
|
||||
WHEN scaleDenominator <= 750000 AND scaleDenominator > 400000 THEN RETURN 10;
|
||||
WHEN scaleDenominator <= 400000 AND scaleDenominator > 200000 THEN RETURN 11;
|
||||
WHEN scaleDenominator <= 200000 AND scaleDenominator > 100000 THEN RETURN 12;
|
||||
WHEN scaleDenominator <= 100000 AND scaleDenominator > 50000 THEN RETURN 13;
|
||||
WHEN scaleDenominator <= 50000 AND scaleDenominator > 25000 THEN RETURN 14;
|
||||
WHEN scaleDenominator <= 25000 AND scaleDenominator > 12500 THEN RETURN 15;
|
||||
WHEN scaleDenominator <= 12500 AND scaleDenominator > 5000 THEN RETURN 16;
|
||||
WHEN scaleDenominator <= 5000 AND scaleDenominator > 2500 THEN RETURN 17;
|
||||
WHEN scaleDenominator <= 2500 AND scaleDenominator > 1500 THEN RETURN 18;
|
||||
WHEN scaleDenominator <= 1500 AND scaleDenominator > 750 THEN RETURN 19;
|
||||
WHEN scaleDenominator <= 750 AND scaleDenominator > 500 THEN RETURN 20;
|
||||
WHEN scaleDenominator <= 500 AND scaleDenominator > 250 THEN RETURN 21;
|
||||
WHEN scaleDenominator <= 250 AND scaleDenominator > 100 THEN RETURN 22;
|
||||
WHEN scaleDenominator <= 100 THEN RETURN 23;
|
||||
END CASE;
|
||||
END
|
||||
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||
-- Maximum supported zoom level
|
||||
CREATE OR REPLACE FUNCTION @extschema@._CDB_MaxSupportedZoom()
|
||||
RETURNS int
|
||||
LANGUAGE SQL
|
||||
IMMUTABLE PARALLEL SAFE
|
||||
AS $$
|
||||
-- The maximum zoom level has to be limited for various reasons,
|
||||
-- e.g. zoom levels greater than 31 would require tile coordinates
|
||||
-- that would not fit in an INTEGER (which is signed, 32 bits long).
|
||||
-- We'll choose 20 as a limit which is safe also when the JavaScript shift
|
||||
-- operator (<<) is used for computing powers of two.
|
||||
SELECT 29;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION @extschema@.CDB_ZoomFromScale(scaleDenominator numeric)
|
||||
RETURNS int
|
||||
LANGUAGE SQL
|
||||
IMMUTABLE PARALLEL SAFE
|
||||
AS $$
|
||||
SELECT
|
||||
CASE
|
||||
WHEN scaleDenominator > 600000000 THEN
|
||||
-- Scale is smaller than zoom level 0
|
||||
NULL
|
||||
WHEN scaleDenominator = 0 THEN
|
||||
-- Actual zoom level would be infinite
|
||||
@extschema@._CDB_MaxSupportedZoom()
|
||||
ELSE
|
||||
CAST (
|
||||
LEAST(
|
||||
ROUND(LOG(2, 559082264.028/scaleDenominator)),
|
||||
@extschema@._CDB_MaxSupportedZoom()
|
||||
)
|
||||
AS INTEGER)
|
||||
END;
|
||||
$$;
|
||||
|
1
scripts-enabled/085-CDB_OverviewsSupport.sql
Symbolic link
1
scripts-enabled/085-CDB_OverviewsSupport.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_OverviewsSupport.sql
|
1
scripts-enabled/270-CDB_AnalysisSupport.sql
Symbolic link
1
scripts-enabled/270-CDB_AnalysisSupport.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_AnalysisSupport.sql
|
1
scripts-enabled/275-CDB_AnalysisCheck.sql
Symbolic link
1
scripts-enabled/275-CDB_AnalysisCheck.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_AnalysisCheck.sql
|
1
scripts-enabled/280-CDB_EstimateRowCount.sql
Symbolic link
1
scripts-enabled/280-CDB_EstimateRowCount.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_EstimateRowCount.sql
|
1
scripts-enabled/290-CDB_GhostTables.sql
Symbolic link
1
scripts-enabled/290-CDB_GhostTables.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_GhostTables.sql
|
1
scripts-enabled/300-CDB_OAuth.sql
Symbolic link
1
scripts-enabled/300-CDB_OAuth.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_OAuth.sql
|
1
scripts-enabled/400-CDB_FederatedServer.sql
Symbolic link
1
scripts-enabled/400-CDB_FederatedServer.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_FederatedServer.sql
|
1
scripts-enabled/401_CDB_FederatedServerTables.sql
Symbolic link
1
scripts-enabled/401_CDB_FederatedServerTables.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_FederatedServerTables.sql
|
1
scripts-enabled/402_CDB_FederatedServerListRemote.sql
Symbolic link
1
scripts-enabled/402_CDB_FederatedServerListRemote.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_FederatedServerListRemote.sql
|
1
scripts-enabled/403-CDB_FederatedServerDiagnostics.sql
Symbolic link
1
scripts-enabled/403-CDB_FederatedServerDiagnostics.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_FederatedServerDiagnostics.sql
|
1
scripts-enabled/500-CDB_RegenerateTable.sql
Symbolic link
1
scripts-enabled/500-CDB_RegenerateTable.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_RegenerateTable.sql
|
1
scripts-enabled/900-CDB_DDLTriggers.sql
Symbolic link
1
scripts-enabled/900-CDB_DDLTriggers.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_DDLTriggers.sql
|
1
scripts-enabled/920-CDB_Username.sql
Symbolic link
1
scripts-enabled/920-CDB_Username.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_Username.sql
|
1
scripts-enabled/CDB_SyncTable.sql
Symbolic link
1
scripts-enabled/CDB_SyncTable.sql
Symbolic link
@ -0,0 +1 @@
|
||||
../scripts-available/CDB_SyncTable.sql
|
@ -1,9 +1,14 @@
|
||||
\set ECHO none
|
||||
\set QUIET on
|
||||
SET client_min_messages TO error;
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION plpythonu;
|
||||
CREATE EXTENSION cartodb;
|
||||
CREATE EXTENSION @@plpythonu@@;
|
||||
CREATE SCHEMA cartodb;
|
||||
\i 'cartodb--unpackaged--@@VERSION@@.sql'
|
||||
CREATE FUNCTION public.cdb_invalidate_varnish(table_name text)
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'cdb_invalidate_varnish(%) called', table_name;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
\set QUIET off
|
||||
|
1
sql/test_setup_expect
Normal file
1
sql/test_setup_expect
Normal file
@ -0,0 +1 @@
|
||||
\set ECHO none
|
20
test/CDB_AnalysisCheckTest.sql
Normal file
20
test/CDB_AnalysisCheckTest.sql
Normal file
@ -0,0 +1,20 @@
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
|
||||
SELECT CDB_SetUserQuotaInBytes(1000000);
|
||||
SELECT _CDB_AnalysisTablesInSchema('public');
|
||||
SELECT _CDB_AnalysisDataSize('public');
|
||||
CREATE TABLE analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5(id int);
|
||||
CREATE TABLE analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94(id int);
|
||||
CREATE TABLE analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da9(id int);
|
||||
SELECT _CDB_AnalysisTablesInSchema('public') t ORDER BY t;
|
||||
SELECT _CDB_AnalysisDataSize('public');
|
||||
SELECT CDB_CheckAnalysisQuota('analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94');
|
||||
SELECT CDB_SetUserQuotaInBytes(1);
|
||||
SELECT CDB_CheckAnalysisQuota('analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94');
|
||||
INSERT INTO analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5(id) VALUES (1),(2),(3),(4),(5);
|
||||
SELECT CDB_CheckAnalysisQuota('analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94');
|
||||
DROP TABLE analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5;
|
||||
DROP TABLE analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94;
|
||||
DROP TABLE analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da9;
|
||||
DROP FUNCTION "public"._CDB_UserQuotaInBytes();
|
18
test/CDB_AnalysisCheckTest_expect
Normal file
18
test/CDB_AnalysisCheckTest_expect
Normal file
@ -0,0 +1,18 @@
|
||||
SET
|
||||
1000000
|
||||
0
|
||||
CREATE TABLE
|
||||
CREATE TABLE
|
||||
CREATE TABLE
|
||||
(analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5,public,analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5)
|
||||
(analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94,public,analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94)
|
||||
0
|
||||
|
||||
1
|
||||
|
||||
INSERT 0 5
|
||||
ERROR: Analysis cache space limits exceeded
|
||||
DROP TABLE
|
||||
DROP TABLE
|
||||
DROP TABLE
|
||||
DROP FUNCTION
|
@ -134,9 +134,9 @@ DROP TABLE t;
|
||||
-- table with single non-geometrical column
|
||||
CREATE TABLE t AS SELECT ST_SetSRID(ST_MakePoint(-1,-1),4326) as the_geom, 1::int as cartodb_id, 'this is a sentence' as description;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'check function idempotence');
|
||||
SELECT * FROM t;
|
||||
SELECT cartodb_id, the_geom, description FROM t;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'check function idempotence');
|
||||
SELECT * FROM t;
|
||||
SELECT cartodb_id, the_geom, description FROM t;
|
||||
DROP TABLE t;
|
||||
|
||||
-- table with existing srid-unconstrained (but type-constrained) the_geom
|
||||
@ -372,6 +372,24 @@ SELECT column_name FROM information_schema.columns WHERE table_name = 'test' AND
|
||||
DROP TABLE test;
|
||||
SET client_min_messages TO error;
|
||||
|
||||
-- Unique identifier generation can break CDB_CartodbfyTable #305
|
||||
BEGIN;
|
||||
DO $$
|
||||
BEGIN
|
||||
FOR i IN 1..150 LOOP
|
||||
EXECUTE 'CREATE TABLE untitled_table();';
|
||||
EXECUTE $query$SELECT CDB_CartodbfyTable('untitled_table');$query$;
|
||||
EXECUTE 'ALTER TABLE untitled_table RENAME TO my_renamed_table_' || i;
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
ROLLBACK;
|
||||
|
||||
-- Long table name could cause possible sequence rename collision #325
|
||||
CREATE TABLE "wadus_table_9473e8f6-2da1-11e8-8bca-0204e4dfe4d8" ( cartodb_id serial primary key );
|
||||
SELECT CDB_CartodbfyTableCheck('wadus_table_9473e8f6-2da1-11e8-8bca-0204e4dfe4d8'::REGCLASS, 'Long table name could cause sequence collision while renaming #325');
|
||||
DROP TABLE "wadus_table_9473e8f6-2da1-11e8-8bca-0204e4dfe4d8";
|
||||
|
||||
-- TODO: table with existing custom-triggered the_geom
|
||||
|
||||
DROP FUNCTION CDB_CartodbfyTableCheck(regclass, text);
|
||||
|
@ -7,9 +7,9 @@ single non-geometrical column cartodbfied fine
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
check function idempotence cartodbfied fine
|
||||
1|0101000020E6100000000000000000F0BF000000000000F0BF|0101000020110F0000DB0B4ADA772DFBC077432E49D22DFBC0|this is a sentence
|
||||
1|0101000020E6100000000000000000F0BF000000000000F0BF|this is a sentence
|
||||
check function idempotence cartodbfied fine
|
||||
1|0101000020E6100000000000000000F0BF000000000000F0BF|0101000020110F0000DB0B4ADA772DFBC077432E49D22DFBC0|this is a sentence
|
||||
1|0101000020E6100000000000000000F0BF000000000000F0BF|this is a sentence
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
srid-unconstrained the_geom cartodbfied fine
|
||||
@ -147,5 +147,11 @@ NOTICE: Trying to recover data from _cartodb_id0 column
|
||||
|
||||
DROP TABLE
|
||||
SET
|
||||
BEGIN
|
||||
DO
|
||||
ROLLBACK
|
||||
CREATE TABLE
|
||||
Long table name could cause sequence collision while renaming #325 cartodbfied fine
|
||||
DROP TABLE
|
||||
DROP FUNCTION
|
||||
DROP FUNCTION
|
||||
|
@ -1,5 +1,6 @@
|
||||
BEGIN
|
||||
CREATE TABLE
|
||||
COPY 3
|
||||
none||
|
||||
only_com_dec|.|,
|
||||
only_dot_dec|,|.
|
||||
|
@ -2,4 +2,10 @@ WITH data AS (
|
||||
SELECT array_agg(x::numeric) s FROM generate_series(1,300) x
|
||||
WHERE x % 5 != 0 AND x % 7 != 0
|
||||
)
|
||||
SELECT round(unnest(CDB_EqualIntervalBins(s, 7)),7) FROM data
|
||||
SELECT round(unnest(CDB_EqualIntervalBins(s, 7)),7) FROM data;
|
||||
|
||||
WITH data_nulls AS (
|
||||
SELECT array_agg(CASE WHEN x % 2 != 0 THEN x ELSE NULL END::numeric) s FROM generate_series(1,100) x
|
||||
WHERE x % 5 != 0 AND x % 7 != 0
|
||||
)
|
||||
SELECT round(unnest(CDB_EqualIntervalBins(s, 7)),7) FROM data_nulls;
|
||||
|
@ -5,3 +5,10 @@
|
||||
213.8571429
|
||||
256.4285714
|
||||
299.0000000
|
||||
15.0000000
|
||||
29.0000000
|
||||
43.0000000
|
||||
57.0000000
|
||||
71.0000000
|
||||
85.0000000
|
||||
99.0000000
|
||||
|
10
test/CDB_EstimateRowCountTest.sql
Normal file
10
test/CDB_EstimateRowCountTest.sql
Normal file
@ -0,0 +1,10 @@
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
CREATE TABLE tmptab1(id INT);
|
||||
INSERT INTO tmptab1(id) VALUES (1), (2), (3);
|
||||
CREATE TABLE tmptab2(id INT, value NUMERIC);
|
||||
INSERT INTO tmptab2(id, value) VALUES (1, 10.0), (2, 20.0);
|
||||
SELECT CDB_EstimateRowCount('SELECT SUM(value) FROM tmptab1 INNER JOIN tmptab2 ON (tmptab1.id = tmptab2.id);') AS row_count;
|
||||
SELECT CDB_EstimateRowCount('UPDATE tmptab2 SET value = 30 WHERE id=2;') AS row_count;
|
||||
DROP TABLE tmptab2;
|
||||
DROP TABLE tmptab1;
|
9
test/CDB_EstimateRowCountTest_expect
Normal file
9
test/CDB_EstimateRowCountTest_expect
Normal file
@ -0,0 +1,9 @@
|
||||
SET
|
||||
CREATE TABLE
|
||||
INSERT 0 3
|
||||
CREATE TABLE
|
||||
INSERT 0 2
|
||||
1
|
||||
1
|
||||
DROP TABLE
|
||||
DROP TABLE
|
220
test/CDB_FederatedServer.sql
Normal file
220
test/CDB_FederatedServer.sql
Normal file
@ -0,0 +1,220 @@
|
||||
-- Setup
|
||||
\set QUIET on
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
SET SESSION AUTHORIZATION postgres;
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
\set QUIET off
|
||||
|
||||
\echo '## List empty servers shows nothing'
|
||||
SELECT '1.1', cartodb.CDB_Federated_Server_List_Servers();
|
||||
|
||||
\echo '## List non-existent server shows nothing'
|
||||
SELECT '1.2', cartodb.CDB_Federated_Server_List_Servers(server => 'doesNotExist');
|
||||
|
||||
\echo '## Create and list a server works'
|
||||
SELECT '1.3', cartodb.CDB_Federated_Server_Register_PG(server => 'myRemote'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "fdw_user",
|
||||
"password": "foobarino"
|
||||
}
|
||||
}'::jsonb);
|
||||
SELECT '1.4', cartodb.CDB_Federated_Server_List_Servers();
|
||||
|
||||
\echo '## Create and list a second server works'
|
||||
SELECT '2.1', cartodb.CDB_Federated_Server_Register_PG(server => 'myRemote2'::text, config => '{
|
||||
"server": {
|
||||
"dbname": "fdw_target",
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@,
|
||||
"extensions": "postgis",
|
||||
"updatable": "false",
|
||||
"use_remote_estimate": "true",
|
||||
"fetch_size": "1000"
|
||||
},
|
||||
"credentials": {
|
||||
"username": "fdw_user",
|
||||
"password": "foobarino"
|
||||
}
|
||||
}'::jsonb);
|
||||
SELECT '2.2', cartodb.CDB_Federated_Server_List_Servers();
|
||||
|
||||
\echo '## List server by name works'
|
||||
SELECT '2.3', cartodb.CDB_Federated_Server_List_Servers(server => 'myRemote');
|
||||
|
||||
|
||||
\echo '## Re-register a second server works'
|
||||
SELECT '3.1', cartodb.CDB_Federated_Server_Register_PG(server => 'myRemote2'::text, config => '{
|
||||
"server": {
|
||||
"dbname": "fdw_target",
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@,
|
||||
"extensions": "postgis",
|
||||
"updatable": "false",
|
||||
"use_remote_estimate": "true",
|
||||
"fetch_size": "1000"
|
||||
},
|
||||
"credentials": {
|
||||
"username": "other_remote_user",
|
||||
"password": "foobarino"
|
||||
}
|
||||
}'::jsonb);
|
||||
SELECT '3.2', cartodb.CDB_Federated_Server_List_Servers();
|
||||
|
||||
\echo '## Unregister server 1 works'
|
||||
SELECT '4.1', cartodb.CDB_Federated_Server_Unregister(server => 'myRemote'::text);
|
||||
SELECT '4.2', cartodb.CDB_Federated_Server_List_Servers();
|
||||
|
||||
\echo '## Unregistering a server that does not exist fails'
|
||||
SELECT '5.1', cartodb.CDB_Federated_Server_Unregister(server => 'doesNotExist'::text);
|
||||
|
||||
\echo '## Unregister the second server works'
|
||||
SELECT '6.1', cartodb.CDB_Federated_Server_Unregister(server => 'myRemote2'::text);
|
||||
SELECT '6.2', cartodb.CDB_Federated_Server_List_Servers();
|
||||
|
||||
\echo '## Create a server with NULL name fails'
|
||||
SELECT '7.0', cartodb.CDB_Federated_Server_Register_PG(server => NULL::text, config => '{ "server": {}, "credentials" : {}}');
|
||||
\echo '## Create a server with NULL config fails'
|
||||
SELECT '7.01', cartodb.CDB_Federated_Server_Register_PG(server => 'empty'::text, config => NULL::jsonb);
|
||||
\echo '## Create a server with empty config fails'
|
||||
SELECT '7.1', cartodb.CDB_Federated_Server_Register_PG(server => 'empty'::text, config => '{}');
|
||||
\echo '## Create a server without credentials fails'
|
||||
SELECT '7.2', cartodb.CDB_Federated_Server_Register_PG(server => 'empty'::text, config => '{
|
||||
"server": {
|
||||
"dbname": "fdw_target",
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@,
|
||||
"extensions": "postgis",
|
||||
"updatable": "false",
|
||||
"use_remote_estimate": "true",
|
||||
"fetch_size": "1000"
|
||||
}
|
||||
}'::jsonb);
|
||||
\echo '## Create a server with empty credentials works'
|
||||
SELECT '7.3', cartodb.CDB_Federated_Server_Register_PG(server => 'empty'::text, config => '{
|
||||
"server": {
|
||||
"dbname": "fdw_target",
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@,
|
||||
"extensions": "postgis",
|
||||
"updatable": "false",
|
||||
"use_remote_estimate": "true",
|
||||
"fetch_size": "1000"
|
||||
},
|
||||
"credentials": { }
|
||||
}'::jsonb);
|
||||
SELECT '7.4', cartodb.CDB_Federated_Server_List_Servers();
|
||||
SELECT '7.5', cartodb.CDB_Federated_Server_Unregister(server => 'empty'::text);
|
||||
\echo '## Create a server without options fails'
|
||||
SELECT '7.6', cartodb.CDB_Federated_Server_Register_PG(server => 'empty'::text, config => '{
|
||||
"credentials": {
|
||||
"username": "other_remote_user",
|
||||
"password": "foobarino"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
\echo '## Create a server with special characters works'
|
||||
SELECT '8.1', cartodb.CDB_Federated_Server_Register_PG(server => 'myRemote" or''not'::text, config => '{
|
||||
"server": {
|
||||
"dbname": "fdw target",
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@,
|
||||
"extensions": "postgis",
|
||||
"updatable": "false",
|
||||
"use_remote_estimate": "true",
|
||||
"fetch_size": "1000"
|
||||
},
|
||||
"credentials": {
|
||||
"username": "fdw user",
|
||||
"password": "foo barino"
|
||||
}
|
||||
}'::jsonb);
|
||||
SELECT '8.2', cartodb.CDB_Federated_Server_List_Servers();
|
||||
SELECT '8.3', cartodb.CDB_Federated_Server_Unregister(server => 'myRemote" or''not'::text);
|
||||
|
||||
-- Test permissions
|
||||
\set QUIET on
|
||||
CREATE ROLE cdb_fs_tester LOGIN PASSWORD 'cdb_fs_passwd';
|
||||
GRANT CONNECT ON DATABASE contrib_regression TO cdb_fs_tester;
|
||||
\set QUIET off
|
||||
|
||||
SELECT '9.1', cartodb.CDB_Federated_Server_Register_PG(server => 'myRemote3'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "fdw_user",
|
||||
"password": "foobarino"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
\c contrib_regression cdb_fs_tester
|
||||
|
||||
\echo '## All users are able to list servers'
|
||||
SELECT '9.2', cartodb.CDB_Federated_Server_List_Servers();
|
||||
|
||||
\echo '## Only superadmins can create servers'
|
||||
SELECT '9.3', cartodb.CDB_Federated_Server_Register_PG(server => 'myRemote4'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "fdw_user",
|
||||
"password": "foobarino"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Granting access to a user works'
|
||||
SELECT '9.5', cartodb.CDB_Federated_Server_Grant_Access(server => 'myRemote3', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT '9.55', cartodb.CDB_Federated_Server_List_Servers();
|
||||
\c contrib_regression postgres
|
||||
SELECT '9.6', cartodb.CDB_Federated_Server_Grant_Access(server => 'does not exist', db_role => 'cdb_fs_tester'::name);
|
||||
SELECT '9.7', cartodb.CDB_Federated_Server_Grant_Access(server => 'myRemote3', db_role => 'does not exist'::name);
|
||||
|
||||
\echo '## Granting access again raises a notice'
|
||||
SELECT '9.8', cartodb.CDB_Federated_Server_Grant_Access(server => 'myRemote3', db_role => 'cdb_fs_tester'::name);
|
||||
|
||||
\echo '## Revoking access to a user works'
|
||||
SELECT '9.9', cartodb.CDB_Federated_Server_Revoke_Access(server => 'myRemote3', db_role => 'cdb_fs_tester'::name);
|
||||
SELECT '9.10', cartodb.CDB_Federated_Server_Grant_Access(server => 'myRemote3', db_role => 'cdb_fs_tester'::name);
|
||||
|
||||
\echo '## Unregistering a server with active grants works'
|
||||
SELECT '9.11', cartodb.CDB_Federated_Server_Unregister(server => 'myRemote3'::text);
|
||||
|
||||
|
||||
\echo '## A user with granted access can not drop a server'
|
||||
SELECT '10.1', cartodb.CDB_Federated_Server_Register_PG(server => 'myRemote4'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "fdw_user",
|
||||
"password": "foobarino"
|
||||
}
|
||||
}'::jsonb);
|
||||
SELECT '10.2', cartodb.CDB_Federated_Server_Grant_Access(server => 'myRemote4', db_role => 'cdb_fs_tester'::name);
|
||||
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT '10.3', cartodb.CDB_Federated_Server_Unregister(server => 'myRemote4'::text);
|
||||
|
||||
\c contrib_regression postgres
|
||||
SELECT '10.4', cartodb.CDB_Federated_Server_Unregister(server => 'myRemote4'::text);
|
||||
|
||||
|
||||
-- Cleanup
|
||||
\set QUIET on
|
||||
REVOKE CONNECT ON DATABASE contrib_regression FROM cdb_fs_tester;
|
||||
DROP ROLE cdb_fs_tester;
|
||||
DROP EXTENSION postgres_fdw;
|
||||
\set QUIET off
|
126
test/CDB_FederatedServerDiagnostics.sql
Normal file
126
test/CDB_FederatedServerDiagnostics.sql
Normal file
@ -0,0 +1,126 @@
|
||||
-- ===================================================================
|
||||
-- create FDW objects
|
||||
-- ===================================================================
|
||||
\set QUIET on
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
|
||||
CREATE ROLE cdb_fs_tester LOGIN PASSWORD 'cdb_fs_passwd';
|
||||
GRANT CONNECT ON DATABASE contrib_regression TO cdb_fs_tester;
|
||||
|
||||
-- Create database to be used as remote
|
||||
CREATE DATABASE cdb_fs_tester OWNER cdb_fs_tester;
|
||||
|
||||
SELECT 'C1', cartodb.CDB_Federated_Server_Register_PG(server => 'loopback'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "cdb_fs_tester",
|
||||
"password": "cdb_fs_passwd"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
SELECT 'C2', cartodb.CDB_Federated_Server_Register_PG(server => 'wrong-port'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": "12345"
|
||||
},
|
||||
"credentials": {
|
||||
"username": "cdb_fs_tester",
|
||||
"password": "cdb_fs_passwd"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
SELECT 'C3', cartodb.CDB_Federated_Server_Register_PG(server => 'loopback-no-port'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost"
|
||||
},
|
||||
"credentials": {
|
||||
"username": "cdb_fs_tester",
|
||||
"password": "cdb_fs_passwd"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
\c cdb_fs_tester postgres
|
||||
CREATE EXTENSION postgis;
|
||||
\c contrib_regression postgres
|
||||
\set QUIET off
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Test server diagnostics function(s)
|
||||
-- ===================================================================
|
||||
\echo '%% It raises an error if the server does not exist'
|
||||
SELECT '1.1', cartodb.CDB_Federated_Server_Diagnostics(server => 'doesNotExist');
|
||||
|
||||
\echo '%% It returns a jsonb object'
|
||||
SELECT '1.2', pg_typeof(cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback'));
|
||||
|
||||
\echo '%% It returns the server version'
|
||||
SELECT '1.3', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> format('{"server_version": "%s"}', setting)::jsonb
|
||||
FROM pg_settings WHERE name = 'server_version';
|
||||
|
||||
\echo '%% It returns the postgis version'
|
||||
SELECT '1.4', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> format('{"postgis_version": "%s"}', extversion)::jsonb
|
||||
FROM pg_extension WHERE extname = 'postgis';
|
||||
|
||||
\echo '%% It returns null as the postgis version if it is not installed'
|
||||
\set QUIET on
|
||||
\c cdb_fs_tester postgres
|
||||
DROP EXTENSION postgis;
|
||||
\c contrib_regression postgres
|
||||
\set QUIET off
|
||||
SELECT '1.5', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> '{"postgis_version": null}'::jsonb;
|
||||
|
||||
\echo '%% It returns the remote server options'
|
||||
SELECT '1.6', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> '{"server_options": {"host": "localhost", "port": "@@PGPORT@@", "updatable": "false", "extensions": "postgis", "fetch_size": "1000", "use_remote_estimate": "true"}}'::jsonb;
|
||||
|
||||
\echo '%% It returns network latency stats to the remote server: min <= avg <= max'
|
||||
WITH latency AS (
|
||||
SELECT CDB_Federated_Server_Diagnostics('loopback')->'server_latency_ms' ms
|
||||
) SELECT '2.1', (latency.ms->'min')::text::float <= (latency.ms->'avg')::text::float, (latency.ms->'avg')::text::float <= (latency.ms->'max')::text::float
|
||||
FROM latency;
|
||||
|
||||
\echo '%% Latency stats: 0 <= min <= max <= 1000 ms (local connection)'
|
||||
WITH latency AS (
|
||||
SELECT CDB_Federated_Server_Diagnostics('loopback')->'server_latency_ms' ms
|
||||
) SELECT '2.2', 0.0 <= (latency.ms->'min')::text::float, (latency.ms->'max')::text::float <= 1000.0
|
||||
FROM latency;
|
||||
|
||||
\echo '%% Latency stats: stdev > 0'
|
||||
WITH latency AS (
|
||||
SELECT CDB_Federated_Server_Diagnostics('loopback')->'server_latency_ms' ms
|
||||
) SELECT '2.3', (latency.ms->'stdev')::text::float >= 0.0
|
||||
FROM latency;
|
||||
|
||||
\echo '%% It raises an error if the wrong port is provided'
|
||||
SELECT '3.0', cartodb.CDB_Federated_Server_Diagnostics(server => 'wrong-port');
|
||||
|
||||
-- Disabled: It's not compatible with Travis since the target database (self) might be in a different port
|
||||
-- \echo '%% Latency stats: can get them on default PG port 5432 when not provided'
|
||||
-- WITH latency AS (
|
||||
-- SELECT CDB_Federated_Server_Diagnostics('loopback-no-port')->'server_latency_ms' ms
|
||||
-- ) SELECT '2.4', 0.0 <= (latency.ms->'min')::text::float, (latency.ms->'max')::text::float <= 1000.0
|
||||
-- FROM latency;
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Cleanup
|
||||
-- ===================================================================
|
||||
\set QUIET on
|
||||
SELECT 'D1', cartodb.CDB_Federated_Server_Unregister(server => 'loopback'::text);
|
||||
SELECT 'D2', cartodb.CDB_Federated_Server_Unregister(server => 'wrong-port'::text);
|
||||
SELECT 'D3', cartodb.CDB_Federated_Server_Unregister(server => 'loopback-no-port'::text);
|
||||
-- Reconnect, using a new session in order to close FDW connections
|
||||
\connect
|
||||
DROP DATABASE cdb_fs_tester;
|
||||
|
||||
-- Drop role
|
||||
REVOKE CONNECT ON DATABASE contrib_regression FROM cdb_fs_tester;
|
||||
DROP ROLE cdb_fs_tester;
|
||||
|
||||
DROP EXTENSION postgres_fdw;
|
||||
\set QUIET off
|
26
test/CDB_FederatedServerDiagnostics_expect
Normal file
26
test/CDB_FederatedServerDiagnostics_expect
Normal file
@ -0,0 +1,26 @@
|
||||
C1|
|
||||
C2|
|
||||
C3|
|
||||
%% It raises an error if the server does not exist
|
||||
ERROR: Server "doesNotExist" does not exist
|
||||
%% It returns a jsonb object
|
||||
1.2|jsonb
|
||||
%% It returns the server version
|
||||
1.3|t
|
||||
%% It returns the postgis version
|
||||
1.4|t
|
||||
%% It returns null as the postgis version if it is not installed
|
||||
1.5|t
|
||||
%% It returns the remote server options
|
||||
1.6|t
|
||||
%% It returns network latency stats to the remote server: min <= avg <= max
|
||||
2.1|t|t
|
||||
%% Latency stats: 0 <= min <= max <= 1000 ms (local connection)
|
||||
2.2|t|t
|
||||
%% Latency stats: stdev > 0
|
||||
2.3|t
|
||||
%% It raises an error if the wrong port is provided
|
||||
ERROR: could not connect to server "cdb_fs_wrong-port"
|
||||
D1|
|
||||
D2|
|
||||
D3|
|
319
test/CDB_FederatedServerListRemote.sql
Normal file
319
test/CDB_FederatedServerListRemote.sql
Normal file
@ -0,0 +1,319 @@
|
||||
-- ===================================================================
|
||||
-- create FDW objects
|
||||
-- ===================================================================
|
||||
\set QUIET on
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
|
||||
CREATE ROLE cdb_fs_tester LOGIN PASSWORD 'cdb_fs_passwd';
|
||||
GRANT CONNECT ON DATABASE contrib_regression TO cdb_fs_tester;
|
||||
CREATE ROLE cdb_fs_tester2 LOGIN PASSWORD 'cdb_fs_passwd2';
|
||||
GRANT CONNECT ON DATABASE contrib_regression TO cdb_fs_tester2;
|
||||
|
||||
-- Create database to be used as remote
|
||||
CREATE DATABASE cdb_fs_tester OWNER cdb_fs_tester;
|
||||
|
||||
SELECT 'C1', cartodb.CDB_Federated_Server_Register_PG(server => 'loopback'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "cdb_fs_tester",
|
||||
"password": "cdb_fs_passwd"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
SELECT 'C2', cartodb.CDB_Federated_Server_Register_PG(server => 'loopback2'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "cdb_fs_tester",
|
||||
"password": "cdb_fs_passwd"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
-- ===================================================================
|
||||
-- Setup 1
|
||||
-- ===================================================================
|
||||
\c cdb_fs_tester cdb_fs_tester
|
||||
|
||||
CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz');
|
||||
CREATE SCHEMA "S 1";
|
||||
CREATE TABLE "S 1"."T 1" (
|
||||
"C 1" int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
c4 timestamptz,
|
||||
c5 timestamp,
|
||||
c6 varchar(10),
|
||||
c7 char(10),
|
||||
c8 user_enum,
|
||||
CONSTRAINT t1_pkey PRIMARY KEY ("C 1")
|
||||
);
|
||||
CREATE TABLE "S 1"."T 2" (
|
||||
c1 int NOT NULL,
|
||||
c2 text,
|
||||
CONSTRAINT t2_pkey PRIMARY KEY (c1)
|
||||
);
|
||||
CREATE TABLE "S 1"."T 3" (
|
||||
c1 int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
CONSTRAINT t3_pkey PRIMARY KEY (c1)
|
||||
);
|
||||
CREATE TABLE "S 1"."T 4" (
|
||||
c1 int NOT NULL,
|
||||
c2 int NOT NULL,
|
||||
c3 text,
|
||||
CONSTRAINT t4_pkey PRIMARY KEY (c1)
|
||||
);
|
||||
|
||||
-- Disable autovacuum for these tables to avoid unexpected effects of that
|
||||
ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false');
|
||||
ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false');
|
||||
ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false');
|
||||
ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false');
|
||||
|
||||
\c contrib_regression postgres
|
||||
SET client_min_messages TO notice;
|
||||
\set VERBOSITY terse
|
||||
\set QUIET off
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Test listing remote schemas
|
||||
-- ===================================================================
|
||||
\echo '## Test listing of remote schemas without permissions before the first instantiation (rainy day)'
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote schemas (sunny day)'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback');
|
||||
|
||||
\echo '## Test listing of remote schemas without permissions after the first instantiation (rainy day)'
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote schemas with permissions (sunny day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server => 'loopback', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote schemas without permissions after revoking access (rainy day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Revoke_Access(server => 'loopback', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote schemas (rainy day): Server does not exist'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'Does Not Exist');
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Test listing remote tables
|
||||
-- ===================================================================
|
||||
|
||||
\echo '## Test listing of remote tables without permissions before the first instantiation (rainy day)'
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'S 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote tables (sunny day)'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'S 1');
|
||||
|
||||
\echo '## Test listing of remote tables without permissions after the first instantiation (rainy day)'
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'S 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote tables with permissions (sunny day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server => 'loopback', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'S 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote tables without permissions after revoking access (rainy day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Revoke_Access(server => 'loopback', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'S 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote tables (rainy day): Server does not exist'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'Does Not Exist', remote_schema => 'S 1');
|
||||
|
||||
\echo '## Test listing of remote tables (rainy day): Remote schema does not exist'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'Does Not Exist');
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Test listing remote columns
|
||||
-- ===================================================================
|
||||
|
||||
\echo '## Test listing of remote columns without permissions before the first instantiation (rainy day)'
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote columns (sunny day)'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
|
||||
\echo '## Test listing of remote columns without permissions after the first instantiation (rainy day)'
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote columns with permissions (sunny day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server => 'loopback', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote columns without permissions after revoking access (rainy day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Revoke_Access(server => 'loopback', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
\c contrib_regression postgres
|
||||
|
||||
\echo '## Test listing of remote columns (rainy day): Server does not exist'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'Does Not Exist', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
|
||||
\echo '## Test listing of remote columns (rainy day): Remote schema does not exist'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'Does Not Exist', remote_table => 'T 1');
|
||||
|
||||
\echo '## Test listing of remote columns (rainy day): Remote table does not exist'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'Does Not Exist');
|
||||
|
||||
\echo '## Test listing of remote columns (rainy day): Remote table is NULL'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => NULL::text);
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Test that using a different user to list tables and dropping it
|
||||
-- does not break the server: We use loopback2 as it's in a clean state
|
||||
-- ===================================================================
|
||||
|
||||
|
||||
\echo '## Test listing of remote objects with permissions (sunny day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server => 'loopback2', db_role => 'cdb_fs_tester2'::name);
|
||||
\c contrib_regression cdb_fs_tester2
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback2');
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback2', remote_schema => 'S 1');
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback2', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
|
||||
\c contrib_regression postgres
|
||||
\echo '## Test that dropping the granted user works fine (sunny day)'
|
||||
REVOKE CONNECT ON DATABASE contrib_regression FROM cdb_fs_tester2;
|
||||
DROP ROLE cdb_fs_tester2;
|
||||
|
||||
\echo '## Test listing of remote objects with other user still works (sunny day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server => 'loopback2', db_role => 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback2');
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback2', remote_schema => 'S 1');
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback2', remote_schema => 'S 1', remote_table => 'T 1');
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Cleanup 1
|
||||
-- ===================================================================
|
||||
\set QUIET on
|
||||
|
||||
\c cdb_fs_tester cdb_fs_tester
|
||||
DROP TABLE "S 1". "T 1";
|
||||
DROP TABLE "S 1". "T 2";
|
||||
DROP TABLE "S 1". "T 3";
|
||||
DROP TABLE "S 1". "T 4";
|
||||
|
||||
DROP SCHEMA "S 1";
|
||||
DROP TYPE user_enum;
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Setup 2: Using Postgis too
|
||||
-- ===================================================================
|
||||
|
||||
\c cdb_fs_tester postgres
|
||||
|
||||
CREATE EXTENSION postgis;
|
||||
|
||||
\c cdb_fs_tester cdb_fs_tester
|
||||
|
||||
CREATE SCHEMA "S 1";
|
||||
CREATE TABLE "S 1"."T 5" (
|
||||
geom geometry(Geometry,4326),
|
||||
geom_wm geometry(GeometryZ,3857),
|
||||
geo_nosrid geometry,
|
||||
geog geography
|
||||
);
|
||||
|
||||
\c contrib_regression postgres
|
||||
SET client_min_messages TO notice;
|
||||
\set VERBOSITY terse
|
||||
\set QUIET off
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Test the listing functions
|
||||
-- ===================================================================
|
||||
|
||||
\echo '## Test listing of remote geometry columns (sunny day)'
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'T 5');
|
||||
\echo '## Test listing of remote geometry columns (sunny day) - Rerun'
|
||||
-- Rerun should be ok
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Columns(server => 'loopback', remote_schema => 'S 1', remote_table => 'T 5');
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Test invalid password
|
||||
-- ===================================================================
|
||||
|
||||
\echo '## Check error message with invalid password (rainy day)'
|
||||
SELECT cartodb.CDB_Federated_Server_Register_PG(server => 'loopback_invalid'::text, config => '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "cdb_fs_tester",
|
||||
"password": "wrong password"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Schemas(server => 'loopback_invalid');
|
||||
|
||||
SELECT cartodb.CDB_Federated_Server_Unregister(server => 'loopback_invalid'::text);
|
||||
|
||||
-- ===================================================================
|
||||
-- Cleanup 2
|
||||
-- ===================================================================
|
||||
\set QUIET on
|
||||
|
||||
\c cdb_fs_tester cdb_fs_tester
|
||||
DROP TABLE "S 1". "T 5";
|
||||
|
||||
DROP SCHEMA "S 1";
|
||||
|
||||
\c contrib_regression postgres
|
||||
\set QUIET on
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
|
||||
SELECT 'D1', cartodb.CDB_Federated_Server_Unregister(server => 'loopback'::text);
|
||||
SELECT 'D2', cartodb.CDB_Federated_Server_Unregister(server => 'loopback2'::text);
|
||||
|
||||
DROP DATABASE cdb_fs_tester;
|
||||
|
||||
-- Drop role
|
||||
REVOKE CONNECT ON DATABASE contrib_regression FROM cdb_fs_tester;
|
||||
DROP ROLE cdb_fs_tester;
|
||||
|
||||
DROP EXTENSION postgres_fdw;
|
||||
|
||||
\set QUIET off
|
162
test/CDB_FederatedServerListRemote_expect
Normal file
162
test/CDB_FederatedServerListRemote_expect
Normal file
@ -0,0 +1,162 @@
|
||||
C1|
|
||||
C2|
|
||||
## Test listing of remote schemas without permissions before the first instantiation (rainy day)
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote schemas (sunny day)
|
||||
S 1
|
||||
information_schema
|
||||
public
|
||||
## Test listing of remote schemas without permissions after the first instantiation (rainy day)
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote schemas with permissions (sunny day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
S 1
|
||||
information_schema
|
||||
public
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote schemas without permissions after revoking access (rainy day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote schemas (rainy day): Server does not exist
|
||||
ERROR: Server "Does Not Exist" does not exist
|
||||
## Test listing of remote tables without permissions before the first instantiation (rainy day)
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote tables (sunny day)
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback"
|
||||
f|T 1|||||[{"Name" : "C 1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}, {"Name" : "c4", "Type" : "timestamp with time zone"}, {"Name" : "c5", "Type" : "timestamp without time zone"}, {"Name" : "c6", "Type" : "character varying"}, {"Name" : "c7", "Type" : "character"}, {"Name" : "c8", "Type" : "USER-DEFINED"}]
|
||||
f|T 2|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "text"}]
|
||||
f|T 3|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
f|T 4|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
## Test listing of remote tables without permissions after the first instantiation (rainy day)
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote tables with permissions (sunny day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback"
|
||||
f|T 1|||||[{"Name" : "C 1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}, {"Name" : "c4", "Type" : "timestamp with time zone"}, {"Name" : "c5", "Type" : "timestamp without time zone"}, {"Name" : "c6", "Type" : "character varying"}, {"Name" : "c7", "Type" : "character"}, {"Name" : "c8", "Type" : "USER-DEFINED"}]
|
||||
f|T 2|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "text"}]
|
||||
f|T 3|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
f|T 4|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote tables without permissions after revoking access (rainy day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote tables (rainy day): Server does not exist
|
||||
ERROR: Server "Does Not Exist" does not exist
|
||||
## Test listing of remote tables (rainy day): Remote schema does not exist
|
||||
## Test listing of remote columns without permissions before the first instantiation (rainy day)
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote columns (sunny day)
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback"
|
||||
C 1|integer
|
||||
c2|integer
|
||||
c3|text
|
||||
c4|timestamp with time zone
|
||||
c5|timestamp without time zone
|
||||
c6|character varying
|
||||
c7|character
|
||||
c8|USER-DEFINED
|
||||
## Test listing of remote columns without permissions after the first instantiation (rainy day)
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote columns with permissions (sunny day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback"
|
||||
C 1|integer
|
||||
c2|integer
|
||||
c3|text
|
||||
c4|timestamp with time zone
|
||||
c5|timestamp without time zone
|
||||
c6|character varying
|
||||
c7|character
|
||||
c8|USER-DEFINED
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote columns without permissions after revoking access (rainy day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test listing of remote columns (rainy day): Server does not exist
|
||||
ERROR: Server "Does Not Exist" does not exist
|
||||
## Test listing of remote columns (rainy day): Remote schema does not exist
|
||||
## Test listing of remote columns (rainy day): Remote table does not exist
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback"
|
||||
## Test listing of remote columns (rainy day): Remote table is NULL
|
||||
ERROR: Remote table name cannot be NULL
|
||||
## Test listing of remote objects with permissions (sunny day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester2".
|
||||
S 1
|
||||
information_schema
|
||||
public
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback2"
|
||||
f|T 1|||||[{"Name" : "C 1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}, {"Name" : "c4", "Type" : "timestamp with time zone"}, {"Name" : "c5", "Type" : "timestamp without time zone"}, {"Name" : "c6", "Type" : "character varying"}, {"Name" : "c7", "Type" : "character"}, {"Name" : "c8", "Type" : "USER-DEFINED"}]
|
||||
f|T 2|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "text"}]
|
||||
f|T 3|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
f|T 4|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback2"
|
||||
C 1|integer
|
||||
c2|integer
|
||||
c3|text
|
||||
c4|timestamp with time zone
|
||||
c5|timestamp without time zone
|
||||
c6|character varying
|
||||
c7|character
|
||||
c8|USER-DEFINED
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
## Test that dropping the granted user works fine (sunny day)
|
||||
REVOKE
|
||||
DROP ROLE
|
||||
## Test listing of remote objects with other user still works (sunny day)
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
S 1
|
||||
information_schema
|
||||
public
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback2"
|
||||
f|T 1|||||[{"Name" : "C 1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}, {"Name" : "c4", "Type" : "timestamp with time zone"}, {"Name" : "c5", "Type" : "timestamp without time zone"}, {"Name" : "c6", "Type" : "character varying"}, {"Name" : "c7", "Type" : "character"}, {"Name" : "c8", "Type" : "USER-DEFINED"}]
|
||||
f|T 2|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "text"}]
|
||||
f|T 3|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
f|T 4|||||[{"Name" : "c1", "Type" : "integer"}, {"Name" : "c2", "Type" : "integer"}, {"Name" : "c3", "Type" : "text"}]
|
||||
INFO: Could not find Postgis installation in the remote "public" schema in server "loopback2"
|
||||
C 1|integer
|
||||
c2|integer
|
||||
c3|text
|
||||
c4|timestamp with time zone
|
||||
c5|timestamp without time zone
|
||||
c6|character varying
|
||||
c7|character
|
||||
c8|USER-DEFINED
|
||||
## Test listing of remote geometry columns (sunny day)
|
||||
geo_nosrid|GEOMETRY,0
|
||||
geog|Geometry,0
|
||||
geom|GEOMETRY,4326
|
||||
geom_wm|GEOMETRY,3857
|
||||
## Test listing of remote geometry columns (sunny day) - Rerun
|
||||
geo_nosrid|GEOMETRY,0
|
||||
geog|Geometry,0
|
||||
geom|GEOMETRY,4326
|
||||
geom_wm|GEOMETRY,3857
|
||||
## Check error message with invalid password (rainy day)
|
||||
|
||||
ERROR: could not connect to server "cdb_fs_loopback_invalid"
|
||||
|
||||
D1|
|
||||
D2|
|
405
test/CDB_FederatedServerTables.sql
Normal file
405
test/CDB_FederatedServerTables.sql
Normal file
@ -0,0 +1,405 @@
|
||||
-- ===================================================================
|
||||
-- create FDW objects
|
||||
-- ===================================================================
|
||||
\set QUIET on
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
|
||||
-- We create a username following the same steps as organization members
|
||||
CREATE ROLE cdb_fs_tester LOGIN PASSWORD 'cdb_fs_passwd';
|
||||
GRANT CONNECT ON DATABASE contrib_regression TO cdb_fs_tester;
|
||||
|
||||
CREATE ROLE cdb_fs_tester2 LOGIN PASSWORD 'cdb_fs_passwd2';
|
||||
GRANT CONNECT ON DATABASE contrib_regression TO cdb_fs_tester2;
|
||||
|
||||
-- Create database to be used as remote
|
||||
CREATE DATABASE cdb_fs_tester OWNER cdb_fs_tester;
|
||||
|
||||
SELECT 'C1', cartodb.CDB_Federated_Server_Register_PG(server := 'loopback'::text, config := '{
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": @@PGPORT@@
|
||||
},
|
||||
"credentials": {
|
||||
"username": "cdb_fs_tester",
|
||||
"password": "cdb_fs_passwd"
|
||||
}
|
||||
}'::jsonb);
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- create objects used through FDW loopback server
|
||||
-- ===================================================================
|
||||
|
||||
\c cdb_fs_tester postgres
|
||||
|
||||
CREATE EXTENSION postgis;
|
||||
|
||||
\c cdb_fs_tester cdb_fs_tester
|
||||
|
||||
CREATE SCHEMA remote_schema;
|
||||
CREATE TABLE remote_schema.remote_geom(id int, another_field text, geom geometry(Geometry,4326));
|
||||
|
||||
INSERT INTO remote_schema.remote_geom VALUES (1, 'patata', 'SRID=4326;POINT(1 1)'::geometry);
|
||||
INSERT INTO remote_schema.remote_geom VALUES (2, 'patata2', 'SRID=4326;POINT(2 2)'::geometry);
|
||||
|
||||
CREATE TABLE remote_schema.remote_geom2(cartodb_id bigint, another_field text, the_geom geometry(Geometry,4326), the_geom_webmercator geometry(Geometry,3857));
|
||||
|
||||
INSERT INTO remote_schema.remote_geom2 VALUES (3, 'patata', 'SRID=4326;POINT(3 3)'::geometry, 'SRID=3857;POINT(3 3)');
|
||||
|
||||
CREATE TABLE remote_schema.remote_other(id bigint, field text, field2 text);
|
||||
INSERT INTO remote_schema.remote_other VALUES (1, 'delicious', 'potatoes');
|
||||
|
||||
CREATE TABLE remote_schema.remote_geom3(id bigint, geom geometry(Geometry,4326), geom_mercator geometry(Geometry,3857));
|
||||
|
||||
-- ===================================================================
|
||||
-- Test the listing functions
|
||||
-- ===================================================================
|
||||
|
||||
\c contrib_regression postgres
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
\set QUIET off
|
||||
|
||||
\echo '## Registering an existing table works'
|
||||
SELECT 'R1', cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
SELECT 'S1', cartodb_id, ST_AsText(the_geom), another_field FROM cdb_fs_loopback.remote_geom;
|
||||
|
||||
Select * FROM CDB_Federated_Server_List_Remote_Tables(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema'
|
||||
);
|
||||
|
||||
\echo '## Registering another existing table works'
|
||||
SELECT 'R2', cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom2',
|
||||
id_column => 'cartodb_id',
|
||||
geom_column => 'the_geom',
|
||||
webmercator_column => 'the_geom_webmercator',
|
||||
local_name => 'myFullTable'
|
||||
);
|
||||
|
||||
SELECT 'S2', cartodb_id, ST_AsText(the_geom), another_field FROM cdb_fs_loopback."myFullTable";
|
||||
Select * FROM CDB_Federated_Server_List_Remote_Tables(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema'
|
||||
);
|
||||
|
||||
\echo '## Re-registering a table works'
|
||||
SELECT 'R3', cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom2',
|
||||
id_column => 'cartodb_id',
|
||||
-- Switch geom and geom_column on purpose to force ST_Transform to be used
|
||||
geom_column => 'the_geom_webmercator',
|
||||
webmercator_column => 'the_geom',
|
||||
local_name => 'different_name'
|
||||
);
|
||||
|
||||
-- The old view should dissapear
|
||||
SELECT 'S3_old', cartodb_id, another_field FROM cdb_fs_loopback."myFullTable";
|
||||
-- And the new appear
|
||||
SELECT 'S3_new', cartodb_id, another_field FROM cdb_fs_loopback.different_name;
|
||||
|
||||
\echo '## Unregistering works'
|
||||
-- Deregistering the first table
|
||||
SELECT 'U1', cartodb.CDB_Federated_Table_Unregister(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom'
|
||||
);
|
||||
-- Selecting from the created view should fail now
|
||||
SELECT 'UCheck1', cartodb_id, ST_AsText(the_geom), another_field FROM remote_geom;
|
||||
|
||||
Select * FROM CDB_Federated_Server_List_Remote_Tables(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema'
|
||||
);
|
||||
|
||||
-- ===================================================================
|
||||
-- Test input
|
||||
-- ===================================================================
|
||||
|
||||
\echo '## Registering a table: Invalid server fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'Does not exist',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: NULL server fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => NULL::text,
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: Invalid schema fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'Does not exist',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: NULL schema fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => NULL::text,
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: Invalid table fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'Does not exist',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: NULL table fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => NULL::text,
|
||||
id_column => 'id',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: Invalid id fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'Does not exist',
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: NULL id fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => NULL::text,
|
||||
geom_column => 'geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: Invalid geom_column fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'Does not exists'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: NULL geom_column is OK: Reuses geom_mercator'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => NULL::text,
|
||||
webmercator_column => 'geom'
|
||||
);
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'remote_schema') where remote_table = 'remote_geom';
|
||||
|
||||
SELECT cartodb.CDB_Federated_Table_Unregister(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: Invalid webmercator_column fails'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom',
|
||||
webmercator_column => 'Does not exists'
|
||||
);
|
||||
|
||||
\echo '## Registering a table: NULL webmercator_column is OK: Reuses geom'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom',
|
||||
webmercator_column => NULL::text
|
||||
);
|
||||
SELECT * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'remote_schema') where remote_table = 'remote_geom';
|
||||
SELECT cartodb.CDB_Federated_Table_Unregister(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom'
|
||||
);
|
||||
|
||||
\echo '##Registering a table with extra columns show the correct information'
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom3',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom',
|
||||
webmercator_column => 'geom_mercator'
|
||||
);
|
||||
Select * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'remote_schema') where remote_table = 'remote_geom3';
|
||||
SELECT cartodb.CDB_Federated_Table_Unregister(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom3'
|
||||
);
|
||||
|
||||
-- ===================================================================
|
||||
-- Test conflicts
|
||||
-- ===================================================================
|
||||
|
||||
\echo '## Target conflict is handled nicely: Table'
|
||||
CREATE TABLE cdb_fs_loopback.localtable (a integer);
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom',
|
||||
local_name => 'localtable');
|
||||
|
||||
\echo '## Target conflict is handled nicely: View'
|
||||
CREATE VIEW cdb_fs_loopback.localtable2 AS Select * from cdb_fs_loopback.localtable;
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom',
|
||||
local_name => 'localtable2');
|
||||
|
||||
DROP VIEW cdb_fs_loopback.localtable2;
|
||||
DROP TABLE cdb_fs_loopback.localtable;
|
||||
|
||||
-- ===================================================================
|
||||
-- Test permissions
|
||||
-- ===================================================================
|
||||
|
||||
|
||||
\echo '## Registering tables does not work without permissions'
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom',
|
||||
local_name => 'localtable');
|
||||
|
||||
\echo '## Normal users can not write in the import schema'
|
||||
CREATE TABLE cdb_fs_loopback.localtable (a integer);
|
||||
|
||||
\echo '## Listing remote tables does not work without permissions'
|
||||
Select * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'remote_schema');
|
||||
|
||||
\echo '## Registering tables works with granted permissions'
|
||||
\c contrib_regression postgres
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server := 'loopback', db_role := 'cdb_fs_tester'::name);
|
||||
\c contrib_regression cdb_fs_tester
|
||||
SELECT cartodb.CDB_Federated_Table_Register(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom',
|
||||
id_column => 'id',
|
||||
geom_column => 'geom',
|
||||
local_name => 'localtable');
|
||||
|
||||
\echo '## Listing remote tables works with granted permissions'
|
||||
Select * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'remote_schema');
|
||||
|
||||
\echo '## Selecting from a registered table with granted permissions works'
|
||||
Select cartodb_id, ST_AsText(the_geom) from cdb_fs_loopback.localtable;
|
||||
|
||||
\echo '## Selecting from a registered table without permissions does not work'
|
||||
\c contrib_regression cdb_fs_tester2
|
||||
CREATE OR REPLACE FUNCTION catch_permission_error(query text)
|
||||
RETURNS bool
|
||||
AS $$
|
||||
BEGIN
|
||||
EXECUTE query;
|
||||
RETURN FALSE;
|
||||
EXCEPTION
|
||||
WHEN insufficient_privilege THEN
|
||||
RETURN TRUE;
|
||||
WHEN OTHERS THEN
|
||||
RAISE WARNING 'Exception %', sqlstate;
|
||||
RETURN FALSE;
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
Select catch_permission_error($$SELECT cartodb_id, ST_AsText(the_geom) from cdb_fs_loopback.localtable$$);
|
||||
DROP FUNCTION catch_permission_error(text);
|
||||
|
||||
\echo '## Deleting a registered table without permissions does not work'
|
||||
SELECT cartodb.CDB_Federated_Table_Unregister(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom'
|
||||
);
|
||||
|
||||
\echo '## Only the owner can grant permissions over the server'
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server := 'loopback', db_role := 'cdb_fs_tester2'::name);
|
||||
|
||||
\echo '## Everything works for a different user when granted permissions'
|
||||
\c contrib_regression postgres
|
||||
SELECT cartodb.CDB_Federated_Server_Grant_Access(server := 'loopback', db_role := 'cdb_fs_tester2'::name);
|
||||
\c contrib_regression cdb_fs_tester2
|
||||
Select * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'remote_schema');
|
||||
Select cartodb_id, ST_AsText(the_geom) from cdb_fs_loopback.localtable;
|
||||
|
||||
\echo '## A different user can unregister a table'
|
||||
SELECT cartodb.CDB_Federated_Table_Unregister(
|
||||
server => 'loopback',
|
||||
remote_schema => 'remote_schema',
|
||||
remote_table => 'remote_geom'
|
||||
);
|
||||
Select * FROM cartodb.CDB_Federated_Server_List_Remote_Tables(server => 'loopback', remote_schema => 'remote_schema');
|
||||
|
||||
\echo '## Only the owner can revoke permissions over the server'
|
||||
SELECT cartodb.CDB_Federated_Server_Revoke_Access(server := 'loopback', db_role := 'cdb_fs_tester'::name);
|
||||
|
||||
-- ===================================================================
|
||||
-- Cleanup
|
||||
-- ===================================================================
|
||||
|
||||
\set QUIET on
|
||||
\c contrib_regression postgres
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY terse
|
||||
|
||||
REVOKE CONNECT ON DATABASE contrib_regression FROM cdb_fs_tester2;
|
||||
DROP ROLE cdb_fs_tester2;
|
||||
|
||||
SELECT 'D1', cartodb.CDB_Federated_Server_Unregister(server := 'loopback'::text);
|
||||
DROP DATABASE cdb_fs_tester;
|
||||
REVOKE CONNECT ON DATABASE contrib_regression FROM cdb_fs_tester;
|
||||
DROP ROLE cdb_fs_tester;
|
||||
DROP EXTENSION postgres_fdw;
|
||||
\set QUIET off
|
116
test/CDB_FederatedServerTables_expect
Normal file
116
test/CDB_FederatedServerTables_expect
Normal file
@ -0,0 +1,116 @@
|
||||
C1|
|
||||
## Registering an existing table works
|
||||
R1|
|
||||
S1|1|POINT(1 1)|patata
|
||||
S1|2|POINT(2 2)|patata2
|
||||
t|remote_geom|cdb_fs_loopback.remote_geom|id|geom|geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
f|remote_geom2|||||[{"Name" : "another_field", "Type" : "text"}, {"Name" : "cartodb_id", "Type" : "bigint"}, {"Name" : "the_geom", "Type" : "GEOMETRY,4326"}, {"Name" : "the_geom_webmercator", "Type" : "GEOMETRY,3857"}]
|
||||
f|remote_geom3|||||[{"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "geom_mercator", "Type" : "GEOMETRY,3857"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
f|remote_other|||||[{"Name" : "field", "Type" : "text"}, {"Name" : "field2", "Type" : "text"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
## Registering another existing table works
|
||||
R2|
|
||||
S2|3|POINT(3 3)|patata
|
||||
t|remote_geom|cdb_fs_loopback.remote_geom|id|geom|geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
t|remote_geom2|cdb_fs_loopback."myFullTable"|cartodb_id|the_geom|the_geom_webmercator|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "cartodb_id", "Type" : "bigint"}, {"Name" : "the_geom", "Type" : "GEOMETRY,4326"}, {"Name" : "the_geom_webmercator", "Type" : "GEOMETRY,3857"}]
|
||||
f|remote_geom3|||||[{"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "geom_mercator", "Type" : "GEOMETRY,3857"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
f|remote_other|||||[{"Name" : "field", "Type" : "text"}, {"Name" : "field2", "Type" : "text"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
## Re-registering a table works
|
||||
R3|
|
||||
ERROR: relation "cdb_fs_loopback.myFullTable" does not exist at character 49
|
||||
S3_new|3|patata
|
||||
## Unregistering works
|
||||
U1|
|
||||
ERROR: relation "remote_geom" does not exist at character 71
|
||||
f|remote_geom|||||[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
t|remote_geom2|cdb_fs_loopback.different_name|cartodb_id|the_geom_webmercator|the_geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "cartodb_id", "Type" : "bigint"}, {"Name" : "the_geom", "Type" : "GEOMETRY,4326"}, {"Name" : "the_geom_webmercator", "Type" : "GEOMETRY,3857"}]
|
||||
f|remote_geom3|||||[{"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "geom_mercator", "Type" : "GEOMETRY,3857"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
f|remote_other|||||[{"Name" : "field", "Type" : "text"}, {"Name" : "field2", "Type" : "text"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
## Registering a table: Invalid server fails
|
||||
ERROR: Server "Does not exist" does not exist
|
||||
## Registering a table: NULL server fails
|
||||
ERROR: Server name cannot be NULL
|
||||
## Registering a table: Invalid schema fails
|
||||
ERROR: Could not import schema "Does not exist" of server "loopback": schema "Does not exist" is not present on foreign server "cdb_fs_loopback"
|
||||
## Registering a table: NULL schema fails
|
||||
ERROR: Schema name cannot be NULL
|
||||
## Registering a table: Invalid table fails
|
||||
ERROR: Could not import table "remote_schema.Does not exist" of server "loopback"
|
||||
## Registering a table: NULL table fails
|
||||
ERROR: Remote table name cannot be NULL
|
||||
## Registering a table: Invalid id fails
|
||||
ERROR: non integer id_column "Does not exist"
|
||||
## Registering a table: NULL id fails
|
||||
ERROR: non integer id_column "<NULL>"
|
||||
## Registering a table: Invalid geom_column fails
|
||||
ERROR: non geometry column "Does not exists"
|
||||
## Registering a table: NULL geom_column is OK: Reuses geom_mercator
|
||||
|
||||
t|remote_geom|cdb_fs_loopback.remote_geom|id|geom|geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
|
||||
## Registering a table: Invalid webmercator_column fails
|
||||
ERROR: non geometry column "Does not exists"
|
||||
## Registering a table: NULL webmercator_column is OK: Reuses geom
|
||||
|
||||
t|remote_geom|cdb_fs_loopback.remote_geom|id|geom|geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
|
||||
##Registering a table with extra columns show the correct information
|
||||
|
||||
t|remote_geom3|cdb_fs_loopback.remote_geom3|id|geom|geom_mercator|[{"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "geom_mercator", "Type" : "GEOMETRY,3857"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
|
||||
## Target conflict is handled nicely: Table
|
||||
CREATE TABLE
|
||||
ERROR: Could not import table "remote_geom" as "cdb_fs_loopback.localtable" already exists
|
||||
## Target conflict is handled nicely: View
|
||||
CREATE VIEW
|
||||
ERROR: Could not import table "remote_geom" as "cdb_fs_loopback.localtable2" already exists
|
||||
DROP VIEW
|
||||
DROP TABLE
|
||||
## Registering tables does not work without permissions
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
## Normal users can not write in the import schema
|
||||
ERROR: permission denied for schema cdb_fs_loopback at character 14
|
||||
## Listing remote tables does not work without permissions
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
## Registering tables works with granted permissions
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
|
||||
|
||||
## Listing remote tables works with granted permissions
|
||||
t|remote_geom|cdb_fs_loopback.localtable|id|geom|geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
t|remote_geom2|cdb_fs_loopback.different_name|cartodb_id|the_geom_webmercator|the_geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "cartodb_id", "Type" : "bigint"}, {"Name" : "the_geom", "Type" : "GEOMETRY,4326"}, {"Name" : "the_geom_webmercator", "Type" : "GEOMETRY,3857"}]
|
||||
f|remote_geom3|||||[{"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "geom_mercator", "Type" : "GEOMETRY,3857"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
f|remote_other|||||[{"Name" : "field", "Type" : "text"}, {"Name" : "field2", "Type" : "text"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
## Selecting from a registered table with granted permissions works
|
||||
1|POINT(1 1)
|
||||
2|POINT(2 2)
|
||||
## Selecting from a registered table without permissions does not work
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester2".
|
||||
CREATE FUNCTION
|
||||
t
|
||||
DROP FUNCTION
|
||||
## Deleting a registered table without permissions does not work
|
||||
ERROR: Not enough permissions to access the server "loopback"
|
||||
## Only the owner can grant permissions over the server
|
||||
ERROR: You do not have rights to grant access on "loopback"
|
||||
## Everything works for a different user when granted permissions
|
||||
You are now connected to database "contrib_regression" as user "postgres".
|
||||
|
||||
You are now connected to database "contrib_regression" as user "cdb_fs_tester2".
|
||||
t|remote_geom|cdb_fs_loopback.localtable|id|geom|geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
t|remote_geom2|cdb_fs_loopback.different_name|cartodb_id|the_geom_webmercator|the_geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "cartodb_id", "Type" : "bigint"}, {"Name" : "the_geom", "Type" : "GEOMETRY,4326"}, {"Name" : "the_geom_webmercator", "Type" : "GEOMETRY,3857"}]
|
||||
f|remote_geom3|||||[{"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "geom_mercator", "Type" : "GEOMETRY,3857"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
f|remote_other|||||[{"Name" : "field", "Type" : "text"}, {"Name" : "field2", "Type" : "text"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
1|POINT(1 1)
|
||||
2|POINT(2 2)
|
||||
## A different user can unregister a table
|
||||
NOTICE: drop cascades to view cdb_fs_loopback.localtable
|
||||
|
||||
f|remote_geom|||||[{"Name" : "another_field", "Type" : "text"}, {"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "id", "Type" : "integer"}]
|
||||
t|remote_geom2|cdb_fs_loopback.different_name|cartodb_id|the_geom_webmercator|the_geom|[{"Name" : "another_field", "Type" : "text"}, {"Name" : "cartodb_id", "Type" : "bigint"}, {"Name" : "the_geom", "Type" : "GEOMETRY,4326"}, {"Name" : "the_geom_webmercator", "Type" : "GEOMETRY,3857"}]
|
||||
f|remote_geom3|||||[{"Name" : "geom", "Type" : "GEOMETRY,4326"}, {"Name" : "geom_mercator", "Type" : "GEOMETRY,3857"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
f|remote_other|||||[{"Name" : "field", "Type" : "text"}, {"Name" : "field2", "Type" : "text"}, {"Name" : "id", "Type" : "bigint"}]
|
||||
## Only the owner can revoke permissions over the server
|
||||
ERROR: You do not have rights to revoke access on "loopback"
|
||||
D1|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user