Compare commits

..

353 Commits

Author SHA1 Message Date
Raúl Marín
da5a566ecf Merge pull request #397 from Algunenano/plpy_warnings
Remove leftover plpython warnings
2020-03-26 17:37:32 +01:00
Raúl Marín
38f26f4e8b Typo 2020-03-26 17:03:27 +01:00
Raúl Marín
422d1a0acd Try to initialize in the same command as the creation 2020-03-26 16:52:45 +01:00
Raúl Marín
f790dc1872 Adapt tests to work with other ports (not 5432) 2020-03-26 16:37:58 +01:00
Raúl Marín
2fcdd56338 Travis: Keep installed releases, and only rebuild the target 2020-03-26 16:37:18 +01:00
Raúl Marín
47f06f40df Remove leftover plpython warnings 2020-03-26 12:49:15 +01:00
Manuel J. Morillo
998402e531 Merge pull request #396 from CartoDB/pg12_compatibility
Make cartodb-postgresql python3 compatible (again)
2020-02-13 17:19:52 +01:00
manmorjim
bd4eeb3f9e Update NEWS with the release date 2020-02-13 17:12:32 +01:00
manmorjim
b9615a912f Adding debug for _CDB_Group_API_Auth 2020-02-12 18:55:44 +01:00
manmorjim
39c071f790 Release 0.36.0 2020-02-12 15:44:12 +01:00
manmorjim
e37cdb4ca0 Update NEWS with changes for python3 compatibility 2020-02-12 10:30:18 +01:00
manmorjim
c8410541a4 Fix variable naming 2020-02-12 10:09:07 +01:00
manmorjim
55ebc73f9e Remove comments to fix syntax errors 2020-02-11 19:31:37 +01:00
manmorjim
027256dbb7 Make cartodb-postgresql python3 compatible
- encoding string with python3
- adapt HTTPConnection signature to python3
2020-02-11 18:51:24 +01:00
Esther Lozano
076cacc6f1 Release 0.35.0 2019-12-30 15:37:16 +01:00
Raúl Marín
741d171f4a Merge pull request #392 from Algunenano/test_bionic
Test bionic
2019-12-26 18:11:30 +01:00
Raul Marin
093e0bcb36 Always use @@plpythonu@@ as variable 2019-12-26 17:59:29 +01:00
Raul Marin
ca1d5b0c91 Avoid outputting python error messages in regress tests 2019-12-26 17:46:10 +01:00
Raul Marin
cd62071757 Try to use bionic for PG12 2019-12-26 15:54:42 +01:00
Raúl Marín
4c15fafd72 Merge pull request #387 from CartoDB/2273_support
use literal substitution instead of identifier
2019-12-24 14:24:32 +01:00
Raúl Marín
b246ada312 Update NEWS 2019-12-24 14:17:03 +01:00
Raúl Marín
ecf0a06448 Merge remote-tracking branch 'carto/master' into 2273_support 2019-12-24 14:14:33 +01:00
Raúl Marín
ec0d94dd0e Add test for CDB_UserDataSize identifier quoation 2019-12-24 14:14:26 +01:00
Raúl Marín
c5bbb180c2 Merge pull request #391 from Algunenano/python3again
Set python3 as dependency for PG12+ again
2019-12-24 14:08:02 +01:00
Raúl Marín
9fa532623d Set python3 as dependency for PG12+ again 2019-12-24 13:57:13 +01:00
Raúl Marín
921800e6a3 Merge pull request #390 from Algunenano/revert_the_revert
Add huge warnings
2019-12-24 01:05:02 +01:00
Raúl Marín
dfd26d930e Add huge warnings 2019-12-24 00:59:11 +01:00
Raúl Marín
c0270068c4 Merge pull request #389 from Algunenano/revert_the_revert
Revert "Revert 0.33.0"
2019-12-24 00:39:22 +01:00
Raúl Marín
9010642326 Revert "Revert 0.33.0"
This reverts commit 24cb6cf9c1.
2019-12-24 00:33:35 +01:00
Raúl Marín
8974986d61 Release 0.34.0 2019-12-23 20:34:11 +01:00
Raúl Marín
fea2474117 Merge pull request #388 from Algunenano/revert0330
Revert changes in 0.33.0
2019-12-23 20:31:51 +01:00
Raúl Marín
654fa3ac02 Update NEWS.md
Co-Authored-By: Daniel G. Aubert <danielgarciaaubert@gmail.com>
2019-12-23 20:25:36 +01:00
Raúl Marín
24cb6cf9c1 Revert 0.33.0 2019-12-23 20:23:02 +01:00
Alberto Romeu
1c7b0c71ec use literal substitution instead of identifier 2019-12-20 16:27:03 +01:00
Daniel García Aubert
5e3adba2d4 Increase version number to 0.33.0 2019-12-20 12:42:09 +01:00
Daniel G. Aubert
89c9025431 Merge pull request #382 from Algunenano/federated_functions
Federated Server functions
2019-12-20 12:39:05 +01:00
Raúl Marín
6ebffe1b2c PR review improvements 2019-12-17 11:26:56 +01:00
Raúl Marín
b3503ea429 FederatedTableList: Fix column parsing with default names 2019-12-12 15:52:50 +01:00
Raúl Marín
3c869e3d5a FServer list: Return the username if the user has been granted access to the server 2019-12-11 18:37:26 +01:00
Raúl Marín
dac135890d CDB_Federated_Table_Register: Reuse webmercator_column if geom_column is NULL 2019-12-10 18:08:25 +01:00
Raúl Marín
9372c9f4e0 FederatedServer: Fix a bug when extracting the webmercator column from the view definition 2019-12-05 16:26:37 +01:00
Raúl Marín
06b6d9140d Merge remote-tracking branch 'carto/master' into federated_functions 2019-12-02 16:51:35 +01:00
Raúl Marín
beb985b64f Merge pull request #386 from Algunenano/revertPG12
Revert PG12 + Python3
2019-12-02 16:50:25 +01:00
Raúl Marín
eea750c904 Revert PG12 + Python3 2019-12-02 16:46:58 +01:00
Raúl Marín
da570c50aa Merge remote-tracking branch 'carto/master' into federated_functions 2019-11-15 13:50:21 +01:00
Raúl Marín
d549ecced4 Merge pull request #385 from CartoDB/federated_functions_permissions
Federated functions permissions
2019-11-15 13:47:16 +01:00
Raúl Marín
fde12c818a Update comments 2019-11-15 13:09:52 +01:00
Raúl Marín
e99e4a02c9 Remove TODO
Since the server registration requires superadmin priviledges
it doesn't make sense to over protect the role creation
2019-11-14 19:00:09 +01:00
Raúl Marín
fa5f0bcbc1 CDB_Federated_Server_Grant_Access: comment about search_path 2019-11-14 18:55:38 +01:00
Raúl Marín
adca727169 Revert "Go back to public user mappings"
This reverts commit 64e185b841.
2019-11-14 18:46:45 +01:00
Raúl Marín
0893fae025 Revert "CDB_Federated_Server_Unregister: Drop owned in cascade"
This reverts commit 1b86517007.
2019-11-14 18:46:02 +01:00
Raúl Marín
e69d3b1c30 Place all views in a shared schema 2019-11-14 18:45:09 +01:00
Rafa de la Torre
a57431a04d Merge pull request #384 from CartoDB/federated_diagnostics_latency
Federated diagnostics latency
2019-11-14 18:29:25 +01:00
Rafa de la Torre
064cc2a76b Fix for missing port in pg_foreign_server: default to standard 5432 2019-11-14 17:57:05 +01:00
Rafa de la Torre
7f9905cbd5 Test what happens when missing PG port 2019-11-14 17:56:29 +01:00
Raúl Marín
6e455efcdd Improve warning 2019-11-14 14:15:56 +01:00
Raúl Marín
1b86517007 CDB_Federated_Server_Unregister: Drop owned in cascade
Since now views might be owned by other roles, we need to drop them
in cascade to force deletion
2019-11-14 14:07:41 +01:00
Raúl Marín
64e185b841 Go back to public user mappings 2019-11-14 13:43:28 +01:00
Raúl Marín
38d8a470bc Federated Server: Grant all over postgis and cartodb schemas 2019-11-14 13:24:32 +01:00
Raúl Marín
4920029560 CDB_FederatedServerTables: Improve permission handling and error messages 2019-11-14 13:10:21 +01:00
Rafa de la Torre
9b91704480 Calculate stats only when they make sense 2019-11-13 19:27:56 +01:00
Rafa de la Torre
a340f98b96 Update tests with latency stats 2019-11-13 19:19:39 +01:00
Rafa de la Torre
57dcfc2a35 Add a bunch of statistics 2019-11-13 18:55:09 +01:00
Rafa de la Torre
3ae7b1fe05 Fix bug: do not initialize samples in every iter 2019-11-13 18:27:14 +01:00
Rafa de la Torre
f7d10adbc2 Fix another python2/3 incompat: xrange 2019-11-13 17:16:53 +01:00
Rafa de la Torre
203bd64058 Fix python2/3 compat issue 2019-11-13 17:02:22 +01:00
Rafa de la Torre
19c6de2096 Comment out suspected line
This may be causing the error

     ERROR:  could not compile PL/Python function "__cdb_fs_tcp_foreign_server_latency"
     DETAIL:  SyntaxError: invalid syntax (<string>, line 26)

when testing in PG12 + plpythonu3
2019-11-13 14:52:24 +01:00
Raúl Marín
e231500c46 Give permissions over cartodb to the FS role 2019-11-13 14:20:08 +01:00
Rafa de la Torre
4b9c048235 Close socket properly before the next iteration
This avoid the errors `[Errno 106] Transport endpoint is already
connected` which are due to connections kept in TIME_WAIT state.

We don't use socket.SO_REUSEADDR as it would make the first connection
a special case compared to the following ones.
2019-11-13 12:25:41 +01:00
Rafa de la Torre
5897d8a9cb Fix timer start in measurements 2019-11-13 12:24:59 +01:00
Rafa de la Torre
980657c4a4 Do not add None to samples in case of connection error
Otherwise we get `TypeError: unsupported operand type(s) for +: 'float'
and 'NoneType'` when calculating the average.
2019-11-13 12:16:21 +01:00
Rafa de la Torre
a4a4a1dff1 Honor n_samples param 2019-11-13 12:13:42 +01:00
Rafa de la Torre
0362825b8c Encapsulate python in __CDB_FS_TCP_Foreign_Server_Latency
Instead of having a generic function, merge
__CDB_FS_TCP_Network_Latency and __CDB_FS_Foreign_Server_Host_PG into
one function so that it can only "ping" federated servers.
2019-11-13 11:44:24 +01:00
Raúl Marín
9eab5e2533 __CDB_FS_Generate_Server_Role_Name: set to STABLE
current_database() is stable, not immutable
2019-11-12 18:15:20 +01:00
Raúl Marín
0d455d9521 __CDB_FS_List_Foreign_Schemas_PG: Remove extra role generation 2019-11-12 18:14:36 +01:00
Rafa de la Torre
364933b6b9 Compatibility with PG 9.6 and 10
See https://www.postgresql.org/message-id/flat/55137679-fd6b-baf0-bfbf-6558ce5cb409%40postgrespro.ru
2019-11-12 15:57:15 +01:00
Rafa de la Torre
18f9f79fe8 Rename key to server_latency_ms to be self-explanatory 2019-11-12 15:08:26 +01:00
Rafa de la Torre
a032cc08e0 Measure latency as the avg of n_samples 2019-11-12 15:01:56 +01:00
Rafa de la Torre
9dc547c838 Add test for wrong port 2019-11-12 13:37:04 +01:00
Rafa de la Torre
d3ae4b808c Remove default port 5432 in generic function 2019-11-12 13:27:42 +01:00
Rafa de la Torre
26f91001ee Retrieve port from the catalog 2019-11-12 13:06:14 +01:00
Rafa de la Torre
43b242e610 Retrieve host from the catalog 2019-11-12 13:02:04 +01:00
Rafa de la Torre
e14c49b223 Add kind of meaningful test for latency 2019-11-12 12:59:36 +01:00
Rafa de la Torre
8c7a75dd0c Implement __CDB_FS_TCP_Network_Latency 2019-11-12 12:42:28 +01:00
Rafa de la Torre
dbed2d1ede More scaffolding for latency in diagnostics 2019-11-12 12:41:37 +01:00
Rafa de la Torre
1c27443ff4 Stub remote server latency (WIP) 2019-11-12 12:41:37 +01:00
Rafa de la Torre
944b111bb1 Merge pull request #383 from CartoDB/federated_diagnostics
Retrieve target PG and postgis versions
2019-11-12 10:45:26 +01:00
Rafa de la Torre
d5ecb39250 Replace hardcoded 5432 by @@PGPORT@@
as requested in review comment
2019-11-12 10:32:25 +01:00
Rafa de la Torre
0ad7cd485d Simplify function with aggregate 2019-11-11 21:34:51 +01:00
Rafa de la Torre
6e9ee296c5 Remove useless commented code 2019-11-11 20:48:14 +01:00
Rafa de la Torre
d87d27d7e5 Use pg_options_to_table instead of manual parsing 2019-11-11 20:42:00 +01:00
Rafa de la Torre
5c9f6964a3 Fix typo: hardcoded loopback server 2019-11-11 18:46:07 +01:00
Rafa de la Torre
d76e3ccc3e Add server options to diagnostics 2019-11-11 16:02:04 +01:00
Rafa de la Torre
eba6cf4565 Test when postgis is not installed in the remote 2019-11-08 20:27:24 +01:00
Rafa de la Torre
6700d24232 Refactor: extract __CDB_FS_Import_If_Not_Exists 2019-11-08 20:16:26 +01:00
Rafa de la Torre
511e24a40e Remove unused var role_name 2019-11-08 20:05:08 +01:00
Rafa de la Torre
80f01d4a51 Implement retrieval of remote postgis version 2019-11-08 20:02:51 +01:00
Rafa de la Torre
d4bc69cd3c Return postgis version in diagnostics info (WIP) 2019-11-08 19:49:16 +01:00
Rafa de la Torre
8730292097 Function to retrieve remote PG server ver 2019-11-08 19:20:08 +01:00
Rafa de la Torre
efd757ffa3 Check against correct server version 2019-11-08 18:18:28 +01:00
Rafa de la Torre
40a2ba9569 Stub function to add server_version to diagnostics 2019-11-08 18:09:22 +01:00
Rafa de la Torre
35b2b7e589 Check the server is of type PG and return jsonb 2019-11-08 18:06:35 +01:00
Rafa de la Torre
4100b66f3b Scaffolding for diagnostic function(s) 2019-11-08 17:11:04 +01:00
Esther Lozano
b1830e49af Increase version number to 0.32.0 2019-11-08 13:09:45 +01:00
Esther Lozano
70f1e00980 Merge pull request #379 from CartoDB/fix-oauth-reassign-functions
Fix OAuth ownership re-assignation for functions
2019-11-08 12:38:43 +01:00
Esther Lozano
8a4a59b340 Document default privileges on functions 2019-11-07 15:03:46 +01:00
Esther Lozano
f06b899605 Add drop permission test for functions 2019-11-07 11:14:26 +01:00
Esther Lozano
09076924c0 Functions are selectable by all roles but not dropable 2019-11-06 21:12:44 +01:00
Raul Marin
d5b3953568 Regress: Remove output that depends on PROJ release 2019-11-06 18:34:20 +01:00
Raul Marin
c64c61781e Make tests path insensitive 2019-11-06 18:26:58 +01:00
Raul Marin
82d8f7e1aa Deprecate FDW user setup ;__( 2019-11-06 18:26:37 +01:00
Esther Lozano
e63f5040d9 Use privileged action on functions for tests 2019-11-06 17:51:00 +01:00
Raul Marin
3cde55ed3a Enforce output order in CDB_Federated_Server_List_Remote_Columns 2019-11-06 17:32:48 +01:00
Raul Marin
a91f14371a Use always the modern notation for named parameters 2019-11-06 17:25:10 +01:00
Raul Marin
899a12fc53 FederatedServer: Use NAME for internal types 2019-11-06 17:12:40 +01:00
Raul Marin
01258530ff Federated Server: Unify remote table listing
- Removes DB_Federated_Server_List_Registered_Tables in favor
of CDB_Federated_Server_List_Remote_Tables that now returns
both registered and unregistered tables and, when they are
registered, the information about the associated view
2019-11-06 17:04:11 +01:00
Raul Marin
90b58fcdec Federated Server: Test with incorrect password 2019-11-06 10:20:53 +01:00
Raul Marin
29e2ee670f Accept PG9.5 and PG10 permission error messages 2019-11-05 18:29:08 +01:00
Raul Marin
e98b18fd25 Add test around grant permissions and accessing registered tables 2019-11-05 18:07:11 +01:00
Raul Marin
e6f07d4f96 Rework API to grant and revoke permissions 2019-11-05 17:18:49 +01:00
Raul Marin
c9b01592e7 CDB_Federated_Table_Register: Handle conflict nicely 2019-11-05 16:53:25 +01:00
Raul Marin
e2beefdb53 Test around NULLs and invalids arguments 2019-11-05 16:39:13 +01:00
Raul Marin
73ee4107d4 CDB_FederatedServerTables: Improve tests readability 2019-11-05 14:48:52 +01:00
Raul Marin
aa481f4219 Permission tests and fixes for CDB_FederatedServerListRemote 2019-11-05 14:35:52 +01:00
Raul Marin
bac7d79ab3 CDB_FederatedServer: Improve tests readability 2019-11-05 13:22:45 +01:00
Raul Marin
98b2b02199 Improve tests readability and test deleting a server from granted access 2019-11-05 11:19:50 +01:00
Raul Marin
10ed64789d Add functions to grant and revoke permissions over federated servers 2019-11-04 19:23:36 +01:00
Raul Marin
0f66c54ce5 Comment cleanup 2019-11-04 16:53:48 +01:00
Raul Marin
03bc3436cd Add more tests for [un-]registering servers 2019-11-04 16:29:18 +01:00
Raul Marin
57f420ab4c CDB_FederatedServerListRemote: Add rainy tests 2019-11-04 15:32:34 +01:00
Raul Marin
c740cfa499 CDB_Federated_Server_List_Remote_Columns: Return also type name 2019-11-04 15:18:46 +01:00
Raul Marin
eea0198864 Add CDB_Federated_Server_List_Remote_Columns 2019-10-31 15:29:37 +01:00
Raul Marin
f75756138a CDB_FederatedServerTables: Avoid decimal mismatch 2019-10-30 15:21:00 +01:00
Raul Marin
28376ffecb CDB_Federated_Table_Register works to refresh a table 2019-10-30 13:53:56 +01:00
Raul Marin
f20a007160 CDB_FederatedServerTables: Some comments 2019-10-30 13:27:44 +01:00
Raul Marin
cf6b00dfd8 CDB_FederatedServerListRemote: Improve comments 2019-10-30 13:20:41 +01:00
Raul Marin
cc5fb37abb CDB_FederatedServer: Improve comments 2019-10-30 13:16:31 +01:00
Raul Marin
b839235801 Add CDB_Federated_Server_List_Registered_Tables 2019-10-30 12:19:30 +01:00
Raul Marin
d5f84784f0 Add CDB_Federated_Table_Unregister 2019-10-30 11:55:02 +01:00
Raul Marin
7bb0b93f8b Test renaming 2019-10-30 11:47:45 +01:00
Raul Marin
a26b96347a Initial implementation of CDB_Federated_Table_Register 2019-10-30 11:40:35 +01:00
Raul Marin
77868a541d Fix symlinks :D 2019-10-29 18:55:18 +01:00
Raul Marin
b76b754811 CDB_FederatedServerTables skeleton 2019-10-29 18:53:05 +01:00
Raul Marin
664468b1e5 Rename CDB_FederatedServerListRemote files 2019-10-29 18:51:47 +01:00
Raul Marin
d85faa8f85 Now trully fix installcheck target 2019-10-29 18:45:10 +01:00
Raul Marin
f7ab3d28a8 __CDB_FS_List_Foreign_Schemas_PG: Handle order and ignore pg_% schemas automatically 2019-10-29 18:35:02 +01:00
Raul Marin
144c4e722b Add federated server list remotes
Includes:
- CDB_Federated_Server_List_Remote_Schemas
- CDB_Federated_Server_List_Remote_Tables
2019-10-29 18:26:52 +01:00
Raul Marin
e22aaee5f5 Test re-registration of a server with different options 2019-10-29 16:47:35 +01:00
Raul Marin
da9ff0c750 Reimplement CDB_Federated_Server_Register_PG and CDB_Federated_Server_Unregister 2019-10-29 16:19:46 +01:00
Raul Marin
e6cb03300b Add auxiliar functions to handle internal naming 2019-10-29 15:38:58 +01:00
Raul Marin
bc7031d872 Move existence check to __CDB_FS_Generate_Object_Name 2019-10-29 13:19:11 +01:00
Raul Marin
427ad94235 Fix installcheck target 2019-10-29 12:34:29 +01:00
Raul Marin
1375894e99 CDB_Federated_Server_Unregister: Better error handling 2019-10-29 12:27:07 +01:00
Raul Marin
f285c806fc Remove error context 2019-10-29 11:57:24 +01:00
Raul Marin
acf1f2dbd4 WIP: Add CDB_Federated_Server_Unregister 2019-10-28 18:34:46 +01:00
Raul Marin
eb02ca4d86 Regress: Allow variables in the tests 2019-10-28 17:12:38 +01:00
Raul Marin
b475eb4356 Federated Server List 2019-10-28 17:10:53 +01:00
Raúl Marín
510a9d12e4 Merge pull request #380 from Algunenano/regress_target
Regress target
2019-10-28 17:08:52 +01:00
Raul Marin
77213f0588 Update NEWS 2019-10-28 16:18:37 +01:00
Raul Marin
8ff0ad74af Add regress target
Runs the regress tests without installing the extension
2019-10-28 16:17:29 +01:00
Raul Marin
a25efc3fc5 Unpackaged: Make it work 2019-10-28 16:14:50 +01:00
Raul Marin
87dec4dbe4 Update README 2019-10-28 16:14:45 +01:00
Gonzalo Riestra
f2e8d029eb check error message 2019-10-25 12:39:30 +02:00
Gonzalo Riestra
036127af9c add schema to function calls 2019-10-25 12:26:21 +02:00
Gonzalo Riestra
2ce4d8ee14 fix grant query 2019-10-25 12:02:08 +02:00
Gonzalo Riestra
a8966270bd add test expect 2019-10-25 11:50:03 +02:00
Gonzalo Riestra
11b30c8f59 fix oauth reassign ownership for functions 2019-10-25 11:34:59 +02:00
Raúl Marín
415220777e Merge pull request #378 from Algunenano/pg12
PG 12 + PLPYTHON 3 compatibility
2019-10-24 17:39:07 +02:00
Raul Marin
1dcc23db15 CDB_UserTablesTest: Make tests work even if the user doesn't have cartodb in the searchpath 2019-10-24 13:45:36 +02:00
Raul Marin
164fba197f test_foreign_tables: Better debugging and query the default database when needed 2019-10-24 12:35:46 +02:00
Raul Marin
7f9a748119 Make CDB_OAuth independent of the PG version 2019-10-24 12:35:46 +02:00
Raul Marin
409b067721 Quota: Remove kb error from regress tests 2019-10-24 11:15:15 +02:00
Raul Marin
21c319eb9a Add postgis 3 build 2019-10-23 17:24:41 +02:00
Raul Marin
dac1f5bffc Update NEWS 2019-10-23 17:15:50 +02:00
Raul Marin
9469871029 Remove unneeded change 2019-10-23 17:06:47 +02:00
Raul Marin
021c7318a3 Adapt QuotaTest to avoid randoms 2019-10-23 16:49:57 +02:00
Raul Marin
29397ff929 Make it work without postgis_raster 2019-10-23 14:21:45 +02:00
Raul Marin
34684507cb Cast sql_identifier to text 2019-10-23 13:46:04 +02:00
Raul Marin
a4bcf4c78f Update error messages 2019-10-22 20:13:38 +02:00
Raul Marin
e787b1d097 Missing qualification 2019-10-22 20:09:15 +02:00
Raul Marin
8245156038 Use distribution package for python redis 2019-10-22 20:06:20 +02:00
Raul Marin
9248ce76bd Update pip3 2019-10-22 19:48:00 +02:00
Raul Marin
d1eb740a00 Missing qualifications 2019-10-22 19:45:42 +02:00
Raul Marin
cb019ff194 Install python3 for PG12 run 2019-10-22 19:35:26 +02:00
Raul Marin
1a99151d0e Install pip and pip3 2019-10-22 18:23:27 +02:00
Raul Marin
8fac0c89eb python3: Try to adapt pathname2url 2019-10-22 18:06:29 +02:00
Raul Marin
9686ad5cfe Try to work with both python2 and python3 2019-10-22 16:49:08 +02:00
Raul Marin
0cecfdb112 log level 2019-10-22 16:32:30 +02:00
Raul Marin
ccbaf785cf Updates 2019-10-22 16:00:29 +02:00
Raul Marin
aed88fd958 Force all PG12 to use plpython3u 2019-10-22 15:29:45 +02:00
Raul Marin
3b4669df11 Try hacking plpythonu -> plpython3u 2019-10-22 14:24:20 +02:00
Raul Marin
9f904b5926 plpython2 is dead, long live plpython 2019-10-22 14:10:14 +02:00
Raul Marin
4802501387 Travis: Test against PG12 2019-10-22 11:59:42 +02:00
Raul Marin
b68ce72889 Release 0.31.0 2019-10-08 12:47:32 +02:00
Raúl Marín
7a23ea815c Merge pull request #371 from Algunenano/sec_definer
Sec definer
2019-10-08 12:32:26 +02:00
Raul Marin
2533d0996c Add NEWs 2019-10-03 18:59:01 +02:00
Raul Marin
048234cd80 CDB_Groups_API: Secure search_path 2019-10-03 18:58:27 +02:00
Raul Marin
5a12033609 Adapt tests to changes 2019-10-03 18:58:27 +02:00
Raul Marin
a580bedefc Set safe schema on some functions 2019-10-03 18:58:27 +02:00
Raul Marin
0e891eff7f Remove cartodb from search path
All calls to the extension functions should, and are,
be properly qualified, so there is no need to keep
the cartodb schema in the search_path
2019-10-03 18:58:27 +02:00
Raul Marin
52b3290d26 CDB_TableMetadata: Use secure search_path 2019-10-03 18:58:27 +02:00
Raul Marin
186ee37a57 CDB_Username: Set secure search_path 2019-10-03 18:58:27 +02:00
Raul Marin
0898881470 Oauth: Set secure search_path 2019-10-03 18:58:27 +02:00
Raul Marin
4dfd71639a CDB_OAuthReassignTableOwnerOnCreation: Use CDB_Conf_GetConf
Instead of accessing the cdb_conf table directly
2019-10-03 18:58:27 +02:00
Raul Marin
c6b90aac8a GhostTables: Set secure search_path 2019-10-03 18:58:27 +02:00
Raúl Marín
8a87f96f04 Merge pull request #370 from Algunenano/ghost_matview
Ghost tables: Add missing tags
2019-10-03 15:06:11 +02:00
Raul Marin
7c10fcc363 Update NEWS 2019-10-01 11:03:53 +02:00
Raul Marin
2f178bd89e _CDB_SetUp_User_PG_FDW_Server: Minor log changes 2019-10-01 11:02:34 +02:00
Raul Marin
32489c4eab Ghost tables: Add missing tags
- Add materialized views commands.
- Add missing alter trigger.
- Add 'IMPORT FOREIGN SCHEMA' since it might create new tables.
- Add 'DROP EXTENSION' and 'DROP TYPE' since they alter table columns.
- Add 'DROP SCHEMA' and 'DROP SERVER' as they drop tables.
2019-10-01 11:02:34 +02:00
Rafa de la Torre
afa52aa92b Prepare release 0.30.0 2019-07-17 09:58:21 +02:00
Rafa de la Torre
746dbea434 Merge pull request #369 from CartoDB/user-defined-fdw
User defined FDW's
2019-07-17 09:52:23 +02:00
Rafa de la Torre
f9bd469ea9 Make oauth tests a bit more robust 2019-07-17 09:46:49 +02:00
Rafa de la Torre
402d97daa6 Merge remote-tracking branch 'origin/master' into user-defined-fdw 2019-07-17 09:18:35 +02:00
Rafa de la Torre
e41d2ec019 Add a flag to force drop of user FDW and related objects
If force = true then it will add the subclause `CASCADE` to the SQL
DDL sentences that support it, otherwise it'll use `RESTRICT` which is
the default and exact opposite.
2019-07-16 17:40:05 +02:00
Rafa de la Torre
3c460f1a85 Add a bunch of RAISE NOTICE's to inform user about progress 2019-07-16 17:03:23 +02:00
Rafa de la Torre
076207c49c Make sure there are no (double)escaping issues 2019-07-16 16:24:02 +02:00
Rafa de la Torre
ce1e9ac41c Prefix all objects created with cdb_fdw_
Build the DB objects related to a user FDW with the following form:
`cdb_fdw_name`. This is aimed at easily inspect and filter them.

As requested in code review.
2019-07-16 15:35:35 +02:00
Rafa de la Torre
0f33ee8b22 Prepend an underscore (_) to functions meant to be run by superuser
_CDB_SetUp_User_PG_FDW_Server and _CDB_Drop_User_PG_FDW_Server are
meant to be executed by a superuser. Therefore they shouldn't be
considered part of the public API and hence the _CDB_Private_Function
naming convention.
2019-07-16 14:32:32 +02:00
Rafa de la Torre
a32dea0282 Remove SECURITY DEFINER from user-defined FDW's 2019-07-16 13:26:03 +02:00
Rafa de la Torre
3a255df9d0 Rename PG-FDW's-specific functions to _PG_FDW_
As per review comment.
2019-07-16 13:14:11 +02:00
Rafa de la Torre
c4e2549dc8 A few more permissions tests for completeness 2019-07-15 18:14:23 +02:00
Rafa de la Torre
2e9f642378 Check when users shall not have permissions to the FDW 2019-07-15 17:32:48 +02:00
Rafa de la Torre
99096d41e0 Drop the role when dropping a user-defined FDW 2019-07-15 17:25:48 +02:00
Rafa de la Torre
3a10ef7e76 Add ability to grant fdw role to org members 2019-07-15 16:54:23 +02:00
Rafa de la Torre
a20676f391 Add a test/example of granting the fdw role 2019-07-15 16:19:06 +02:00
Rafa de la Torre
37004db047 Add new function to drop a user-defined foreign server 2019-07-15 16:14:07 +02:00
Rafa de la Torre
1189d70b2a Request host auth to use password
This is required for non superusers to use FDW's. See
https://www.postgresql.org/docs/11/postgres-fdw.html#id-1.11.7.42.10
2019-07-15 14:52:39 +02:00
Rafa de la Torre
8cfc8e65cf Test with a regular user (non-superadmin) 2019-07-15 14:44:21 +02:00
Gonzalo Riestra
32db4fd81e Merge pull request #367 from CartoDB/oauth_create_table_trigger
Oauth create table trigger
2019-07-15 13:43:08 +02:00
Gonzalo Riestra
a1e3e9a8df update news 2019-07-15 13:42:43 +02:00
Rafa de la Torre
6e34e16b8d Add basic test for user-defined FDW's 2019-07-15 13:16:14 +02:00
Rafa de la Torre
70220e04c1 Allow for imports of tables in different source schemas 2019-07-15 13:13:21 +02:00
Gonzalo Riestra
a5cb9f268d reassign ownership for sequences and functions as well 2019-07-15 12:31:44 +02:00
Rafa de la Torre
d2d909145d Add convenience function to import fdw tables 2019-07-12 16:53:34 +02:00
Rafa de la Torre
34dec227c4 Rename to CDB_SetUp_User_Foreign_Server 2019-07-12 16:53:34 +02:00
Rafa de la Torre
99e92e2505 Create a "PUBLIC" user mapping 2019-07-12 16:53:34 +02:00
Rafa de la Torre
c58a084102 Tweak ownership of db objects 2019-07-12 16:53:34 +02:00
Rafa de la Torre
b7907ff82f Fix typo granting perms 2019-07-12 16:53:34 +02:00
Rafa de la Torre
12d955075a Fix bug iterating user_mapping options 2019-07-12 16:53:34 +02:00
Rafa de la Torre
10a4d85c01 Fix typo in example: missing closing } 2019-07-12 16:53:24 +02:00
Rafa de la Torre
524bb6ad42 Fix copy/paste typos 2019-07-12 12:49:59 +02:00
Rafa de la Torre
4da89d8abd First version of the function WIP 2019-07-12 12:40:02 +02:00
Rafa de la Torre
c7311ba48e A stub of a convenient FDW function 2019-07-12 12:00:30 +02:00
Gonzalo Riestra
2eae7876e2 fix makefile 2019-07-12 08:38:21 +02:00
Mario de Frutos Dieguez
91c3b86d45 Updated NEWS and tests README 2019-07-04 17:53:11 +02:00
Mario de Frutos Dieguez
f55d789c41 Make some tests to have different expects for different PG versions 2019-07-04 17:48:01 +02:00
Mario de Frutos Dieguez
3eb8ab24d8 Improved tests 2019-07-04 17:48:01 +02:00
Mario de Frutos Dieguez
0f1c98c743 Check for empty strings as well 2019-07-04 17:48:01 +02:00
Mario de Frutos Dieguez
8ecd2cd5e2 Reuser creator_role
Co-Authored-By: Alberto Romeu <alrocar@users.noreply.github.com>
2019-07-04 17:48:00 +02:00
Mario de Frutos Dieguez
f4be59cae0 Added tests 2019-07-04 17:48:00 +02:00
Mario de Frutos Dieguez
fe66b2865a Upgrade version to 0.29.0 2019-07-04 17:48:00 +02:00
Mario de Frutos Dieguez
2be9d2d81a Enable OAuth scripts 2019-07-04 17:47:36 +02:00
Mario de Frutos Dieguez
5744921065 OAuth functions
- Create/drop reassign event trigger and the function with the logic
- Function that reassings owner to ownership role if defined
2019-07-04 17:47:36 +02:00
Rafa de la Torre
4c93258cd2 Prepare release of 0.28.1 2019-07-04 16:09:53 +02:00
Javier Goizueta
bf140890d8 Merge pull request #365 from CartoDB/table-syncer-notmps
Sync tables without using temporary hash tables
2019-07-04 16:05:21 +02:00
Rafa de la Torre
29a31d4c40 Merge pull request #362 from CartoDB/fdw-affected-tables
Make CDB_Get_Foreign_Updated_At robust to missing CDB_TableMetadata
2019-07-04 16:02:46 +02:00
Javier Goizueta
dbd403a2f6 Fix cases of double-quoting identifiers 2019-07-04 12:47:58 +02:00
Javier Goizueta
cb353ec6a8 Add tests for quoted table & column names 2019-07-04 12:47:01 +02:00
Javier Goizueta
2beabfced6 Add test for double schema-quoting 2019-07-03 18:48:49 +02:00
Javier Goizueta
7bdee5c13e Avoid double-quoting
Since dst_schema is a REGNAMESPACE, it is automatically quoted when casted to TEXT
2019-07-03 18:34:54 +02:00
Javier Goizueta
c07784566a NEWS for next release 2019-07-03 17:02:36 +02:00
Javier Goizueta
2e1fe2933c Fix whitespace 2019-07-03 16:56:29 +02:00
Rafa de la Torre
d378ca6fe0 Test for missing foreign CDB_TableMetadata 2019-07-03 16:25:16 +02:00
Rafa de la Torre
446f4113d9 Return NULL instead of NOW()
The absence of foreign CDB_TableMetadata actually means that we cannot
really tell when a remote table was modified.

Therefore we're using NULL with the meaning of "I don't know when it
was last modified".

To be taken in other caching layers, to adjust headers accordingly.
2019-07-03 16:19:46 +02:00
Javier Goizueta
5963c67376 Order the columns of a cartodbfied table consistently
The final order of the columns of a cartodbfied table wasn't uniquely specified, so could vary across PG versions.
This was a problem in particular for having deterministic test results.
2019-07-03 16:16:37 +02:00
Javier Goizueta
f5f18ca57c Fix the order of the columns 2019-07-02 18:14:18 +02:00
Javier Goizueta
d8c840d126 Quote table and column names when necessary
Also use type NAME when appropriate. Note that quoted column names are not NAMES (may be longer).
2019-07-02 18:14:04 +02:00
Javier Goizueta
65483743b4 Remove unused vars 2019-07-02 17:44:06 +02:00
Javier Goizueta
4651883454 Fix order of test results 2019-07-02 17:43:13 +02:00
Javier Goizueta
470bae6268 Merge branch 'master' into table-syncer-notmps
# Conflicts:
#	scripts-available/CDB_SyncTable.sql
2019-07-02 12:21:49 +02:00
Javier Goizueta
75ba13b834 Merge pull request #364 from CartoDB/synctable-doc
Document CDB_SyncTable
2019-07-02 10:20:24 +02:00
Javier Goizueta
057695361d Fix typo 2019-07-01 17:49:42 +02:00
Javier Goizueta
0e1aeb0a76 Minor copy edit 2019-07-01 17:00:22 +02:00
Javier Goizueta
f2dae651b3 More complete sync table example 2019-07-01 16:58:24 +02:00
Javier Goizueta
9f414938ba Document CDB_SyncTable 2019-07-01 16:41:40 +02:00
Javier Goizueta
4f7b07f922 Merge pull request #363 from CartoDB/fix-sync-schema
Fix cartodb schema references
2019-07-01 16:15:25 +02:00
Javier Goizueta
69cc56b589 Remove schema from public function examples 2019-07-01 15:13:48 +02:00
Javier Goizueta
1028b24333 Fix cartodb schema references 2019-07-01 14:48:42 +02:00
Javier Goizueta
4d1c4f6a22 Release 0.28.0 2019-07-01 10:56:06 +02:00
Javier Goizueta
83ab128a01 Merge pull request #355 from CartoDB/table-syncer
[WIP] Table syncer
2019-07-01 10:49:06 +02:00
Rafa de la Torre
b0b4a92240 Add a test case for missing foreign CDB_Tablementadata 2019-06-28 16:19:57 +02:00
Rafa de la Torre
c06d24aa19 Make CDB_Get_Foreign_Updated_At robust to missing CDB_TableMetadata
This may happen with non-carto DB's, when checking the updated_at
times and not finding the corresponding remote.cdb_tablemetadata
imported from the foreign non-carto DB.

Instead of failing, return a NOW() timestampt, so that caching logic
just assumes there may have been changes.

This makes it work today, and leaves open the possibility of adding
the required carto metadata for homogeneous caching in the future.
2019-06-28 16:25:52 +02:00
Javier Goizueta
4f4df2de8d Merge branch 'table-syncer' into table-syncer-notmps 2019-06-28 15:28:06 +02:00
Javier Goizueta
b5f36902c5 Merge branch 'master' into table-syncer 2019-06-28 13:50:46 +02:00
Javier Goizueta
0bcbf6708a Remove usage of temporary tables 2019-06-28 13:49:15 +02:00
Gonzalo Riestra
2e665a56b4 release 0.27.2 2019-06-21 09:19:04 +02:00
Gonzalo Riestra
1e8ef2e1d9 Merge pull request #360 from CartoDB/fix-ghost-tables-trigger
Improve Ghost Tables functions
2019-06-21 09:15:20 +02:00
Gonzalo Riestra
e6ecde4346 add CREATE/ALTER/DROP FOREIGN TABLE to ddl commands 2019-06-20 11:20:48 +02:00
Gonzalo Riestra
2d42e6197a use bigint to store values from txid_current() 2019-06-20 11:20:03 +02:00
Gonzalo Riestra
5605fdd9b2 fix event trigger 2019-06-19 15:27:28 +02:00
Sergio Conde Gómez
ab7082d4c3 Merge pull request #359 from CartoDB/fix_carto-package
Fix carto-package requirements to be SemVer compliant
2019-06-17 13:40:35 +02:00
Sergio Conde Gomez
f9ac627e0e Fix carto-package requirements to be SemVer compliant 2019-06-17 13:33:24 +02:00
Raul Marin
cc2066ea89 Release 0.27.1 2019-06-03 17:57:42 +02:00
Raúl Marín
aa380171b4 Merge pull request #358 from Algunenano/missing
Add missing qualifications
2019-06-03 16:26:10 +02:00
Raul Marin
a43abb37e0 Add missing qualifications 2019-06-03 15:22:52 +02:00
Raul Marin
787b513715 Release 0.27.0 2019-06-03 12:51:25 +02:00
Raúl Marín
362af5e6a0 Merge pull request #357 from Algunenano/full_schema_rebase
Qualify all function calls
2019-06-03 12:45:04 +02:00
Raul Marin
8dbd797429 Update CONTRIBUTING.md 2019-06-03 12:31:06 +02:00
Raul Marin
57ac26cbe7 Travis: Add PG 9.6 to the test matrix 2019-05-31 16:26:07 +02:00
Raul Marin
010cbe6e18 Remove unnecessary changes 2019-05-31 15:52:55 +02:00
Raul Marin
8e68f2a0a7 Add NEWS 2019-05-31 15:38:29 +02:00
Raul Marin
52aab9d564 Raise minimum requirement to Postgis 9.6 2019-05-31 15:37:15 +02:00
Raul Marin
06f563bb73 Tests: Only create publicuser if necessary
Also, don't try to drop it since it's used by default
CARTO installation
2019-05-31 15:37:15 +02:00
Raul Marin
78077a6ec1 Bash tests improvements
- Stop loading sql files manually.
- Make tests independent.
- Single DB setup (not once per test)
2019-05-31 15:37:15 +02:00
Raul Marin
c45ef6c540 Fully qualify function calls 2019-05-31 15:37:15 +02:00
Rafa de la Torre
42dc03d77b Qualify functions with extension schema
This avoids some issues with search_path and scripting black magic.
2019-05-30 16:56:12 +02:00
Rafa de la Torre
9254723719 Make format expression more readable
Make an EXECUTE format('', param1, ..., paramN) more readable by
adding the index of the argument to print (`position`) in the format
specifier.
2019-05-29 13:13:30 +02:00
Rafa de la Torre
26ad966ab6 Add some timing info 2019-05-28 16:40:01 +02:00
Rafa de la Torre
a2723a3c90 Exclude certain columns from sync if instructed to do so
For the Geocoding (and in general for LDS use cases) it may come in
handy to exclude geometry columns from the list of stuff to
syncrhonize. Otherwise they may be lost, overwritten with NULL values.
2019-05-28 16:11:56 +02:00
Rafa de la Torre
2f8ea7e4ea Avoid tables name clashing when executing within same transaction
Generate more unique temp table names when the CDB_SyncTable function
is executed multiple times within the same transaction.

When executed in isolation, there will be always an implicit
surrounding transaction.

But when executed several times within the same transaction it can
give an `ERROR:  relation "src_sync_718794" already exists`.

E.g:

```
BEGIN;
SELECT cartodb.CDB_SyncTable('source1', 'public', 'dest1');
SELECT cartodb.CDB_SyncTable('source12, 'public', 'dest2');
COMMIT;
```
2019-05-28 15:39:02 +02:00
Rafa de la Torre
45c21d7f42 Make other tests resilient to CDB_SyncTable test failures
Use a transaction to avoid leaving stuff after our tests, that affect
Quota and User Tables tests.
2019-05-28 15:33:55 +02:00
Rafa de la Torre
f442c21fa4 Fix the test/CDB_AnalysisCheckTest.sql
These tests are failing in PG11:

```
*** /home/travis/build/CartoDB/cartodb-postgresql/expected/test/CDB_AnalysisCheckTest.out	2019-05-27 16:09:45.063543994 +0000
--- /home/travis/build/CartoDB/cartodb-postgresql/results/test/CDB_AnalysisCheckTest.out	2019-05-27 16:12:39.770847666 +0000
***************
*** 5,12 ****
  CREATE TABLE
  CREATE TABLE
  CREATE TABLE
- (analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5,public,analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5)
  (analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94,public,analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94)
  0

  1
--- 5,12 ----
  CREATE TABLE
  CREATE TABLE
  CREATE TABLE
  (analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94,public,analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94)
+ (analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5,public,analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d5)
  0

  1
```

The reason for that is that they rely on row ordering that cannot be
guaranteed as per SQL Standard. Forcing that assumed ordering is
enough to get it working again.
2019-05-28 09:34:10 +02:00
Rafa de la Torre
ee9d08a2be Some functional E2E tests for the CDB_SyncTable() function 2019-05-27 17:56:36 +02:00
Rafa de la Torre
7606585672 Perf optimization: use EXCEPT instead of NOT IN
With javitonino's help, greatly reduce the processing time by using
EXCEPT instead of NOT IN, which causes it to use a `HashSetOp Except`
plan on the subqueries rather than a `Seq Scan` on `Materialize`'d
subtables.
2019-05-27 15:20:06 +02:00
Rafa de la Torre
81d0f338cf Create HASH indices on the temp tables 2019-05-27 15:18:13 +02:00
Rafa de la Torre
951f257654 Simplify code by using ON COMMIT DROP
Simplify code by relying on automatic removal of temp tables with ON
COMMIT DROP, so that we avoid the messy EXCEPTION management.
2019-05-27 12:02:12 +02:00
Rafa de la Torre
f461faf0b6 Simplify udpate: use * instead of column list 2019-05-27 11:56:41 +02:00
Rafa de la Torre
a8d57abda6 Update changed rows 2019-05-27 11:54:38 +02:00
Rafa de la Torre
982ddfdeff Helper function to generate UPDATE SET clause 2019-05-27 11:31:07 +02:00
Rafa de la Torre
da4331ac78 WIP: A function to sync tables
It assumes there's a cartodb_id column in both source and target. It
does not perform unnecessary actions. It respects augmented columns in
target table, if they exist. It is meant to be efficient.
2019-05-24 18:38:28 +02:00
Gonzalo Riestra
07d43275f8 Merge pull request #354 from CartoDB/update-readme
Add Redis dependency to Readme
2019-05-23 10:58:33 +02:00
Gonzalo Riestra
ec38444b7d better link 2019-05-23 08:12:57 +02:00
Gonzalo Riestra
ee9010dc23 add redis module to dependencies in readme 2019-05-23 08:03:37 +02:00
Gonzalo Riestra
74210c5b5c release 0.26.1 2019-03-19 17:43:36 +01:00
Gonzalo Riestra
d14d1d9994 Merge pull request #351 from CartoDB/remove_default_tis_config
Remove default TIS config in Ghost Table function
2019-03-14 12:23:06 +01:00
Gonzalo Riestra
5b19d0fc5e fix test correctly 2019-03-14 12:08:33 +01:00
Gonzalo Riestra
2baee24f30 fix test 2019-03-14 11:47:41 +01:00
Gonzalo Riestra
85d6164956 add log when there is no tig config 2019-03-14 11:39:10 +01:00
Gonzalo Riestra
a5b2b66bb6 fix test comment 2019-03-14 10:06:04 +01:00
Gonzalo Riestra
3faa389860 improve logs 2019-03-13 18:16:08 +01:00
Gonzalo Riestra
bc5d532735 remove default TIS config 2019-03-13 18:12:48 +01:00
Gonzalo Riestra
4f3d19ce7a Merge pull request #350 from CartoDB/explicit-schema-for-ghost-tables
Add explicit schema for Ghost Tables functions
2019-03-13 10:42:13 +01:00
Gonzalo Riestra
45fed9cf1b more explicit schemas 2019-03-13 10:29:50 +01:00
Gonzalo Riestra
e19489144c simplify queries 2019-03-13 09:08:13 +01:00
Gonzalo Riestra
83707297de add explicit schema for all functions 2019-03-12 18:39:48 +01:00
Gonzalo Riestra
2f40261b8d Merge pull request #349 from CartoDB/ghost_tables
Ghost tables functions
2019-03-11 15:29:17 +01:00
Gonzalo Riestra
b1202011f6 update news 2019-03-11 15:28:48 +01:00
Gonzalo Riestra
65d51fd8bd move cdb_ddl_execution table creation/drop to functions 2019-03-11 14:02:45 +01:00
Gonzalo Riestra
1a271d977b bump version 2019-03-11 12:24:14 +01:00
Gonzalo Riestra
9c6294d95b move trigger drop/creation inside functions 2019-03-11 11:28:45 +01:00
Gonzalo Riestra
c7bba14e9a simplify code 2019-03-11 09:20:05 +01:00
Gonzalo Riestra
667f896cfb add again views to the trigger 2019-03-08 13:21:11 +01:00
Gonzalo Riestra
cc1df0a708 use redis module version from carto 2019-03-08 13:01:56 +01:00
Gonzalo Riestra
d1ee383d9b add redis module for python to travis 2019-03-08 12:50:51 +01:00
Gonzalo Riestra
a794fb3d31 read tis config from cdb_conf 2019-03-08 12:03:55 +01:00
Gonzalo Riestra
50e41179fc fix test 2019-03-08 09:18:46 +01:00
Gonzalo Riestra
e3138cd56a make enable/disable trigger idempotent 2019-03-08 08:43:22 +01:00
Gonzalo Riestra
ab6720ad32 add create/drop/alter view to the trigger 2019-03-08 08:28:24 +01:00
Gonzalo Riestra
5f154a5859 use TIS instead of Redis 2019-03-08 08:28:06 +01:00
Gonzalo Riestra
99dd7cefc7 ghost tables functions using redis 2019-03-06 18:20:04 +01:00
Raúl Marín
85997e2445 Merge pull request #348 from Algunenano/shiftLongitude
Update ST_Shift_Longitude to ST_ShiftLongitude
2019-03-01 12:06:24 +01:00
Raul Marin
1bd4b9a6e3 Update NEWS 2019-03-01 11:47:30 +01:00
Raul Marin
5d4f1d98d7 Update ST_Shift_Longitude to ST_ShiftLongitude 2019-03-01 11:27:09 +01:00
98 changed files with 5017 additions and 915 deletions

View File

@@ -1,4 +1,3 @@
dist: xenial
language: c
sudo: required
@@ -7,27 +6,30 @@ env:
- PGUSER=postgres
- PGDATABASE=postgres
- PGOPTIONS='-c client_min_messages=NOTICE'
- PGPORT=5432
- POSTGIS_VERSION="2.5"
matrix:
- POSTGRESQL_VERSION="9.5"
- POSTGRESQL_VERSION="10"
- POSTGRESQL_VERSION="11"
before_install:
- sudo service postgresql stop;
- 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.5' ]]; then sudo apt-get install -y postgresql-contrib-9.5; fi;
- sudo apt-get install -y --allow-unauthenticated postgresql-$POSTGRESQL_VERSION-postgis-$POSTGIS_VERSION postgresql-$POSTGRESQL_VERSION-postgis-$POSTGIS_VERSION-scripts postgis postgresql-plpython-$POSTGRESQL_VERSION
- sudo pg_dropcluster --stop $POSTGRESQL_VERSION main
- sudo rm -rf /etc/postgresql/$POSTGRESQL_VERSION /var/lib/postgresql/$POSTGRESQL_VERSION
- sudo pg_createcluster -u postgres $POSTGRESQL_VERSION main -- -A trust
- sudo /etc/init.d/postgresql start $POSTGRESQL_VERSION || sudo journalctl -xe
jobs:
include:
- env: POSTGRESQL_VERSION="9.6" POSTGIS_VERSION="2.5"
dist: xenial
- env: POSTGRESQL_VERSION="10" POSTGIS_VERSION="2.5"
dist: xenial
- 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
script:
- 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 != '12' ]]; then sudo apt-get install -y postgresql-plpython-$POSTGRESQL_VERSION python python-redis; else sudo apt-get install -y postgresql-plpython3-12 python3 python3-redis; fi;
- sudo pg_dropcluster --stop $POSTGRESQL_VERSION main
- 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
- make installcheck
@@ -36,3 +38,5 @@ after_failure:
- pg_lsclusters
- cat regression.out
- cat regression.diffs
- echo $PGPORT
- cat /var/log/postgresql/postgresql-$POSTGRESQL_VERSION-main.log

View File

@@ -24,14 +24,13 @@ version of objects in other cases.
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.
As PARALLEL labels need to be stripped for incompatible PostgreSQL versions
please use _PARALLEL SAFE/RESTRICTED/UNSAFE_ in uppercase so it's handled
automatically.
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.
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 the 'Writing testcases' section.

View File

@@ -1,7 +1,7 @@
# cartodb/Makefile
EXTENSION = cartodb
EXTVERSION = 0.25.0
EXTVERSION = 0.36.0
SED = sed
AWK = awk
@@ -94,6 +94,21 @@ UPGRADABLE = \
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 \
$(EXTVERSION)dev \
$(EXTVERSION)next \
$(END)
@@ -117,28 +132,29 @@ 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)
PG_PARALLEL := $(shell $(PG_CONFIG) --version | ($(AWK) '{$$2*=1000; if ($$2 >= 9600) print 1; else print 0;}' 2> /dev/null || echo 0))
include $(PGXS)
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 >> $@
ifeq ($(PG_PARALLEL), 0)
# Remove PARALLEL in aggregates and functions
$(eval TMPFILE := $(shell mktemp /tmp/$(basename $0).XXXXXXXX))
$(SED) -e 's/PARALLEL \= [A-Z]*,/''/g' \
-e 's/PARALLEL [A-Z]*/''/g' $@ > $(TMPFILE)
mv $(TMPFILE) $@
endif
$(EXTENSION)--unpackaged--$(EXTVERSION).sql: $(EXTENSION)--$(EXTVERSION).sql util/create_from_unpackaged.sh Makefile
./util/create_from_unpackaged.sh $(EXTVERSION)
@@ -150,13 +166,10 @@ $(EXTENSION)--$(EXTVERSION)--$(EXTVERSION)next.sql: $(EXTENSION)--$(EXTVERSION).
cp $< $@
$(EXTENSION).control: $(EXTENSION).control.in Makefile
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' $< > $@
ifeq ($(PG_PARALLEL), 0)
echo -e "\033[0;31mExtension created without PARALLEL support\033[0m"
endif
$(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)/' $< > $@
$(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
@@ -165,6 +178,10 @@ 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; \
@@ -173,10 +190,23 @@ legacy_regress: $(REGRESS_OLD) Makefile
echo '\t' >> $${of}; \
echo '\set QUIET off' >> $${of}; \
cat $${f} | \
$(SED) -e 's/public\./cartodb./g' >> $${of}; \
$(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 >> $${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:
@@ -185,7 +215,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

62
NEWS.md
View File

@@ -1,3 +1,65 @@
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

View File

@@ -10,8 +10,9 @@ See [the cartodb-postgresql wiki](https://github.com/CartoDB/cartodb-postgresql/
Dependencies
------------
* PostgreSQL 9.4+ (with plpythonu extension and xml support)
* [PostGIS extension](http://postgis.net)
* PostgreSQL 9.6+ (with plpythonu extension and xml support). For PostgreSQL 12+ plpython3u is required instead of plpythonu.
* [PostGIS extension](http://postgis.net)
* Python with [Redis module](https://pypi.org/project/redis/)
Install
-------

View File

@@ -2,8 +2,8 @@
"name": "carto_postgresql_ext",
"current_version": {
"requires": {
"postgresql": ">=9.5.0",
"postgis": ">=2.2.0.0"
"postgresql": ">=10.0.0",
"postgis": ">=2.4.0.0"
},
"works_with": {
}

View File

@@ -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'

View File

@@ -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;

56
doc/CDB_SyncTable.md Normal file
View 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

View File

@@ -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

View File

@@ -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'
@@ -34,7 +34,7 @@ cartodb.cdb_analysis_catalog (
-- 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_analysis_catalog', '');
PERFORM pg_catalog.pg_extension_config_dump('@extschema@.cdb_analysis_catalog', '');
END
$$;
@@ -45,7 +45,7 @@ $$;
DO $$
BEGIN
BEGIN
ALTER TABLE cartodb.cdb_analysis_catalog ADD COLUMN last_modified_by uuid;
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN last_modified_by uuid;
EXCEPTION
WHEN duplicate_column THEN END;
END;
@@ -54,7 +54,7 @@ $$;
DO $$
BEGIN
BEGIN
ALTER TABLE cartodb.cdb_analysis_catalog ADD COLUMN last_error_message text;
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN last_error_message text;
EXCEPTION
WHEN duplicate_column THEN END;
END;
@@ -63,7 +63,7 @@ $$;
DO $$
BEGIN
BEGIN
ALTER TABLE cartodb.cdb_analysis_catalog ADD COLUMN cache_tables regclass[] NOT NULL DEFAULT '{}';
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN cache_tables regclass[] NOT NULL DEFAULT '{}';
EXCEPTION
WHEN duplicate_column THEN END;
END;
@@ -72,7 +72,7 @@ $$;
DO $$
BEGIN
BEGIN
ALTER TABLE cartodb.cdb_analysis_catalog ADD COLUMN username text;
ALTER TABLE @extschema@.cdb_analysis_catalog ADD COLUMN username text;
EXCEPTION
WHEN duplicate_column THEN END;
END;
@@ -84,12 +84,12 @@ DO LANGUAGE 'plpgsql' $$
DECLARE
column_index int;
BEGIN
SELECT ordinal_position FROM information_schema.columns WHERE table_name='cdb_analysis_catalog' AND table_schema='cartodb' AND column_name='username' INTO column_index;
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 cartodb.cdb_analysis_catalog ADD COLUMN username_final text;
UPDATE cartodb.cdb_analysis_catalog SET username_final = username;
ALTER TABLE cartodb.cdb_analysis_catalog DROP COLUMN username;
ALTER TABLE cartodb.cdb_analysis_catalog RENAME COLUMN username_final TO username;
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;
$$;

View File

@@ -1,24 +1,27 @@
-- Read configuration parameter analysis_quota_factor, making it
-- accessible to regular users (which don't have access to cdb_conf)
CREATE OR REPLACE FUNCTION _CDB_GetConfAnalysisQuotaFactor()
CREATE OR REPLACE FUNCTION @extschema@._CDB_GetConfAnalysisQuotaFactor()
RETURNS float8 AS
$$
BEGIN
RETURN CDB_Conf_GetConf('analysis_quota_factor')::text::float8;
RETURN @extschema@.CDB_Conf_GetConf('analysis_quota_factor')::text::float8;
END;
$$
LANGUAGE 'plpgsql' STABLE PARALLEL SAFE SECURITY DEFINER;
$$ 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 _CDB_AnalysisQuotaFactor()
CREATE OR REPLACE FUNCTION @extschema@._CDB_AnalysisQuotaFactor()
RETURNS float8 AS
$$
DECLARE
factor float8;
BEGIN
-- We use a floating point cdb_conf parameter
factor := _CDB_GetConfAnalysisQuotaFactor();
factor := @extschema@._CDB_GetConfAnalysisQuotaFactor();
-- With a default value
IF factor IS NULL THEN
factor := 2;
@@ -33,7 +36,7 @@ LANGUAGE 'plpgsql' STABLE PARALLEL SAFE;
-- 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 CDB_CheckAnalysisQuota(table_name TEXT)
CREATE OR REPLACE FUNCTION @extschema@.CDB_CheckAnalysisQuota(table_name TEXT)
RETURNS void AS
$$
DECLARE
@@ -54,7 +57,7 @@ BEGIN
SELECT current_schema() INTO schema_name;
EXECUTE FORMAT('SELECT %I._CDB_UserQuotaInBytes();', schema_name) INTO nominal_quota;
IF nominal_quota*_CDB_AnalysisQuotaFactor() < _CDB_AnalysisDataSize(schema_name) THEN
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;

View File

@@ -2,7 +2,7 @@
-- This function returns TRUE if a given table name corresponds to a Camshaft cached analysis table
-- Scope: private.
CREATE OR REPLACE FUNCTION _CDB_IsAnalysisTableName(table_name TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_IsAnalysisTableName(table_name TEXT)
RETURNS BOOLEAN
AS $$
BEGIN
@@ -15,10 +15,10 @@ $$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
-- 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_AnalysisTablesInSchema(schema_name text DEFAULT NULL)
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 _CDB_UserTablesInSchema(schema_name) WHERE _CDB_IsAnalysisTableName(table_name);
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
@@ -26,24 +26,24 @@ $$ LANGUAGE 'sql' STABLE PARALLEL SAFE;
-- 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_NonAnalysisTablesInSchema(schema_name text DEFAULT NULL)
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 _CDB_UserTablesInSchema(schema_name) WHERE Not _CDB_IsAnalysisTableName(table_name);
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 _CDB_AnalysisDataSize(schema_name TEXT DEFAULT NULL)
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 _CDB_AnalysisTablesInSchema(schema_name) t
SELECT t.schema_name, t.table_name FROM @extschema@._CDB_AnalysisTablesInSchema(schema_name) t
)
SELECT COALESCE(INT8(SUM(_CDB_total_relation_size(analysis_tables.schema_name, analysis_tables.table_name))))::int8
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;

View File

@@ -9,13 +9,13 @@
-- 1) Required checks before running cartodbfication
-- Either will pass silenty or raise an exception
CREATE OR REPLACE FUNCTION _CDB_check_prerequisites(schema_name TEXT, reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_check_prerequisites(schema_name TEXT, reloid REGCLASS)
RETURNS void
AS $$
DECLARE
sql TEXT;
BEGIN
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;
@@ -29,7 +29,7 @@ END;
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- Drop cartodb triggers (might prevent changing columns)
CREATE OR REPLACE FUNCTION _CDB_drop_triggers(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_drop_triggers(reloid REGCLASS)
RETURNS void
AS $$
DECLARE
@@ -53,7 +53,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- Cartodb_id creation & validation or renaming if invalid
CREATE OR REPLACE FUNCTION _CDB_create_cartodb_id_column(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_create_cartodb_id_column(reloid REGCLASS)
RETURNS void
AS $$
DECLARE
@@ -200,7 +200,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- Create all triggers
-- NOTE: drop/create has the side-effect of re-enabling disabled triggers
CREATE OR REPLACE FUNCTION _CDB_create_triggers(schema_name TEXT, reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_create_triggers(schema_name TEXT, reloid REGCLASS)
RETURNS void
AS $$
DECLARE
@@ -209,28 +209,28 @@ BEGIN
-- "track_updates"
sql := 'CREATE trigger track_updates AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON '
|| reloid::text
|| ' FOR EACH STATEMENT EXECUTE PROCEDURE public.cdb_tablemetadata_trigger()';
|| ' FOR EACH STATEMENT EXECUTE PROCEDURE @extschema@.cdb_tablemetadata_trigger()';
EXECUTE sql;
-- "update_the_geom_webmercator"
-- TODO: why _before_ and not after ?
sql := 'CREATE trigger update_the_geom_webmercator_trigger BEFORE INSERT OR UPDATE OF the_geom ON '
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public._CDB_update_the_geom_webmercator()';
|| ' FOR EACH ROW EXECUTE PROCEDURE @extschema@._CDB_update_the_geom_webmercator()';
EXECUTE sql;
-- "test_quota" and "test_quota_per_row"
sql := 'CREATE TRIGGER test_quota BEFORE UPDATE OR INSERT ON '
|| reloid::text
|| ' EXECUTE PROCEDURE public.CDB_CheckQuota(0.1, ''-1'', '''
|| ' EXECUTE PROCEDURE @extschema@.CDB_CheckQuota(0.1, ''-1'', '''
|| schema_name::text
|| ''')';
EXECUTE sql;
sql := 'CREATE TRIGGER test_quota_per_row BEFORE UPDATE OR INSERT ON '
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public.CDB_CheckQuota(0.001, ''-1'', '''
|| ' FOR EACH ROW EXECUTE PROCEDURE @extschema@.CDB_CheckQuota(0.001, ''-1'', '''
|| schema_name::text
|| ''')';
EXECUTE sql;
@@ -239,7 +239,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 8.b) Create all raster triggers
-- NOTE: drop/create has the side-effect of re-enabling disabled triggers
CREATE OR REPLACE FUNCTION _CDB_create_raster_triggers(schema_name TEXT, reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_create_raster_triggers(schema_name TEXT, reloid REGCLASS)
RETURNS void
AS $$
DECLARE
@@ -248,21 +248,21 @@ BEGIN
-- "track_updates"
sql := 'CREATE trigger track_updates AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON '
|| reloid::text
|| ' FOR EACH STATEMENT EXECUTE PROCEDURE public.cdb_tablemetadata_trigger()';
|| ' FOR EACH STATEMENT EXECUTE PROCEDURE @extschema@.cdb_tablemetadata_trigger()';
EXECUTE sql;
-- "test_quota" and "test_quota_per_row"
sql := 'CREATE TRIGGER test_quota BEFORE UPDATE OR INSERT ON '
|| reloid::text
|| ' EXECUTE PROCEDURE public.CDB_CheckQuota(1, ''-1'', '''
|| ' EXECUTE PROCEDURE @extschema@.CDB_CheckQuota(1, ''-1'', '''
|| schema_name::text
|| ''')';
EXECUTE sql;
sql := 'CREATE TRIGGER test_quota_per_row BEFORE UPDATE OR INSERT ON '
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public.CDB_CheckQuota(0.001, ''-1'', '''
|| ' FOR EACH ROW EXECUTE PROCEDURE @extschema@.CDB_CheckQuota(0.001, ''-1'', '''
|| schema_name::text
|| ''')';
EXECUTE sql;
@@ -272,11 +272,11 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- Update the_geom_webmercator
CREATE OR REPLACE FUNCTION _CDB_update_the_geom_webmercator()
CREATE OR REPLACE FUNCTION @extschema@._CDB_update_the_geom_webmercator()
RETURNS trigger
AS $$
BEGIN
NEW.the_geom_webmercator := public.CDB_TransformToWebmercator(NEW.the_geom);
NEW.the_geom_webmercator := @extschema@.CDB_TransformToWebmercator(NEW.the_geom);
RETURN NEW;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
@@ -284,7 +284,7 @@ $$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
--- Trigger to update the updated_at column. No longer added by default
--- but kept here for compatibility with old tables which still have this behavior
--- and have it added
CREATE OR REPLACE FUNCTION _CDB_update_updated_at()
CREATE OR REPLACE FUNCTION @extschema@._CDB_update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at := now();
@@ -293,7 +293,7 @@ END;
$$ LANGUAGE plpgsql VOLATILE;
-- Auxiliary function
CREATE OR REPLACE FUNCTION cartodb._CDB_is_raster_table(schema_name TEXT, reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_is_raster_table(schema_name TEXT, reloid REGCLASS)
RETURNS BOOLEAN
AS $$
DECLARE
@@ -301,7 +301,7 @@ DECLARE
is_raster BOOLEAN;
rel_name TEXT;
BEGIN
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;
@@ -331,11 +331,11 @@ $$ LANGUAGE PLPGSQL STABLE PARALLEL UNSAFE;
-- Ensure a table is a "cartodb" table (See https://github.com/CartoDB/cartodb/wiki/CartoDB-user-table)
DROP FUNCTION IF EXISTS CDB_CartodbfyTable(reloid REGCLASS);
CREATE OR REPLACE FUNCTION CDB_CartodbfyTable(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@.CDB_CartodbfyTable(reloid REGCLASS)
RETURNS REGCLASS
AS $$
BEGIN
RETURN cartodb.CDB_CartodbfyTable('public', reloid);
RETURN @extschema@.CDB_CartodbfyTable('public', reloid);
END;
$$ LANGUAGE PLPGSQL;
@@ -388,7 +388,7 @@ $$ LANGUAGE PLPGSQL;
-- -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
CREATE OR REPLACE FUNCTION _CDB_Columns(OUT pkey TEXT, OUT geomcol TEXT, OUT mercgeomcol TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Columns(OUT pkey TEXT, OUT geomcol TEXT, OUT mercgeomcol TEXT)
RETURNS record
AS $$
BEGIN
@@ -401,7 +401,7 @@ END;
$$ LANGUAGE 'plpgsql' IMMUTABLE PARALLEL SAFE;
CREATE OR REPLACE FUNCTION _CDB_Error(message TEXT, funcname TEXT DEFAULT '_CDB_Error')
CREATE OR REPLACE FUNCTION @extschema@._CDB_Error(message TEXT, funcname TEXT DEFAULT '_CDB_Error')
RETURNS void
AS $$
BEGIN
@@ -412,7 +412,7 @@ END;
$$ LANGUAGE 'plpgsql' VOLATILE PARALLEL SAFE;
CREATE OR REPLACE FUNCTION _CDB_SQL(sql TEXT, funcname TEXT DEFAULT '_CDB_SQL')
CREATE OR REPLACE FUNCTION @extschema@._CDB_SQL(sql TEXT, funcname TEXT DEFAULT '_CDB_SQL')
RETURNS void
AS $$
BEGIN
@@ -432,7 +432,7 @@ $$ LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
-- aware. Find a unique relation name in the given schema, starting from the
-- template given. If the template is already unique, just return it;
-- otherwise, append an increasing integer until you find a unique variant.
CREATE OR REPLACE FUNCTION _CDB_Unique_Relation_Name(schemaname TEXT, relationname TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Unique_Relation_Name(schemaname TEXT, relationname TEXT)
RETURNS TEXT
AS $$
DECLARE
@@ -451,7 +451,7 @@ $$ LANGUAGE 'plpgsql' VOLATILE PARALLEL SAFE;
-- aware. Find a unique column name in the given relation, starting from the
-- column name given. If the column name is already unique, just return it;
-- otherwise, append an increasing integer until you find a unique variant.
CREATE OR REPLACE FUNCTION _CDB_Unique_Column_Name(reloid REGCLASS, columnname TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Unique_Column_Name(reloid REGCLASS, columnname TEXT)
RETURNS TEXT
AS $$
DECLARE
@@ -471,7 +471,7 @@ $$ LANGUAGE 'plpgsql' VOLATILE PARALLEL SAFE;
-- we can no-op on the table copy and just ensure that the
-- indexes and triggers are in place
DROP FUNCTION IF EXISTS _CDB_Has_Usable_Primary_ID(reloid REGCLASS);
CREATE OR REPLACE FUNCTION _CDB_Has_Usable_Primary_ID(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Has_Usable_Primary_ID(reloid REGCLASS)
RETURNS BOOLEAN
AS $$
DECLARE
@@ -485,7 +485,7 @@ BEGIN
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', 'entered function';
-- Read in the names of the CartoDB columns
const := _CDB_Columns();
const := @extschema@._CDB_Columns();
-- Do we already have a properly named column?
SELECT a.attname, i.indisprimary, i.indisunique, a.attnotnull, a.atttypid
@@ -537,8 +537,8 @@ BEGIN
-- Clean up test constraint
IF useable_key THEN
PERFORM _CDB_SQL(Format('ALTER TABLE %s DROP CONSTRAINT %s_pk', reloid::text, const.pkey));
PERFORM _CDB_SQL(Format('ALTER TABLE %s DROP CONSTRAINT %s_integer', reloid::text, const.pkey));
PERFORM @extschema@._CDB_SQL(Format('ALTER TABLE %s DROP CONSTRAINT %s_pk', reloid::text, const.pkey));
PERFORM @extschema@._CDB_SQL(Format('ALTER TABLE %s DROP CONSTRAINT %s_integer', reloid::text, const.pkey));
-- Move non-valid column out of the way
ELSE
@@ -546,7 +546,7 @@ BEGIN
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %',
Format('found non-valid ''%s''', const.pkey);
PERFORM _CDB_Error(sql, Format('_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, %s', const.pkey));
PERFORM @extschema@._CDB_Error(sql, Format('_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, %s', const.pkey));
END IF;
@@ -569,7 +569,7 @@ BEGIN
-- Yes! Ok, rename it.
IF FOUND THEN
PERFORM _CDB_SQL(Format('ALTER TABLE %s RENAME COLUMN %s TO %s', reloid::text, rec.attname, const.pkey),'_CDB_Has_Usable_Primary_ID');
PERFORM @extschema@._CDB_SQL(Format('ALTER TABLE %s RENAME COLUMN %s TO %s', reloid::text, rec.attname, const.pkey),'_CDB_Has_Usable_Primary_ID');
RETURN true;
ELSE
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %',
@@ -586,7 +586,7 @@ END;
$$ LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
CREATE OR REPLACE FUNCTION _CDB_Has_Usable_PK_Sequence(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Has_Usable_PK_Sequence(reloid REGCLASS)
RETURNS BOOLEAN
AS $$
DECLARE
@@ -595,7 +595,7 @@ DECLARE
has_sequence BOOLEAN = false;
BEGIN
const := _CDB_Columns();
const := @extschema@._CDB_Columns();
SELECT pg_get_serial_sequence(reloid::text, const.pkey)
INTO STRICT seq;
@@ -607,19 +607,19 @@ $$ LANGUAGE 'plpgsql' STABLE PARALLEL SAFE;
-- Return a set of columns that can be candidates to be the_geom[webmercator]
-- with some extra information to analyze them.
CREATE OR REPLACE FUNCTION _cdb_geom_candidate_columns(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._cdb_geom_candidate_columns(reloid REGCLASS)
RETURNS TABLE (attname name, srid integer, typname name, desired_attname text, desired_srid integer)
AS $$
DECLARE
const RECORD;
BEGIN
const := _CDB_Columns();
const := @extschema@._CDB_Columns();
RETURN QUERY
SELECT
a.attname,
CASE WHEN t.typname = 'geometry' THEN postgis_typmod_srid(a.atttypmod) ELSE NULL END AS srid,
CASE WHEN t.typname = 'geometry' THEN @postgisschema@.postgis_typmod_srid(a.atttypmod) ELSE NULL END AS srid,
t.typname,
f.desired_attname, f.desired_srid
FROM pg_class c
@@ -629,15 +629,16 @@ BEGIN
WHERE c.oid = reloid
AND a.attnum > 0
AND NOT a.attisdropped
AND postgis_typmod_srid(a.atttypmod) IN (4326, 3857, 0)
AND @postgisschema@.postgis_typmod_srid(a.atttypmod) IN (4326, 3857, 0)
ORDER BY t.oid ASC;
END;
$$ LANGUAGE 'plpgsql' STABLE PARALLEL SAFE;
DO $$
BEGIN
SET search_path TO @extschema@;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = '_cdb_has_usable_geom_record') THEN
CREATE TYPE _cdb_has_usable_geom_record
CREATE TYPE @extschema@._cdb_has_usable_geom_record
AS (has_usable_geoms boolean,
text_geom_column boolean,
text_geom_column_name text,
@@ -650,8 +651,8 @@ BEGIN
END$$;
DROP FUNCTION IF EXISTS _CDB_Has_Usable_Geom(REGCLASS);
CREATE OR REPLACE FUNCTION _CDB_Has_Usable_Geom(reloid REGCLASS)
RETURNS _cdb_has_usable_geom_record
CREATE OR REPLACE FUNCTION @extschema@._CDB_Has_Usable_Geom(reloid REGCLASS)
RETURNS @extschema@._cdb_has_usable_geom_record
AS $$
DECLARE
r1 RECORD;
@@ -679,11 +680,11 @@ BEGIN
RAISE DEBUG 'CDB(_CDB_Has_Usable_Geom): %', 'entered function';
-- Read in the names of the CartoDB columns
const := _CDB_Columns();
const := @extschema@._CDB_Columns();
-- Do we have a column we can use?
FOR r1 IN
SELECT * FROM _cdb_geom_candidate_columns(reloid)
SELECT * FROM @extschema@._cdb_geom_candidate_columns(reloid)
LOOP
RAISE DEBUG 'CDB(_CDB_Has_Usable_Geom): %', Format('checking column ''%s''', r1.attname);
@@ -697,7 +698,7 @@ BEGIN
RAISE DEBUG 'CDB(_CDB_Has_Usable_Geom): %', Format('column ''%s'' is a text column', r1.attname);
BEGIN
sql := Format('SELECT Max(ST_SRID(%I::geometry)) AS srid FROM %I', r1.attname, reloid::text);
sql := Format('SELECT Max(@postgisschema@.ST_SRID(%I::@postgisschema@.geometry)) AS srid FROM %I', r1.attname, reloid::text);
EXECUTE sql INTO srid;
-- This gets skipped if EXCEPTION happens
-- Let the table writer know we need to convert from text
@@ -714,9 +715,9 @@ BEGIN
WHEN others THEN
IF SQLERRM = 'parse error - invalid geometry' THEN
text_geom_column := false;
str := cartodb._CDB_Unique_Column_Identifier(NULL, r1.attname, NULL, reloid);
str := @extschema@._CDB_Unique_Column_Identifier(NULL, r1.attname, NULL, reloid);
sql := Format('ALTER TABLE %s RENAME COLUMN %s TO %I', reloid::text, r1.attname, str);
PERFORM _CDB_SQL(sql,'_CDB_Has_Usable_Geom');
PERFORM @extschema@._CDB_SQL(sql,'_CDB_Has_Usable_Geom');
RAISE DEBUG 'CDB(_CDB_Has_Usable_Geom): %',
Format('Text column %s is not convertible to geometry, renamed to %s', r1.attname, str);
ELSE
@@ -726,9 +727,9 @@ BEGIN
-- Just change its name so we can write a new column into that name.
ELSE
str := cartodb._CDB_Unique_Column_Identifier(NULL, r1.attname, NULL, reloid);
str := @extschema@._CDB_Unique_Column_Identifier(NULL, r1.attname, NULL, reloid);
sql := Format('ALTER TABLE %s RENAME COLUMN %s TO %I', reloid::text, r1.attname, str);
PERFORM _CDB_SQL(sql,'_CDB_Has_Usable_Geom');
PERFORM @extschema@._CDB_SQL(sql,'_CDB_Has_Usable_Geom');
RAISE DEBUG 'CDB(_CDB_Has_Usable_Geom): %',
Format('%s is the wrong type, renamed to %s', r1.attname, str);
END IF;
@@ -784,7 +785,7 @@ $$ LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
-- a "good" one, and the same for the geometry columns. If all the required
-- columns are in place already, it no-ops and just renames the table to
-- the destination if necessary.
CREATE OR REPLACE FUNCTION _CDB_Rewrite_Table(reloid REGCLASS, destschema TEXT DEFAULT NULL)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Rewrite_Table(reloid REGCLASS, destschema TEXT DEFAULT NULL)
RETURNS BOOLEAN
AS $$
DECLARE
@@ -820,7 +821,7 @@ BEGIN
RAISE DEBUG 'CDB(_CDB_Rewrite_Table): %', 'entered function';
-- Read CartoDB standard column names in
const := _CDB_Columns();
const := @extschema@._CDB_Columns();
-- Save the raw schema/table names for later
SELECT n.nspname, c.relname, c.relname
@@ -836,7 +837,7 @@ BEGIN
-- See if there is a primary key column we need to carry along to the
-- new table. If this is true, it implies there is an indexed
-- primary key of integer type named (by default) cartodb_id
SELECT _CDB_Has_Usable_Primary_ID(reloid)
SELECT @extschema@._CDB_Has_Usable_Primary_ID(reloid)
INTO STRICT has_usable_primary_key;
RAISE DEBUG 'CDB(_CDB_Rewrite_Table): has_usable_primary_key %', has_usable_primary_key;
@@ -854,23 +855,23 @@ BEGIN
-- transformation of the table, we can just ensure proper
-- indexes are in place and apply a rename
SELECT *
FROM _CDB_Has_Usable_Geom(reloid)
FROM @extschema@._CDB_Has_Usable_Geom(reloid)
INTO STRICT gc;
-- If geom is the wrong name, just rename it.
IF gc.has_geom AND gc.has_geom_name != const.geomcol THEN
sql := Format('ALTER TABLE %s DROP COLUMN IF EXISTS %I', reloid::text, const.geomcol);
PERFORM _CDB_SQL(sql,'_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql,'_CDB_Rewrite_Table');
sql := Format('ALTER TABLE %s RENAME COLUMN %I TO %I', reloid::text, gc.has_geom_name, const.geomcol);
PERFORM _CDB_SQL(sql,'_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql,'_CDB_Rewrite_Table');
END IF;
-- If mercgeom is the wrong name, just rename it.
IF gc.has_mercgeom AND gc.has_mercgeom_name != const.mercgeomcol THEN
sql := Format('ALTER TABLE %s DROP COLUMN IF EXISTS %I', reloid::text, const.mercgeomcol);
PERFORM _CDB_SQL(sql,'_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql,'_CDB_Rewrite_Table');
sql := Format('ALTER TABLE %s RENAME COLUMN %I TO %I', reloid::text, gc.has_mercgeom_name, const.mercgeomcol);
PERFORM _CDB_SQL(sql,'_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql,'_CDB_Rewrite_Table');
END IF;
@@ -885,7 +886,7 @@ BEGIN
IF destschema != relschema THEN
RAISE DEBUG 'CDB(_CDB_Rewrite_Table): perfect table needs to be moved to schema (%)', destschema;
PERFORM _CDB_SQL(Format('ALTER TABLE %s SET SCHEMA %I', reloid::text, destschema), '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(Format('ALTER TABLE %s SET SCHEMA %I', reloid::text, destschema), '_CDB_Rewrite_Table');
ELSE
@@ -911,21 +912,21 @@ BEGIN
INTO relseq;
-- If it's the name we want, then rename it
IF relseq IS NOT NULL AND relseq = Format('%I.%I', destschema, destseq) THEN
PERFORM _CDB_SQL(Format('ALTER SEQUENCE %s RENAME TO %I', relseq, Format('tmp_%s', destseq)), '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(Format('ALTER SEQUENCE %s RENAME TO %I', relseq, Format('tmp_%s', destseq)), '_CDB_Rewrite_Table');
END IF;
END IF;
-- Put the primary key sequence in the right schema
-- If the new table is not moving, better ensure the sequence name
-- is unique
destseq := cartodb._CDB_Unique_Identifier(NULL, relname, '_' || const.pkey || '_seq', destschema);
destseq := @extschema@._CDB_Unique_Identifier(NULL, relname, '_' || const.pkey || '_seq', destschema);
destseq := Format('%I.%I', destschema, destseq);
PERFORM _CDB_SQL(Format('CREATE SEQUENCE %s', destseq), '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(Format('CREATE SEQUENCE %s', destseq), '_CDB_Rewrite_Table');
-- Temporary table name if we are re-writing in place
-- Note copyname is already escaped and safe to use as identifier
IF destschema = relschema THEN
copyname := Format('%I.%I', destschema, cartodb._CDB_Unique_Identifier(NULL, destname, NULL), destschema);
copyname := Format('%I.%I', destschema, @extschema@._CDB_Unique_Identifier(NULL, destname, NULL), destschema);
ELSE
copyname := Format('%I.%I', destschema, destname);
END IF;
@@ -966,11 +967,11 @@ BEGIN
ORDER BY a.attnum
LIMIT 1
)
SELECT ', ST_Transform('
SELECT ', @postgisschema@.ST_Transform('
|| t.missing_srid_start || t.attname || '::geometry' || t.missing_srid_end
|| ',4326)::Geometry(GEOMETRY,4326) AS '
|| const.geomcol
|| ', cartodb.CDB_TransformToWebmercator('
|| ', @extschema@.CDB_TransformToWebmercator('
|| t.missing_srid_start || t.attname || '::geometry' || t.missing_srid_end
|| ')::Geometry(GEOMETRY,3857) AS '
|| const.mercgeomcol,
@@ -1009,7 +1010,7 @@ BEGIN
-- column and read the first SRID off it it, if there is a row
-- to read.
IF FOUND THEN
EXECUTE Format('SELECT ST_SRID(%s) AS srid FROM %s LIMIT 1', rec.attname, reloid::text)
EXECUTE Format('SELECT @postgisschema@.ST_SRID(%s) AS srid FROM %s LIMIT 1', rec.attname, reloid::text)
INTO geom_srid;
ELSE
geom_srid := 0;
@@ -1025,7 +1026,7 @@ BEGIN
SELECT
a.attname,
postgis_typmod_type(a.atttypmod) AS geomtype,
CASE WHEN postgis_typmod_srid(a.atttypmod) = 0 AND srid.srid = 0 THEN 'ST_SetSRID(' ELSE '' END AS missing_srid_start,
CASE WHEN postgis_typmod_srid(a.atttypmod) = 0 AND srid.srid = 0 THEN '@postgisschema@.ST_SetSRID(' ELSE '' END AS missing_srid_start,
CASE WHEN postgis_typmod_srid(a.atttypmod) = 0 AND srid.srid = 0 THEN ',4326)' ELSE '' END AS missing_srid_end
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
@@ -1038,11 +1039,11 @@ BEGIN
ORDER BY a.attnum
LIMIT 1
)
SELECT ', ST_Transform('
SELECT ', @postgisschema@.ST_Transform('
|| t.missing_srid_start || t.attname || t.missing_srid_end
|| ',4326)::Geometry(GEOMETRY,4326) AS '
|| const.geomcol
|| ', cartodb.CDB_TransformToWebmercator('
|| ', @extschema@.CDB_TransformToWebmercator('
|| t.missing_srid_start || t.attname || t.missing_srid_end
|| ')::Geometry(GEOMETRY,3857) AS '
|| const.mercgeomcol,
@@ -1070,7 +1071,7 @@ BEGIN
-- by selecting their names into an array and
-- joining the array with a comma
SELECT
',' || array_to_string(array_agg(Format('%I',a.attname)),',') AS column_name_sql,
',' || array_to_string(array_agg(Format('%I',a.attname) ORDER BY a.attnum),',') AS column_name_sql,
Count(*) AS count
INTO rec
FROM pg_class c
@@ -1096,7 +1097,7 @@ BEGIN
RAISE DEBUG 'CDB(_CDB_Rewrite_Table): %', sql;
-- Run it!
PERFORM _CDB_SQL(sql, '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Rewrite_Table');
-- Set up the primary key sequence
-- If we copied the primary key from the original data, we need
@@ -1106,38 +1107,38 @@ BEGIN
INTO destseqmax;
IF destseqmax IS NOT NULL THEN
PERFORM _CDB_SQL(Format('SELECT setval(''%s'', %s)', destseq, destseqmax), '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(Format('SELECT setval(''%s'', %s)', destseq, destseqmax), '_CDB_Rewrite_Table');
END IF;
-- Make the primary key use the sequence as its default value
sql := Format('ALTER TABLE %s ALTER COLUMN %s SET DEFAULT nextval(''%s'')',
copyname, const.pkey, destseq);
PERFORM _CDB_SQL(sql, '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Rewrite_Table');
-- Make the sequence owned by the table, so when the table drops,
-- the sequence does too
sql := Format('ALTER SEQUENCE %s OWNED BY %s.%s', destseq, copyname, const.pkey);
PERFORM _CDB_SQL(sql,'_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql,'_CDB_Rewrite_Table');
-- We just made a copy, so we can drop the original now
sql := Format('DROP TABLE %s', reloid::text);
PERFORM _CDB_SQL(sql, '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Rewrite_Table');
-- If the table is being created by a SECURITY DEFINER function
-- make sure the user is set back to the user who is connected
IF current_user != session_user THEN
sql := Format('ALTER TABLE IF EXISTS %s OWNER TO %s', copyname, session_user);
PERFORM _CDB_SQL(sql, '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Rewrite_Table');
sql := Format('ALTER SEQUENCE IF EXISTS %s OWNER TO %s', destseq, session_user);
PERFORM _CDB_SQL(sql, '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Rewrite_Table');
END IF;
-- If we used a temporary destination table
-- we can now rename it into place
IF destschema = relschema THEN
sql := Format('ALTER TABLE %s RENAME TO %I', copyname, destname);
PERFORM _CDB_SQL(sql, '_CDB_Rewrite_Table');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Rewrite_Table');
END IF;
RETURN true;
@@ -1149,7 +1150,7 @@ $$ LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
-- Assumes the table already has the right metadata columns
-- (primary key and two geometry columns) and adds primary key
-- and geometry indexes if necessary.
CREATE OR REPLACE FUNCTION _CDB_Add_Indexes(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Add_Indexes(reloid REGCLASS)
RETURNS BOOLEAN
AS $$
DECLARE
@@ -1163,7 +1164,7 @@ BEGIN
RAISE DEBUG 'CDB(_CDB_Add_Indexes): %', 'entered function';
-- Read CartoDB standard column names in
const := _CDB_Columns();
const := @extschema@._CDB_Columns();
-- Extract just the relname to use for the index names
SELECT c.relname
@@ -1189,7 +1190,7 @@ BEGIN
IF FOUND THEN
RAISE DEBUG 'CDB(_CDB_Add_Indexes): dropping unwanted primary key ''%''', rec.pkey;
sql := Format('ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s', reloid::text, rec.pkey);
PERFORM _CDB_SQL(sql, '_CDB_Add_Indexes');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Add_Indexes');
END IF;
@@ -1210,7 +1211,7 @@ BEGIN
-- No primary key? Add one.
IF NOT FOUND THEN
sql := Format('ALTER TABLE %s ADD PRIMARY KEY (%s)', reloid::text, const.pkey);
PERFORM _CDB_SQL(sql, '_CDB_Add_Indexes');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Add_Indexes');
END IF;
-- Add geometry indexes to all "special geometry columns" that
@@ -1239,7 +1240,7 @@ BEGIN
AND am.amname != 'gist'
LOOP
sql := Format('CREATE INDEX ON %s USING GIST (%s)', reloid::text, rec.attname);
PERFORM _CDB_SQL(sql, '_CDB_Add_Indexes');
PERFORM @extschema@._CDB_SQL(sql, '_CDB_Add_Indexes');
END LOOP;
RETURN true;
@@ -1247,8 +1248,8 @@ BEGIN
END;
$$ LANGUAGE 'plpgsql' VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS CDB_CartodbfyTable(destschema TEXT, reloid REGCLASS);
CREATE OR REPLACE FUNCTION CDB_CartodbfyTable(destschema TEXT, reloid REGCLASS)
DROP FUNCTION IF EXISTS @extschema@.CDB_CartodbfyTable(destschema TEXT, reloid REGCLASS);
CREATE OR REPLACE FUNCTION @extschema@.CDB_CartodbfyTable(destschema TEXT, reloid REGCLASS)
RETURNS REGCLASS
AS $$
DECLARE
@@ -1270,7 +1271,7 @@ BEGIN
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE c.oid = reloid;
PERFORM cartodb._CDB_check_prerequisites(destschema, reloid);
PERFORM @extschema@._CDB_check_prerequisites(destschema, reloid);
-- Check destination schema exists
-- Throws an exception of there is no matching schema
@@ -1287,29 +1288,29 @@ BEGIN
END IF;
-- Drop triggers first
PERFORM _CDB_drop_triggers(reloid);
PERFORM @extschema@._CDB_drop_triggers(reloid);
-- Rasters only get a cartodb_id and a limited selection of triggers
-- underlying assumption is that they are already formed up correctly
SELECT cartodb._CDB_is_raster_table(destschema, reloid) INTO is_raster;
SELECT @extschema@._CDB_is_raster_table(destschema, reloid) INTO is_raster;
IF is_raster THEN
PERFORM cartodb._CDB_create_cartodb_id_column(reloid);
PERFORM cartodb._CDB_create_raster_triggers(destschema, reloid);
PERFORM @extschema@._CDB_create_cartodb_id_column(reloid);
PERFORM @extschema@._CDB_create_raster_triggers(destschema, reloid);
ELSE
-- Rewrite (or rename) the table to the new location
PERFORM _CDB_Rewrite_Table(reloid, destschema);
PERFORM @extschema@._CDB_Rewrite_Table(reloid, destschema);
-- The old regclass might not be valid anymore if we re-wrote the table...
destoid := (destschema || '.' || destname)::regclass;
-- Add indexes to the destination table, as necessary
PERFORM _CDB_Add_Indexes(destoid);
PERFORM @extschema@._CDB_Add_Indexes(destoid);
-- Add triggers to the destination table, as necessary
PERFORM _CDB_create_triggers(destschema, destoid);
PERFORM @extschema@._CDB_create_triggers(destschema, destoid);
END IF;

View File

@@ -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
@@ -13,4 +13,4 @@ $$ 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;

View File

@@ -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
@@ -13,4 +13,4 @@ $$ 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;

View File

@@ -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 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 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 PARALLEL SAFE;

View File

@@ -3,12 +3,12 @@
-- Introduced again to make sure that updates do not leave dangling functions
--
DROP FUNCTION IF EXISTS cartodb.cdb_handle_create_table();
DROP FUNCTION IF EXISTS cartodb.cdb_handle_drop_table();
DROP FUNCTION IF EXISTS cartodb.cdb_handle_alter_column();
DROP FUNCTION IF EXISTS cartodb.cdb_handle_drop_column();
DROP FUNCTION IF EXISTS cartodb.cdb_handle_add_column();
DROP FUNCTION IF EXISTS cartodb.cdb_disable_ddl_hooks();
DROP FUNCTION IF EXISTS cartodb.cdb_enable_ddl_hooks();
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();

View File

@@ -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
@@ -16,7 +16,7 @@ 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

View File

@@ -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;

View File

@@ -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,7 +60,7 @@ BEGIN
i := i + 1;
END LOOP;
signature = _CDB_DistTypeClassify(ajus);
signature = @extschema@._CDB_DistTypeClassify(ajus);
END IF;
RETURN signature;
@@ -69,7 +69,7 @@ $$ 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;

View File

@@ -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;

View File

@@ -11,7 +11,7 @@
--
--
CREATE OR REPLACE FUNCTION CDB_EqualIntervalBins ( in_array anyarray, breaks INT ) RETURNS anyarray as $$
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)
@@ -21,4 +21,4 @@ SELECT array_agg(bins)
FROM stats) q;
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
DROP FUNCTION IF EXISTS CDB_EqualIntervalBins( numeric[], integer);
DROP FUNCTION IF EXISTS @extschema@.CDB_EqualIntervalBins( numeric[], integer);

View File

@@ -1,5 +1,5 @@
-- Internal function to generate stats for a table if they don't exist
CREATE OR REPLACE FUNCTION _CDB_GenerateStats(reloid REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_GenerateStats(reloid REGCLASS)
RETURNS VOID
AS $$
DECLARE
@@ -12,17 +12,22 @@ BEGIN
EXECUTE Format('ANALYZE %s;', reloid);
END IF;
END
$$ LANGUAGE 'plpgsql' VOLATILE STRICT PARALLEL UNSAFE SECURITY DEFINER;
$$ 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 CDB_EstimateRowCount(query text)
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 _CDB_GenerateStats(tabname) FROM unnest(CDB_QueryTablesText(query)) AS tabname;
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;

View File

@@ -1,2 +1,2 @@
SELECT pg_catalog.pg_extension_config_dump('cartodb.cdb_tablemetadata','');
SELECT pg_catalog.pg_extension_config_dump('@extschema@.cdb_tablemetadata','');

View File

@@ -1,10 +1,10 @@
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 || '''';
@@ -12,7 +12,7 @@ BEGIN
END;
$$ 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);

View 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;

View 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;

View 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;

View 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;

View File

@@ -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,12 +58,12 @@ 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);
@@ -71,37 +71,37 @@ END
$$
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 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 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);
@@ -109,7 +109,7 @@ END
$$
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 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((
@@ -149,18 +156,18 @@ $$ LANGUAGE SQL VOLATILE PARALLEL UNSAFE;
-- 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,8 +179,8 @@ 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 VOLATILE PARALLEL UNSAFE;
@@ -182,7 +189,7 @@ $$ 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 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);

View 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;

View File

@@ -1,23 +1,23 @@
-- 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;

View File

@@ -6,14 +6,14 @@
-- 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 PARALLEL UNSAFE;
@@ -23,72 +23,72 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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 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 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 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 PARALLEL UNSAFE;
@@ -100,67 +100,67 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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 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 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 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 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;
@@ -179,26 +179,26 @@ $$ 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 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 PARALLEL UNSAFE;
@@ -208,14 +208,14 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-----------------------
-- 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
@@ -227,7 +227,7 @@ $$ 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;
@@ -241,7 +241,7 @@ $$ 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;

View File

@@ -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 PARALLEL UNSAFE 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 PARALLEL UNSAFE 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 PARALLEL UNSAFE 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 PARALLEL UNSAFE 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 PARALLEL UNSAFE 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 PARALLEL UNSAFE 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 PARALLEL UNSAFE;
$$ 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 PARALLEL UNSAFE;
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 PARALLEL UNSAFE;
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;

View File

@@ -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;

View File

@@ -5,7 +5,7 @@
-- 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
@@ -24,10 +24,10 @@ BEGIN
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, '');
@@ -59,14 +59,14 @@ BEGIN
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' 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
@@ -85,10 +85,10 @@ BEGIN
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, '');
@@ -114,14 +114,14 @@ BEGIN
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' 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
@@ -162,7 +162,7 @@ $$ 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 cartodb._CDB_Table_Exists(table_name_with_optional_schema TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_Table_Exists(table_name_with_optional_schema TEXT)
RETURNS bool
AS $$
DECLARE

View File

@@ -1,18 +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 PARALLEL SAFE;
-- In older versions of the extension, CDB_HexagonGrid had a different signature
DROP FUNCTION IF EXISTS cartodb.CDB_HexagonGrid(GEOMETRY, FLOAT8, GEOMETRY);
DROP FUNCTION IF EXISTS @extschema@.CDB_HexagonGrid(GEOMETRY, FLOAT8, GEOMETRY);
--
-- Fill given extent with an hexagonal coverage
@@ -35,7 +35,7 @@ DROP FUNCTION IF EXISTS cartodb.CDB_HexagonGrid(GEOMETRY, FLOAT8, GEOMETRY);
-- 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, maxcells INTEGER DEFAULT 512*512)
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
@@ -80,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;
@@ -96,19 +96,19 @@ 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 ];
@@ -125,21 +125,21 @@ BEGIN
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;

View File

@@ -13,7 +13,7 @@
--
--
CREATE OR REPLACE FUNCTION CDB_JenksBins(in_array NUMERIC[], breaks INT, iterations INT DEFAULT 0, invert 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
@@ -95,7 +95,7 @@ BEGIN
IF i > breaks THEN EXIT; END IF;
END LOOP;
best_result = CDB_JenksBinsIteration(in_matrix, breaks, classes, invert, sdam, 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;
@@ -126,7 +126,7 @@ BEGIN
IF i > breaks THEN EXIT; END IF;
END LOOP;
curr_result = CDB_JenksBinsIteration(in_matrix, breaks, classes, invert, sdam, shuffles);
curr_result = @extschema@.CDB_JenksBinsIteration(in_matrix, breaks, classes, invert, sdam, shuffles);
IF curr_result[1] > best_result[1] THEN
best_result = curr_result;
@@ -146,9 +146,9 @@ $$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL RESTRICTED;
-- Returns an array with:
-- - First element: gvf
-- - Second to 2+n: Category limits
DROP FUNCTION IF EXISTS CDB_JenksBinsIteration ( in_matrix NUMERIC[], breaks INT, classes INT[], invert BOOLEAN, element_count INT4, arr_mean NUMERIC, max_search INT); -- Old signature
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 CDB_JenksBinsIteration ( in_matrix NUMERIC[], breaks INT, classes INT[], invert BOOLEAN, sdam NUMERIC, max_search INT DEFAULT 50) RETURNS NUMERIC[] as $$
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;

View File

@@ -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);
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);
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;

View File

@@ -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
@@ -15,12 +15,12 @@ $BODY$
$BODY$
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='{}'
);

View 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;

View File

@@ -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 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,11 +26,11 @@ 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 PARALLEL UNSAFE;
@@ -31,19 +38,25 @@ $$ 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 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,15 +65,15 @@ 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);
@@ -68,15 +81,15 @@ END
$$ 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
@@ -86,7 +99,7 @@ $$ 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
@@ -96,16 +109,16 @@ END
$$ 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 PARALLEL UNSAFE;
CREATE OR REPLACE
FUNCTION cartodb._CDB_Organization_Get_Table_Sequences(from_schema text, table_name text)
FUNCTION @extschema@._CDB_Organization_Get_Table_Sequences(from_schema text, table_name text)
RETURNS SETOF TEXT
AS $$
BEGIN
@@ -124,7 +137,7 @@ END
$$ 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_Add_Table_Read_Write_Permission(from_schema text, table_name text, to_role_name text)
RETURNS void
AS $$
DECLARE
@@ -133,24 +146,24 @@ 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 cartodb._CDB_Organization_Get_Table_Sequences(from_schema, table_name) LOOP
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 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 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
@@ -162,10 +175,17 @@ END
$$ 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 PARALLEL UNSAFE;
--------------------------------------------------------------------------------
-- Deprecated
--------------------------------------------------------------------------------
DROP FUNCTION IF EXISTS @extschema@.CDB_Organization_Grant_Role(name);
DROP FUNCTION IF EXISTS @extschema@.CDB_Organization_Revoke_Role(name);

View File

@@ -2,7 +2,7 @@
-- 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
@@ -10,9 +10,9 @@ 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;
@@ -29,20 +29,20 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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 STABLE PARALLEL RESTRICTED;
@@ -56,18 +56,18 @@ $$ LANGUAGE PLPGSQL STABLE PARALLEL RESTRICTED;
-- 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 STABLE PARALLEL SAFE;
@@ -76,11 +76,11 @@ $$ LANGUAGE SQL STABLE PARALLEL SAFE;
-- 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
@@ -89,7 +89,7 @@ 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'
);
@@ -99,7 +99,7 @@ AS $$
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;
@@ -116,7 +116,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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
@@ -135,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
@@ -146,27 +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
least(
-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)),
_CDB_MaxOverviewLevel()+1
-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
)
@@ -174,12 +174,12 @@ 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(t.x*2 + c.xx, t.y*2 + c.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 < 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;
@@ -193,7 +193,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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
@@ -210,15 +210,15 @@ 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 least(_CDB_MaxOverviewLevel()+1, 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 VOLATILE PARALLEL UNSAFE;
@@ -231,7 +231,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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
@@ -240,9 +240,9 @@ 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 PARALLEL SAFE;
@@ -254,7 +254,7 @@ $$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
-- 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, has_overview_created BOOLEAN DEFAULT FALSE)
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
@@ -268,11 +268,11 @@ AS $$
overview_table_name TEXT;
creation_clause TEXT;
BEGIN
overview_rel := _CDB_Overview_Name(reloid, ref_z, overview_z);
overview_rel := @extschema@._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;
SELECT * FROM @extschema@._cdb_split_table_name(reloid) INTO schema_name, table_name;
overview_table_name := Format('%I.%I', schema_name, overview_rel);
IF has_overview_created THEN
@@ -299,7 +299,7 @@ AS $$
%1$s SELECT * FROM %2$s
WHERE ctid = ANY (
ARRAY[
(SELECT CDB_RandomTids(''%2$s'', %3$s))
(SELECT @extschema@.CDB_RandomTids(''%2$s'', %3$s))
]
);
', creation_clause, reloid, num_samples);
@@ -318,9 +318,9 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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
@@ -334,10 +334,10 @@ AS $$
-- 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;
SELECT * FROM @extschema@._cdb_split_table_name(dataset) INTO dataset_scheme, dataset_name;
SELECT * FROM @extschema@._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
overview_name = @extschema@._CDB_OverviewTableName(dataset_name, overview_z) THEN
-- preserve the owner of the base table
SELECT u.usename
@@ -356,13 +356,17 @@ AS $$
WHERE c_from.oid = dataset
AND c_to.oid = overview_table;
PERFORM _CDB_Add_Indexes(overview_table);
PERFORM @extschema@._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;
END
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE 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
@@ -371,10 +375,10 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE 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
)
@@ -386,14 +390,14 @@ $$ LANGUAGE SQL STABLE PARALLEL SAFE;
-- 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;
@@ -401,7 +405,7 @@ END
$$ 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 (
@@ -416,7 +420,7 @@ AS $$
);
$$ 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
@@ -445,7 +449,7 @@ BEGIN
END;
$$ 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
@@ -456,11 +460,11 @@ $$
$$
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='{}'
);
@@ -473,7 +477,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
@@ -490,10 +494,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';
@@ -503,24 +507,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;
@@ -542,15 +546,15 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL RESTRICTED;
-- 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;
@@ -562,14 +566,14 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL RESTRICTED;
-- 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
@@ -589,7 +593,7 @@ $$ LANGUAGE PLPGSQL STABLE PARALLEL SAFE;
-- 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, has_overview_created BOOLEAN DEFAULT FALSE)
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
@@ -613,7 +617,7 @@ AS $$
overview_table_name TEXT;
creation_clause TEXT;
BEGIN
SELECT _CDB_GeometryTypes(reloid) INTO gtypes;
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;
@@ -621,22 +625,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;
@@ -658,7 +662,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 (
@@ -666,12 +670,12 @@ AS $$
CASE c
WHEN 'cartodb_id' THEN 'cartodb_id'
WHEN 'the_geom' THEN
Format('ST_Transform(%s, 4326) AS the_geom', point_geom)
Format('@postgisschema@.ST_Transform(%s, 4326) AS the_geom', point_geom)
WHEN 'the_geom_webmercator' THEN
Format('%s AS the_geom_webmercator', point_geom)
ELSE c
ELSE c::text
END AS column
FROM CDB_ColumnNames(reloid) c
FROM @extschema@.CDB_ColumnNames(reloid) c
)
SELECT string_agg(s.column, ',') FROM (
SELECT * FROM cols
@@ -701,8 +705,8 @@ 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,
Floor(@postgisschema@.ST_X(f.the_geom_webmercator)/%2$s)::int AS gx,
Floor(@postgisschema@.ST_Y(f.the_geom_webmercator)/%2$s)::int AS gy,
MIN(cartodb_id) AS cartodb_id
FROM %1$s f
WHERE f.the_geom_webmercator IS NOT NULL
@@ -716,7 +720,7 @@ AS $$
$$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- This strategy places the aggregation of each cluster at the centroid of the cluster members.
CREATE OR REPLACE FUNCTION _CDB_GridClusterCentroid_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, grid_px FLOAT8 DEFAULT NULL, has_overview_created BOOLEAN DEFAULT FALSE)
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
@@ -740,7 +744,7 @@ AS $$
overview_table_name TEXT;
creation_clause TEXT;
BEGIN
SELECT _CDB_GeometryTypes(reloid) INTO gtypes;
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;
@@ -748,22 +752,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;
@@ -785,7 +789,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 (
@@ -793,10 +797,10 @@ AS $$
CASE c
WHEN 'cartodb_id' THEN 'cartodb_id'
WHEN 'the_geom' THEN
'ST_Transform(ST_SetSRID(ST_MakePoint(_sum_of_x/n, _sum_of_y/n), 3857), 4326) AS the_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
'ST_SetSRID(ST_MakePoint(_sum_of_x/n, _sum_of_y/n), 3857) AS the_geom_webmercator'
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
)
@@ -828,10 +832,10 @@ AS $$
SELECT
%5$s
count(*) AS n,
SUM(ST_X(f.the_geom_webmercator)) AS _sum_of_x,
SUM(ST_Y(f.the_geom_webmercator)) AS _sum_of_y,
Floor(ST_Y(f.the_geom_webmercator)/%2$s)::int AS gy,
Floor(ST_X(f.the_geom_webmercator)/%2$s)::int AS gx,
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
@@ -844,7 +848,7 @@ AS $$
$$ 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 _CDB_GridClusterSample_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER, grid_px FLOAT8 DEFAULT NULL, has_overview_created BOOLEAN DEFAULT FALSE)
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
@@ -868,7 +872,7 @@ AS $$
overview_table_name TEXT;
creation_clause TEXT;
BEGIN
SELECT _CDB_GeometryTypes(reloid) INTO gtypes;
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;
@@ -876,22 +880,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;
@@ -913,16 +917,16 @@ 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 (
SELECT
CASE c
WHEN 'cartodb_id' THEN 'cartodb_id'
ELSE c
ELSE c::text
END AS column
FROM CDB_ColumnNames(reloid) c
FROM @extschema@.CDB_ColumnNames(reloid) c
)
SELECT string_agg(s.column, ',') FROM (
SELECT * FROM cols
@@ -952,8 +956,8 @@ 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,
Floor(@postgisschema@.ST_X(_f.the_geom_webmercator)/%2$s)::int AS gx,
Floor(@postgisschema@.ST_Y(_f.the_geom_webmercator)/%2$s)::int AS gy,
MIN(cartodb_id) AS cartodb_id
FROM %1$s _f
GROUP BY gx, gy
@@ -980,7 +984,7 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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,BOOLEAN)'::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
@@ -988,12 +992,12 @@ DECLARE
BEGIN
-- Use the default tolerance
tolerance_px := 1.0;
RETURN CDB_CreateOverviewsWithToleranceInPixels(reloid, tolerance_px, refscale_strategy, reduce_strategy);
RETURN @extschema@.CDB_CreateOverviewsWithToleranceInPixels(reloid, tolerance_px, refscale_strategy, reduce_strategy);
END;
$$ 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,BOOLEAN)'::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
@@ -1045,7 +1049,7 @@ BEGIN
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'
SELECT * FROM @extschema@.CDB_ColumnNames(reloid) as colname WHERE colname = '_feature_count'
) INTO has_counter_column;
IF NOT has_counter_column THEN
EXECUTE Format('
@@ -1061,10 +1065,10 @@ $$ LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
-- 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_GridCluster_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8);
DROP FUNCTION IF EXISTS _CDB_GridClusterCentroid_Reduce_Strategy(REGCLASS, INTEGER, INTEGER, FLOAT8);
DROP FUNCTION IF EXISTS _CDB_GridClusterSample_Reduce_Strategy(REGCLASS, INTEGER, INTEGER, FLOAT8);
DROP FUNCTION IF EXISTS _CDB_Sampling_Reduce_Strategy(REGCLASS,INTEGER,INTEGER);
DROP FUNCTION IF EXISTS _CDB_Sampling_Reduce_Strategy(REGCLASS,INTEGER,INTEGER,FLOAT8);
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);

View File

@@ -1,7 +1,7 @@
-- Auxiliary overviews FUNCTIONS
-- Maximum zoom level for which overviews may be created
CREATE OR REPLACE FUNCTION _CDB_MaxOverviewLevel()
CREATE OR REPLACE FUNCTION @extschema@._CDB_MaxOverviewLevel()
RETURNS INTEGER
AS $$
BEGIN
@@ -18,7 +18,7 @@ AS $$
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
-- Maximum zoom level usable with integer coordinates
CREATE OR REPLACE FUNCTION _CDB_MaxZoomLevel()
CREATE OR REPLACE FUNCTION @extschema@._CDB_MaxZoomLevel()
RETURNS INTEGER
AS $$
BEGIN
@@ -31,7 +31,7 @@ $$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
-- 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)
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
@@ -43,7 +43,7 @@ AS $$
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', 'cartodb')
THEN n.nspname NOT IN ('pg_catalog', 'information_schema', 'topology', '@extschema@')
ELSE n.nspname = schema_name
END;
$$ LANGUAGE 'sql' STABLE PARALLEL SAFE;
@@ -51,7 +51,7 @@ $$ 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 _CDB_OverviewTableDiscriminator()
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTableDiscriminator()
RETURNS TEXT
AS $$
BEGIN
@@ -63,18 +63,18 @@ $$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
-- Pattern matched by the overview tables of a given base table name.
-- Scope: private.
CREATE OR REPLACE FUNCTION _CDB_OverviewTablePattern(base_table TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTablePattern(base_table TEXT)
RETURNS TEXT
AS $$
BEGIN
RETURN _CDB_OverviewTableDiscriminator() || base_table;
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 _CDB_OverviewTableName(base_table TEXT, z INTEGER)
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTableName(base_table TEXT, z INTEGER)
RETURNS TEXT
AS $$
BEGIN
@@ -84,39 +84,39 @@ $$ 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 _CDB_IsOverviewTableOf(base_table TEXT, otable TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_IsOverviewTableOf(base_table TEXT, otable TEXT)
RETURNS BOOLEAN
AS $$
BEGIN
RETURN otable SIMILAR TO _CDB_OverviewTablePattern(base_table);
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 _CDB_OverviewTableZ(otable TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewTableZ(otable TEXT)
RETURNS INTEGER
AS $$
BEGIN
RETURN substring(otable from _CDB_OverviewTableDiscriminator())::integer;
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 _CDB_OverviewBaseTableName(overview_table TEXT)
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewBaseTableName(overview_table TEXT)
RETURNS TEXT
AS $$
BEGIN
IF _CDB_OverviewTableZ(overview_table) IS NULL THEN
IF @extschema@._CDB_OverviewTableZ(overview_table) IS NULL THEN
RETURN overview_table;
ELSE
RETURN regexp_replace(overview_table, _CDB_OverviewTableDiscriminator(), '');
RETURN regexp_replace(overview_table, @extschema@._CDB_OverviewTableDiscriminator(), '');
END IF;
END;
$$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
CREATE OR REPLACE FUNCTION _CDB_OverviewBaseTable(overview_table REGCLASS)
CREATE OR REPLACE FUNCTION @extschema@._CDB_OverviewBaseTable(overview_table REGCLASS)
RETURNS REGCLASS
AS $$
DECLARE
@@ -125,8 +125,8 @@ AS $$
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);
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
@@ -142,7 +142,7 @@ $$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
-- 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)
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
@@ -158,7 +158,7 @@ $$ LANGUAGE PLPGSQL IMMUTABLE PARALLEL SAFE;
-- 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)
CREATE OR REPLACE FUNCTION @extschema@._cdb_schema_name(reloid REGCLASS)
RETURNS TEXT
AS $$
DECLARE

View File

@@ -7,7 +7,7 @@
-- @param breaks The number of bins you want to find.
--
--
CREATE OR REPLACE FUNCTION CDB_QuantileBins(in_array numeric[], breaks int)
CREATE OR REPLACE FUNCTION @extschema@.CDB_QuantileBins(in_array numeric[], breaks int)
RETURNS numeric[]
AS $$
SELECT

View File

@@ -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 PARALLEL SAFE;
$$ language '@@plpythonu@@' IMMUTABLE STRICT PARALLEL SAFE;

View File

@@ -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
@@ -14,7 +14,7 @@ BEGIN
tables := '{}';
FOR rec IN SELECT CDB_QueryStatements(query) q LOOP
FOR rec IN SELECT @extschema@.CDB_QueryStatements(query) q LOOP
BEGIN
EXECUTE 'EXPLAIN (FORMAT XML, VERBOSE) ' || rec.q INTO STRICT exp;
EXCEPTION WHEN syntax_error THEN
@@ -66,10 +66,10 @@ $$ 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 PARALLEL UNSAFE;

View File

@@ -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,7 +7,7 @@ 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;
@@ -15,38 +15,55 @@ $$
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 _CDB_NonAnalysisTablesInSchema(schema_name)
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)
OR table_name SIMILAR TO _CDB_OverviewTableDiscriminator() || '[\w\d]*'
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;
@@ -60,15 +77,15 @@ 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 CDB_UserDataSize('public');
SELECT @extschema@.CDB_UserDataSize('public');
$$
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
@@ -80,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
@@ -111,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;
@@ -124,13 +141,13 @@ $$
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;
@@ -145,11 +162,11 @@ $$
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 PARALLEL UNSAFE;

View File

@@ -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

View File

@@ -1,5 +1,5 @@
-- In older versions of the extension, CDB_RectangleGrid had a different signature
DROP FUNCTION IF EXISTS cartodb.CDB_RectangleGrid(GEOMETRY, FLOAT8, FLOAT8, GEOMETRY);
DROP FUNCTION IF EXISTS @extschema@.CDB_RectangleGrid(GEOMETRY, FLOAT8, FLOAT8, GEOMETRY);
--
-- Fill given extent with a rectangular coverage
@@ -22,7 +22,7 @@ DROP FUNCTION IF EXISTS cartodb.CDB_RectangleGrid(GEOMETRY, FLOAT8, FLOAT8, GEOM
-- if the grid requires more cells to cover the extent
-- and exception will occur.
--
CREATE OR REPLACE FUNCTION CDB_RectangleGrid(ext GEOMETRY, width FLOAT8, height FLOAT8, origin GEOMETRY DEFAULT NULL, maxcells INTEGER DEFAULT 512*512)
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
@@ -44,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;
@@ -72,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);
@@ -94,10 +94,10 @@ BEGIN
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

View File

@@ -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

View File

@@ -9,7 +9,7 @@
--
-- 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;
@@ -32,7 +32,7 @@ END;
$$ 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;

View File

@@ -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

View 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;

View File

@@ -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 $$
@@ -24,4 +24,4 @@ $$ 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;

View File

@@ -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 m JOIN pg_catalog.pg_class c ON m.tabname::oid = c.oid
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 PARALLEL UNSAFE 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,29 +119,32 @@ BEGIN
RETURN NULL;
END;
$$
LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE 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;

View File

@@ -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,18 +27,18 @@ 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
BEGIN
RETURN ST_Transform(latlon_input, 3857);
RETURN @postgisschema@.ST_Transform(latlon_input, 3857);
EXCEPTION WHEN OTHERS THEN
RETURN NULL;
END;
@@ -47,33 +47,33 @@ BEGIN
-- 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;
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
-- 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;

View File

@@ -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 $$
@@ -15,7 +15,7 @@ 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 n.nspname NOT IN ('pg_catalog', 'information_schema', 'topology', 'cartodb')
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')
@@ -25,4 +25,4 @@ $$ 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;

View File

@@ -1,6 +1,10 @@
-- Returns the cartodb username of the current PostgreSQL session
CREATE OR REPLACE FUNCTION CDB_Username()
CREATE OR REPLACE FUNCTION @extschema@.CDB_Username()
RETURNS text
AS $$
SELECT CDB_Conf_GetConf(CONCAT('api_keys_', session_user))->>'username';
$$ LANGUAGE SQL STABLE PARALLEL SAFE SECURITY DEFINER;
SELECT @extschema@.CDB_Conf_GetConf(concat('api_keys_', session_user))->>'username';
$$ LANGUAGE SQL
STABLE
PARALLEL SAFE
SECURITY DEFINER
SET search_path = pg_temp;

View File

@@ -1,7 +1,7 @@
-- {
-- 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
@@ -15,7 +15,7 @@ $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE 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
@@ -34,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;
@@ -56,7 +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 PARALLEL SAFE;
-- }

View File

@@ -1,5 +1,5 @@
-- Maximum supported zoom level
CREATE OR REPLACE FUNCTION _CDB_MaxSupportedZoom()
CREATE OR REPLACE FUNCTION @extschema@._CDB_MaxSupportedZoom()
RETURNS int
LANGUAGE SQL
IMMUTABLE PARALLEL SAFE
@@ -12,7 +12,7 @@ AS $$
SELECT 29;
$$;
CREATE OR REPLACE FUNCTION cartodb.CDB_ZoomFromScale(scaleDenominator numeric)
CREATE OR REPLACE FUNCTION @extschema@.CDB_ZoomFromScale(scaleDenominator numeric)
RETURNS int
LANGUAGE SQL
IMMUTABLE PARALLEL SAFE
@@ -24,12 +24,12 @@ AS $$
NULL
WHEN scaleDenominator = 0 THEN
-- Actual zoom level would be infinite
_CDB_MaxSupportedZoom()
@extschema@._CDB_MaxSupportedZoom()
ELSE
CAST (
LEAST(
ROUND(LOG(2, 559082264.028/scaleDenominator)),
_CDB_MaxSupportedZoom()
@extschema@._CDB_MaxSupportedZoom()
)
AS INTEGER)
END;

View File

@@ -0,0 +1 @@
../scripts-available/CDB_GhostTables.sql

View File

@@ -0,0 +1 @@
../scripts-available/CDB_OAuth.sql

View File

@@ -0,0 +1 @@
../scripts-available/CDB_FederatedServer.sql

View File

@@ -0,0 +1 @@
../scripts-available/CDB_FederatedServerTables.sql

View File

@@ -0,0 +1 @@
../scripts-available/CDB_FederatedServerListRemote.sql

View File

@@ -0,0 +1 @@
../scripts-available/CDB_FederatedServerDiagnostics.sql

View File

@@ -0,0 +1 @@
../scripts-available/CDB_SyncTable.sql

View File

@@ -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
View File

@@ -0,0 +1 @@
\set ECHO none

View File

@@ -7,7 +7,7 @@ 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');
SELECT _CDB_AnalysisTablesInSchema('public') t ORDER BY t;
SELECT _CDB_AnalysisDataSize('public');
SELECT CDB_CheckAnalysisQuota('analysis_2f13a3dbd7_f00cee44e9e6152b450bde3a92eb9ae0d099da94');
SELECT CDB_SetUserQuotaInBytes(1);

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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|

View 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

View 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|

View 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

View 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|

View File

@@ -0,0 +1,69 @@
## List empty servers shows nothing
## List non-existent server shows nothing
## Create and list a server works
1.3|
1.4|(myRemote,postgres_fdw,localhost,@@PGPORT@@,,read-only,fdw_user)
## Create and list a second server works
2.1|
2.2|(myRemote,postgres_fdw,localhost,@@PGPORT@@,,read-only,fdw_user)
2.2|(myRemote2,postgres_fdw,localhost,@@PGPORT@@,fdw_target,read-only,fdw_user)
## List server by name works
2.3|(myRemote,postgres_fdw,localhost,@@PGPORT@@,,read-only,fdw_user)
## Re-register a second server works
3.1|
3.2|(myRemote,postgres_fdw,localhost,@@PGPORT@@,,read-only,fdw_user)
3.2|(myRemote2,postgres_fdw,localhost,@@PGPORT@@,fdw_target,read-only,other_remote_user)
## Unregister server 1 works
4.1|
4.2|(myRemote2,postgres_fdw,localhost,@@PGPORT@@,fdw_target,read-only,other_remote_user)
## Unregistering a server that does not exist fails
ERROR: Server "doesNotExist" does not exist
## Unregister the second server works
6.1|
## Create a server with NULL name fails
ERROR: Server name cannot be NULL
## Create a server with NULL config fails
7.01|
## Create a server with empty config fails
ERROR: Server information is mandatory
## Create a server without credentials fails
ERROR: Credentials are mandatory
## Create a server with empty credentials works
7.3|
7.4|(empty,postgres_fdw,localhost,@@PGPORT@@,fdw_target,read-only,)
7.5|
## Create a server without options fails
ERROR: Server information is mandatory
## Create a server with special characters works
8.1|
8.2|("myRemote"" or'not",postgres_fdw,localhost,@@PGPORT@@,"fdw target",read-only,"fdw user")
8.3|
9.1|
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
## All users are able to list servers
9.2|(myRemote3,postgres_fdw,localhost,@@PGPORT@@,,read-only,)
## Only superadmins can create servers
ERROR: Could not create server myRemote4: permission denied for foreign-data wrapper postgres_fdw
You are now connected to database "contrib_regression" as user "postgres".
## Granting access to a user works
9.5|
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
9.55|(myRemote3,postgres_fdw,localhost,@@PGPORT@@,,read-only,fdw_user)
You are now connected to database "contrib_regression" as user "postgres".
ERROR: Server "does not exist" does not exist
ERROR: Could not grant access on "myRemote3" to "does not exist": role "does not exist" does not exist
## Granting access again raises a notice
NOTICE: role "cdb_fs_tester" is already a member of role "cdb_fs_role_95b63382aabca4433e7bd9cba6c30368"
9.8|
## Revoking access to a user works
9.9|
9.10|
## Unregistering a server with active grants works
9.11|
## A user with granted access can not drop a server
10.1|
10.2|
You are now connected to database "contrib_regression" as user "cdb_fs_tester".
ERROR: Not enough permissions to drop the server "myRemote4"
You are now connected to database "contrib_regression" as user "postgres".
10.4|

102
test/CDB_GhostTables.sql Normal file
View File

@@ -0,0 +1,102 @@
-- Create user and enable Ghost tables trigger
\set QUIET on
SET client_min_messages TO error;
-- Recreate the function without extra error messages as it changes depending on the python-redis version
CREATE OR REPLACE FUNCTION cartodb._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 cartodb.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')
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')
break
tis_retry -= 1 # try reconnecting
$$ LANGUAGE '@@plpythonu@@' VOLATILE PARALLEL UNSAFE;
SELECT CDB_EnableGhostTablesTrigger();
CREATE ROLE "fulano" LOGIN;
GRANT ALL ON SCHEMA cartodb TO "fulano";
GRANT SELECT ON cartodb.cdb_ddl_execution TO "fulano";
GRANT EXECUTE ON FUNCTION CDB_Username() TO "fulano";
GRANT EXECUTE ON FUNCTION CDB_LinkGhostTables(text) TO "fulano";
SELECT cartodb.CDB_Conf_SetConf('api_keys_fulano', '{"username": "fulanito", "permissions":[]}');
DELETE FROM cdb_conf WHERE key = 'invalidation_service';
SET SESSION AUTHORIZATION "fulano";
SET client_min_messages TO notice;
\set QUIET off
SELECT CDB_LinkGhostTables(); -- _CDB_LinkGhostTables called (configuration not found)
-- Add TIS configuration
\set QUIET on
SET SESSION AUTHORIZATION postgres;
SELECT cartodb.CDB_Conf_SetConf('invalidation_service', '{"host": "fake-tis-host", "port": 3142}');
SET SESSION AUTHORIZATION "fulano";
\set QUIET off
SELECT CDB_LinkGhostTables(); -- _CDB_LinkGhostTables called
BEGIN;
SELECT to_regclass('cartodb.cdb_ddl_execution'); -- exists
SELECT COUNT(*) FROM cartodb.cdb_ddl_execution; -- 0
CREATE TABLE tmp(id INT);
SELECT COUNT(*) FROM cartodb.cdb_ddl_execution; -- 1
END; -- _CDB_LinkGhostTables called
-- Disable Ghost tables trigger
\set QUIET on
SET SESSION AUTHORIZATION postgres;
SELECT CDB_DisableGhostTablesTrigger();
SET SESSION AUTHORIZATION "fulano";
\set QUIET off
SELECT to_regclass('cartodb.cdb_ddl_execution'); -- not exists
DROP TABLE tmp; -- _CDB_LinkGhostTables not called
-- Cleanup
\set QUIET on
SET SESSION AUTHORIZATION postgres;
REVOKE EXECUTE ON FUNCTION CDB_LinkGhostTables(text) FROM "fulano";
REVOKE EXECUTE ON FUNCTION CDB_Username() FROM "fulano";
REVOKE ALL ON SCHEMA cartodb FROM "fulano";
DROP ROLE "fulano";
DELETE FROM cdb_conf WHERE key = 'api_keys_fulano' OR key = 'invalidation_service';
\set QUIET off

View File

@@ -0,0 +1,20 @@
WARNING: Invalidation service configuration not found. Skipping Ghost Tables linking.
INFO: _CDB_LinkGhostTables() called with username=fulanito, event_name=USER
WARNING: Error calling Invalidation Service to link Ghost Tables
INFO: _CDB_LinkGhostTables() called with username=fulanito, event_name=USER
BEGIN
cdb_ddl_execution
0
CREATE TABLE
1
WARNING: Error calling Invalidation Service to link Ghost Tables
INFO: _CDB_LinkGhostTables() called with username=fulanito, event_name=CREATE TABLE
COMMIT
DROP TABLE

223
test/CDB_OAuth.sql Normal file
View File

@@ -0,0 +1,223 @@
-- Create user and enable OAuth event trigger
\set QUIET on
SET client_min_messages TO error;
-- The permission error changed between pre PG11 and post 11 (before everything was "relation", now it's "view", "table" and so on
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';
DROP ROLE IF EXISTS "creator_role";
CREATE ROLE "creator_role" LOGIN;
DROP ROLE IF EXISTS "ownership_role";
CREATE ROLE "ownership_role" LOGIN;
GRANT ALL ON SCHEMA cartodb TO "creator_role";
SELECT CDB_Conf_SetConf('api_keys_creator_role', '{"username": "creator_role", "permissions":[]}');
SET SESSION AUTHORIZATION "creator_role";
SET client_min_messages TO notice;
\set QUIET off
-- First part without event trigger
CREATE TABLE test(id INT);
INSERT INTO test VALUES(1);
CREATE TABLE test_tablesas AS SELECT * FROM test;
CREATE VIEW test_view AS SELECT * FROM test;
CREATE MATERIALIZED VIEW test_mview AS SELECT * FROM test;
SELECT * INTO test_selectinto FROM test;
CREATE FUNCTION test_function() RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL;
SELECT * FROM test;
SELECT * FROM test_tablesas;
SELECT * FROM test_view;
SELECT * FROM test_mview;
SELECT * FROM test_selectinto;
SELECT test_function();
-- Postgres grants default execute privilege on functions to PUBLIC. So in order to check the different permissions
-- between creator and owner roles is not enough with performing a selection, we need to DROP the table (which only the owner can do)
DROP FUNCTION test_function();
\set QUIET on
CREATE FUNCTION test_function() RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL;
SET SESSION AUTHORIZATION "ownership_role";
\set QUIET off
SELECT 'denied_table', catch_permission_error($$SELECT * FROM test;$$);
SELECT 'denied_tableas', catch_permission_error($$SELECT * FROM test_tablesas;$$);
SELECT 'denied_view', catch_permission_error($$SELECT * FROM test_view;$$);
SELECT 'denied_mview', catch_permission_error($$SELECT * FROM test_mview;$$);
SELECT 'denied_selectinto', catch_permission_error($$SELECT * FROM test_selectinto;$$);
SELECT test_function();
SELECT 'denied_function', catch_permission_error($$DROP FUNCTION test_function();$$);
\set QUIET on
SET SESSION AUTHORIZATION "creator_role";
\set QUIET off
DROP TABLE test_tablesas;
DROP VIEW test_view;
DROP MATERIALIZED VIEW test_mview;
DROP TABLE test_selectinto;
DROP TABLE test;
DROP FUNCTION test_function();
-- Second part with event trigger but without ownership_role_name in cdb_conf
\set QUIET on
SET SESSION AUTHORIZATION postgres;
SELECT CDB_EnableOAuthReassignTablesTrigger();
SET SESSION AUTHORIZATION "creator_role";
\set QUIET off
CREATE TABLE test2(id INT);
INSERT INTO test2 VALUES(1);
CREATE TABLE test2_tablesas AS SELECT * FROM test2;
CREATE VIEW test2_view AS SELECT * FROM test2;
CREATE MATERIALIZED VIEW test2_mview AS SELECT * FROM test2;
SELECT * INTO test2_selectinto FROM test2;
CREATE FUNCTION test2_function() RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL;
SELECT * FROM test2;
SELECT * FROM test2_tablesas;
SELECT * FROM test2_view;
SELECT * FROM test2_mview;
SELECT * FROM test2_selectinto;
SELECT test2_function();
DROP FUNCTION test2_function();
\set QUIET on
CREATE FUNCTION test2_function() RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL;
SET SESSION AUTHORIZATION "ownership_role";
\set QUIET off
SELECT 'denied_table2', catch_permission_error($$SELECT * FROM test2;$$);
SELECT 'denied_tableas2', catch_permission_error($$SELECT * FROM test2_tablesas;$$);
SELECT 'denied_view2', catch_permission_error($$SELECT * FROM test2_view;$$);
SELECT 'denied_mview2', catch_permission_error($$SELECT * FROM test2_mview;$$);
SELECT 'denied_selectinto2', catch_permission_error($$SELECT * FROM test2_selectinto;$$);
SELECT test2_function();
SELECT 'denied_function2', catch_permission_error($$DROP FUNCTION test2_function();$$);
\set QUIET on
SET SESSION AUTHORIZATION "creator_role";
\set QUIET off
DROP TABLE test2_tablesas;
DROP VIEW test2_view;
DROP MATERIALIZED VIEW test2_mview;
DROP TABLE test2_selectinto;
DROP TABLE test2;
DROP FUNCTION test2_function();
-- Third part with event trigger but with empty ownership_role_name in cdb_conf
\set QUIET on
SET SESSION AUTHORIZATION postgres;
SELECT CDB_Conf_SetConf('api_keys_creator_role', '{"username": "creator_role", "permissions":[], "ownership_role_name": ""}');
SET SESSION AUTHORIZATION "creator_role";
\set QUIET off
CREATE TABLE test3(id INT);
INSERT INTO test3 VALUES(1);
CREATE TABLE test3_tablesas AS SELECT * FROM test3;
CREATE VIEW test3_view AS SELECT * FROM test3;
CREATE MATERIALIZED VIEW test3_mview AS SELECT * FROM test3;
SELECT * INTO test3_selectinto FROM test3;
CREATE FUNCTION test3_function() RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL;
SELECT * FROM test3;
SELECT * FROM test3_tablesas;
SELECT * FROM test3_view;
SELECT * FROM test3_mview;
SELECT * FROM test3_selectinto;
SELECT test3_function();
DROP FUNCTION test3_function();
\set QUIET on
CREATE FUNCTION test3_function() RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL;
SET SESSION AUTHORIZATION "ownership_role";
\set QUIET off
SELECT 'denied_table3', catch_permission_error($$SELECT * FROM test3;$$);
SELECT 'denied_tableas3', catch_permission_error($$SELECT * FROM test3_tablesas;$$);
SELECT 'denied_view3', catch_permission_error($$SELECT * FROM test3_view;$$);
SELECT 'denied_mview3', catch_permission_error($$SELECT * FROM test3_mview;$$);
SELECT 'denied_selectinto3', catch_permission_error($$SELECT * FROM test3_selectinto;$$);
SELECT test3_function();
SELECT 'denied_function3', catch_permission_error($$DROP FUNCTION test3_function();$$);
\set QUIET on
SET SESSION AUTHORIZATION "creator_role";
\set QUIET off
DROP TABLE test3_tablesas;
DROP VIEW test3_view;
DROP MATERIALIZED VIEW test3_mview;
DROP TABLE test3_selectinto;
DROP TABLE test3;
DROP FUNCTION test3_function();
-- Fourth part with the event trigger active and configured
\set QUIET on
SET SESSION AUTHORIZATION postgres;
SELECT CDB_Conf_SetConf('api_keys_creator_role', '{"username": "creator_role", "permissions":[], "ownership_role_name": "ownership_role"}');
SET SESSION AUTHORIZATION "creator_role";
\set QUIET off
CREATE TABLE test4(id INT);
INSERT INTO test4 VALUES(1);
CREATE TABLE test4_tablesas AS SELECT * FROM test4;
CREATE VIEW test4_view AS SELECT * FROM test4;
CREATE MATERIALIZED VIEW test4_mview AS SELECT * FROM test4;
SELECT * INTO test4_selectinto FROM test4;
CREATE FUNCTION test4_function() RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE PLPGSQL;
SELECT * FROM test4;
SELECT * FROM test4_tablesas;
SELECT * FROM test4_view;
SELECT * FROM test4_mview;
SELECT * FROM test4_selectinto;
SELECT test4_function();
SELECT 'denied_function4', catch_permission_error($$DROP FUNCTION test4_function();$$);
\set QUIET on
SET SESSION AUTHORIZATION "ownership_role";
\set QUIET off
SELECT * FROM test4;
SELECT * FROM test4_tablesas;
SELECT * FROM test4_view;
SELECT * FROM test4_mview;
SELECT * FROM test4_selectinto;
SELECT test4_function();
-- Ownership role drops the tables
DROP TABLE test4_tablesas;
DROP VIEW test4_view;
DROP MATERIALIZED VIEW test4_mview;
DROP TABLE test4_selectinto;
DROP TABLE test4;
DROP FUNCTION test4_function();
-- Cleanup
\set QUIET on
SET SESSION AUTHORIZATION postgres;
SELECT CDB_DisableOAuthReassignTablesTrigger();
DROP ROLE "ownership_role";
REVOKE ALL ON SCHEMA cartodb FROM "creator_role";
DROP ROLE "creator_role";
DELETE FROM cdb_conf WHERE key = 'api_keys_creator_role';
DROP FUNCTION catch_permission_error(text);
\set QUIET off

113
test/CDB_OAuth_expect Normal file
View File

@@ -0,0 +1,113 @@
CREATE TABLE
INSERT 0 1
SELECT 1
CREATE VIEW
SELECT 1
SELECT 1
CREATE FUNCTION
1
1
1
1
1
1
DROP FUNCTION
denied_table|t
denied_tableas|t
denied_view|t
denied_mview|t
denied_selectinto|t
1
denied_function|t
DROP TABLE
DROP VIEW
DROP MATERIALIZED VIEW
DROP TABLE
DROP TABLE
DROP FUNCTION
NOTICE: event trigger "oauth_reassign_tables_trigger" does not exist, skipping
CREATE TABLE
INSERT 0 1
SELECT 1
CREATE VIEW
SELECT 1
SELECT 1
CREATE FUNCTION
1
1
1
1
1
1
DROP FUNCTION
denied_table2|t
denied_tableas2|t
denied_view2|t
denied_mview2|t
denied_selectinto2|t
1
denied_function2|t
DROP TABLE
DROP VIEW
DROP MATERIALIZED VIEW
DROP TABLE
DROP TABLE
DROP FUNCTION
CREATE TABLE
INSERT 0 1
SELECT 1
CREATE VIEW
SELECT 1
SELECT 1
CREATE FUNCTION
1
1
1
1
1
1
DROP FUNCTION
denied_table3|t
denied_tableas3|t
denied_view3|t
denied_mview3|t
denied_selectinto3|t
1
denied_function3|t
DROP TABLE
DROP VIEW
DROP MATERIALIZED VIEW
DROP TABLE
DROP TABLE
DROP FUNCTION
CREATE TABLE
INSERT 0 1
SELECT 1
CREATE VIEW
SELECT 1
SELECT 1
CREATE FUNCTION
1
1
1
1
1
1
denied_function4|t
1
1
1
1
1
1
DROP TABLE
DROP VIEW
DROP MATERIALIZED VIEW
DROP TABLE
DROP TABLE
DROP FUNCTION

View File

@@ -1,30 +1,48 @@
set client_min_messages to error;
\set VERBOSITY TERSE
-- See the dice
SELECT setseed(0.5);
-- Runs a query and returns whether an error was thrown
-- Useful when the error message depends on the execution plan or db settings
-- The error message outputs the extra quota, and this might depend on the database setup and version
CREATE OR REPLACE FUNCTION catch_error(query text)
RETURNS bool
AS $$
BEGIN
EXECUTE query;
RETURN FALSE;
EXCEPTION
WHEN OTHERS THEN
RETURN TRUE;
END
$$ LANGUAGE 'plpgsql';
CREATE TABLE big(a int);
-- Try the legacy interface
-- See https://github.com/CartoDB/cartodb-postgresql/issues/13
CREATE TRIGGER test_quota BEFORE UPDATE OR INSERT ON big
EXECUTE PROCEDURE CDB_CheckQuota(1, 1, 'public');
EXECUTE PROCEDURE cartodb.CDB_CheckQuota(2, 1, 'public');
INSERT INTO big VALUES (1); -- allowed, check runs before
INSERT INTO big VALUES (2); -- disallowed, quota exceeds before
SELECT CDB_SetUserQuotaInBytes(0);
SELECT CDB_CartodbfyTable('big');
SELECT 'excess1', catch_error($$INSERT INTO big VALUES (2); $$); -- disallowed, quota exceeds before
SELECT cartodb.CDB_SetUserQuotaInBytes(0);
SELECT cartodb.CDB_CartodbfyTable('big');
-- Creating the trigger should fail as it was created by CDB_CartodbfyTable
CREATE TRIGGER test_quota BEFORE UPDATE OR INSERT ON big
EXECUTE PROCEDURE cartodb.CDB_CheckQuota(2, 1, 'public');
-- Drop the trigger and recreate it forcing a 100% checks
DROP TRIGGER test_quota ON big;
CREATE TRIGGER test_quota BEFORE UPDATE OR INSERT ON big
EXECUTE PROCEDURE cartodb.CDB_CheckQuota(2, 1, 'public');
INSERT INTO big SELECT generate_series(2049,4096);
INSERT INTO big SELECT generate_series(4097,6144);
INSERT INTO big SELECT generate_series(6145,8192);
-- Test for #108: https://github.com/CartoDB/cartodb-postgresql/issues/108
SELECT CDB_UserDataSize();
SELECT cartodb._CDB_total_relation_size('public', 'big');
SELECT cartodb.CDB_UserDataSize() < 500000 AND cartodb.CDB_UserDataSize() > 0;
SELECT cartodb._CDB_total_relation_size('public', 'big') < 1000000;
SELECT cartodb._CDB_total_relation_size('public', 'nonexistent_table_name');
-- END Test for #108
SELECT setseed(0.9);
SELECT CDB_SetUserQuotaInBytes(2);
INSERT INTO big VALUES (8193);
SELECT CDB_SetUserQuotaInBytes(0);
SELECT cartodb.CDB_SetUserQuotaInBytes(2);
SELECT 'excess2', catch_error($$INSERT INTO big VALUES (8193);$$);
SELECT cartodb.CDB_SetUserQuotaInBytes(0);
INSERT INTO big VALUES (8194);
DROP TABLE big;
@@ -32,16 +50,20 @@ DROP TABLE big;
--analysis tables should be excluded from quota:
CREATE TABLE big(a int);
CREATE TRIGGER test_quota BEFORE UPDATE OR INSERT ON big
EXECUTE PROCEDURE CDB_CheckQuota(1, 1, 'public');
SELECT CDB_SetUserQuotaInBytes(1);
EXECUTE PROCEDURE cartodb.CDB_CheckQuota(2, 1, 'public');
SELECT cartodb.CDB_SetUserQuotaInBytes(1);
CREATE TABLE analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d4(id int);
INSERT INTO analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d4(id) VALUES (1),(2),(3),(4),(5);
INSERT INTO big VALUES (1); -- allowed, check runs before
DROP TABLE analysis_2f13a3dbd7_41bd92976fc6dd97072afe4ee450054f4c0715d4;
INSERT INTO big VALUES (2); -- disallowed, quota exceeds before
SELECT 'excess3', catch_error($$INSERT INTO big VALUES (3);$$); -- disallowed, quota exceeds before
DROP TABLE big;
SELECT CDB_SetUserQuotaInBytes(0);
CREATE SCHEMA "complex-name%_with'quotes""";
SELECT CDB_UserDataSize('"complex-name%_with''quotes"');
DROP SCHEMA "complex-name%_with'quotes""";
set client_min_messages to NOTICE;
DROP FUNCTION catch_error(text);
DROP FUNCTION _CDB_UserQuotaInBytes();

View File

@@ -1,20 +1,22 @@
SET
CREATE FUNCTION
CREATE TABLE
CREATE TRIGGER
INSERT 0 1
ERROR: Quota exceeded by 3.9990234375KB
excess1|t
0
big
ERROR: trigger "test_quota" for relation "big" already exists
DROP TRIGGER
CREATE TRIGGER
INSERT 0 2048
INSERT 0 2048
INSERT 0 2048
454656
909312
t
t
0
2
ERROR: Quota exceeded by 443.998046875KB
excess2|t
0
INSERT 0 1
DROP TABLE
@@ -25,8 +27,12 @@ CREATE TABLE
INSERT 0 5
INSERT 0 1
DROP TABLE
ERROR: Quota exceeded by 3.9990234375KB
excess3|t
DROP TABLE
0
CREATE SCHEMA
0
DROP SCHEMA
SET
DROP FUNCTION
DROP FUNCTION

View File

@@ -0,0 +1,94 @@
-- Setup: create and populate a table to test the syncs
\set QUIET on
BEGIN;
SET client_min_messages TO error;
CREATE TABLE test_sync_source (
cartodb_id bigint,
lat double precision,
lon double precision,
name text
);
INSERT INTO test_sync_source VALUES
(1, 1.0, 1.0, 'foo'),
(2, 2.0, 2.0, 'bar'),
(3, 3.0, 3.0, 'patata'),
(4, 4.0, 4.0, 'melon');
SET client_min_messages TO notice;
\set QUIET off
\echo 'First table sync: it should be simply just copied to the destination'
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest');
\echo 'Next table sync: there shall be no changes'
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest');
\echo 'Remove a row from the source and check it is deleted from the dest table'
DELETE FROM test_sync_source WHERE cartodb_id = 3;
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest');
\echo 'Insert a new row and check that it is inserted in the dest table'
INSERT INTO test_sync_source VALUES (5, 5.0, 5.0, 'sandia');
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest');
\echo 'Modify row and check that it is modified in the dest table'
UPDATE test_sync_source SET name = 'cantaloupe' WHERE cartodb_id = 4;
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest');
\echo 'Sanity check: the end result is the same source table'
SELECT * FROM test_sync_source ORDER BY cartodb_id;
SELECT * FROM test_sync_dest ORDER BY cartodb_id;
\echo 'It shall exclude geom columns if instructed to do so'
\set QUIET on
SET client_min_messages TO error;
SELECT cartodb.CDB_SetUserQuotaInBytes(0); -- Set user quota to infinite
SELECT cartodb.CDB_CartodbfyTable('test_sync_source');
SELECT cartodb.CDB_CartodbfyTable('test_sync_dest');
UPDATE test_sync_dest SET the_geom = cartodb.CDB_LatLng(lat, lon); -- A "gecoding"
\set QUIET off
SET client_min_messages TO notice;
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest', '{the_geom, the_geom_webmercator}');
SELECT * FROM test_sync_source ORDER BY cartodb_id;
SELECT cartodb_id, the_geom, lat, lon, name FROM test_sync_dest ORDER BY cartodb_id;
\echo 'It will work with schemas that need quoting'
\set QUIET on
SET client_min_messages TO error;
CREATE SCHEMA "sch-ema";
CREATE TABLE "test_sync_source2" AS SELECT * FROM test_sync_source;
\set QUIET off
SELECT cartodb.CDB_SyncTable('test_sync_source2', 'sch-ema', 'test_sync_dest');
INSERT INTO test_sync_source2(cartodb_id, lat, lon, name) VALUES (6, 6.0, 6.0, 'papaya');
DELETE FROM test_sync_source2 WHERE cartodb_id = 4;
UPDATE test_sync_source2 SET lat = 2.5 WHERE cartodb_id = 2;
SET client_min_messages TO notice;
SELECT cartodb.CDB_SyncTable('test_sync_source2', 'sch-ema', 'test_sync_dest');
\echo 'It will work with table names that need quoting'
\set QUIET on
SET client_min_messages TO error;
CREATE TABLE "test-sync-source" AS SELECT * FROM test_sync_source;
\set QUIET off
SELECT cartodb.CDB_SyncTable('test-sync-source', 'public', 'test-sync-dest');
INSERT INTO "test-sync-source"(cartodb_id, lat, lon, name) VALUES (6, 6.0, 6.0, 'papaya');
DELETE FROM "test-sync-source" WHERE cartodb_id = 4;
UPDATE "test-sync-source" SET lat = 2.5 WHERE cartodb_id = 2;
SET client_min_messages TO notice;
SELECT cartodb.CDB_SyncTable('test-sync-source', 'public', 'test-sync-dest');
\echo 'It will work with column names that need quoting'
\set QUIET on
SET client_min_messages TO error;
ALTER TABLE test_sync_source ADD COLUMN "a-column" int;
\set QUIET off
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest2');
INSERT INTO test_sync_source(cartodb_id, lat, lon, name) VALUES (6, 6.0, 6.0, 'papaya');
DELETE FROM test_sync_source WHERE cartodb_id = 4;
UPDATE test_sync_source SET lat = 2.5 WHERE cartodb_id = 2;
SET client_min_messages TO notice;
SELECT cartodb.CDB_SyncTable('test_sync_source', 'public', 'test_sync_dest2');
-- Cleanup
ROLLBACK;

View File

@@ -0,0 +1,94 @@
First table sync: it should be simply just copied to the destination
NOTICE: INSERTED 4 row(s)
Next table sync: there shall be no changes
NOTICE: relation "test_sync_dest" already exists, skipping
NOTICE: DELETED 0 row(s)
NOTICE: INSERTED 0 row(s)
NOTICE: MODIFIED 0 row(s)
Remove a row from the source and check it is deleted from the dest table
DELETE 1
NOTICE: relation "test_sync_dest" already exists, skipping
NOTICE: DELETED 1 row(s)
NOTICE: INSERTED 0 row(s)
NOTICE: MODIFIED 0 row(s)
Insert a new row and check that it is inserted in the dest table
INSERT 0 1
NOTICE: relation "test_sync_dest" already exists, skipping
NOTICE: DELETED 0 row(s)
NOTICE: INSERTED 1 row(s)
NOTICE: MODIFIED 0 row(s)
Modify row and check that it is modified in the dest table
UPDATE 1
NOTICE: relation "test_sync_dest" already exists, skipping
NOTICE: DELETED 0 row(s)
NOTICE: INSERTED 0 row(s)
NOTICE: MODIFIED 1 row(s)
Sanity check: the end result is the same source table
1|1|1|foo
2|2|2|bar
4|4|4|cantaloupe
5|5|5|sandia
1|1|1|foo
2|2|2|bar
4|4|4|cantaloupe
5|5|5|sandia
It shall exclude geom columns if instructed to do so
0
test_sync_source
test_sync_dest
SET
NOTICE: relation "test_sync_dest" already exists, skipping
NOTICE: cdb_invalidate_varnish(public.test_sync_dest) called
NOTICE: DELETED 0 row(s)
NOTICE: cdb_invalidate_varnish(public.test_sync_dest) called
NOTICE: INSERTED 0 row(s)
NOTICE: cdb_invalidate_varnish(public.test_sync_dest) called
NOTICE: MODIFIED 0 row(s)
1|||1|1|foo
2|||2|2|bar
4|||4|4|cantaloupe
5|||5|5|sandia
1|0101000020E6100000000000000000F03F000000000000F03F|1|1|foo
2|0101000020E610000000000000000000400000000000000040|2|2|bar
4|0101000020E610000000000000000010400000000000001040|4|4|cantaloupe
5|0101000020E610000000000000000014400000000000001440|5|5|sandia
It will work with schemas that need quoting
INSERT 0 1
DELETE 1
UPDATE 1
SET
NOTICE: relation "test_sync_dest" already exists, skipping
NOTICE: DELETED 1 row(s)
NOTICE: INSERTED 1 row(s)
NOTICE: MODIFIED 1 row(s)
It will work with table names that need quoting
INSERT 0 1
DELETE 1
UPDATE 1
SET
NOTICE: relation "test-sync-dest" already exists, skipping
NOTICE: DELETED 1 row(s)
NOTICE: INSERTED 1 row(s)
NOTICE: MODIFIED 1 row(s)
It will work with column names that need quoting
INSERT 0 1
DELETE 1
UPDATE 1
SET
NOTICE: relation "test_sync_dest2" already exists, skipping
NOTICE: DELETED 1 row(s)
NOTICE: INSERTED 1 row(s)
NOTICE: MODIFIED 1 row(s)
ROLLBACK

View File

@@ -1,19 +1,32 @@
CREATE ROLE publicuser LOGIN;
SET client_min_messages TO ERROR;
DO
$do$
BEGIN
IF NOT EXISTS (
SELECT *
FROM pg_catalog.pg_user
WHERE usename = 'publicuser') THEN
CREATE ROLE publicuser LOGIN;
END IF;
END
$do$;
SET client_min_messages TO NOTICE;
CREATE TABLE pub(a int);
CREATE TABLE prv(a int);
GRANT SELECT ON TABLE pub TO publicuser;
REVOKE SELECT ON TABLE prv FROM publicuser;
SELECT CDB_UserTables() ORDER BY 1;
SELECT 'all',CDB_UserTables('all') ORDER BY 2;
SELECT 'public',CDB_UserTables('public') ORDER BY 2;
SELECT 'private',CDB_UserTables('private') ORDER BY 2;
SELECT '--unsupported--',CDB_UserTables('--unsupported--') ORDER BY 2;
SELECT cartodb.CDB_UserTables() ORDER BY 1;
SELECT 'all', cartodb.CDB_UserTables('all') ORDER BY 2;
SELECT 'public', cartodb.CDB_UserTables('public') ORDER BY 2;
SELECT 'private', cartodb.CDB_UserTables('private') ORDER BY 2;
SELECT '--unsupported--', cartodb.CDB_UserTables('--unsupported--') ORDER BY 2;
-- now tests with public user
\c contrib_regression publicuser
SELECT 'all_publicuser',CDB_UserTables('all') ORDER BY 2;
SELECT 'public_publicuser',CDB_UserTables('public') ORDER BY 2;
SELECT 'private_publicuser',CDB_UserTables('private') ORDER BY 2;
SELECT 'all_publicuser', cartodb.CDB_UserTables('all') ORDER BY 2;
SELECT 'public_publicuser', cartodb.CDB_UserTables('public') ORDER BY 2;
SELECT 'private_publicuser', cartodb.CDB_UserTables('private') ORDER BY 2;
\c contrib_regression postgres
DROP TABLE pub;
DROP TABLE prv;
DROP ROLE publicuser;

View File

@@ -1,4 +1,6 @@
CREATE ROLE
SET
DO
SET
CREATE TABLE
CREATE TABLE
GRANT
@@ -15,4 +17,3 @@ public_publicuser|pub
You are now connected to database "contrib_regression" as user "postgres".
DROP TABLE
DROP TABLE
DROP ROLE

View File

@@ -19,4 +19,5 @@ SET SESSION AUTHORIZATION postgres;
REVOKE USAGE ON SCHEMA cartodb FROM fulano;
REVOKE EXECUTE ON FUNCTION CDB_Username() FROM fulano;
DROP ROLE fulano;
DELETE FROM cdb_conf WHERE key = 'api_keys_fulano';
\set QUIET off

View File

@@ -1,4 +1,4 @@
postgres
@@PGUSER@@
fulano
fulanito

View File

@@ -6,6 +6,7 @@ Example, to add a test for CDB_Something function, you'd add:
- CDB_SomethingTest.sql
- CDB_SomethingTest_expect
To easy the generation of the expected file you can initially omit it,
then run "make -C .. installcheck" from the top-level dir and copy
../results/test/CDB_SomethingTest.out to CDB_SomethingTest_expect chopping

View File

@@ -13,11 +13,14 @@ DATABASE=test_extension
CMD='echo psql'
CMD=psql
SED=sed
PG_PARALLEL=$(pg_config --version | awk '{$2*=1000; if ($2 >= 9600) print 1; else print 0;}' 2> /dev/null || echo 0)
OK=0
PARTIALOK=0
function reset_default_database() {
DATABASE=test_extension
}
function set_failed() {
OK=1
PARTIALOK=1
@@ -28,30 +31,6 @@ function clear_partial_result() {
PARTIALOK=0
}
function load_sql_file() {
if [[ $PG_PARALLEL -eq 0 ]]
then
tmp_file=/tmp/$(basename $1)_no_parallel
${SED} $1 -e 's/PARALLEL \= [A-Z]*/''/g' -e 's/PARALLEL [A-Z]*/''/g' > $tmp_file
${CMD} -d ${DATABASE} -f $tmp_file
rm $tmp_file
else
${CMD} -d ${DATABASE} -f $1
fi
}
function load_sql_file_schema() {
if [[ $PG_PARALLEL -eq 0 ]]
then
tmp_file=/tmp/$(basename $1)_no_parallel
${SED} $1 -e 's/PARALLEL \= [A-Z]*/''/g' -e 's/PARALLEL [A-Z]*/''/g' > $tmp_file
PGOPTIONS="$PGOPTIONS --search_path=\"$2\"" ${CMD} -d ${DATABASE} -f $tmp_file
rm $tmp_file
else
PGOPTIONS="$PGOPTIONS --search_path=\"$2\"" ${CMD} -d ${DATABASE} -f $1
fi
}
function sql() {
local ROLE
@@ -65,10 +44,10 @@ function sql() {
fi
if [ -n "${ROLE}" ]; then
log_debug "Executing query '${QUERY}' as ${ROLE}"
log_debug "Executing query '${QUERY}' as '${ROLE}' in '${DATABASE}'"
RESULT=`${CMD} -U "${ROLE}" ${DATABASE} -c "${QUERY}" -A -t`
else
log_debug "Executing query '${QUERY}'"
log_debug "Executing query '${QUERY}' in '${DATABASE}'"
RESULT=`${CMD} ${DATABASE} -c "${QUERY}" -A -t`
fi
CODERESULT=$?
@@ -143,12 +122,15 @@ function create_role_and_schema() {
sql "CREATE ROLE ${ROLE} LOGIN;"
sql "GRANT CONNECT ON DATABASE \"${DATABASE}\" TO ${ROLE};"
sql "CREATE SCHEMA ${ROLE} AUTHORIZATION ${ROLE};"
sql "GRANT USAGE ON SCHEMA cartodb TO ${ROLE};"
sql "SELECT cartodb.CDB_Organization_Create_Member('${ROLE}');"
sql "ALTER ROLE ${ROLE} SET search_path TO ${ROLE},cartodb,public;"
}
function drop_role_and_schema() {
local ROLE=$1
sql "REVOKE USAGE ON SCHEMA cartodb FROM ${ROLE};"
sql "DROP SCHEMA \"${ROLE}\" CASCADE;"
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM \"${ROLE}\";"
sql "DROP ROLE \"${ROLE}\";"
@@ -200,21 +182,17 @@ function drop_raster_table() {
function setup_database() {
${CMD} -c "CREATE DATABASE ${DATABASE}"
sql "CREATE SCHEMA cartodb;"
sql "GRANT USAGE ON SCHEMA cartodb TO public;"
sql "CREATE EXTENSION postgis;"
sql "CREATE EXTENSION plpythonu;"
log_info "########################### BOOTSTRAP ###########################"
load_sql_file scripts-available/CDB_Organizations.sql
load_sql_file scripts-available/CDB_OverviewsSupport.sql
load_sql_file scripts-available/CDB_AnalysisSupport.sql
load_sql_file_schema scripts-available/CDB_Quota.sql cartodb
load_sql_file_schema scripts-available/CDB_TableMetadata.sql cartodb
load_sql_file_schema scripts-available/CDB_ColumnNames.sql cartodb
load_sql_file_schema scripts-available/CDB_ColumnType.sql cartodb
load_sql_file_schema scripts-available/CDB_AnalysisCatalog.sql cartodb
sql postgres "DO
\$\$
BEGIN
IF substring(postgis_lib_version() FROM 1 FOR 1) = '3' THEN
CREATE EXTENSION postgis_raster;
END IF;
END
\$\$;"
sql "CREATE EXTENSION cartodb CASCADE;"
${CMD} -c "ALTER DATABASE ${DATABASE} SET search_path = public, cartodb;"
}
function setup() {
@@ -238,6 +216,8 @@ function tear_down_database() {
${CMD} -c "DROP DATABASE ${DATABASE}"
}
function tear_down() {
reset_default_database
log_info "########################### USER TEAR DOWN ###########################"
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2');"
sql cdb_testmember_2 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_2', 'bar', 'cdb_testmember_1');"
@@ -258,7 +238,6 @@ function tear_down() {
sql 'DROP ROLE cdb_testmember_2;'
tear_down_database
DATABASE=postgres sql postgres 'DROP ROLE IF EXISTS publicuser';
}
@@ -277,6 +256,7 @@ function run_tests() {
else
TESTS=`cat $0 | perl -n -e'/function (test.*)\(\)/ && print "$1\n"'`
fi
setup
for t in ${TESTS}
do
echo "####################################################################"
@@ -284,15 +264,15 @@ function run_tests() {
echo "# Running: ${t}"
echo "#"
echo "####################################################################"
clear_partial_result
setup
eval ${t}
if [[ ${PARTIALOK} -ne 0 ]]
then
FAILED_TESTS+=(${t})
fi
tear_down
done
tear_down
if [[ ${OK} -ne 0 ]]
then
echo
@@ -333,20 +313,20 @@ function test_quota_for_each_user() {
}
function test_cdb_tablemetadatatouch() {
sql "CREATE TABLE touch_example (a int)"
sql postgres "CREATE TABLE touch_example (a int)"
sql postgres "SELECT updated_at FROM CDB_TableMetadata WHERE tabname = 'touch_example'::regclass;" should ''
sql "SELECT CDB_TableMetadataTouch('touch_example');"
sql postgres "SELECT CDB_TableMetadataTouch('touch_example');"
sql postgres "SELECT updated_at FROM CDB_TableMetadata WHERE tabname = 'touch_example'::regclass;" should-not ''
# Another call doesn't fail
sql "SELECT CDB_TableMetadataTouch('touch_example');"
sql postgres "SELECT CDB_TableMetadataTouch('touch_example');"
sql postgres "SELECT updated_at FROM CDB_TableMetadata WHERE tabname = 'touch_example'::regclass;" should-not ''
# Works with qualified tables
sql "SELECT CDB_TableMetadataTouch('public.touch_example');"
sql "SELECT CDB_TableMetadataTouch('public.\"touch_example\"');"
sql "SELECT CDB_TableMetadataTouch('\"public\".touch_example');"
sql "SELECT CDB_TableMetadataTouch('\"public\".\"touch_example\"');"
sql postgres "SELECT CDB_TableMetadataTouch('public.touch_example');"
sql postgres "SELECT CDB_TableMetadataTouch('public.\"touch_example\"');"
sql postgres "SELECT CDB_TableMetadataTouch('\"public\".touch_example');"
sql postgres "SELECT CDB_TableMetadataTouch('\"public\".\"touch_example\"');"
# Works with OID
sql postgres "SELECT tabname from CDB_TableMetadata;" should 'touch_example'
@@ -354,25 +334,25 @@ function test_cdb_tablemetadatatouch() {
TABLE_OID=`${CMD} -U postgres ${DATABASE} -c "SELECT attrelid FROM pg_attribute WHERE attrelid = 'touch_example'::regclass limit 1;" -A -t`
# quoted OID works
sql "SELECT CDB_TableMetadataTouch('${TABLE_OID}');"
sql postgres "SELECT CDB_TableMetadataTouch('${TABLE_OID}');"
sql postgres "SELECT tabname from CDB_TableMetadata;" should 'touch_example'
sql postgres "SELECT count(*) from CDB_TableMetadata;" should 1
# non quoted OID works
sql "SELECT CDB_TableMetadataTouch(${TABLE_OID});"
sql postgres "SELECT CDB_TableMetadataTouch(${TABLE_OID});"
sql postgres "SELECT tabname from CDB_TableMetadata;" should 'touch_example'
sql postgres "SELECT count(*) from CDB_TableMetadata;" should 1
#### test tear down
sql 'DROP TABLE touch_example;'
sql postgres 'DROP TABLE touch_example;'
}
function test_cdb_tablemetadatatouch_fails_for_unexistent_table() {
sql postgres "SELECT CDB_TableMetadataTouch('unexistent_example');" fails
sql cdb_testmember_1 "SELECT CDB_TableMetadataTouch('unexistent_example');" fails
}
function test_cdb_tablemetadatatouch_fails_from_user_without_permission() {
sql "CREATE TABLE touch_example (a int);"
sql postgres "CREATE TABLE touch_example (a int);"
sql postgres "SELECT CDB_TableMetadataTouch('touch_example');"
sql cdb_testmember_1 "SELECT CDB_TableMetadataTouch('touch_example');" fails
@@ -381,11 +361,14 @@ function test_cdb_tablemetadatatouch_fails_from_user_without_permission() {
sql cdb_testmember_1 "SELECT CDB_TableMetadataTouch('touch_example');"
sql postgres "REVOKE ALL ON CDB_TableMetadata FROM cdb_testmember_1;"
#### test tear down
sql postgres 'DROP TABLE touch_example;'
}
function test_cdb_tablemetadatatouch_fully_qualifies_names() {
sql postgres "CREATE TABLE touch_invalidations (table_name text);"
sql postgres "create or replace function cartodb.cdb_invalidate_varnish(table_name text) returns void as \$\$ begin insert into touch_invalidations select table_name; end; \$\$ language 'plpgsql';"
sql postgres "create or replace function cartodb.cdb_invalidate_varnish(table_name text) returns void as \$\$ begin insert into public.touch_invalidations select table_name; end; \$\$ language 'plpgsql';"
#default schema
sql "CREATE TABLE touch_example (a int);"
@@ -421,9 +404,9 @@ function test_cdb_tablemetadatatouch_fully_qualifies_names() {
function test_cdb_tablemetadata_text() {
#create and touch tables
sql "CREATE TABLE touch_ex_a (id int);"
sql "CREATE TABLE touch_ex_b (id int);"
sql "CREATE TABLE touch_ex_c (id int);"
sql postgres "CREATE TABLE touch_ex_a (id int);"
sql postgres "CREATE TABLE touch_ex_b (id int);"
sql postgres "CREATE TABLE touch_ex_c (id int);"
sql postgres "SELECT CDB_TableMetadataTouch('touch_ex_a');"
sql postgres "SELECT CDB_TableMetadataTouch('touch_ex_b');"
sql postgres "SELECT CDB_TableMetadataTouch('touch_ex_c');"
@@ -442,9 +425,9 @@ function test_cdb_tablemetadata_text() {
sql postgres "$QUERY" should "t"
#cleanup
sql "DROP TABLE touch_ex_a;"
sql "DROP TABLE touch_ex_b;"
sql "DROP TABLE touch_ex_c;"
sql postgres "DROP TABLE touch_ex_a;"
sql postgres "DROP TABLE touch_ex_b;"
sql postgres "DROP TABLE touch_ex_c;"
}
@@ -483,9 +466,6 @@ function test_cdb_column_type() {
}
function test_cdb_querytables_schema_and_table_names_with_dots() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
sql postgres 'CREATE SCHEMA "foo.bar";'
sql postgres 'CREATE TABLE "foo.bar"."c.a.r.t.o.d.b" (a int);'
sql postgres 'INSERT INTO "foo.bar"."c.a.r.t.o.d.b" values (1);'
@@ -499,9 +479,6 @@ function test_cdb_querytables_schema_and_table_names_with_dots() {
}
function test_cdb_querytables_table_name_with_dots() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
sql postgres 'CREATE TABLE "w.a.d.u.s" (a int);';
sql postgres 'SELECT CDB_QueryTablesText($q$select * from "w.a.d.u.s"$q$);' should '{"public.\"w.a.d.u.s\""}'
@@ -511,9 +488,6 @@ function test_cdb_querytables_table_name_with_dots() {
}
function test_cdb_querytables_happy_cases() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
sql postgres 'CREATE TABLE wadus (a int);';
sql postgres 'CREATE TABLE "FOOBAR" (a int);';
sql postgres 'CREATE SCHEMA foo;'
@@ -535,18 +509,8 @@ function test_cdb_querytables_happy_cases() {
}
function test_foreign_tables() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
load_sql_file scripts-available/CDB_TableMetadata.sql
load_sql_file scripts-available/CDB_Conf.sql
load_sql_file scripts-available/CDB_ForeignTable.sql
DATABASE=fdw_target setup_database
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
load_sql_file scripts-available/CDB_TableMetadata.sql
DATABASE=fdw_target sql postgres "DO
\$\$
BEGIN
@@ -559,6 +523,7 @@ BEGIN
END IF;
END
\$\$;"
DATABASE=fdw_target sql postgres 'CREATE SCHEMA test_fdw;'
DATABASE=fdw_target sql postgres 'CREATE TABLE test_fdw.foo (a int);'
DATABASE=fdw_target sql postgres 'INSERT INTO test_fdw.foo (a) values (42);'
@@ -573,6 +538,8 @@ END
DATABASE=fdw_target sql postgres "SELECT cdb_tablemetadatatouch('test_fdw.foo'::regclass);"
DATABASE=fdw_target sql postgres "SELECT cdb_tablemetadatatouch('test_fdw.foo2'::regclass);"
reset_default_database
# Add PGPORT to conf if it is set
PORT_SPEC=""
if [[ "$PGPORT" != "" ]] ; then
@@ -583,15 +550,9 @@ END
sql postgres "SELECT cartodb._CDB_Setup_FDW('test_fdw')"
sql postgres "SHOW server_version_num"
if [ "$RESULT" -gt 90499 ]
then
sql postgres "SELECT cartodb.CDB_Add_Remote_Table('test_fdw', 'foo')"
sql postgres "SELECT * from test_fdw.foo;"
else
echo "NOTICE: PostgreSQL version is less than 9.5 ($RESULT). Skipping CDB_Add_Remote_Table."
sql postgres "CREATE FOREIGN TABLE test_fdw.foo (a int) SERVER test_fdw OPTIONS (table_name 'foo', schema_name 'test_fdw')"
fi
sql postgres "SELECT cartodb.CDB_Add_Remote_Table('test_fdw', 'foo')"
sql postgres "SELECT * from test_fdw.foo;"
sql postgres "SELECT n.nspname,
c.relname,
@@ -633,14 +594,21 @@ test_extension|public|"local-table-with-dashes"'
sql postgres "SELECT cartodb.CDB_Last_Updated_Time(ARRAY['test_extension.public.\"local-table-with-dashes\"']::text[]) < now()" should 't'
sql postgres "SELECT cartodb.CDB_Last_Updated_Time(ARRAY['test_extension.public.\"local-table-with-dashes\"']::text[]) > (now() - interval '1 minute')" should 't'
# Check CDB_Get_Foreign_Updated_At is robust to unimported CDB_TableMetadata
sql postgres "DROP FOREIGN TABLE IF EXISTS test_fdw.cdb_tablemetadata;"
sql postgres "SELECT cartodb.CDB_Get_Foreign_Updated_At('test_fdw.foo') IS NULL" should 't'
# Teardown
DATABASE=fdw_target sql postgres 'REVOKE USAGE ON SCHEMA test_fdw FROM fdw_user;'
DATABASE=fdw_target sql postgres 'REVOKE SELECT ON test_fdw.foo FROM fdw_user;'
DATABASE=fdw_target sql postgres 'REVOKE SELECT ON test_fdw.foo2 FROM fdw_user;'
DATABASE=fdw_target sql postgres 'REVOKE SELECT ON cdb_tablemetadata_text FROM fdw_user;'
DATABASE=fdw_target sql postgres 'DROP ROLE fdw_user;'
reset_default_database
sql postgres "select pg_terminate_backend(pid) from pg_stat_activity where datname='fdw_target';"
DATABASE=fdw_target tear_down_database
reset_default_database
}
function test_cdb_catalog_basic_node() {

View File

@@ -10,7 +10,6 @@
DATABASE=test_organizations
CMD=psql
SED=sed
PG_PARALLEL=$(pg_config --version | awk '{$2*=1000; if ($2 >= 9600) print 1; else print 0;}' 2> /dev/null || echo 0)
OK=0
PARTIALOK=0
@@ -25,19 +24,6 @@ function clear_partial_result() {
PARTIALOK=0
}
function load_sql_file() {
if [[ $PG_PARALLEL -eq 0 ]]
then
tmp_file=/tmp/$(basename $1)_no_parallel
${SED} $1 -e 's/PARALLEL \= [A-Z]*/''/g' -e 's/PARALLEL [A-Z]*/''/g' > $tmp_file
${CMD} -d ${DATABASE} -f $tmp_file
rm $tmp_file
else
${CMD} -d ${DATABASE} -f $1
fi
}
function sql() {
local ROLE
local QUERY
@@ -146,6 +132,7 @@ function create_role_and_schema() {
sql "GRANT CONNECT ON DATABASE \"${DATABASE}\" TO ${ROLE};"
sql "CREATE SCHEMA ${ROLE} AUTHORIZATION ${ROLE};"
sql "SELECT cartodb.CDB_Organization_Create_Member('${ROLE}')"
sql "ALTER ROLE ${ROLE} SET search_path TO ${ROLE},cartodb,public;"
}
@@ -168,34 +155,46 @@ function create_table() {
sql ${ROLE} "CREATE TABLE ${ROLE}.${TABLENAME} ( a int );"
}
function truncate_table() {
if [[ $# -ne 2 ]]
then
log_error "truncate_table requires two arguments: role and table_name"
exit 1
fi
local ROLE="$1"
local TABLENAME="$2"
sql ${ROLE} "TRUNCATE TABLE ${ROLE}.${TABLENAME};"
}
function setup() {
${CMD} -c "CREATE DATABASE ${DATABASE}"
sql "CREATE SCHEMA cartodb;"
sql "CREATE EXTENSION plpythonu;"
sql "GRANT USAGE ON SCHEMA cartodb TO public;"
${CMD} -c "ALTER DATABASE ${DATABASE} SET search_path = public, cartodb;"
sql "CREATE EXTENSION cartodb CASCADE;"
${CMD} -c "ALTER DATABASE ${DATABASE} SET search_path = public, cartodb;"
log_info "########################### BOOTSTRAP ###########################"
load_sql_file scripts-available/CDB_Organizations.sql
load_sql_file scripts-available/CDB_Conf.sql
load_sql_file scripts-available/CDB_Groups.sql
load_sql_file scripts-available/CDB_Groups_API.sql
log_info "############################# SETUP #############################"
create_role_and_schema cdb_org_admin
sql "SELECT cartodb.CDB_Organization_AddAdmin('cdb_org_admin');"
create_role_and_schema cdb_testmember_1
create_role_and_schema cdb_testmember_2
sql "CREATE ROLE publicuser LOGIN;"
sql postgres "DO
\$\$
BEGIN
IF NOT EXISTS (
SELECT *
FROM pg_catalog.pg_user
WHERE usename = 'publicuser') THEN
CREATE ROLE publicuser LOGIN;
END IF;
END
\$\$;"
sql "GRANT CONNECT ON DATABASE \"${DATABASE}\" TO publicuser;"
create_table cdb_testmember_1 foo
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (1), (2), (3), (4), (5);'
sql cdb_testmember_1 'SELECT * FROM cdb_testmember_1.foo;'
create_table cdb_testmember_2 bar
sql cdb_testmember_2 'INSERT INTO bar VALUES (1), (2), (3), (4), (5);'
sql cdb_testmember_2 'SELECT * FROM cdb_testmember_2.bar;'
sql "SELECT cartodb.CDB_Group_CreateGroup('group_a_tmp')"
sql "SELECT cartodb.CDB_Group_RenameGroup('group_a_tmp', 'group_a')"
@@ -235,7 +234,6 @@ function tear_down() {
sql 'DROP ROLE cdb_testmember_1;'
sql 'DROP ROLE cdb_testmember_2;'
sql 'DROP ROLE publicuser;'
sql 'DROP ROLE cdb_org_admin;'
${CMD} -c "DROP DATABASE ${DATABASE}"
@@ -251,6 +249,8 @@ function run_tests() {
else
TESTS=`cat $0 | perl -n -e'/function (test.*)\(\)/ && print "$1\n"'`
fi
setup
for t in ${TESTS}
do
echo "####################################################################"
@@ -259,15 +259,15 @@ function run_tests() {
echo "#"
echo "####################################################################"
clear_partial_result
setup
log_info "############################# TESTS #############################"
eval ${t}
if [[ ${PARTIALOK} -ne 0 ]]
then
FAILED_TESTS+=(${t})
fi
tear_down
done
tear_down
if [[ ${OK} -ne 0 ]]
then
echo
@@ -289,9 +289,14 @@ function test_member_1_cannot_grant_read_permission_to_other_schema_than_its_one
}
function test_member_1_grants_read_permission_and_member_2_can_read() {
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);'
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_2.bar;' fails
# Cleanup
truncate_table cdb_testmember_1 foo
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
}
function test_member_2_cannot_add_table_to_member_1_schema_after_table_permission_added() {
@@ -300,10 +305,18 @@ function test_member_2_cannot_add_table_to_member_1_schema_after_table_permissio
}
function test_grant_read_permission_between_two_members() {
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);'
sql cdb_testmember_2 'INSERT INTO cdb_testmember_2.bar VALUES (5), (6), (7), (8), (9);'
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_2', 'bar', 'cdb_testmember_1')"
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_2.bar;' should 5
# Cleanup
truncate_table cdb_testmember_1 foo
truncate_table cdb_testmember_2 bar
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_2', 'bar', 'cdb_testmember_1')"
}
function test_member_2_cannot_write_to_member_1_table() {
@@ -317,11 +330,15 @@ function test_member_1_cannot_grant_read_write_permission_to_other_schema_than_i
function test_member_2_can_write_to_member_1_table_after_write_permission_is_added() {
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Write_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);'
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 10
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 10
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'DELETE FROM cdb_testmember_1.foo where a = 9;'
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 9
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 9
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
# Cleanup
truncate_table cdb_testmember_1 foo
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
}
function test_member_2_can_write_to_member_1_table_and_sequence_after_write_permission_is_added() {
@@ -329,13 +346,17 @@ function test_member_2_can_write_to_member_1_table_and_sequence_after_write_perm
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Write_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);'
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 10
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 10
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'DELETE FROM cdb_testmember_1.foo where a = 9;'
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 9
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 9
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_1 "ALTER TABLE cdb_testmember_1.foo DROP cartodb_id;"
# Cleanup
truncate_table cdb_testmember_1 foo
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
}
function test_member_2_can_write_to_member_1_table_with_non_sequence_cartodb_id_after_write_permission_is_added() {
@@ -343,20 +364,28 @@ function test_member_2_can_write_to_member_1_table_with_non_sequence_cartodb_id_
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Write_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);'
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 10
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 10
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'DELETE FROM cdb_testmember_1.foo where a = 9;'
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 9
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 9
sql cdb_testmember_1 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_1 "ALTER TABLE cdb_testmember_1.foo DROP cartodb_id;"
# Cleanup
truncate_table cdb_testmember_1 foo
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
}
function test_member_1_removes_access_and_member_2_can_no_longer_query_the_table() {
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9), (10);'
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 6
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'SELECT * FROM cdb_testmember_1.foo;' fails
# Cleanup
truncate_table cdb_testmember_1 foo
}
function test_member_1_removes_access_and_member_2_can_no_longer_write_to_the_table() {
@@ -364,12 +393,16 @@ function test_member_1_removes_access_and_member_2_can_no_longer_write_to_the_ta
sql cdb_testmember_2 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);'
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);' fails
# Cleanup
truncate_table cdb_testmember_1 foo
}
function test_giving_permissions_to_two_tables_and_removing_from_first_table_should_not_remove_from_second() {
#### test setup
# create an extra table for cdb_testmember_1
create_table cdb_testmember_1 foo_2
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (1), (2), (3), (4);'
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo_2 VALUES (1), (2), (3), (4), (5);'
sql cdb_testmember_1 'SELECT * FROM cdb_testmember_1.foo_2;'
@@ -378,7 +411,7 @@ function test_giving_permissions_to_two_tables_and_removing_from_first_table_sho
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_1', 'foo_2', 'cdb_testmember_2')"
# cdb_testmember_2 has access to both tables
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo_2;' should 5
# cdb_testmember_1 removes access to foo table
@@ -390,57 +423,60 @@ function test_giving_permissions_to_two_tables_and_removing_from_first_table_sho
#### test tear down
truncate_table cdb_testmember_1 foo
sql cdb_testmember_1 'DROP TABLE cdb_testmember_1.foo_2;'
}
function test_cdb_org_member_role_allows_reading_to_all_users_without_explicit_permission() {
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (1), (2), (3), (4);'
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' fails
sql cdb_testmember_1 "SELECT cartodb.CDB_Organization_Add_Table_Organization_Read_Permission('cdb_testmember_1', 'foo');"
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
# Cleanup
sql cdb_testmember_1 "SELECT cartodb.CDB_Organization_Remove_Organization_Access_Permission('cdb_testmember_1', 'foo');"
truncate_table cdb_testmember_1 foo
}
function test_user_can_read_when_it_has_permission_after_organization_permission_is_removed() {
create_role_and_schema cdb_testmember_3
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (1), (2), (3), (4);'
# shares with cdb_testmember_2 and can read but cdb_testmember_3 cannot
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_3 'SELECT count(*) FROM cdb_testmember_1.foo;' fails
# granting to organization allows to read to both: cdb_testmember_2 and cdb_testmember_3
sql cdb_testmember_1 "SELECT cartodb.CDB_Organization_Add_Table_Organization_Read_Permission('cdb_testmember_1', 'foo');"
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_3 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_3 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
# removing access from organization should keep permission on cdb_testmember_2 but drop it to cdb_testmember_3
sql cdb_testmember_1 "SELECT cartodb.CDB_Organization_Remove_Organization_Access_Permission('cdb_testmember_1', 'foo');"
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 5
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' should 4
sql cdb_testmember_3 'SELECT count(*) FROM cdb_testmember_1.foo;' fails
# Cleanup
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
truncate_table cdb_testmember_1 foo
drop_role_and_schema cdb_testmember_3
}
function test_cdb_querytables_returns_schema_and_table_name() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
sql cdb_testmember_1 "select * from CDB_QueryTables('select * from foo');" should "{cdb_testmember_1.foo}"
}
function test_cdb_querytables_works_with_parentheses() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
sql cdb_testmember_1 "select * from CDB_QueryTables('(select * from foo)');" should "{cdb_testmember_1.foo}"
}
function test_cdb_querytables_returns_schema_and_table_name_for_several_schemas() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
sql postgres "select * from CDB_QueryTables('select * from cdb_testmember_1.foo, cdb_testmember_2.bar');" should "{cdb_testmember_1.foo,cdb_testmember_2.bar}"
}
function test_cdb_querytables_does_not_return_functions_as_part_of_the_resultset() {
load_sql_file scripts-available/CDB_QueryStatements.sql
load_sql_file scripts-available/CDB_QueryTables.sql
sql postgres "select * from CDB_QueryTables('select * from cdb_testmember_1.foo, cdb_testmember_2.bar, plainto_tsquery(''foo'')');" should "{cdb_testmember_1.foo,cdb_testmember_2.bar}"
}
@@ -464,10 +500,6 @@ function test_cdb_usertables_should_work_with_orgusers() {
# this is required to enable select from other schema
sql postgres "GRANT USAGE ON SCHEMA cdb_testmember_1 TO publicuser";
# test CDB_UserTables with publicuser
load_sql_file scripts-available/CDB_UserTables.sql
sql publicuser "SELECT count(*) FROM CDB_UserTables('all')" should 1
sql publicuser "SELECT count(*) FROM CDB_UserTables('public')" should 1
sql publicuser "SELECT count(*) FROM CDB_UserTables('private')" should 0
@@ -483,6 +515,7 @@ function test_cdb_usertables_should_work_with_orgusers() {
# test cdb_testmember_2 can select from cdb_testmember_1's public table
sql cdb_testmember_2 "SELECT * FROM cdb_testmember_1.test_perms_pub" should 1
sql postgres 'REVOKE USAGE ON SCHEMA cdb_testmember_1 FROM publicuser;'
sql cdb_testmember_1 "DROP TABLE test_perms_pub"
sql cdb_testmember_1 "DROP TABLE test_perms_priv"
}

View File

@@ -10,6 +10,7 @@ echo "-- Script generated by $0 on `date`" > ${output}
cat ${input} |
grep '^ *CREATE OR REPLACE FUNCTION' |
grep -v ' cartodb\.' | # should only match DDL hooks
grep -v '.*\quit.*' |
sed 's/).*$/)/' |
sed 's/DEFAULT [^ ,)]*//g' |
sed 's/CREATE OR REPLACE FUNCTION /ALTER FUNCTION public./' |
@@ -19,59 +20,4 @@ cat ${input} |
cat >> ${output}
# Upgrade all functions
cat ${input} | grep -v 'duplicated extension$' >> ${output}
# Migrate CDB_TableMetadata
cat >> ${output} <<'EOF'
ALTER TABLE cartodb.CDB_TableMetadata DISABLE TRIGGER ALL;
INSERT INTO cartodb.CDB_TableMetadata SELECT * FROM public.CDB_TableMetadata;
ALTER TABLE cartodb.CDB_TableMetadata ENABLE TRIGGER ALL;
DROP TABLE public.CDB_TableMetadata;
-- Set user quota
-- NOTE: will fail if user quota wasn't set at database level, see
-- http://github.com/CartoDB/cartodb-postgresql/issues/18
DO $$
DECLARE
qmax int8;
BEGIN
BEGIN
qmax := public._CDB_UserQuotaInBytes();
EXCEPTION WHEN undefined_function THEN
RAISE EXCEPTION 'Please set user quota before switching to cartodb extension';
END;
PERFORM cartodb.CDB_SetUserQuotaInBytes(qmax);
DROP FUNCTION public._CDB_UserQuotaInBytes();
END;
$$ LANGUAGE 'plpgsql';
EOF
## Cartodbfy tables with a trigger using 'CDB_CheckQuota' or
## 'CDB_TableMetadata_Trigger' from the 'public' schema
#cat >> ${output} <<'EOF'
#select cartodb.CDB_CartodbfyTable(relname::regclass) from (
# -- names of tables using public.CDB_CheckQuota or
# -- public.CDB_TableMetadata_Trigger in their triggers
# SELECT distinct c.relname
# FROM
# pg_trigger t,
# pg_class c,
# pg_proc p,
# pg_namespace n
# WHERE
# n.nspname = 'public' AND
# p.pronamespace = n.oid AND
# p.proname IN ( 'cdb_checkquota', 'cdb_tablemetadata_trigger' ) AND
# t.tgrelid = c.oid AND
# p.oid = t.tgfoid
#) as foo;
#EOF
## Drop any leftover function from public schema (there should be none)
#cat ${input} |
# grep '^ *CREATE OR REPLACE FUNCTION' |
# grep -v ' cartodb\.' | # should only match DDL hooks
# sed 's/).*$/);/' |
# sed 's/DEFAULT [^ ,)]*//g' |
# sed 's/CREATE OR REPLACE FUNCTION /DROP FUNCTION IF EXISTS public./' |
# cat >> ${output}
cat ${input} | grep -v 'duplicated extension$' | grep -v '\quit$' | grep -v 'pg_extension_config_dump' >> ${output}