Compare commits

...

192 Commits
v0.2.1 ... cdb

Author SHA1 Message Date
javi
513488da0e change random value for quota test again 2015-07-28 09:05:03 +02:00
javi
7921cad4f5 readded removed tests by error 2015-07-27 16:49:59 +02:00
javi santana
10ba742324 Merge pull request #100 from CartoDB/release_0.8.2
Release 0.8.2
2015-07-27 16:19:16 +02:00
javi
788d2a7f10 ready for 0.8.2 2015-07-27 16:12:42 +02:00
javi
189a73ce4d fixed tests 2015-07-27 16:12:15 +02:00
javi santana
d52e05c474 Merge pull request #99 from CartoDB/usertables_fix
added tests for usertables to check private tables are not returned
2015-07-27 16:04:45 +02:00
javi
400248cd5d remove connection permissions to public user 2015-07-27 11:20:55 +02:00
javi
92b5d1f8f4 creating in setup 2015-07-27 11:15:14 +02:00
javi
68fdd9ce33 just add login permissions to public user 2015-07-27 11:07:46 +02:00
javi
53e6b38c32 enabling user again (no sense) 2015-07-27 11:03:21 +02:00
javi
c71faf21e2 do not create publicuser since a previous tests is doing it 2015-07-27 10:57:38 +02:00
javi
12260b9fc3 test cleanup 2015-07-27 10:54:43 +02:00
javi
1b3db28a74 added tests for organization 2015-07-27 10:51:20 +02:00
javi
5d6c2111bf updates tests 2015-07-27 10:37:07 +02:00
javi
df36e83cb5 revoke permissions to list private tables to public user 2015-07-27 10:31:55 +02:00
javi
e05613d5c4 added tests for usertables to check private tables are not returned 2015-07-27 10:25:44 +02:00
Paul Ramsey
565046c3d4 Merge pull request #97 from CartoDB/b0.8
PR for 0.8.1 release
2015-07-08 12:55:02 -07:00
Paul Ramsey
011a007f04 Return test results in deterministic order (closes #96) 2015-07-07 06:13:02 -07:00
Paul Ramsey
08cdb38730 Prepare for 0.8.1 release 2015-07-07 06:07:41 -07:00
Paul Ramsey
734561de4c Use 'publicuser' as public role, not 'public', closes #95.
This is consistent with cartodb behaviour, but not exactly
the same as the contract that the 'public' role guarantees
access to public resources. Possibly a better fix would be
to audit (ug) everything and make sure that it's really
using the public role to mean public, rather than the
'publicuser' connection role. That CDB creates.
2015-07-07 05:49:28 -07:00
Paul Ramsey
8516cbd4c3 Splling fix 2015-07-01 07:03:13 -07:00
Paul Ramsey
509944ea6d Move doc from wiki to repo, closes #85 2015-07-01 07:01:50 -07:00
Rafa de la Torre
ac8203eec4 Merge pull request #93 from CartoDB/fix-upgrade-issue
Fix for upgrade issue from 0.7.4 to 0.8.0
2015-06-30 12:58:23 +02:00
Rafa de la Torre
a6fd829669 Fix for upgrade issue from 0.7.4 to 0.8.0
This fixes the following problem found during testing:
```
ALTER EXTENSION cartodb UPDATE TO '0.8.0';
ERROR:  cannot change return type of existing function
HINT:  Use DROP FUNCTION cdb_usertables(text) first.
```
2015-06-30 12:20:29 +02:00
Rafa de la Torre
0045fb20e8 Update NEWS.md 2015-06-30 11:34:59 +02:00
Raul Ochoa
8190edb461 Merge pull request #83 from CartoDB/64-usertables
Replace CDB_UserTables with something that can handle multi-user accounts
2015-06-30 11:33:32 +02:00
Rafa de la Torre
373e9f5db8 Merge pull request #92 from CartoDB/fix-versioning
Fix versioning by creating a 0.8.0
2015-06-30 11:25:32 +02:00
Rafa de la Torre
6b29c9e67d Fix versioning by creating a 0.8.0
Fix versioning by creating a new major version since it contains new
features. Keep version 0.7.4 that should've never existed and provide an
upgrade path for the new version.
2015-06-30 11:11:24 +02:00
Rafa de la Torre
2b46a2d56f Merge pull request #91 from CartoDB/upgrade-version-0-7-4
Update Makefile and NEWS.md for new version
2015-06-29 12:37:30 +02:00
Rafa de la Torre
371d84ea0c Update Makefile and NEWS.md for new version 2015-06-29 12:09:35 +02:00
Andy Eschbacher
e5897f3dad Merge pull request #87 from CartoDB/categ-distrib
Function deciding criteria for using a category column in a map
2015-06-25 11:50:56 -04:00
Andy Eschbacher
b9fe204007 Merge pull request #81 from CartoDB/equalint
adding equal interval function for consistency
2015-06-25 11:08:24 -04:00
Rafa de la Torre
9b2cff15c5 Merge pull request #88 from CartoDB/86-CDB_QueryTables-fix-long-names
Add a new function CDB_QueryTablesText #86
2015-06-25 16:20:08 +02:00
Andy Eschbacher
13946b4d47 update test output 2015-06-25 08:17:41 -04:00
Andy Eschbacher
97140b17c9 added more flexible output values 2015-06-24 11:03:16 -04:00
Rafa de la Torre
c3eea08f66 Remove ECHO from expectation #86
Remove the `\set ECHO none` from expectation that is automatically
removed by the test harness but still appears in the output when a test
fails.
2015-06-24 16:29:40 +02:00
Rafa de la Torre
22fc962d09 Change expectation #86
Just add CONTEXT lines since they are now added in case of
WARNING/ERROR as a result of having CDB_QueryTables calling
CDB_QueryTablesText.
2015-06-24 16:01:45 +02:00
Rafa de la Torre
ddb6b2c5b5 Return text instead of regclass #86
This way the schema is always returned and backwards compatibility is
kept, should it be needed.
2015-06-24 14:14:00 +02:00
Rafa de la Torre
9a94b3879a Add a new function CDB_QueryTablesRegclass #86
The return values of it can be safely used when len(schema.table_name)
exceeds the 63 char limit of the postgres type `name`.
2015-06-24 11:53:09 +02:00
Andy Eschbacher
d124776c4e simplified assignment 2015-06-23 18:49:59 -04:00
Andy Eschbacher
5941b473ca removed notice 2015-06-23 18:39:15 -04:00
Andy Eschbacher
3ad3038c5e fixed symlink path, other minor items 2015-06-23 18:32:16 -04:00
Andy Eschbacher
c7bb57b405 add symlink 2015-06-23 18:08:32 -04:00
Andy Eschbacher
f8542af57a add tests 2015-06-23 18:07:48 -04:00
Andy Eschbacher
cda6953ea6 initial commit 2015-06-23 16:27:27 -04:00
Andy Eschbacher
189309e1a5 Merge pull request #84 from CartoDB/add-dist-classify
Add distribution classifier
2015-06-12 08:53:42 -04:00
Andy Eschbacher
1d223b77cc changed subfunction name, replaced function with case statement 2015-06-10 10:50:01 -04:00
Andy Eschbacher
6ab1b1d3d0 removed unneeded variables 2015-06-08 18:36:07 -04:00
Andy Eschbacher
c7f4209270 added alias and line 2015-06-08 15:11:58 -04:00
Andy Eschbacher
8e2d86414f updating function 2015-06-08 15:02:39 -04:00
Andy Eschbacher
9cb1fe30d8 adding tests 2015-06-08 15:01:50 -04:00
Andy Eschbacher
424564e324 initial commit 2015-06-08 13:37:27 -04:00
Paul Ramsey
dd3f125339 Fix test expectation 2015-06-01 11:10:06 -07:00
Paul Ramsey
e7ef5e7e8e Clean up after tests
For #64
2015-06-01 10:57:30 -07:00
Paul Ramsey
54973142f6 Handle "unsupported argument" case for CDB_UserTables
in support of #64
2015-06-01 10:37:46 -07:00
Paul Ramsey
ba521461fe Replace CDB_UserTables with new version
fix for #64
2015-06-01 09:47:02 -07:00
Andy Eschbacher
42a617e79c ugh bad filename 2015-05-19 16:03:54 -04:00
Andy Eschbacher
bf4a31842b new result 2015-05-19 15:50:34 -04:00
Andy Eschbacher
a3c8d7bce4 initial commit 2015-05-19 15:28:17 -04:00
Andy Eschbacher
737dc1c1f1 updated formating of test 2015-05-18 12:12:55 -04:00
Andy Eschbacher
d0c85855f5 fixed test expectation value 2015-05-18 12:00:42 -04:00
Andy Eschbacher
ee1df92561 fixed precision in tests 2015-05-14 15:45:57 -04:00
Andy Eschbacher
16d0dc739a added tests improved func 2015-05-14 15:32:58 -04:00
Andy Eschbacher
dcd35fc3d7 Merge branch 'master' into equalint 2015-05-07 17:07:34 -04:00
Andy Eschbacher
2ad3ff547d initial 2015-05-07 15:49:35 -04:00
Andy Eschbacher
b1e1723e75 Merge pull request #80 from CartoDB/sariogonfer-master
updates quantile bins algorithm
2015-05-07 10:55:19 -04:00
Andy Eschbacher
d9e254dbd5 missed updating value 2015-04-28 10:21:34 -04:00
Andy Eschbacher
7d0efa95fb updated test 2015-04-28 09:59:57 -04:00
Andy Eschbacher
1552c03dd4 removed group by; made binning more reliable 2015-04-27 17:59:40 -04:00
sariogonfer
de418ab36d Merge pull request #1 from sariogonfer/sariogonfer-patch-2
Update CDB_QuantileBins.sql
2015-04-27 12:05:23 +02:00
sariogonfer
cbd3c447b6 Update CDB_QuantileBins.sql 2015-04-14 20:45:37 +02:00
Raul Ochoa
fc95566ddd Remove test for unexistent table as there was already one 2015-03-31 16:01:49 +02:00
Raul Ochoa
7f58e1f690 Adds tests for cdb_tablemetadatatouch
- fixes tableoid by using the proper table oid
 - tests quoted and unqouted call with OID
 - tests non existent table to fail
2015-03-31 14:48:45 +02:00
Rafa de la Torre
ca643b2e03 Merge pull request #75 from CartoDB/73-fix-upgrade-of-cdb-stringtodate
73 fix upgrade of cdb stringtodate
2015-03-03 17:27:45 +01:00
Rafa de la Torre
a7a52a23ea fix indentation of Makefile #73 2015-03-03 16:10:56 +00:00
Rafa de la Torre
1c9e5f241f Fix upgrade of CDB_StringToDate function #73 2015-03-03 16:09:57 +00:00
Raul Ochoa
38d32371c8 Adds test to validate CDB_TableMetadataTouch usage with OID 2015-03-03 12:09:25 +01:00
Rafa de la Torre
e80ea92c57 Merge pull request #74 from CartoDB/73-fix-timestamps
Fix conversion of strings to datetimes #73
2015-03-03 11:03:27 +01:00
Rafa de la Torre
61804187c8 Fix conversion of strings to datetimes #73 2015-03-03 09:49:33 +00:00
Kartones
9114d4e463 #revert-size_calc Stop this madness 2015-02-27 11:08:19 +01:00
Kartones
5db9dc6a9f #revert-size_calc 3KB diff at expected result 2015-02-27 10:58:13 +01:00
Kartones
0a3d08edc3 #revert-size_calc 2015-02-27 10:53:14 +01:00
Raul Ochoa
b913defebb Stubs next version 2015-02-19 14:34:32 +01:00
Raul Ochoa
978742f868 Release 0.7.0 2015-02-19 14:33:21 +01:00
Raul Ochoa
5080e3d985 Merge pull request #71 from CartoDB/CDB_ZoomFromScale
Adds CDB_ZoomFromScale function
2015-02-19 14:28:30 +01:00
Raul Ochoa
cb61fd8747 Enable DB_ZoomFromScale 2015-02-19 14:24:10 +01:00
Raul Ochoa
ebcca141bd Adds CDB_ZoomFromScale function. Closes #62 2015-02-19 14:20:35 +01:00
Raul Ochoa
9d679614c8 Stubs next version 2015-02-19 14:11:54 +01:00
Raul Ochoa
bebebc255a Release 0.6.0 2015-02-19 14:10:15 +01:00
Raul Ochoa
11bf7f6fdf Fix release notes for 0.5.3 2015-02-19 14:09:49 +01:00
Raul Ochoa
05617382a9 Merge pull request #70 from CartoDB/remove-grant-select-from-cdb_tablemetadata-to-public
Remove grant select from cdb tablemetadata to public
2015-02-19 14:04:56 +01:00
Raul Ochoa
f16f53ceab Adds test for non-authorized writes to cdb_tablemetadata through CDB_TableMetadataTouch 2015-02-19 14:02:27 +01:00
Raul Ochoa
27aec0d4b4 Adds tests for qualified table names 2015-02-18 18:01:29 +01:00
Raul Ochoa
f18232037d Release notes and version bump 2015-02-18 17:31:04 +01:00
Raul Ochoa
da7b3b7080 Adds CDB_TableMetadataTouch function to be able to upsert updated_at
value in cdb_tablemetadata
2015-02-18 17:08:46 +01:00
Raul Ochoa
38fe98d983 Merge branch 'master' into remove-grant-select-from-cdb_tablemetadata-to-public 2015-02-18 15:43:16 +01:00
Kartones
39e16ebc59 Update NEWS.md 2015-02-17 16:25:16 +01:00
Kartones
dbc0e069c5 #69 Updated version at makefile 2015-02-17 15:47:18 +01:00
Kartones
6c7706672f #69 now using pg_relation_size 2015-02-17 15:40:26 +01:00
javi
d43e141291 updated news 2015-02-17 11:34:49 +01:00
javi santana
0d5a1c3e49 Merge pull request #68 from CartoDB/fix_querytables
try to fix security problem
2015-02-17 11:27:46 +01:00
javi
519ea075d6 fixed reg exp 2015-02-13 17:32:14 +01:00
javi
0223d00a54 fixed security problem 2015-02-12 10:57:12 +01:00
Raul Ochoa
3cf62ecd2e Do not grant select permission to public user
- Fake the behaviour in tests by switching between users
2015-02-10 15:27:42 +01:00
Rafa de la Torre
ee8a031ea0 Merge pull request #67 from CartoDB/faster-quota-check
Optimize CDB_UserDataSize (on behalf of @javisantana) #65
2015-01-29 18:06:46 +01:00
Rafa de la Torre
f0bf8a85a5 Fix Makefile versioning stuff #65 2015-01-29 16:37:59 +00:00
Rafa de la Torre
693b147ef1 Fix corner case (no tables) in CDB_UserDataSize #65 2015-01-29 15:46:08 +00:00
Rafa de la Torre
73232b8802 Increase version number to 0.5.2 #65 2015-01-29 14:02:38 +00:00
Rafa de la Torre
a4e42571cd Add a comment to CDB_UserDataSize (from PR) #65 2015-01-28 17:10:04 +00:00
Rafa de la Torre
bf622ae5a6 Optimize CDB_UserDataSize (on behalf of @javisantana) #65 2015-01-28 16:54:50 +00:00
Kartones
dbb6f42b99 #1368 fixed escapings 2014-12-05 17:30:47 +01:00
Kartones
626b883cfc Reactivating sh specs after stabilization 2014-11-19 10:53:52 +01:00
Kartones
cd9e44b266 #1138 Fixed DDL triggers to properly use new cartodbfy signature, fixed cartodbfy to properly send params to check raster table 2014-11-19 10:00:04 +01:00
Kartones
fd9d79372f #1138 Found issue with DDL triggers 2014-11-18 19:00:33 +01:00
Kartones
61b47617b8 #1138 fixes for specs 2014-11-18 17:35:11 +01:00
Kartones
3b5c1f65cb #1138 Quota changes, raster import cartodbfication and quota spec 2014-11-18 15:24:54 +01:00
Kartones
b7c2336ae0 #1138 not counting raster overviews for quota 2014-11-14 10:34:18 +01:00
Kartones
6f80b52c92 Merge branch 'master' of github.com:CartoDB/cartodb-postgresql 2014-10-21 16:48:51 +02:00
Kartones
dc6ac7f56b Merge tag '0.4.1'
v0.4.1
2014-10-21 16:48:23 +02:00
Kartones
68e132ade5 Merge pull request #56 from CartoDB/CDB-4418
CDB-4418 CDB_CartodbfyTable does not create Primary Key on cartodb_id
2014-10-21 16:44:41 +02:00
Kartones
c9ff282b17 CDB-4418 2014-10-21 16:37:59 +02:00
Kartones
f251e12d35 CDB-4418 2014-10-21 16:19:44 +02:00
Raul Ochoa
5a3b93fd6b Release 0.4.0 2014-08-27 14:34:37 +02:00
Raul Ochoa
eaee6d3d70 Merge pull request #53 from CartoDB/CDB-3504
New versioning mechanism
2014-08-25 17:12:57 +02:00
Raul Ochoa
7840e7c50b Prepares version 0.4.0 with new versioning mechanism 2014-08-25 12:00:45 +02:00
Raul Ochoa
5b0a7bf9ad Adds highlighting 2014-08-21 19:05:15 +02:00
Raul Ochoa
2ef6d5901e Fixes typos 2014-08-21 19:05:04 +02:00
Raul Ochoa
3d37b3646e Renames files to take advantage of markdown 2014-08-21 19:01:19 +02:00
Raul Ochoa
51d48c7629 CDB-3656 Removes revision from the extension version 2014-08-21 18:52:21 +02:00
Raul Ochoa
25a0e5ec68 Stubs next version 2014-08-21 18:41:09 +02:00
Raul Ochoa
daa61a6aa8 Merge pull request #52 from CartoDB/CDB-3483
Adds CDB_Math_Model
2014-08-21 17:48:36 +02:00
javi
723a08e814 added CDB_Math_mode 2014-08-19 18:09:27 +02:00
Raul Ochoa
41a2c7363e Merge tag '0.3.6' into cdb
Release 0.3.6
2014-08-11 16:39:20 +02:00
Raul Ochoa
45d07bc5a8 Release 0.3.6 2014-08-11 16:38:41 +02:00
Raul Ochoa
55615e1a32 Stubs next version 2014-08-11 16:18:02 +02:00
Raul Ochoa
4b397d6bc0 Merge tag '0.3.5'
Release 0.3.5
2014-08-11 16:16:50 +02:00
Raul Ochoa
8349e3c94c Merge tag '0.3.5' into cdb
Release 0.3.5 fixed
2014-08-11 16:14:00 +02:00
Raul Ochoa
a747f16ecc Fixes release dates 2014-08-11 16:13:04 +02:00
Raul Ochoa
93de996acc Fixes 0.3.5 version:
- Version in Makefile
- Upgrade path from 0.3.4
2014-08-11 15:41:53 +02:00
Raul Ochoa
a98b100182 Ignore idea ide based settings 2014-08-11 15:41:33 +02:00
Kartones
72d8cf6210 Merge pull request #46 from CartoDB/CDB-3870
CDB-3870
2014-08-11 15:30:11 +02:00
Kartones
6c57640901 CDB-3870 replaced priority of qmax inside checkquota 2014-08-11 15:26:13 +02:00
Raul Ochoa
a67f324001 Release 0.3.4 version 2014-08-01 18:21:48 +02:00
Raul Ochoa
5bb638b995 Merge pull request #45 from CartoDB/CDB-3743
CDB_QueryTables returns only results for schemas associated to Relation-Names
2014-08-01 18:14:53 +02:00
Raul Ochoa
2637742c2e CDB-3743 Makes CDB_QueryTables to return only results for schemas associated to Relation-Names 2014-08-01 18:06:06 +02:00
Kartones
b75b492490 Merge pull request #44 from CartoDB/CDB-3497
Cdb 3497
2014-07-30 16:01:09 +02:00
Kartones
f9ec3c46ee Merge branch 'master' into CDB-3497
Conflicts:
	Makefile
	NEWS
2014-07-29 14:16:52 +02:00
Kartones
679af1d4a3 CDB-3497 Finished stabilizing specs 2014-07-28 17:53:19 +02:00
Raul Ochoa
a9005dc93c Updates apt-get so it can pick up latest postgres packages 2014-07-28 15:49:43 +02:00
Raul Ochoa
e81a75b716 Make 0.3.0dev version upgradeable 2014-07-28 15:39:19 +02:00
Kartones
6d665ab163 CDB-3497 ongoing spec stabilization 2014-07-28 15:16:19 +02:00
Kartones
e5c8015e17 CDB-3497 Splitted 2014-07-28 13:11:51 +02:00
Kartones
f61d07518d CDB-3497 ongoing splitting 2014-07-28 12:25:42 +02:00
Kartones
b5589fdf09 CDB-3497 2014-07-25 12:19:40 +02:00
Kartones
de2d3f818e CDB-3497 Updating travis.yml to make it work with newer PG version 2014-07-25 10:56:54 +02:00
Kartones
9500010a67 CDB-3497 Partial splitting 2014-07-25 10:52:46 +02:00
Luis Bosque
eff2f8b619 Dummy version. Start using semanting versioning 2014-07-22 18:15:17 +02:00
Raul Ochoa
f1504fb0b1 Make dev flavour upgradeable to current version 2014-07-17 15:43:19 +02:00
Raul Ochoa
13140dbc63 Version 0.3.0 2014-07-16 12:30:19 +02:00
Luis Bosque
348b8871f3 Update NEWS 2014-07-15 16:57:23 +02:00
Raul Ochoa
2bd0b223a6 CDB_SetUserQuotaInBytes returning set value instead of previous value. 2014-07-07 10:36:51 +02:00
Raul Ochoa
5806ac8f8d Force schema so it can calculate the size of the database with the proper schema 2014-07-04 18:50:46 +02:00
Kartones
1c86599f9d CDB-3094 Discovered that EXECUTE FORMAT seems to quote schema names when it needs to 2014-07-04 16:39:19 +02:00
Kartones
3c5251e6c6 CDB-3303 Cartodbfication also supporting optional schemas 2014-07-04 15:55:08 +02:00
Kartones
3d595976f6 CDB-3302 CDB_CheckQuota now also accepts a schema name. Added a convenience method to check if a schema is valid or not 2014-07-04 11:48:53 +02:00
Kartones
021d922d84 CDB-3304 Method cdb_userdatasize now has schema_name param, and added backwards-compatible version without it 2014-07-04 10:57:55 +02:00
Luis Bosque
82f2e7336b Quoted schema references in organization sql 2014-07-03 15:33:36 +02:00
Raul Ochoa
1573a199c8 Merge remote-tracking branch 'origin/delete_permission' into CDB-3094 2014-06-27 01:37:53 +01:00
Raul Ochoa
558be8b693 CDB-3094 DELETE permission for read-write rule 2014-06-27 00:01:25 +01:00
javi
77182321ff added delete permission with write permission for organization 2014-06-26 15:50:56 +02:00
Raul Ochoa
b98419aeb8 Merge remote-tracking branch 'rambo/cdb_querytables_support_schemas' into CDB-3094 2014-06-26 08:12:41 +02:00
javi
da9b9a640f CDB_QueryTables return tables with schema 2014-06-25 19:38:29 +02:00
Raul Ochoa
84174c4f71 CDB-3094 no hardcoded coloring output by creating a new log_warning function 2014-06-25 12:43:46 +02:00
Raul Ochoa
55efde5039 CDB-3094 Makes organization member group role name unique to database by using the database name md5 hash 2014-06-25 12:38:14 +02:00
Raul Ochoa
8624dd1a00 CDB-3094 Improves bash test runner by displaying failing tests after execution 2014-06-25 12:37:09 +02:00
Raul Ochoa
12e90ef6a4 CDB-3094 Adds cdb_org_member role group and functions to handle access to tables through it 2014-06-24 19:56:17 +02:00
Raul Ochoa
625b01ed56 CDB-3094 Removes for now the role groups management as it is not clear if we need them 2014-06-19 20:16:10 +02:00
Raul Ochoa
5e5471c474 CDB-3094 changes signature to allow specifying the schema because it does not have to be the role name.
- fixes tests to match new signature.
- does not revoke access to the schema when revoking access to a table. TODO
2014-06-19 20:16:10 +02:00
Sandro Santilli
d61de906d5 Use "cdb_test" prefix for test member names
User names are global to a cluster, better try to reduce likelyhood
of clashes
2014-06-18 11:57:12 +02:00
Sandro Santilli
91bec69a68 Have travis specify "postgres" as the testsuite running user 2014-06-18 11:54:28 +02:00
Sandro Santilli
50060a2529 Add note about the need to be postgresql superuser when running tests 2014-06-18 11:52:01 +02:00
Sandro Santilli
36dca36927 Do not assume "postgres" user exists 2014-06-18 11:49:56 +02:00
Raul Ochoa
2743b1767b Merge branch 'master' of https://github.com/CartoDB/cartodb-postgresql into CDB-3094 2014-06-17 11:44:34 +02:00
Raul Ochoa
b69b2fbdcf CDB-3094 enables organizations functions 2014-06-17 11:43:38 +02:00
Sandro Santilli
1815096a37 Move in-place upgrade of functions from README to CONTRIBUTING 2014-06-17 10:43:49 +02:00
Sandro Santilli
44d32e4db5 Add CONTRIBUTING file 2014-06-17 10:38:18 +02:00
Raul Ochoa
8dec91de87 Merge branch 'master' of https://github.com/CartoDB/cartodb-postgresql into CDB-3094 2014-06-16 18:56:34 +02:00
Raul Ochoa
fb68f225d5 CDB-3094 no hardcoded database. it also creates the database between tests and the cartodb schema to install the functions 2014-06-16 17:13:13 +02:00
Raul Ochoa
9d7de169fb CDB-3094 use bash command instead of sh 2014-06-16 16:28:28 +02:00
Raul Ochoa
9a55b03d6f CDB-3094 initial function to share tables/schemas between users within the same database 2014-06-16 16:10:53 +02:00
Sandro Santilli
1bb8b85503 Merge branch 'b0.2'
Conflicts:
	Makefile
	NEWS
2014-06-11 19:30:32 +02:00
Sandro Santilli
5a8b9021c8 Prepare for 0.3.0 2014-06-09 13:04:42 +02:00
68 changed files with 2500 additions and 393 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ results/
regression.*
expected/test
sql/test
.idea/*

View File

@@ -4,7 +4,9 @@ addons:
postgresql: 9.3
before_install:
- sudo apt-get update
#- sudo apt-get install -q postgresql-9.3-postgis-2.1
- sudo apt-get update
- sudo apt-get install -q postgresql-server-dev-9.3
- sudo apt-get install -q postgresql-plpython-9.3
# Install schema_triggers
@@ -19,5 +21,5 @@ before_install:
script:
- make
- sudo make install
- PGOPTIONS='-c client_min_messages=NOTICE' make installcheck ||
- PGOPTIONS='-c client_min_messages=NOTICE' PGUSER=postgres make installcheck ||
{ cat regression.diffs; false; }

60
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,60 @@
The development tracker for cartodb-postgresql is on github:
http://github.com/cartodb/cartodb-postgresql/
Bug fixes are best reported as pull requests over there.
Features are best discussed on the mailing list:
https://groups.google.com/d/forum/cartodb
Adding features to the extension
--------------------------------
Extension features are coded in scripts found under the
"scripts-available" directory. A feature can be a single function
or a group of function with a specific scope.
The "scripts-enabled" directory contains symlinks to the scripts
in "scripts-available". Any symlink in that directory is automatically
included in the extension. Numbering can be used to enforce the order
in which those scripts are loaded.
Scripts would be best coded in a way to be usable both for creation
and upgrade of the objects. This means using CREATE OR REPLACE for
the functions, and whatever it takes to check existence of any previous
version of objects in other cases.
When used as an extension (probably always from version 0.2.0 onwards)
all the objects will be installed in a "cartodb" schema. Take this into
account to fully-qualify internal calls to avoid (possibly dangerous)
name clashes.
Every new feature (as well as bugfixes) should come with a test case,
see next section.
Writing testcases
-----------------
Tests reside in the test/ directory.
You can find information about how to write tests in test/README
Testing changes live
--------------------
Testing changes made during development requires upgrading
the extension into your test database.
During development the cartodb extension version doesn't change with
every commit, so testing latest change requires cheating with PostgreSQL
so to enforce re-load of the scripts. To help with cheating, "make install"
also installs migration scripts to go from "V" to "V"next and from "V"next
to "V". Example to upgrade a 0.2.0dev version:
```sql
ALTER EXTENSION cartodb UPDATE TO '0.2.0devnext';
ALTER EXTENSION cartodb UPDATE TO '0.2.0dev';
```
Starting with 0.2.0, the in-place reload can be done with an ad-hoc function:
```sql
SELECT cartodb.cdb_extension_reload();
```

View File

@@ -1,7 +1,7 @@
# cartodb/Makefile
EXTENSION = cartodb
EXTVERSION = 0.2.1
EXTVERSION = 0.8.2
SED = sed
@@ -18,6 +18,30 @@ UPGRADABLE = \
0.1.0 \
0.1.1 \
0.2.0 \
0.2.1 \
0.3.0 \
0.3.0dev \
0.3.1 \
0.3.2 \
0.3.3 \
0.3.4 \
0.3.5 \
0.3.6 \
0.4.0 \
0.4.1 \
0.5.0 \
0.5.1 \
0.5.2 \
0.5.3 \
0.6.0 \
0.7.0 \
0.7.1 \
0.7.2 \
0.7.3 \
0.7.4 \
0.8.0 \
0.8.1 \
$(EXTVERSION)dev \
$(EXTVERSION)next \
$(END)
@@ -27,7 +51,6 @@ UPGRADES = \
$(SED) 's/$$/--$(EXTVERSION).sql/' | \
$(SED) 's/ /--$(EXTVERSION).sql $(EXTENSION)--/g')
REV=$(shell git describe)
GITDIR=$(shell test -d .git && echo '.git' || cat .git | $(SED) 's/^gitdir: //')
DATA_built = \
@@ -69,7 +92,7 @@ $(EXTENSION).control: $(EXTENSION).control.in Makefile
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' $< > $@
cartodb_version.sql: cartodb_version.sql.in Makefile $(GITDIR)/index
$(SED) -e 's/@@VERSION@@/$(EXTVERSION) $(REV)/' $< > $@
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' $< > $@
legacy_regress: $(REGRESS_OLD) Makefile
mkdir -p sql/test/
@@ -78,18 +101,24 @@ legacy_regress: $(REGRESS_OLD) Makefile
for f in $(REGRESS_OLD); do \
tn=`basename $${f} .sql`; \
of=sql/test/$${tn}.sql; \
echo '\\set ECHO off' > $${of}; \
echo '\\set ECHO none' > $${of}; \
echo '\\a' >> $${of}; \
echo '\\t' >> $${of}; \
echo '\\set QUIET off' >> $${of}; \
cat $${f} | \
$(SED) -e 's/public\./cartodb./g' >> $${of}; \
exp=expected/test/$${tn}.out; \
echo '\\set ECHO off' > $${exp}; \
echo '\\set ECHO none' > $${exp}; \
cat test/$${tn}_expect >> $${exp}; \
done
test_organization:
bash test/organization/test.sh
test_extension_new:
bash test/extension/test.sh
legacy_tests: legacy_regress
installcheck: legacy_tests
installcheck: legacy_tests test_extension_new test_organization

48
NEWS
View File

@@ -1,48 +0,0 @@
0.2.1 - 2014-06-11
------------------
Enhancements:
- Do not force re-cartodbfication on CREATE FROM unpackaged
- Drop useless DEFAULT specification in plpgsql variable declarations
- List plpythonu requirement first, to get pg_catalog scanned before public
Bug fixes:
- Do not add unique index on cartodb_id if already a primary key (#38)
0.2.0 - 2014-06-09
------------------
Important changes:
- This release adds dependency on "plpythonu" extension
- Roles are not created anymore, previously private functions
for table information extraction (CDB_UserTables, CDB_TableIndexes,
CDB_ColumnNames, CDB_ColumnType) will now be callable by anyone while
only returning information about tables over which the calling user
has SELECT privilege (#36)
Bug fixes:
- Fix recursive trigger on create table (#32)
- Ensure cartodb_id uses an associated sequence (#33)
- Fully qualify call to cdb_disable_ddl_hooks from cdb_enable_ddl_hooks
- Fully qualify call to CDB_UserDataSize from quota trigger
- Fully qualify call to CDB_TransformToWebmercator from CDB_CartodbfyTable
- Fix potential infinite loop in CDB_CartodbfyTable
- Fix potential infinite loop in CDB_QueryStatements
Enhancements:
- Include revision info in cdb_version() output (#34)
New features:
- Add a cdb_extension_reload() function
0.1.0 - 2014-05-23
------------------
Initial release

149
NEWS.md Normal file
View File

@@ -0,0 +1,149 @@
0.8.2 (2015-07-27)
------------------
* Fix for CDB_UserTables returning wrong listings when publicuser is used
0.8.1 (2015-06-30)
------------------
* Fix for [#95](https://github.com/CartoDB/cartodb-postgresql/issues/95) *cdb_usertables should return public tables when the user is publicuser*
0.8.0 (2015-06-30)
------------------
* Adds new function CDB_QueryTablesText that can deal with "schema.table_name"
longer than 63 chars.
* Adds a set of statistical functions:
- CDB_DistType
- CDB_DistinctMeasure
- CDB_EqualIntervalBins
* Fix for CDB_UserTables returns 0 entries for multiuser accounts [#64](https://github.com/CartoDB/cartodb-postgresql/issues/64)
0.7.4 (2015-06-29)
------------------
Dummy transitional version.
0.7.3 (2015-03-03)
------------------
* Fix upgrade of CDB_StringToDate function
* Add a test for to validate CDB_TableMetadataTouch usage with OID
0.7.2 (2015-03-03)
------------------
* Fix conversion of strings to datetime
0.7.1 (2015-02-27)
------------------
* Revert quota checks to `pg_total_relation_size`
0.7.0 (2015-02-19)
------------------
* Adds CDB_ZoomFromScale function
0.6.0 (2015-02-19)
------------------
* Select permission in CDB_TableMetadata no longer granted to public
* New function to upsert the updated_at in CDB_TableMetadata for a regclass
0.5.3 (2015-02-17)
------------------
* Fixed security problem related with system tables
* Changed quota checks to use `pg_relation_size` instead of `pg_total_relation_size`
0.5.2 (2015-01-29)
------------------
* Improvement: make CDB_UserDataSize functions much faster.
0.5.1 (2014-11-21)
------------------
* Bugfix: Quota check and some organization permissions functions were not properly escaping table name.
0.5.0 (2014-11-03)
------------------
* Support of raster tables for cartodbfication
* Modified quota functions: vector tables stay the same, raster tables count as full size (as have no
the_geom + the_geom_webmercator combo) and raster overviews are not counted
0.4.1 (2014-09-21)
------------------
* Bugfix for Cartodbfication: Set primary key of the table if not already present (e.g. tables created from SQL API)
0.4.0 (2014-08-27)
------------------
Added CDB_Math_Mode function
Changes in versioning: no revision is attached so it no longer uses `git describe` for the version.
0.3.6 (2014-08-11)
------------------
Dummy release to solve some issues with cdb branch/tag
0.3.5 (2014-08-11)
------------------
Inverting priority of CDB_CheckQuota qmax so gies more priority to existing user quota function over parameter value.
0.3.4 (2014-08-01)
------------------
Fixes issue with schemas in CDB_QueryTables
0.3.3 (2014-07-30)
------------------
* Splitting of CartodbfyTable method in subfunctions to be able to call in fragments and evade timeouts on hot zones
0.3.2 (2014-07-28)
------------------
* Make 0.3.0dev version upgradeable
0.3.1 (2014-07-22)
------------------
* Dummy version. We start using semantic versioning
0.3.0 (2014-07-15)
------------------
* Permission management functions
* Adapt functions to use schemas
0.2.1 - 2014-06-11
------------------
Enhancements:
- Do not force re-cartodbfication on CREATE FROM unpackaged
- Drop useless DEFAULT specification in plpgsql variable declarations
- List plpythonu requirement first, to get pg_catalog scanned before public
Bug fixes:
- Do not add unique index on cartodb_id if already a primary key (#38)
0.2.0 - 2014-06-09
------------------
Important changes:
- This release adds dependency on "plpythonu" extension
- Roles are not created anymore, previously private functions
for table information extraction (CDB_UserTables, CDB_TableIndexes,
CDB_ColumnNames, CDB_ColumnType) will now be callable by anyone while
only returning information about tables over which the calling user
has SELECT privilege (#36)
Bug fixes:
- Fix recursive trigger on create table (#32)
- Ensure cartodb_id uses an associated sequence (#33)
- Fully qualify call to cdb_disable_ddl_hooks from cdb_enable_ddl_hooks
- Fully qualify call to CDB_UserDataSize from quota trigger
- Fully qualify call to CDB_TransformToWebmercator from CDB_CartodbfyTable
- Fix potential infinite loop in CDB_CartodbfyTable
- Fix potential infinite loop in CDB_QueryStatements
Enhancements:
- Include revision info in cdb_version() output (#34)
New features:
- Add a cdb_extension_reload() function
0.1.0 - 2014-05-23
------------------
Initial release

View File

@@ -11,7 +11,7 @@ See https://github.com/CartoDB/cartodb/wiki/CartoDB-PostgreSQL-extension
Dependencies
------------
* PostgreSQL 9.3+ (with plpythonu extension)
* PostgreSQL 9.3+ (with plpythonu extension and xml support)
* [PostGIS extension](http://postgis.net)
* [Schema triggers extension]
(https://bitbucket.org/malloclabs/pg_schema_triggers)
@@ -20,17 +20,24 @@ Dependencies
Install
-------
make all install
```sh
make all install
```
Test installation
-----------------
make installcheck
```sh
make installcheck
```
NOTE: if ``test_ddl_triggers`` fails it's likely due to an incomplete
installation of schema_triggers: you need to add ``schema_triggers.so``
to the ``shared_preload_libraries`` setting in postgresql.conf !
NOTE: you need to run the installcheck as a superuser, use PGUSER
env variable if needed, like: PGUSER=postgres make installcheck
Enable database
---------------
@@ -87,19 +94,5 @@ ls `pg_config --sharedir`/extension/cartodb*
```
During development the cartodb extension version doesn't change with
every commit, so testing latest change requires cheating with PostgreSQL
so to enforce re-load of the scripts. To help with cheating, "make install"
also installs migration scripts to go from "V" to "V"next and from "V"next
to "V". Example to upgrade a 0.2.0dev version:
```sql
ALTER EXTENSION cartodb UPDATE TO '0.2.0devnext';
ALTER EXTENSION cartodb UPDATE TO '0.2.0dev';
```
Starting with 0.2.0, the in-place reload can be done with an ad-hoc function:
```sql
SELECT cartodb.cdb_extension_reload();
```
every commit, so testing latest change requires special steps documented
in the CONTRIBUTING document, under "Testing changes live".

14
doc/CDB_ColumnNames.md Normal file
View File

@@ -0,0 +1,14 @@
Retrieve all column names in a particular table
#### Using the function
```sql
SELECT CDB_ColumnNames('table_name')
--- Returns a set of rows with column names
```
#### Arguments
CDB_ColumnNames(table_name)
* **table_name** text

15
doc/CDB_ColumnType.md Normal file
View File

@@ -0,0 +1,15 @@
Returns a column type for any column in a table
#### Using the function
```sql
SELECT CDB_ColumnType('column_name','table_name')
--- Returns a set of rows with column types
```
#### Arguments
CDB_ColumnType(column_name, table_name)
* **column_name** text
* **table_name** text

21
doc/CDB_HeadsTailsBins.md Normal file
View File

@@ -0,0 +1,21 @@
Find the breaks for N categories in a numerical column based on the [Heads/Tails optimization](http://arxiv.org/pdf/1209.2801v1.pdf). Below, Heads/Tails used to color based on the area of the polygons.
![headtails](https://f.cloud.github.com/assets/370259/140655/6eebb918-7228-11e2-89fa-149745f25d34.png)
#### Using the function
We can determine the 7 most optimal breaks in a column of numerical data as follows,
```sql
SELECT CDB_HeadsTailsBins(array_agg(numeric_column), 7) FROM table_name
-- Results in an ordered array like, {7824,23492,52696,233857,666089,1001709,1638094}
-- Each break happens up to, and equal, to a number:
-- (bin1 is less than or equal to 7824, bin2 is less than or equal to 23492, etc.)
```
#### Arguments
CDB_HeadsTailsBins(in_array, breaks)
* **in_array** numeric[]. A NUMERIC array of values.
* **breaks** int. The number of categories you want to create

43
doc/CDB_HexagonGrid.md Normal file
View File

@@ -0,0 +1,43 @@
Fill given extent with an hexagonal coverage
#### Using the function
Create a hexagonal grid from a polygon geometry. For example, take the geometry
```sql
ST_SetSRID(
ST_Envelope(
ST_Collect(
ST_MakePoint(10000000,-10000000),
ST_MakePoint(-10000000,10000000)
)
),
3857)
```
We can create a grid as follows,
```sql
SELECT CDB_HexagonGrid(
ST_SetSRID(
ST_Envelope(
ST_Collect(
ST_MakePoint(10000000,-10000000),
ST_MakePoint(-10000000,10000000)
)
),
3857),
1000000) the_geom_webmercator
```
Which will look something like this,
![grid tile](http://i.imgur.com/4rZXGMb.png)
#### Arguments
CDB_HexagonGrid(ext, side, origin)
* **ext** geometry. Extent to fill. Only hexagons with center point falling inside the extent (or at the lower or leftmost edge) will be emitted. The returned hexagons will have the same SRID as this extent.
* **side** float. Side measure for the hexagon. Maximum diameter will be 2 * side. Measure is in the same projection as **ext**
* **origin** OPTIONAL geometry. Optional origin to allow for exact tiling. If omitted the origin will be 0,0. The parameter is checked for having the same SRID as the extent.

23
doc/CDB_JenksBins.md Normal file
View File

@@ -0,0 +1,23 @@
Find the breaks for N categories in a numerical column based on the [Jenks optimization](http://en.wikipedia.org/wiki/Jenks_natural_breaks_optimization). Below, Jenks used to color based on the area of the polygons.
![Jenks](https://f.cloud.github.com/assets/370259/140093/b64a9382-7210-11e2-81a4-c65cce3c885e.png)
#### Using the function
We can determine the 7 most optimal breaks in a column of numerical data as follows,
```sql
SELECT CDB_JenksBins(array_agg(numeric_column), 7) FROM table_name
-- Results in an ordered array like, {0,73,2568,9408,29411,768230,1638094}
-- Each break happens up to, and equal, to a number:
-- (bin1 is less than or equal to 0, bin2 is less than or equal to 73, etc.)
```
#### Arguments
CDB_JenksBins(in_array, breaks, invert)
* **in_array** numeric[]. A NUMERIC array of values.
* **breaks** int. The number of categories you want to create
* **iterations** OPTIONAL int. The number of iterations used for calculating breaks.
* **invert** OPTIONAL boolean. Flips whether you receive top down breaks or bottom up breaks. Default is top down (so, <=). Bottom up would give you values that define the lower-end start of a bin (so >=).

21
doc/CDB_MakeHexagon.md Normal file
View File

@@ -0,0 +1,21 @@
Return an Hexagon with given center and side (or maximal radius)
#### Using the function
Running the following SQL
```sql
SELECT CDB_MakeHexagon(ST_MakePoint(0,0),10000000)
```
Would give you back a single hexagon geometry,
![hexagon](http://i.imgur.com/6jeGStb.png)
#### Arguments
CDB_MakeHexagon(center, radius)
* **center** geometry
* **radius** float. Radius of hexagon measured in same projection as **center**

21
doc/CDB_QuantileBins.md Normal file
View File

@@ -0,0 +1,21 @@
Find the breaks for N categories in a numerical column based on the [Quantile bins]. Below, the quantile method is used to determine color based on the area of the polygons.
![qunatile](https://f.cloud.github.com/assets/370259/140714/932ed0e6-722b-11e2-9807-ffbd0fddb9ac.png)
#### Using the function
We can determine the 7 most optimal breaks in a column of numerical data as follows,
```sql
SELECT CDB_QuantileBins(array_agg(numeric_column), 7) FROM table_name
-- Results in an ordered array like, {80,2281,7162,17652,39730,91077,1638094}
-- Each break happens up to, and equal, to a number:
-- (bin1 is less than or equal to 80, bin2 is less than or equal to 2281, etc.)
```
#### Arguments
CDB_QuantileBins(in_array, breaks)
* **in_array** numeric[]. A NUMERIC array of values.
* **breaks** int. The number of categories you want to create

46
doc/CDB_RectangleGrid.md Normal file
View File

@@ -0,0 +1,46 @@
Fill given extent with a rectangular coverage
#### Using the function
Create a rectangular grid from a polygon geometry. For example, take the geometry
```sql
ST_SetSRID(
ST_Envelope(
ST_Collect(
ST_MakePoint(10000000,-10000000),
ST_MakePoint(-10000000,10000000)
)
),
3857)
```
We can create a grid as follows,
```sql
SELECT CDB_RectangleGrid(
ST_SetSRID(
ST_Envelope(
ST_Collect(
ST_MakePoint(10000000,-10000000),
ST_MakePoint(-10000000,10000000)
)
),
3857),
1000000,
1000000
) the_geom_webmercator
```
Which will look something like this,
![rect grid](http://i.imgur.com/HuhOJRs.png)
#### Arguments
CDB_RectangleGrid(ext, width, height, origin)
* **ext** geometry. Extent to fill. Only rectangles with center point falling inside the extent (or at the lower or leftmost edge) will be emitted. The returned hexagons will have the same SRID as this extent.
* **width** float. Width of each rectangle. Measure is in the same projection as **ext**
* **height** float. Height of each rectangle. Measure is in the same projection as **ext**
* **origin** OPTIONAL geometry. Optional origin to allow for exact tiling. If omitted the origin will be 0,0. The parameter is checked for having the same SRID as the extent.

View File

@@ -0,0 +1,11 @@
Sets user quota in bytes (superuser only)
#### Using the function
```sql
SELECT CDB_SetUserQuotaInBytes(10485760);
--- Returns the previously set quota.
--- Use 0 to disable quota.
```
REF: https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_Quota.sql

View File

@@ -0,0 +1,44 @@
Function to "safely" transform to webmercator. This function is most useful for rendering custom geometries using the CartoDB tiler. Often, transforming a projection like WGS84 can cause issues with extents beyond what are actually valid in webmercator, this attempts to fix those issues.
#### Using the function
Using a box that is nearly the full globe,
```sql
ST_SetSRID(
ST_Envelope(
ST_Collect(
ST_MakePoint(-180,60),
ST_MakePoint(180,-60)
)
),
4326
)
```
We can then convert it to a renderable webmercator geometry.
```sql
SELECT CDB_TransformToWebmercator(
ST_SetSRID(
ST_Envelope(
ST_Collect(
ST_MakePoint(-10,60),
ST_MakePoint(300,-60)
)
),
4326
)
)
```
Would give you back a single valid rectangle in webmercator. Since a longitude of 300 would convert to an unallowed webmercator coordinate, it gets clipped first. Valid extent is WGS84 (-180, -89, 180, 89)
![valid geom](http://i.imgur.com/EFdXiqt.png)
#### Arguments
CDB_TransformToWebmercator(geom)
* **geom** geometry

11
doc/CDB_UserTables.md Normal file
View File

@@ -0,0 +1,11 @@
List the name of available tables (only the usable ones)
#### Using the function
```sql
--- Returns a row for each table having given permission with the table name
--- Currently accepted permissions are: 'public', 'private' or 'all'
SELECT CDB_UserTables(perms)
```
REF: https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_UserTables.sql

22
doc/CDB_XYZ_Extent.md Normal file
View File

@@ -0,0 +1,22 @@
Determine the spatial extent of a tile based on the tile's XYZ coordinate.
#### Using the function
Take a common tile with coordinates x=3, y=2, z=2,
![2/3/2](https://viz2.cartodb.com/tiles/quantile_breaks/2/3/2.png)
To determine its extent you would run,
```sql
SELECT CDB_XYZ_Extent(3,2,2)
--- Returns a WKB polygon in Webmercator (SRID 3857)
```
#### Arguments
CDB_XYZ_Extent(x,y,z)
* **x** integer
* **y** integer
* **z** integer

20
doc/CDB_XYZ_Resolution.md Normal file
View File

@@ -0,0 +1,20 @@
Return pixel resolution of tiles at a given zoom level
#### Using the function
Take a common tile with zoom, z=2,
![2/3/2](https://viz2.cartodb.com/tiles/quantile_breaks/2/3/2.png)
To determine the resolution of these pixels,
```sql
SELECT CDB_XYZ_Resolution(2)
--- Returns a float, 39135.7587890625
```
#### Arguments
CDB_XYZ_Resolution(z)
* **z** integer

38
doc/CartoDB-PLpgSQL.md Normal file
View File

@@ -0,0 +1,38 @@
INTRODUCTION
============
CartoDB uses a number of custom [PLpgSQL](http://www.postgresql.org/docs/8.3/static/plpgsql.html) functions to perform a few magical things. Those functions are accessible to users on CartoDB as well, so we would like to document what they are and what they do here.
## Spatial functions
[CDB_HexagonGrid](CDB_HexagonGrid) - create hexagonal grid from extent and size
[CDB_MakeHexagon](CDB_MakeHexagon) - make a hexagon with given center and side
[CDB_RectangleGrid](CDB_RectangleGrid) - fill given extent with a rectangular coverage
##### Tile based
[CDB_XYZ_Extent](CDB_XYZ_Extent) - Find the extent of a tile by XYZ
[CDB_XYZ_Resolution](CDB_XYZ_Resolution) - Find the pixel resolution of tiles
[CDB_TransformToWebmercator](CDB_TransformToWebmercator) - Convert a geometry to valid webmercator
## Statistical functions
[CDB_JenksBins](CDB_JenksBins) - Find breaks in an array of numbers using Jenks method
[CDB_HeadsTailsBins](CDB_HeadsTailsBins) - Find breaks in an array of numbers using Heads/Tails method
[CDB_QuantileBins](CDB_QuantileBins) - Find quantile breaks in an array of numbers
## System functions
[CDB_UserTables](CDB_UserTables) - Get a list of all tables in your account
[[CDB_SetUserQuotaInBytes]] - Set maximum user quota in bytes
column names - now returned in JSON response
column types - now returned in JSON response

29
doc/CartoDB-user-table.md Normal file
View File

@@ -0,0 +1,29 @@
A "cartodb" user table is a table with a well-known set of fields and a well-known set of triggers attached on.
The fields are:
- `cartodb_id`, a numerical primary key of serial type
- `created_at`, timestamp with timezone not null default now()
- `updated_at`, timestamp with timezone not null default now()
- `the_geom`, geometry, GiST indexed, constrained (see below)
- `the_geom_webmercator`, geometry, GiST indexed, constrained (see below)
The values of "the_geom" and "the_geom_webmercator" must match these constraints:
- Only POINT, MULTILINE, MULTIPOLYGON types ? Maybe UNCONSTRAINED
- Only 2 dimensions ? Maybe UNCONSTRAINED
- SRID=4326 for the_geom and SRID=3857 for the_geom_webmercator
The triggers are:
- `track_updates` after modifying statement updates cdb_tablemetadata
- `test_quota` before changing statement to forbid if overquota
- `test_quota_per_row` before changing row to forbod if overquota (checked on a probabilistic basis)
- `update_the_geom_webmercator` before insert or update row to maintain the_geom_webmercator
- `update_updated_at_trigger` before update row to maintain updated_at
Some conversions will be attempted to perform upon cartodbfication when certain fields appear:
- `cartodb_id`: If found type TEXT will be attempted to cast
- `created_at`: If found type TEXT will be attempted to cast
- `updated_at`: If found type TEXT will be attempted to cast

48
doc/README.md Normal file
View File

@@ -0,0 +1,48 @@
# Contents
* [CartoDB-user-table](CartoDB-user-table.md)
* [CartoDB-PLpgSQL](CartoDB-PLpgSQL.md)
* [CDB_ColumnNames](CDB_ColumnNames.md)
* [CDB_ColumnType](CDB_ColumnType.md)
* [CDB_HeadsTailsBins](CDB_HeadsTailsBins.md)
* [CDB_HexagonGrid](CDB_HexagonGrid.md)
* [CDB_JenksBins](CDB_JenksBins.md)
* [CDB_MakeHexagon](CDB_MakeHexagon.md)
* [CDB_QuantileBins](CDB_QuantileBins.md)
* [CDB_RectangleGrid](CDB_RectangleGrid.md)
* [CDB_SetUserQuotaInBytes](CDB_SetUserQuotaInBytes.md)
* [CDB_TransformToWebmercator](CDB_TransformToWebmercator.md)
* [CDB_UserTables](CDB_UserTables.md)
* [CDB_XYZ_Extent](CDB_XYZ_Extent.md)
* [CDB_XYZ_Resolution](CDB_XYZ_Resolution.md)
The CartoDB PostgreSQL extension is a module to load into each CartoDB user database to perform cartodb-specific security and functionality checks.
# Checks
No user other than the superuser should be allowed to change `statement_timeout` for the session (SET statement_timeout disallowed).
User tables need to match certain structure criteria (See [[CartoDB-user-table]]) so the extension should provide a mean to enforce such structure everytime an attempt to change structure is encountered.
# Events
The events we want some function to be called upon are:
| event | arguments to handler function | function duty | OK* |
|------------------------|--------------------------------------|----------------------------------|-----|
| SET variable | name of variable | forbid changing some vars | |
| RENAME table | old and new name + oid of the table | flush cache | |
| ADD/DROP/ALTER column | oid of the table | flush cache, cartodbfy, upd meta | Y |
| DISABLE/DROP trigger | oid of table, name of trigger | cartodbfy or forbid | |
| DROP table | oid of the table | flush cache and metadata | Y |
| CREATE table | oid of the table | cartodby, upd metadata | Y |
| GRANT | oid of table, privilege, role | flush cache, upd metadata |
| REVOKE | oid of table, privilege, role | flush cache, upd metadata |
* event available by installing https://github.com/CartoDB/pg_schema_triggers
At this stage we don't need more than this, but the number of events and the number of arguments passed to the handler function may expand in the future, so the extension should be written in a way to easily allow that.
Some of the handler will need to act _after_ the statement is completed (CREATE TABLE, for example).

View File

@@ -19,6 +19,12 @@ NOTICE: event trigger "cdb_on_add_column" does not exist, skipping
(1 row)
create schema c;
SELECT CDB_SetUserQuotaInBytes('c', 0);
cdb_setuserquotainbytes
-------------------------
0
(1 row)
CREATE USER cartodb_postgresql_unpriv_user;
GRANT ALL ON SCHEMA c to cartodb_postgresql_unpriv_user;
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
@@ -26,6 +32,7 @@ SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
----------------------
-- CREATE TABLE
----------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select 1 as i INTO c.t3;
NOTICE: trigger "track_updates" for table "c.t3" does not exist, skipping
NOTICE: trigger "update_the_geom_webmercator_trigger" for table "c.t3" does not exist, skipping
@@ -49,6 +56,7 @@ from c.t3;
1 | t | t | | | 1
(1 row)
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)) as age
@@ -58,6 +66,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
c.t3 | 0
(1 row)
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
-- Table with cartodb_id field, see
-- http://github.com/CartoDB/cartodb-postgresql/issues/32
select 1 as cartodb_id INTO c.t4;
@@ -85,6 +94,7 @@ from c.t4;
1 | t | t | |
(1 row)
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)) as age
@@ -97,6 +107,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t4'::regclass;
----------------------------
-- ALTER TABLE RENAME COLUMN
----------------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
pg_sleep
----------
@@ -125,6 +136,7 @@ from c.t3;
1 | t | t | | | 1 |
(1 row)
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs
@@ -134,6 +146,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
c.t3 | 0
(1 row)
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
pg_sleep
----------
@@ -162,6 +175,7 @@ from c.t3;
1 | t | t | | | 1 | |
(1 row)
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs
@@ -174,6 +188,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
----------------------------
-- ALTER TABLE DROP COLUMN
----------------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
pg_sleep
----------
@@ -202,6 +217,7 @@ from c.t3;
1 | t | t | | | 1 | |
(1 row)
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs
@@ -214,6 +230,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
----------------------------
-- ALTER TABLE ADD COLUMN
----------------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
pg_sleep
----------
@@ -233,6 +250,7 @@ from c.t3;
1 | t | t | | | 1 | | |
(1 row)
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs
@@ -247,7 +265,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
----------------------------
RESET SESSION AUTHORIZATION;
drop schema c cascade;
NOTICE: drop cascades to 2 other objects
NOTICE: drop cascades to 3 other objects
select count(*) from CDB_TableMetadata;
count
-------

View File

@@ -1,54 +1,40 @@
-- Depends on:
-- * CDB_ExtensionUtils.sql
-- * CDB_TransformToWebmercator.sql
-- * CDB_TableMetadata.sql
-- * CDB_Quota.sql
-- * _CDB_UserQuotaInBytes() function, installed by rails
-- (user.rebuild_quota_trigger, called by rake task
-- cartodb:db:update_test_quota_trigger)
-- (user.rebuild_quota_trigger, called by rake task cartodb:db:update_test_quota_trigger)
-- Update the_geom_webmercator
CREATE OR REPLACE FUNCTION _CDB_update_the_geom_webmercator()
RETURNS trigger AS $$
BEGIN
NEW.the_geom_webmercator := public.CDB_TransformToWebmercator(NEW.the_geom);
RETURN NEW;
END;
$$ LANGUAGE plpgsql VOLATILE;
CREATE OR REPLACE FUNCTION _CDB_update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at := now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql VOLATILE;
-- Ensure a table is a "cartodb" table
-- See https://github.com/CartoDB/cartodb/wiki/CartoDB-user-table
CREATE OR REPLACE FUNCTION CDB_CartodbfyTable(reloid REGCLASS)
RETURNS void
-- 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)
RETURNS void
AS $$
DECLARE
sql TEXT;
rec RECORD;
rec2 RECORD;
tabinfo RECORD;
had_column BOOLEAN;
i INTEGER;
new_name TEXT;
quota_in_bytes INT8;
exists_geom_cols BOOLEAN[];
BEGIN
IF cartodb.schema_exists(schema_name) = false THEN
RAISE EXCEPTION 'Invalid schema name "%"', schema_name;
END IF;
-- TODO: Check that user quota is set ?
BEGIN
PERFORM public._CDB_UserQuotaInBytes();
EXCEPTION WHEN undefined_function THEN
RAISE EXCEPTION 'Please set user quota before cartodbfying tables.';
EXECUTE FORMAT('SELECT %I._CDB_UserQuotaInBytes();', schema_name::text) INTO sql;
EXCEPTION WHEN undefined_function THEN
RAISE EXCEPTION 'Please set user quota before cartodbfying tables.';
END;
END;
$$ LANGUAGE PLPGSQL;
-- Drop cartodb triggers (might prevent changing columns)
-- 2) Drop cartodb triggers (might prevent changing columns)
CREATE OR REPLACE FUNCTION _CDB_drop_triggers(reloid REGCLASS)
RETURNS void
AS $$
DECLARE
sql TEXT;
BEGIN
-- "track_updates"
sql := 'DROP TRIGGER IF EXISTS track_updates ON ' || reloid::text;
EXECUTE sql;
@@ -66,10 +52,23 @@ BEGIN
EXECUTE sql;
sql := 'DROP TRIGGER IF EXISTS test_quota_per_row ON ' || reloid::text;
EXECUTE sql;
END;
$$ LANGUAGE PLPGSQL;
-- Ensure required fields exist
-- We need a cartodb_id column
-- 3) Cartodb_id creation & validation or renaming if invalid
CREATE OR REPLACE FUNCTION _CDB_create_cartodb_id_column(reloid REGCLASS)
RETURNS void
AS $$
DECLARE
sql TEXT;
rec RECORD;
rec2 RECORD;
had_column BOOLEAN;
i INTEGER;
new_name TEXT;
cartodb_id_name TEXT;
BEGIN
<< cartodb_id_setup >>
LOOP --{
had_column := FALSE;
@@ -77,18 +76,17 @@ BEGIN
sql := 'ALTER TABLE ' || reloid::text || ' ADD cartodb_id SERIAL NOT NULL UNIQUE';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
cartodb_id_name := 'cartodb_id';
EXIT cartodb_id_setup;
EXCEPTION
WHEN duplicate_column THEN
RAISE NOTICE 'Column cartodb_id already exists';
had_column := TRUE;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (cartodb_id): % (%)',
reloid, SQLERRM, SQLSTATE;
EXCEPTION
WHEN duplicate_column THEN
RAISE NOTICE 'Column cartodb_id already exists';
had_column := TRUE;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (cartodb_id): % (%)', reloid, SQLERRM, SQLSTATE;
END;
IF had_column THEN
SELECT pg_catalog.pg_get_serial_sequence(reloid::text, 'cartodb_id')
AS seq INTO rec2;
@@ -96,9 +94,9 @@ BEGIN
SELECT
pg_catalog.pg_get_serial_sequence(reloid::text, 'cartodb_id') as seq,
t.typname, t.oid, a.attnotnull FROM pg_type t, pg_attribute a
WHERE a.atttypid = t.oid AND a.attrelid = reloid AND NOT a.attisdropped
AND a.attname = 'cartodb_id'
WHERE a.atttypid = t.oid AND a.attrelid = reloid AND NOT a.attisdropped AND a.attname = 'cartodb_id'
INTO STRICT rec;
-- 20=int2, 21=int4, 23=int8
IF rec.oid NOT IN (20,21,23) THEN -- {
RAISE NOTICE 'Existing cartodb_id field is of invalid type % (need int2, int4 or int8), renaming', rec.typname;
@@ -107,28 +105,27 @@ BEGIN
ELSE -- }{
sql := 'ALTER TABLE ' || reloid::text || ' ALTER COLUMN cartodb_id SET NOT NULL';
IF NOT EXISTS ( SELECT c.conname FROM pg_constraint c, pg_attribute a
WHERE c.conkey = ARRAY[a.attnum] AND c.conrelid = reloid
AND a.attrelid = reloid
AND NOT a.attisdropped
AND a.attname = 'cartodb_id'
AND c.contype IN ( 'u', 'p' ) ) -- unique or pkey
WHERE c.conkey = ARRAY[a.attnum] AND c.conrelid = reloid
AND a.attrelid = reloid
AND NOT a.attisdropped
AND a.attname = 'cartodb_id'
AND c.contype IN ( 'u', 'p' ) ) -- unique or pkey
THEN
sql := sql || ', ADD unique(cartodb_id)';
END IF;
BEGIN
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
cartodb_id_name := 'cartodb_id';
EXIT cartodb_id_setup;
EXCEPTION
WHEN unique_violation OR not_null_violation THEN
RAISE NOTICE '%, renaming', SQLERRM;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (cartodb_id): % (%)',
reloid, SQLERRM, SQLSTATE;
EXCEPTION
WHEN unique_violation OR not_null_violation THEN
RAISE NOTICE '%, renaming', SQLERRM;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (cartodb_id): % (%)', reloid, SQLERRM, SQLSTATE;
END;
END IF; -- }
-- invalid column, need rename and re-create it
i := 0;
<< rename_column >>
@@ -138,18 +135,17 @@ BEGIN
sql := 'ALTER TABLE ' || reloid::text || ' RENAME COLUMN cartodb_id TO ' || new_name;
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXCEPTION
WHEN duplicate_column THEN
i := i+1;
CONTINUE rename_column;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (renaming cartodb_id): % (%)',
reloid, SQLERRM, SQLSTATE;
EXCEPTION
WHEN duplicate_column THEN
i := i+1;
CONTINUE rename_column;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (renaming cartodb_id): % (%)', reloid, SQLERRM, SQLSTATE;
END;
cartodb_id_name := new_name;
EXIT rename_column;
END LOOP; --}
CONTINUE cartodb_id_setup;
END IF;
END LOOP; -- }
@@ -158,12 +154,11 @@ BEGIN
RAISE NOTICE 'Trying to recover data from % column', new_name;
BEGIN
-- Copy existing values to new field
-- NOTE: using ALTER is a workaround to a PostgreSQL bug and
-- is also known to be faster for tables with many rows
-- NOTE: using ALTER is a workaround to a PostgreSQL bug and is also known to be faster for tables with many rows
-- See http://www.postgresql.org/message-id/20140530143150.GA11051@localhost
sql := 'ALTER TABLE ' || reloid::text
|| ' ALTER cartodb_id TYPE int USING '
|| new_name || '::int4';
|| ' ALTER cartodb_id TYPE int USING '
|| new_name || '::int4';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
@@ -178,23 +173,51 @@ BEGIN
-- Reset sequence name
sql := 'ALTER SEQUENCE ' || rec2.seq::text
|| ' RESTART WITH ' || rec.max + 1;
|| ' RESTART WITH ' || rec.max + 1;
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
-- Drop old column (all went find if we got here)
-- Drop old column (all went fine if we got here)
sql := 'ALTER TABLE ' || reloid::text || ' DROP ' || new_name;
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Could not initialize cartodb_id with existing values: % (%)',
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Could not initialize cartodb_id with existing values: % (%)',
SQLERRM, SQLSTATE;
END;
END IF;
-- We need created_at and updated_at
-- Set primary key of the table if not already present (e.g. tables created from SQL API)
IF cartodb_id_name IS NULL THEN
RAISE EXCEPTION 'Cartodbfying % (Didnt get cartodb_id field name)', reloid;
END IF;
BEGIN
sql := 'ALTER TABLE ' || reloid::text || ' ADD PRIMARY KEY (cartodb_id)';
EXECUTE sql;
EXCEPTION
WHEN others THEN
RAISE DEBUG 'Table % Already had PRIMARY KEY', reloid;
END;
END;
$$ LANGUAGE PLPGSQL;
-- 4) created_at and updated_at creation & validation or renaming if invalid
CREATE OR REPLACE FUNCTION _CDB_create_timestamp_columns(reloid REGCLASS)
RETURNS void
AS $$
DECLARE
sql TEXT;
rec RECORD;
rec2 RECORD;
had_column BOOLEAN;
i INTEGER;
new_name TEXT;
BEGIN
FOR rec IN SELECT * FROM ( VALUES ('created_at'), ('updated_at') ) t(cname)
LOOP --{
new_name := null;
@@ -203,48 +226,43 @@ BEGIN
had_column := FALSE;
BEGIN
sql := 'ALTER TABLE ' || reloid::text || ' ADD ' || rec.cname
|| ' TIMESTAMPTZ NOT NULL DEFAULT now()';
|| ' TIMESTAMPTZ NOT NULL DEFAULT now()';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXIT column_setup;
EXCEPTION
WHEN duplicate_column THEN
RAISE NOTICE 'Column % already exists', rec.cname;
had_column := TRUE;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (%): % (%)',
reloid, rec.cname, SQLERRM, SQLSTATE;
EXCEPTION
WHEN duplicate_column THEN
RAISE NOTICE 'Column % already exists', rec.cname;
had_column := TRUE;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (%): % (%)', reloid, rec.cname, SQLERRM, SQLSTATE;
END;
IF had_column THEN
-- Check data type is a TIMESTAMP WITH TIMEZONE
SELECT t.typname, t.oid, a.attnotnull FROM pg_type t, pg_attribute a
WHERE a.atttypid = t.oid AND a.attrelid = reloid
AND NOT a.attisdropped AND a.attname = rec.cname
WHERE a.atttypid = t.oid AND a.attrelid = reloid AND NOT a.attisdropped AND a.attname = rec.cname
INTO STRICT rec2;
IF rec2.oid NOT IN (1184) THEN -- timestamptz {
RAISE NOTICE 'Existing % field is of invalid type % (need timestamptz), renaming', rec.
cname, rec2.typname;
RAISE NOTICE 'Existing % field is of invalid type % (need timestamptz), renaming', rec.cname, rec2.typname;
ELSE -- }{
-- Ensure data type is a TIMESTAMP WITH TIMEZONE
sql := 'ALTER TABLE ' || reloid::text
|| ' ALTER ' || rec.cname
|| ' SET NOT NULL,'
|| ' ALTER ' || rec.cname
|| ' SET DEFAULT now()';
|| ' ALTER ' || rec.cname
|| ' SET NOT NULL,'
|| ' ALTER ' || rec.cname
|| ' SET DEFAULT now()';
BEGIN
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXIT column_setup;
EXCEPTION
WHEN not_null_violation THEN -- failed not-null
RAISE NOTICE '%, renaming', SQLERRM;
WHEN cannot_coerce THEN -- failed cast
RAISE NOTICE '%, renaming', SQLERRM;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (%): % (%)',
reloid, rec.cname, SQLERRM, SQLSTATE;
EXCEPTION
WHEN not_null_violation THEN -- failed not-null
RAISE NOTICE '%, renaming', SQLERRM;
WHEN cannot_coerce THEN -- failed cast
RAISE NOTICE '%, renaming', SQLERRM;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (%): % (%)', reloid, rec.cname, SQLERRM, SQLSTATE;
END;
END IF; -- }
@@ -257,18 +275,17 @@ cname, rec2.typname;
sql := 'ALTER TABLE ' || reloid::text || ' RENAME COLUMN ' || rec.cname || ' TO ' || new_name;
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXCEPTION
WHEN duplicate_column THEN
i := i+1;
CONTINUE rename_column;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (renaming %): % (%)',
EXCEPTION
WHEN duplicate_column THEN
i := i+1;
CONTINUE rename_column;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (renaming %): % (%)',
reloid, rec.cname, SQLERRM, SQLSTATE;
END;
EXIT rename_column;
END LOOP; --}
CONTINUE column_setup;
END IF;
END LOOP; -- }
@@ -277,12 +294,11 @@ cname, rec2.typname;
RAISE NOTICE 'Trying to recover data from % coumn', new_name;
BEGIN
-- Copy existing values to new field
-- NOTE: using ALTER is a workaround to a PostgreSQL bug and
-- is also known to be faster for tables with many rows
-- NOTE: using ALTER is a workaround to a PostgreSQL bug and is also known to be faster for tables with many rows
-- See http://www.postgresql.org/message-id/20140530143150.GA11051@localhost
sql := 'ALTER TABLE ' || reloid::text || ' ALTER ' || rec.cname
|| ' TYPE TIMESTAMPTZ USING '
|| new_name || '::timestamptz';
|| ' TYPE TIMESTAMPTZ USING '
|| new_name || '::timestamptz';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
@@ -291,21 +307,36 @@ cname, rec2.typname;
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Could not initialize % with existing values: % (%)',
rec.cname, SQLERRM, SQLSTATE;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Could not initialize % with existing values: % (%)', rec.cname, SQLERRM, SQLSTATE;
END;
END IF; -- }
END LOOP; -- }
END;
$$ LANGUAGE PLPGSQL;
-- 5) the_geom and the_geom_webmercator creation & validation or renaming if invalid
CREATE OR REPLACE FUNCTION _CDB_create_the_geom_columns(reloid REGCLASS)
RETURNS BOOLEAN[]
AS $$
DECLARE
sql TEXT;
rec RECORD;
rec2 RECORD;
had_column BOOLEAN;
i INTEGER;
new_name TEXT;
exists_geom_cols BOOLEAN[];
BEGIN
-- We need the_geom and the_geom_webmercator
FOR rec IN SELECT * FROM ( VALUES ('the_geom',4326), ('the_geom_webmercator',3857) ) t(cname,csrid) LOOP --{
<< column_setup >> LOOP --{
BEGIN
sql := 'ALTER TABLE ' || reloid::text || ' ADD ' || rec.cname
|| ' GEOMETRY(geometry,' || rec.csrid || ')';
|| ' GEOMETRY(geometry,' || rec.csrid || ')';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
sql := 'CREATE INDEX ON ' || reloid::text || ' USING GIST ( ' || rec.cname || ')';
@@ -313,25 +344,22 @@ cname, rec2.typname;
EXECUTE sql;
exists_geom_cols := array_append(exists_geom_cols, false);
EXIT column_setup;
EXCEPTION
WHEN duplicate_column THEN
exists_geom_cols := array_append(exists_geom_cols, true);
RAISE NOTICE 'Column % already exists', rec.cname;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (%): % (%)',
reloid, rec.cname, SQLERRM, SQLSTATE;
EXCEPTION
WHEN duplicate_column THEN
exists_geom_cols := array_append(exists_geom_cols, true);
RAISE NOTICE 'Column % already exists', rec.cname;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (%): % (%)', reloid, rec.cname, SQLERRM, SQLSTATE;
END;
<< column_fixup >>
LOOP --{
-- Check data type is a GEOMETRY
SELECT t.typname, t.oid, a.attnotnull,
postgis_typmod_srid(a.atttypmod) as srid,
postgis_typmod_type(a.atttypmod) as gtype
FROM pg_type t, pg_attribute a
WHERE a.atttypid = t.oid AND a.attrelid = reloid AND NOT a.attisdropped
AND a.attname = rec.cname
postgis_typmod_srid(a.atttypmod) as srid,
postgis_typmod_type(a.atttypmod) as gtype
FROM pg_type t, pg_attribute a
WHERE a.atttypid = t.oid AND a.attrelid = reloid AND NOT a.attisdropped AND a.attname = rec.cname
INTO STRICT rec2;
IF rec2.typname NOT IN ('geometry') THEN -- {
@@ -340,46 +368,44 @@ cname, rec2.typname;
END IF; -- }
IF rec2.srid != rec.csrid THEN -- {
BEGIN
sql := 'ALTER TABLE ' || reloid::text || ' ALTER ' || rec.cname
|| ' TYPE geometry(' || rec2.gtype || ',' || rec.csrid || ') USING ST_Transform('
|| rec.cname || ',' || rec.csrid || ')';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
BEGIN
sql := 'ALTER TABLE ' || reloid::text || ' ALTER ' || rec.cname
|| ' TYPE geometry(' || rec2.gtype || ',' || rec.csrid || ') USING ST_Transform('
|| rec.cname || ',' || rec.csrid || ')';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'Could not enforce SRID % to column %: %, renaming', rec.csrid, rec.cname, SQLERRM;
EXIT column_fixup; -- cannot fix, will rename
END;
END;
END IF; -- }
-- add gist indices if not there already
IF NOT EXISTS ( SELECT ir.relname
FROM pg_am am, pg_class ir,
pg_class c, pg_index i,
pg_attribute a
pg_class c, pg_index i,
pg_attribute a
WHERE c.oid = reloid AND i.indrelid = c.oid
AND a.attname = rec.cname
AND i.indexrelid = ir.oid AND i.indnatts = 1
AND i.indkey[0] = a.attnum AND a.attrelid = c.oid
AND NOT a.attisdropped AND am.oid = ir.relam
AND am.amname = 'gist' )
AND a.attname = rec.cname
AND i.indexrelid = ir.oid AND i.indnatts = 1
AND i.indkey[0] = a.attnum AND a.attrelid = c.oid
AND NOT a.attisdropped AND am.oid = ir.relam
AND am.amname = 'gist' )
THEN -- {
BEGIN
sql := 'CREATE INDEX ON ' || reloid::text || ' USING GIST ( ' || rec.cname || ')';
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXCEPTION
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (% index): % (%)',
reloid, rec.cname, SQLERRM, SQLSTATE;
EXCEPTION
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (% index): % (%)', reloid, rec.cname, SQLERRM, SQLSTATE;
END;
END IF; -- }
-- if we reached this line, all went good
EXIT column_setup;
END LOOP; -- } column_fixup
END LOOP; -- } column_fixup
-- invalid column, need rename and re-create it
i := 0;
@@ -390,73 +416,236 @@ cname, rec2.typname;
sql := 'ALTER TABLE ' || reloid::text || ' RENAME COLUMN ' || rec.cname || ' TO ' || new_name;
RAISE DEBUG 'Running %', sql;
EXECUTE sql;
EXCEPTION
WHEN duplicate_column THEN
i := i+1;
CONTINUE rename_column;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (rename %): % (%)',
reloid, rec.cname, SQLERRM, SQLSTATE;
EXCEPTION
WHEN duplicate_column THEN
i := i+1;
CONTINUE rename_column;
WHEN others THEN
RAISE EXCEPTION 'Cartodbfying % (rename %): % (%)', reloid, rec.cname, SQLERRM, SQLSTATE;
END;
EXIT rename_column;
END LOOP; --}
CONTINUE column_setup;
END LOOP; -- } column_setup
END LOOP; -- } column_setup
END LOOP; -- } on expected geometry columns
-- Initialize the_geom with values from the_geom_webmercator
-- do this only if the_geom_webmercator was found (not created)
-- _and_ the_geom was NOT found.
IF exists_geom_cols[2] AND NOT exists_geom_cols[1] THEN
RETURN exists_geom_cols;
END;
$$ LANGUAGE PLPGSQL;
-- 6) Initialize the_geom with values from the_geom_webmercator
-- do this only if the_geom_webmercator was found (not created) and the_geom was NOT found.
CREATE OR REPLACE FUNCTION _CDB_populate_the_geom_from_the_geom_webmercator(reloid REGCLASS, geom_columns_exist BOOLEAN[])
RETURNS void
AS $$
DECLARE
sql TEXT;
BEGIN
IF geom_columns_exist[2] AND NOT geom_columns_exist[1] THEN
sql := 'UPDATE ' || reloid::text || ' SET the_geom = ST_Transform(the_geom_webmercator, 4326) ';
EXECUTE sql;
END IF;
END;
$$ LANGUAGE PLPGSQL;
-- Initialize the_geom_webmercator with values from the_geom
-- do this only if the_geom was found (not created)
-- _and_ the_geom_webmercator was NOT found.
IF exists_geom_cols[1] AND NOT exists_geom_cols[2] THEN
-- 7) Initialize the_geom_webmercator with values from the_geom
-- do this only if the_geom was found (not created) and the_geom_webmercator was NOT found.
CREATE OR REPLACE FUNCTION _CDB_populate_the_geom_webmercator_from_the_geom(reloid REGCLASS, geom_columns_exist BOOLEAN[])
RETURNS void
AS $$
DECLARE
sql TEXT;
BEGIN
IF geom_columns_exist[1] AND NOT geom_columns_exist[2] THEN
sql := 'UPDATE ' || reloid::text || ' SET the_geom_webmercator = public.CDB_TransformToWebmercator(the_geom) ';
EXECUTE sql;
END IF;
END;
$$ LANGUAGE PLPGSQL;
-- Re-create all triggers
-- NOTE: drop/create has the side-effect of re-enabling disabled triggers
-- "track_updates"
-- 8.a) 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)
RETURNS void
AS $$
DECLARE
sql TEXT;
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()';
|| reloid::text
|| ' FOR EACH STATEMENT EXECUTE PROCEDURE public.cdb_tablemetadata_trigger()';
EXECUTE sql;
-- "update_the_geom_webmercator"
-- TODO: why _before_ and not after ?
-- "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()';
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public._CDB_update_the_geom_webmercator()';
EXECUTE sql;
-- "update_updated_at"
-- TODO: why _before_ and not after ?
-- "update_updated_at"
-- TODO: why _before_ and not after ?
sql := 'CREATE trigger update_updated_at_trigger BEFORE UPDATE ON '
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public._CDB_update_updated_at()';
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public._CDB_update_updated_at()';
EXECUTE sql;
-- "test_quota" and "test_quota_per_row"
-- "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)';
|| reloid::text
|| ' EXECUTE PROCEDURE public.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)';
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public.CDB_CheckQuota(0.001, ''-1'', '''
|| schema_name::text
|| ''')';
EXECUTE sql;
END;
$$ LANGUAGE PLPGSQL;
-- 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)
RETURNS void
AS $$
DECLARE
sql TEXT;
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()';
EXECUTE sql;
-- "update_updated_at"
-- TODO: why _before_ and not after ?
sql := 'CREATE trigger update_updated_at_trigger BEFORE UPDATE ON '
|| reloid::text
|| ' FOR EACH ROW EXECUTE PROCEDURE public._CDB_update_updated_at()';
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'', '''
|| 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'', '''
|| schema_name::text
|| ''')';
EXECUTE sql;
END;
$$ LANGUAGE PLPGSQL;
-- Update the_geom_webmercator
CREATE OR REPLACE FUNCTION _CDB_update_the_geom_webmercator()
RETURNS trigger
AS $$
BEGIN
NEW.the_geom_webmercator := public.CDB_TransformToWebmercator(NEW.the_geom);
RETURN NEW;
END;
$$ LANGUAGE plpgsql VOLATILE;
CREATE OR REPLACE FUNCTION _CDB_update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at := now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql VOLATILE;
-- Auxiliary function
CREATE OR REPLACE FUNCTION cartodb._CDB_is_raster_table(schema_name TEXT, reloid REGCLASS)
RETURNS BOOLEAN
AS $$
DECLARE
sql TEXT;
is_raster BOOLEAN;
rel_name TEXT;
BEGIN
IF cartodb.schema_exists(schema_name) = FALSE THEN
RAISE EXCEPTION 'Invalid schema name "%"', schema_name;
END IF;
SELECT relname FROM pg_class WHERE oid=reloid INTO rel_name;
BEGIN
sql := 'SELECT the_raster_webmercator FROM '
|| quote_ident(schema_name::TEXT)
|| '.'
|| quote_ident(rel_name::TEXT)
|| ' LIMIT 1';
is_raster = TRUE;
EXECUTE sql;
EXCEPTION WHEN undefined_column THEN
is_raster = FALSE;
END;
RETURN is_raster;
END;
$$ LANGUAGE PLPGSQL;
-- ////////////////////////////////////////////////////
-- Ensure a table is a "cartodb" table (See https://github.com/CartoDB/cartodb/wiki/CartoDB-user-table)
-- Rails code replicates this call at User.cartodbfy()
CREATE OR REPLACE FUNCTION CDB_CartodbfyTable(schema_name TEXT, reloid REGCLASS)
RETURNS void
AS $$
DECLARE
exists_geom_cols BOOLEAN[];
is_raster BOOLEAN;
BEGIN
PERFORM cartodb._CDB_check_prerequisites(schema_name, reloid);
PERFORM cartodb._CDB_drop_triggers(reloid);
-- Ensure required fields exist
PERFORM cartodb._CDB_create_cartodb_id_column(reloid);
PERFORM cartodb._CDB_create_timestamp_columns(reloid);
SELECT cartodb._CDB_is_raster_table(schema_name, reloid) INTO is_raster;
IF is_raster THEN
PERFORM cartodb._CDB_create_raster_triggers(schema_name, reloid);
ELSE
SELECT cartodb._CDB_create_the_geom_columns(reloid) INTO exists_geom_cols;
-- Both only populate if proceeds
PERFORM cartodb._CDB_populate_the_geom_from_the_geom_webmercator(reloid, exists_geom_cols);
PERFORM cartodb._CDB_populate_the_geom_webmercator_from_the_geom(reloid, exists_geom_cols);
PERFORM cartodb._CDB_create_triggers(schema_name, reloid);
END IF;
END;
$$ LANGUAGE PLPGSQL;
CREATE OR REPLACE FUNCTION CDB_CartodbfyTable(reloid REGCLASS)
RETURNS void
AS $$
BEGIN
PERFORM cartodb.CDB_CartodbfyTable('public', reloid);
END;
$$ LANGUAGE PLPGSQL;

View File

@@ -4,14 +4,17 @@ CREATE OR REPLACE FUNCTION cartodb.cdb_handle_create_table ()
RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
DECLARE
event_info RECORD;
rel_namespace TEXT;
BEGIN
event_info := schema_triggers.get_relation_create_eventinfo();
-- We're only interested in real relations
IF (event_info.new).relkind != 'r' THEN RETURN; END IF;
RAISE DEBUG 'Relation % of kind % created in namespace oid %',
event_info.relation, (event_info.new).relkind, (event_info.new).relnamespace;
SELECT nspname FROM pg_namespace WHERE oid=(event_info.new).relnamespace INTO rel_namespace;
RAISE DEBUG 'Relation % of kind % created in table % namespace % (oid %)',
event_info.relation, (event_info.new).relkind, (event_info.new).relname::TEXT, rel_namespace, (event_info.new).relnamespace;
-- We don't want to react to alters triggered by superuser,
IF current_setting('is_superuser') = 'on' THEN
@@ -22,7 +25,7 @@ BEGIN
PERFORM cartodb.cdb_disable_ddl_hooks();
-- CDB_CartodbfyTable must not create tables, or infinite loop will happen
PERFORM cartodb.CDB_CartodbfyTable(event_info.relation);
PERFORM cartodb.CDB_CartodbfyTable(rel_namespace, event_info.relation);
PERFORM cartodb.cdb_enable_ddl_hooks();
@@ -63,6 +66,7 @@ RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
DECLARE
event_info RECORD;
rel RECORD;
rel_namespace TEXT;
BEGIN
event_info := schema_triggers.get_column_alter_eventinfo();
@@ -80,9 +84,11 @@ BEGIN
RETURN;
END IF;
SELECT nspname FROM pg_namespace WHERE oid = rel.relnamespace INTO rel_namespace;
PERFORM cartodb.cdb_disable_ddl_hooks();
PERFORM cartodb.CDB_CartodbfyTable(event_info.relation);
PERFORM cartodb.CDB_CartodbfyTable(rel_namespace, event_info.relation);
PERFORM cartodb.cdb_enable_ddl_hooks();
@@ -100,6 +106,7 @@ RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
DECLARE
event_info RECORD;
rel RECORD;
rel_namespace TEXT;
BEGIN
event_info := schema_triggers.get_column_drop_eventinfo();
@@ -117,9 +124,11 @@ BEGIN
RETURN;
END IF;
SELECT nspname FROM pg_namespace WHERE oid = rel.relnamespace INTO rel_namespace;
PERFORM cartodb.cdb_disable_ddl_hooks();
PERFORM cartodb.CDB_CartodbfyTable(event_info.relation);
PERFORM cartodb.CDB_CartodbfyTable(rel_namespace, event_info.relation);
PERFORM cartodb.cdb_enable_ddl_hooks();

View File

@@ -0,0 +1,122 @@
--
-- CDB_DistType classifies the histograms of a column into
-- one of the basic types listed by Galtung: http://druedin.com/2012/12/08/galtungs-ajus-system/
--
-- Future improvements:
-- variable number of bins (7 is baked in right now)
-- catch the number of items to ensure that the sample is large enough
--
-- Refs:
-- 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 $$
DECLARE
element_count INT4;
minv numeric;
maxv numeric;
bins numeric[];
freqs numeric[];
ajus INT[];
freq INT4;
signature text;
i INT := 1;
BEGIN
SELECT min(e), max(e), count(e) INTO minv, maxv, element_count FROM ( SELECT unnest(in_array) e ) x;
IF abs(maxv - minv) < 1e-7 THEN -- if max and min are nearly equal, call if 'F' (make relative to maxv?)
signature = 'F';
ELSE
-- Calculate bins and count in bins
EXECUTE 'WITH stats as (
SELECT min(e) as minv,
max(e) as maxv,
count(e) as total
FROM (SELECT unnest($1) e) x
WHERE e is not null
),
hist as (
SELECT width_bucket(e, s.minv, s.maxv, 7) bucket,
count(*) freq
FROM (SELECT unnest($1) e) x, stats s
WHERE e is not null
GROUP BY 1
ORDER BY 1
)
SELECT array_agg(round(100.0 * hist.freq::numeric / stats.total::numeric,1)) freqs,
array_agg(hist.bucket) buckets
FROM hist, stats'
INTO freqs, bins
USING in_array;
LOOP
IF i < 7 THEN
ajus[i] = CASE WHEN freqs[i] > freqs[i+1] THEN -1
WHEN abs(freqs[i] - freqs[i+1]) <= 0.05 THEN 0
ELSE 1 END;
ELSE
EXIT;
END IF;
i := i + 1;
END LOOP;
signature = _CDB_DistTypeClassify(ajus);
END IF;
RETURN signature;
END;
$$ language plpgsql IMMUTABLE;
-- Classify data into AJUSFL
CREATE OR REPLACE FUNCTION _CDB_DistTypeClassify ( in_array INT[] ) RETURNS text as $$
DECLARE
element_count INT4;
maxv numeric;
minv numeric;
uniques INT[];
type text;
BEGIN
SELECT max(e), min(e) INTO maxv, minv FROM ( SELECT unnest(in_array) e ) x;
IF (maxv = 0 AND minv = 0) THEN
type = 'F';
ELSIF maxv < 1 THEN
type = 'L';
ELSIF minv > -1 THEN
type = 'J';
ELSE
-- Get distinct elements ordered by original position
EXECUTE 'WITH b AS (
SELECT a
FROM (SELECT unnest($1) a) x
),
c AS (
SELECT a, row_number() OVER () r
FROM b
),
d AS (
SELECT DISTINCT a
FROM c
),
e AS (
SELECT a FROM d ORDER BY (
SELECT r FROM c WHERE d.a = c.a ORDER BY r ASC LIMIT 1
) ASC)
SELECT array_agg(a) FROM e'
INTO uniques
USING in_array;
-- Decide if it's an A, U, or other
IF (uniques = ARRAY[1,-1] OR uniques = ARRAY[1,0,-1] OR uniques = ARRAY[1,-1,0] OR uniques = ARRAY[0,1,-1]) THEN
type = 'A';
ELSIF (uniques = ARRAY[-1,1] OR uniques = ARRAY[-1,0,1] OR uniques = ARRAY[-1,1,0] OR uniques = ARRAY[0,-1,1]) THEN
type = 'U';
ELSE
type = 'S';
END IF;
END IF;
RETURN type;
END;
$$ language plpgsql IMMUTABLE;

View File

@@ -0,0 +1,46 @@
--
-- CDB_DistinctMeasure
-- calculates the fraction of rows in the 10 most common distinct categories
-- returns true if the number of rows in these 10 categories is >= 0.9 * total number of rows
--
--
CREATE OR REPLACE FUNCTION CDB_DistinctMeasure ( in_array text[], threshold numeric DEFAULT null ) RETURNS numeric as $$
DECLARE
element_count INT4;
maxval numeric;
passes numeric;
BEGIN
SELECT count(e) INTO element_count FROM ( SELECT unnest(in_array) e ) x;
-- count number of occurrences per bin
-- calculate the normalized cumulative sum
-- return the max value: which corresponds nth entry
-- for n <= 10 depending on # of distinct values
EXECUTE 'WITH a As (
SELECT
count(*) cnt
FROM
(SELECT * FROM unnest($2) e ) x
WHERE e is not null
GROUP BY e
ORDER BY cnt DESC
),
b As (
SELECT
sum(cnt) OVER (ORDER BY cnt DESC) / $1 As cumsum
FROM a
LIMIT 10
)
SELECT max(cumsum) maxval FROM b'
INTO maxval
USING element_count, in_array;
IF threshold is null THEN
passes = maxval;
ELSE
passes = CASE WHEN (maxval >= threshold) THEN 1 ELSE 0 END;
END IF;
RETURN passes;
END;
$$ language plpgsql IMMUTABLE;

View File

@@ -0,0 +1,37 @@
--
-- Calculate the equal interval bins for a given column
--
-- @param in_array A numeric array of numbers to determine the best
-- to determine the bin boundary
--
-- @param breaks The number of bins you want to find.
--
--
-- Returns: upper edges of bins
--
--
CREATE OR REPLACE FUNCTION CDB_EqualIntervalBins ( in_array NUMERIC[], breaks INT ) RETURNS NUMERIC[] as $$
DECLARE
diff numeric;
min_val numeric;
max_val numeric;
tmp_val numeric;
i INT := 1;
reply numeric[];
BEGIN
SELECT min(e), max(e) INTO min_val, max_val FROM ( SELECT unnest(in_array) e ) x WHERE e IS NOT NULL;
diff = (max_val - min_val) / breaks::numeric;
LOOP
IF i < breaks THEN
tmp_val = min_val + i::numeric * diff;
reply = array_append(reply, tmp_val);
i := i+1;
ELSE
reply = array_append(reply, max_val);
EXIT;
END IF;
END LOOP;
RETURN reply;
END;
$$ language plpgsql IMMUTABLE;

View File

@@ -11,3 +11,10 @@ BEGIN
EXECUTE sql;
END;
$$ language 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION cartodb.schema_exists(schema_name text)
RETURNS boolean AS
$$
SELECT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = schema_name::text);
$$
language sql VOLATILE;

View File

@@ -0,0 +1,26 @@
-- CartoDB Math SQL functions
-- Mode
-- https://wiki.postgresql.org/wiki/Aggregate_Mode
CREATE OR REPLACE FUNCTION cartodb._CDB_Math_final_mode(anyarray)
RETURNS anyelement AS
$BODY$
SELECT a
FROM unnest($1) a
GROUP BY 1
ORDER BY COUNT(1) DESC, 1
LIMIT 1;
$BODY$
LANGUAGE 'sql' IMMUTABLE;
DROP AGGREGATE IF EXISTS cartodb.CDB_Math_Mode(anyelement);
CREATE AGGREGATE cartodb.CDB_Math_Mode(anyelement) (
SFUNC=array_append,
STYPE=anyarray,
FINALFUNC=_CDB_Math_final_mode,
INITCOND='{}'
);

View File

@@ -0,0 +1,90 @@
CREATE OR REPLACE
FUNCTION cartodb.CDB_Organization_Member_Group_Role_Member_Name()
RETURNS TEXT
AS 'SELECT ''cdb_org_member''::text || ''_'' || md5(current_database());'
LANGUAGE SQL IMMUTABLE;
DO LANGUAGE 'plpgsql' $$
DECLARE
cdb_org_member_role_name TEXT;
BEGIN
cdb_org_member_role_name := cartodb.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;';
END IF;
END
$$;
CREATE OR REPLACE
FUNCTION cartodb.CDB_Organization_Create_Member(role_name text)
RETURNS void
AS $$
BEGIN
EXECUTE 'GRANT "' || cartodb.CDB_Organization_Member_Group_Role_Member_Name() || '" TO "' || role_name || '"';
END
$$ LANGUAGE PLPGSQL VOLATILE;
-------------------------------------------------------------------------------
-- Sharing tables
-------------------------------------------------------------------------------
CREATE OR REPLACE
FUNCTION cartodb.CDB_Organization_Add_Table_Read_Permission(from_schema text, table_name text, to_role_name text)
RETURNS void
AS $$
BEGIN
EXECUTE 'GRANT USAGE ON SCHEMA "' || from_schema || '" TO "' || to_role_name || '"';
EXECUTE 'GRANT SELECT ON "' || from_schema || '"."' || table_name || '" TO "' || to_role_name || '"';
END
$$ LANGUAGE PLPGSQL VOLATILE;
CREATE OR REPLACE
FUNCTION cartodb.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() || ''');';
END
$$ LANGUAGE PLPGSQL VOLATILE;
CREATE OR REPLACE
FUNCTION cartodb.CDB_Organization_Add_Table_Read_Write_Permission(from_schema text, table_name text, to_role_name text)
RETURNS void
AS $$
BEGIN
EXECUTE 'GRANT USAGE ON SCHEMA "' || from_schema || '" TO "' || to_role_name || '"';
EXECUTE 'GRANT SELECT, INSERT, UPDATE, DELETE ON "' || from_schema || '"."' || table_name || '" TO "' || to_role_name || '"';
END
$$ LANGUAGE PLPGSQL VOLATILE;
CREATE OR REPLACE
FUNCTION cartodb.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() || ''');';
END
$$ LANGUAGE PLPGSQL VOLATILE;
CREATE OR REPLACE
FUNCTION cartodb.CDB_Organization_Remove_Access_Permission(from_schema text, table_name text, to_role_name text)
RETURNS void
AS $$
BEGIN
EXECUTE 'REVOKE ALL PRIVILEGES ON TABLE "' || from_schema || '"."' || table_name || '" FROM "' || to_role_name || '"';
-- EXECUTE 'REVOKE USAGE ON SCHEMA ' || from_schema || ' FROM "' || to_role_name || '"';
-- We need to revoke usage on schema only if we are revoking privileges from the last table where to_role_name has
-- any permission granted within the schema from_schema
END
$$ LANGUAGE PLPGSQL VOLATILE;
CREATE OR REPLACE
FUNCTION cartodb.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() || ''');';
END
$$ LANGUAGE PLPGSQL VOLATILE;

View File

@@ -15,18 +15,29 @@ DECLARE
i INT := 1;
reply numeric[];
BEGIN
-- get our unique values
SELECT array_agg(e) INTO in_array FROM (SELECT unnest(in_array) e GROUP BY e ORDER BY e ASC) x;
-- get the total size of our row
element_count := array_upper(in_array, 1) - array_lower(in_array, 1);
-- sort our values
SELECT array_agg(e) INTO in_array FROM (SELECT unnest(in_array) e ORDER BY e ASC) x;
-- get the total size of our data
element_count := array_length(in_array, 1);
break_size := element_count::numeric / breaks;
-- slice our bread
LOOP
IF i > breaks THEN EXIT; END IF;
SELECT e INTO tmp_val FROM ( SELECT unnest(in_array) e LIMIT 1 OFFSET round(break_size * i)) x;
IF i < breaks THEN
IF break_size * i % 1 > 0 THEN
SELECT e INTO tmp_val FROM ( SELECT unnest(in_array) e LIMIT 1 OFFSET ceil(break_size * i) - 1) x;
ELSE
SELECT avg(e) INTO tmp_val FROM ( SELECT unnest(in_array) e LIMIT 2 OFFSET ceil(break_size * i) - 1 ) x;
END IF;
ELSIF i = breaks THEN
-- select the last value
SELECT max(e) INTO tmp_val FROM ( SELECT unnest(in_array) e ) x;
ELSE
EXIT;
END IF;
reply = array_append(reply, tmp_val);
i := i+1;
END LOOP;
RETURN reply;
i := i+1;
END LOOP;
RETURN reply;
END;
$$ language plpgsql IMMUTABLE;
$$ language plpgsql IMMUTABLE;

View File

@@ -2,12 +2,12 @@
--
-- Requires PostgreSQL 9.x+
--
CREATE OR REPLACE FUNCTION CDB_QueryTables(query text)
RETURNS name[]
CREATE OR REPLACE FUNCTION CDB_QueryTablesText(query text)
RETURNS text[]
AS $$
DECLARE
exp XML;
tables NAME[];
tables text[];
rec RECORD;
rec2 RECORD;
BEGIN
@@ -16,13 +16,13 @@ BEGIN
FOR rec IN SELECT CDB_QueryStatements(query) q LOOP
IF NOT ( rec.q ilike 'select %' or rec.q ilike 'with %' ) THEN
--RAISE WARNING 'Skipping %', rec.q;
CONTINUE;
IF NOT ( rec.q ilike 'select%' or rec.q ilike 'with%' ) THEN
--RAISE WARNING 'Skipping %', rec.q;
CONTINUE;
END IF;
BEGIN
EXECUTE 'EXPLAIN (FORMAT XML) ' || rec.q INTO STRICT exp;
EXECUTE 'EXPLAIN (FORMAT XML, VERBOSE) ' || rec.q INTO STRICT exp;
EXCEPTION WHEN others THEN
-- TODO: if error is 'relation "xxxxxx" does not exist', take xxxxxx as
-- the affected table ?
@@ -33,14 +33,19 @@ BEGIN
-- Now need to extract all values of <Relation-Name>
--RAISE DEBUG 'Explain: %', exp;
-- RAISE DEBUG 'Explain: %', exp;
FOR rec2 IN WITH
inp AS ( SELECT xpath('//x:Relation-Name/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as x )
SELECT unnest(x)::name as p from inp
inp AS (
SELECT
xpath('//x:Relation-Name/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as x,
xpath('//x:Relation-Name/../x:Schema/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as s
)
SELECT unnest(x) as p, unnest(s) as sc from inp
LOOP
--RAISE DEBUG 'tab: %', rec2.p;
tables := array_append(tables, rec2.p);
-- RAISE DEBUG 'tab: %', rec2.p;
-- RAISE DEBUG 'sc: %', rec2.sc;
tables := array_append(tables, (rec2.sc || '.' || rec2.p));
END LOOP;
-- RAISE DEBUG 'Tables: %', tables;
@@ -60,3 +65,14 @@ BEGIN
return tables;
END
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;
-- 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)
RETURNS name[]
AS $$
BEGIN
RETURN CDB_QueryTablesText(query)::name[];
END
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;

View File

@@ -1,61 +1,106 @@
-- Return the estimated size of user data. Used for quota checking.
CREATE OR REPLACE FUNCTION CDB_UserDataSize(schema_name TEXT)
RETURNS bigint AS
$$
DECLARE
total_size INT8;
BEGIN
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()
),
user_tables AS (
SELECT table_name FROM information_schema.tables
WHERE table_catalog = current_database() AND table_schema = schema_name
AND table_name != 'spatial_ref_sys'
AND table_name != 'cdb_tablemetadata'
AND table_type = 'BASE TABLE'
),
table_cat AS (
SELECT
table_name,
EXISTS(select * from raster_tables where o_table_name = table_name) AS is_overview,
EXISTS(SELECT * FROM raster_tables WHERE r_table_name = table_name) AS is_raster
FROM user_tables
),
sizes AS (
SELECT COALESCE(INT8(SUM(pg_total_relation_size('"' || schema_name || '"."' || 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;
IF total_size IS NOT NULL THEN
RETURN total_size;
ELSE
RETURN 0;
END IF;
END;
$$
LANGUAGE 'plpgsql' VOLATILE;
-- Return the estimated size of user data. Used for quota checking.
-- Implicit schema version for backwards compatibility
CREATE OR REPLACE FUNCTION CDB_UserDataSize()
RETURNS bigint AS
$$
-- TODO: double check this query. Maybe use CDB_TableMetadata for lookup ?
-- also, it's "table_name" sounds sensible to search_path
--
-- NOTE: division by 2 is an hack for the_geom_webmercator
--
SELECT coalesce(int8(sum(pg_total_relation_size(quote_ident(table_name))) / 2), 0)
AS quota
FROM information_schema.tables
WHERE table_catalog = current_database() AND table_schema = 'public'
AND table_name != 'spatial_ref_sys'
AND table_name != 'cdb_tablemetadata'
AND table_type = 'BASE TABLE';
SELECT public.CDB_UserDataSize('public');
$$
LANGUAGE 'sql' VOLATILE;
-- Triggers cannot have declared arguments: pbfact float8, qmax int8, schema_name text
CREATE OR REPLACE FUNCTION CDB_CheckQuota()
RETURNS trigger AS
$$
DECLARE
pbfact float8;
qmax int8;
schema_name text;
dice float8;
quota float8;
BEGIN
IF TG_NARGS = 3 THEN
schema_name := TG_ARGV[2];
IF cartodb.schema_exists(schema_name) = false THEN
RAISE EXCEPTION 'Invalid schema name "%"', schema_name;
END IF;
ELSE
schema_name := 'public';
END IF;
-- By default try to use quota function, and if not present then rely on the one specified by params
BEGIN
EXECUTE FORMAT('SELECT %I._CDB_UserQuotaInBytes();', schema_name) INTO qmax;
EXCEPTION WHEN undefined_function THEN
BEGIN
IF TG_NARGS >= 2 AND TG_ARGV[1] <> '-1' THEN
qmax := TG_ARGV[1];
ELSE
RAISE EXCEPTION 'Missing "%"._CDB_UserQuotaInBytes()', schema_name;
END IF;
END;
END;
pbfact := TG_ARGV[0];
dice := random();
-- RAISE DEBUG 'CDB_CheckQuota enter: pbfact=% dice=%', pbfact, dice;
dice := random();
IF dice < pbfact THEN
RAISE DEBUG 'Checking quota on table % (dice:%, needed:<%)', TG_RELID::text, dice, pbfact;
BEGIN
qmax := public._CDB_UserQuotaInBytes();
EXCEPTION WHEN undefined_function THEN
IF TG_NARGS > 1 THEN
RAISE NOTICE 'Using quota specified via trigger parameter';
qmax := TG_ARGV[1];
ELSE
RAISE EXCEPTION 'Missing _CDB_UserQuotaInBytes(), and no quota provided as parameter';
END IF;
END;
IF qmax = 0 THEN
RETURN NEW;
END IF;
SELECT public.CDB_UserDataSize() INTO quota;
SELECT public.CDB_UserDataSize(schema_name) INTO quota;
IF quota > qmax THEN
RAISE EXCEPTION 'Quota exceeded by %KB', (quota-qmax)/1024;
RAISE EXCEPTION 'Quota exceeded by %KB', (quota-qmax)/1024;
ELSE RAISE DEBUG 'User quota in bytes: % < % (max allowed)', quota, qmax;
END IF;
-- ELSE RAISE DEBUG 'Not checking quota on table % (dice:%, needed:<%)', TG_RELID::text, dice, pbfact;
END IF;
RETURN NEW;
@@ -63,26 +108,33 @@ END;
$$
LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION CDB_SetUserQuotaInBytes(bytes int8)
CREATE OR REPLACE FUNCTION CDB_SetUserQuotaInBytes(schema_name text, bytes int8)
RETURNS int8 AS
$$
DECLARE
current_quota int8;
sql text;
BEGIN
BEGIN
current_quota := public._CDB_UserQuotaInBytes();
EXCEPTION WHEN undefined_function THEN
current_quota := 0;
END;
IF cartodb.schema_exists(schema_name::text) = false THEN
RAISE EXCEPTION 'Invalid schema name "%"', schema_name::text;
END IF;
sql := 'CREATE OR REPLACE FUNCTION public._CDB_UserQuotaInBytes() '
sql := 'CREATE OR REPLACE FUNCTION "' || schema_name::text || '"._CDB_UserQuotaInBytes() '
|| 'RETURNS int8 AS $X$ SELECT ' || bytes
|| '::int8 $X$ LANGUAGE sql IMMUTABLE';
EXECUTE sql;
return current_quota;
return bytes;
END
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;
CREATE OR REPLACE FUNCTION CDB_SetUserQuotaInBytes(bytes int8)
RETURNS int8 AS
$$
BEGIN
return public.CDB_SetUserQuotaInBytes('public', bytes);
END;
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;

View File

@@ -1,8 +1,9 @@
-- Convert string to date
--
DROP FUNCTION IF EXISTS CDB_StringToDate(character varying);
CREATE OR REPLACE FUNCTION CDB_StringToDate(input character varying)
RETURNS date AS $$
DECLARE output DATE;
RETURNS TIMESTAMP AS $$
DECLARE output TIMESTAMP;
BEGIN
BEGIN
output := input::date;

View File

@@ -5,9 +5,9 @@ CREATE TABLE IF NOT EXISTS
updated_at timestamp with time zone not null default now()
);
-- Anyone can see this, but updates are only possible trough
-- the security definer trigger
GRANT SELECT ON public.CDB_TableMetadata TO public;
-- No one can see this
-- Updates are only possible trough the security definer trigger
-- GRANT SELECT ON public.CDB_TableMetadata TO public;
--
-- Trigger logging updated_at in the CDB_TableMetadata
@@ -118,3 +118,21 @@ CREATE TRIGGER table_modified AFTER INSERT OR UPDATE
ON CDB_TableMetadata FOR EACH ROW EXECUTE PROCEDURE
_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)
RETURNS void AS
$$
BEGIN
WITH upsert AS (
UPDATE public.cdb_tablemetadata
SET updated_at = NOW()
WHERE tabname = tablename
RETURNING *
)
INSERT INTO public.cdb_tablemetadata (tabname, updated_at)
SELECT tablename, NOW()
WHERE NOT EXISTS (SELECT * FROM upsert);
END;
$$
LANGUAGE 'plpgsql' VOLATILE STRICT;

View File

@@ -5,35 +5,22 @@
--
-- 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')
RETURNS SETOF information_schema.sql_identifier
RETURNS SETOF name
AS $$
WITH usertables AS (
-- TODO: query CDB_TableMetadata for this ?
-- See http://github.com/CartoDB/cartodb/issues/254#issuecomment-26044777
SELECT table_name as t
FROM information_schema.tables
WHERE
table_type='BASE TABLE'
AND table_schema='public'
AND table_name NOT IN (
'cdb_tablemetadata',
'spatial_ref_sys'
)
), perms AS (
SELECT t, has_table_privilege('public', 'public'||'.'||t, 'SELECT') as p
FROM usertables
)
SELECT t FROM perms
WHERE (
p = CASE WHEN $1 = 'private' THEN false
WHEN $1 = 'public' THEN true
ELSE not p -- none
END
OR $1 = 'all'
)
AND has_table_privilege('public'||'.'||t, 'SELECT')
;
SELECT c.relname
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND c.relname NOT IN ('cdb_tablemetadata', 'spatial_ref_sys')
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'topology')
AND CASE WHEN perm = 'public' THEN has_table_privilege('publicuser', c.oid, 'SELECT')
WHEN perm = 'private' THEN has_table_privilege(current_user, c.oid, 'SELECT') AND NOT has_table_privilege('publicuser', c.oid, 'SELECT')
WHEN perm = 'all' THEN has_table_privilege(current_user, c.oid, 'SELECT') OR has_table_privilege('publicuser', c.oid, 'SELECT')
ELSE false END;
$$ LANGUAGE 'sql';
-- This is to migrate from pre-0.2.0 version

View File

@@ -0,0 +1,31 @@
CREATE OR REPLACE FUNCTION cartodb.CDB_ZoomFromScale(scaleDenominator numeric) RETURNS int AS $$
BEGIN
CASE
WHEN scaleDenominator > 1000000000 THEN RETURN 0;
WHEN scaleDenominator <= 1000000000 AND scaleDenominator > 500000000 THEN RETURN 1;
WHEN scaleDenominator <= 500000000 AND scaleDenominator > 200000000 THEN RETURN 2;
WHEN scaleDenominator <= 200000000 AND scaleDenominator > 100000000 THEN RETURN 3;
WHEN scaleDenominator <= 100000000 AND scaleDenominator > 50000000 THEN RETURN 3;
WHEN scaleDenominator <= 50000000 AND scaleDenominator > 25000000 THEN RETURN 4;
WHEN scaleDenominator <= 25000000 AND scaleDenominator > 12500000 THEN RETURN 5;
WHEN scaleDenominator <= 12500000 AND scaleDenominator > 6500000 THEN RETURN 6;
WHEN scaleDenominator <= 6500000 AND scaleDenominator > 3000000 THEN RETURN 7;
WHEN scaleDenominator <= 3000000 AND scaleDenominator > 1500000 THEN RETURN 8;
WHEN scaleDenominator <= 1500000 AND scaleDenominator > 750000 THEN RETURN 9;
WHEN scaleDenominator <= 750000 AND scaleDenominator > 400000 THEN RETURN 10;
WHEN scaleDenominator <= 400000 AND scaleDenominator > 200000 THEN RETURN 11;
WHEN scaleDenominator <= 200000 AND scaleDenominator > 100000 THEN RETURN 12;
WHEN scaleDenominator <= 100000 AND scaleDenominator > 50000 THEN RETURN 13;
WHEN scaleDenominator <= 50000 AND scaleDenominator > 25000 THEN RETURN 14;
WHEN scaleDenominator <= 25000 AND scaleDenominator > 12500 THEN RETURN 15;
WHEN scaleDenominator <= 12500 AND scaleDenominator > 5000 THEN RETURN 16;
WHEN scaleDenominator <= 5000 AND scaleDenominator > 2500 THEN RETURN 17;
WHEN scaleDenominator <= 2500 AND scaleDenominator > 1500 THEN RETURN 18;
WHEN scaleDenominator <= 1500 AND scaleDenominator > 750 THEN RETURN 19;
WHEN scaleDenominator <= 750 AND scaleDenominator > 500 THEN RETURN 20;
WHEN scaleDenominator <= 500 AND scaleDenominator > 250 THEN RETURN 21;
WHEN scaleDenominator <= 250 AND scaleDenominator > 100 THEN RETURN 22;
WHEN scaleDenominator <= 100 THEN RETURN 23;
END CASE;
END
$$ LANGUAGE plpgsql IMMUTABLE;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,8 @@ SELECT cartodb.cdb_enable_ddl_hooks();
create schema c;
SELECT CDB_SetUserQuotaInBytes('c', 0);
CREATE USER cartodb_postgresql_unpriv_user;
GRANT ALL ON SCHEMA c to cartodb_postgresql_unpriv_user;
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
@@ -16,7 +18,7 @@ SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
----------------------
-- CREATE TABLE
----------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select 1 as i INTO c.t3;
select
@@ -26,11 +28,13 @@ select
i
from c.t3;
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)) as age
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
-- Table with cartodb_id field, see
-- http://github.com/CartoDB/cartodb-postgresql/issues/32
select 1 as cartodb_id INTO c.t4;
@@ -39,6 +43,8 @@ select
NOW() - updated_at < '1 secs' as "u<1s",
the_geom, the_geom_webmercator
from c.t4;
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)) as age
@@ -47,6 +53,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t4'::regclass;
----------------------------
-- ALTER TABLE RENAME COLUMN
----------------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
alter table c.t3 rename column the_geom_webmercator to webmerc;
@@ -58,11 +65,13 @@ select
i, webmerc
from c.t3;
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
alter table c.t3 rename column the_geom_webmercator to webmerc2;
@@ -73,6 +82,7 @@ select
i, webmerc, webmerc2
from c.t3;
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs
@@ -81,7 +91,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
----------------------------
-- ALTER TABLE DROP COLUMN
----------------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
alter table c.t3 drop column the_geom_webmercator;
@@ -92,6 +102,7 @@ select
i, webmerc, webmerc2
from c.t3;
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs
@@ -100,7 +111,7 @@ FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
----------------------------
-- ALTER TABLE ADD COLUMN
----------------------------
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
select pg_sleep(.1);
alter table c.t3 add column id2 int;
@@ -111,6 +122,7 @@ select
i, webmerc, webmerc2, id2
from c.t3;
RESET SESSION AUTHORIZATION;
select
tabname::text,
round(extract('secs' from now() - updated_at)*10) as agecs

View File

@@ -1,5 +1,5 @@
SET client_min_messages TO error;
\set VERBOSITY terse;
\set VERBOSITY default
CREATE OR REPLACE FUNCTION CDB_CartodbfyTableCheck(tabname regclass, label text)
RETURNS text AS
@@ -30,7 +30,7 @@ BEGIN
END IF;
END LOOP;
PERFORM CDB_CartodbfyTable(tabname);
PERFORM CDB_CartodbfyTable('public', tabname);
sql := 'INSERT INTO ' || tabname::text || '(the_geom) values ( CDB_LatLng(2,1) ) RETURNING cartodb_id';
EXECUTE sql INTO STRICT id;
@@ -132,7 +132,7 @@ LANGUAGE 'plpgsql';
-- table with single non-geometrical column
CREATE TABLE t AS SELECT 1::int as a;
SELECT CDB_CartodbfyTable('t'); -- should fail
SELECT CDB_CartodbfyTable('public', 't'); -- should fail
SELECT CDB_SetUserQuotaInBytes(0); -- Set user quota to infinite
SELECT CDB_CartodbfyTableCheck('t', 'single non-geometrical column');
DROP TABLE t;

View File

@@ -2,6 +2,8 @@ SET
CREATE FUNCTION
SELECT 1
ERROR: Please set user quota before cartodbfying tables.
CONTEXT: SQL statement "SELECT cartodb._CDB_check_prerequisites(schema_name, reloid)"
PL/pgSQL function cdb_cartodbfytable(text,regclass) line 7 at PERFORM
0
single non-geometrical column cartodbfied fine
DROP TABLE

View File

@@ -0,0 +1,4 @@
WITH data AS (
SELECT pow(x,3)::numeric x FROM generate_series(-100,100) x
)
SELECT CDB_DistType(array_agg(x)) FROM data

View File

@@ -0,0 +1 @@
A

View File

@@ -0,0 +1,20 @@
-- a - j add up to 89%, k-m add up to 11%
WITH a As (
SELECT (
repeat('a',12) ||
repeat('b',11) ||
repeat('c',11) ||
repeat('d',10) ||
repeat('e',10) ||
repeat('f',9) ||
repeat('g',8) ||
repeat('h',7) ||
repeat('i',6) ||
repeat('j',5) ||
repeat('k',4) ||
repeat('l',4) ||
repeat('m',3)
)::text AS x
)
SELECT CDB_DistinctMeasure(string_to_array(x,null),0.90) from a

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,5 @@
WITH data AS (
SELECT array_agg(x::numeric) s FROM generate_series(1,300) x
WHERE x % 5 != 0 AND x % 7 != 0
)
SELECT round(unnest(CDB_EqualIntervalBins(s, 7)),7) FROM data

View File

@@ -0,0 +1,7 @@
43.5714286
86.1428571
128.7142857
171.2857143
213.8571429
256.4285714
299.0000000

4
test/CDB_MathTest.sql Normal file
View File

@@ -0,0 +1,4 @@
SELECT cdb_math_mode(a) from unnest(ARRAY[1,2,2,3]) a;
SELECT cdb_math_mode(a) from unnest(ARRAY[1,2,3]) a;
SELECT cdb_math_mode(a) from unnest(ARRAY[1]) a;

3
test/CDB_MathTest_expect Normal file
View File

@@ -0,0 +1,3 @@
2
1
1

View File

@@ -1,7 +1,7 @@
16
13
29
43
57
71
83
86
99

View File

@@ -25,3 +25,15 @@ WITH inp AS ( select 'create table test (a int); insert into test values (1); se
WITH inp AS ( select 'WITH a AS (select * from pg_class) select * from a'::text as q )
SELECT q, CDB_QueryTables(q) from inp;
CREATE SCHEMA sc;
create table sc.test (a int);
insert into sc.test values (1);
WITH inp AS ( select 'select * from sc.test'::text as q )
SELECT q, CDB_QueryTables(q) from inp;
DROP TABLE sc.test;
DROP SCHEMA sc;
WITH inp AS ( select 'SELECT
* FROM geometry_columns'::text as q )
SELECT q, CDB_QueryTables(q) from inp;

View File

@@ -1,11 +1,23 @@
SELECT * FROM geometry_columns|{pg_attribute,pg_class,pg_namespace,pg_type}
SELECT a.attname FROM pg_class c JOIN pg_attribute a on (a.attrelid = c.oid)|{pg_attribute,pg_class}
SELECT * FROM geometry_columns|{pg_catalog.pg_attribute,pg_catalog.pg_class,pg_catalog.pg_namespace,pg_catalog.pg_type}
SELECT a.attname FROM pg_class c JOIN pg_attribute a on (a.attrelid = c.oid)|{pg_catalog.pg_attribute,pg_catalog.pg_class}
CREATE table "my'tab;le" as select 1|{}
SELECT a.oid, b.oid FROM pg_class a, pg_class b|{pg_class}
SELECT a.oid, b.oid FROM pg_class a, pg_class b|{pg_catalog.pg_class}
SELECT 1 as col1; select 2 as col2|{}
WARNING: CDB_QueryTables cannot explain query: select 1 from nonexistant (42P01: relation "nonexistant" does not exist)
CONTEXT: PL/pgSQL function cdb_querytables(text) line 3 at RETURN
ERROR: relation "nonexistant" does not exist
begin; select * from pg_class; commit;|{pg_class}
CONTEXT: PL/pgSQL function cdb_querytables(text) line 3 at RETURN
begin; select * from pg_class; commit;|{pg_catalog.pg_class}
WARNING: CDB_QueryTables cannot explain query: select * from test (42P01: relation "test" does not exist)
CONTEXT: PL/pgSQL function cdb_querytables(text) line 3 at RETURN
ERROR: relation "test" does not exist
WITH a AS (select * from pg_class) select * from a|{pg_class}
CONTEXT: PL/pgSQL function cdb_querytables(text) line 3 at RETURN
WITH a AS (select * from pg_class) select * from a|{pg_catalog.pg_class}
CREATE SCHEMA
CREATE TABLE
INSERT 0 1
select * from sc.test|{sc.test}
DROP TABLE
DROP SCHEMA
SELECT
* FROM geometry_columns|{pg_catalog.pg_attribute,pg_catalog.pg_class,pg_catalog.pg_namespace,pg_catalog.pg_type}

View File

@@ -1,16 +1,19 @@
set client_min_messages to ERROR;
\set VERBOSITY terse
set client_min_messages to error;
\set VERBOSITY default
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);
EXECUTE PROCEDURE CDB_CheckQuota(1, 1, 'public');
INSERT INTO big VALUES (1); -- allowed, check runs before
INSERT INTO big VALUES (1); -- disallowed, quota exceeds before
SELECT CDB_SetUserQuotaInBytes(0);
SELECT CDB_CartodbfyTable('big');
INSERT INTO big SELECT generate_series(1,1024);
SELECT CDB_SetUserQuotaInBytes(8);
INSERT INTO big SELECT generate_series(1,2048);
INSERT INTO big SELECT generate_series(1,2048);
INSERT INTO big SELECT generate_series(1,2048);
SELECT CDB_SetUserQuotaInBytes(2);
INSERT INTO big VALUES (1);
SELECT CDB_SetUserQuotaInBytes(0);
INSERT INTO big VALUES (1);

View File

@@ -5,10 +5,12 @@ INSERT 0 1
ERROR: Quota exceeded by 3.9990234375KB
0
INSERT 0 1024
INSERT 0 2048
INSERT 0 2048
INSERT 0 2048
2
ERROR: Quota exceeded by 567.998046875KB
0
ERROR: Quota exceeded by 103.9921875KB
8
INSERT 0 1
DROP TABLE
SET

View File

@@ -1,11 +1,19 @@
create table pub(a int);
create table prv(a int);
GRANT SELECT ON TABLE pub TO public;
REVOKE SELECT ON TABLE prv FROM public;
CREATE ROLE publicuser LOGIN;
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;
drop table pub;
drop table prv;
-- 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;
\c contrib_regression postgres
DROP TABLE pub;
DROP TABLE prv;
DROP ROLE publicuser;

View File

@@ -1,3 +1,4 @@
CREATE ROLE
CREATE TABLE
CREATE TABLE
GRANT
@@ -8,5 +9,10 @@ all|prv
all|pub
public|pub
private|prv
You are now connected to database "contrib_regression" as user "publicuser".
all_publicuser|pub
public_publicuser|pub
You are now connected to database "contrib_regression" as user "postgres".
DROP TABLE
DROP TABLE
DROP ROLE

View File

@@ -5,5 +5,5 @@ inp AS ( select z0.z, r1.r as x, r2.r as y FROM zoom z0, range r1, range r2 WHER
ext AS ( select x,y,z,CDB_XYZ_Extent(x,y,z) as g from inp )
select X::text || ',' || Y::text || ',' || Z::text as xyz,
st_xmin(g), st_xmax(g), st_ymin(g), st_ymax(g)
from ext;
from ext order by xyz;

View File

@@ -1,13 +1,13 @@
0,0,0|-20037508.5|20037508.5|-20037508.5|20037508.5
0,0,1|-20037508.5|0|0|20037508.5
0,1,1|-20037508.5|0|-20037508.5|0
1,0,1|0|20037508.5|0|20037508.5
1,1,1|0|20037508.5|-20037508.5|0
0,0,2|-20037508.5|-10018754.25|10018754.25|20037508.5
0,1,1|-20037508.5|0|-20037508.5|0
0,1,2|-20037508.5|-10018754.25|0|10018754.25
0,2,2|-20037508.5|-10018754.25|-10018754.25|0
0,3,2|-20037508.5|-10018754.25|-20037508.5|-10018754.25
1,0,1|0|20037508.5|0|20037508.5
1,0,2|-10018754.25|0|10018754.25|20037508.5
1,1,1|0|20037508.5|-20037508.5|0
1,1,2|-10018754.25|0|0|10018754.25
1,2,2|-10018754.25|0|-10018754.25|0
1,3,2|-10018754.25|0|-20037508.5|-10018754.25

View File

@@ -0,0 +1,4 @@
SET SCHEMA 'cartodb';
\i scripts-available/CDB_Quota.sql
\i scripts-available/CDB_TableMetadata.sql
SET SCHEMA 'public';

344
test/extension/test.sh Executable file
View File

@@ -0,0 +1,344 @@
#!/bin/sh
#
# Tests for the extension since version 0.5.0. They don't replace SQL based ones, for now need to run both
#
# It is expected that you run this script as a PostgreSQL superuser, for example:
#
# PGUSER=postgres bash ./test.sh
#
DATABASE=test_extension
CMD='echo psql'
CMD=psql
OK=0
PARTIALOK=0
function set_failed() {
OK=1
PARTIALOK=1
}
function clear_partial_result() {
PARTIALOK=0
}
function sql() {
local ROLE
local QUERY
if [[ $# -ge 2 ]]
then
ROLE="$1"
QUERY="$2"
else
QUERY="$1"
fi
if [ -n "${ROLE}" ]; then
log_debug "Executing query '${QUERY}' as ${ROLE}"
RESULT=`${CMD} -U "${ROLE}" ${DATABASE} -c "${QUERY}" -A -t`
else
log_debug "Executing query '${QUERY}'"
RESULT=`${CMD} ${DATABASE} -c "${QUERY}" -A -t`
fi
CODERESULT=$?
echo ${RESULT}
echo
if [[ ${CODERESULT} -ne 0 ]]
then
echo -n "FAILED TO EXECUTE QUERY: "
log_warning "${QUERY}"
if [[ "$3" != "fails" ]]
then
log_error "${QUERY}"
set_failed
fi
else
if [[ "$3" == "fails" ]]
then
log_error "QUERY: '${QUERY}' was expected to fail and it did not fail"
set_failed
fi
fi
if [[ "$3" == "should" ]]
then
if [[ "${RESULT}" != "$4" ]]
then
log_error "QUERY '${QUERY}' expected result '${4}' but got '${RESULT}'"
set_failed
fi
fi
if [[ "$3" == "should-not" ]]
then
if [[ "${RESULT}" == "$4" ]]
then
log_error "QUERY '${QUERY}' did not expect '${RESULT}'"
set_failed
fi
fi
}
function log_info()
{
echo
echo
echo
_log "1;34m" "$1"
}
function log_error() {
_log "1;31m" "$1"
}
function log_debug() {
_log "1;32m" "> $1"
}
function log_warning() {
_log "0;33m" "$1"
}
function _log() {
echo -e "\033[$1$2\033[0m"
}
# '############################ HELPERS #############################'
function create_role_and_schema() {
local ROLE=$1
sql "CREATE ROLE ${ROLE} LOGIN;"
sql "GRANT CONNECT ON DATABASE \"${DATABASE}\" TO ${ROLE};"
sql "CREATE SCHEMA ${ROLE} AUTHORIZATION ${ROLE};"
sql "SELECT cartodb.CDB_Organization_Create_Member('${ROLE}');"
}
function drop_role_and_schema() {
local ROLE=$1
sql "DROP SCHEMA \"${ROLE}\";"
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM \"${ROLE}\";"
sql "DROP ROLE \"${ROLE}\";"
}
function create_table() {
if [[ $# -ne 2 ]]
then
log_error "create_table requires two arguments: role and table_name"
exit 1
fi
local ROLE="$1"
local TABLENAME="$2"
sql ${ROLE} "CREATE TABLE ${ROLE}.${TABLENAME} ( a int );"
}
function create_raster_table() {
if [[ $# -ne 2 ]]
then
log_error "create_raster_table requires two arguments: role and table_name"
exit 1
fi
local RASTER_COL="the_raster_webmercator"
local ROLE="$1"
local TABLENAME="$2"
local OVERVIEW_TABLENAME="o_2_${TABLENAME}"
sql ${ROLE} "CREATE TABLE ${ROLE}.${TABLENAME} (rid serial PRIMARY KEY, ${RASTER_COL} raster);"
sql ${ROLE} "CREATE TABLE ${ROLE}.${OVERVIEW_TABLENAME} (rid serial PRIMARY KEY, ${RASTER_COL} raster);"
sql ${ROLE} "SELECT AddOverviewConstraints('${ROLE}','${OVERVIEW_TABLENAME}','${RASTER_COL}','${ROLE}','${TABLENAME}','${RASTER_COL}',2);"
}
function drop_raster_table() {
if [[ $# -ne 2 ]]
then
log_error "drop_raster_table requires two arguments: role and table_name"
exit 1
fi
local ROLE="$1"
local TABLENAME="$2"
local OVERVIEW_TABLENAME="o_2_${TABLENAME}"
sql ${ROLE} "DROP TABLE ${ROLE}.${OVERVIEW_TABLENAME};"
sql ${ROLE} "DROP TABLE ${ROLE}.${TABLENAME};"
}
function setup() {
${CMD} -c "CREATE DATABASE ${DATABASE}"
sql "CREATE SCHEMA cartodb;"
sql "GRANT USAGE ON SCHEMA cartodb TO public;"
sql "CREATE EXTENSION postgis;"
log_info "########################### BOOTSTRAP ###########################"
${CMD} -d ${DATABASE} -f scripts-available/CDB_Organizations.sql
# trick to allow forcing a schema when loading SQL files (see: http://bit.ly/1HeLnhL)
${CMD} -d ${DATABASE} -f test/extension/run_at_cartodb_schema.sql
log_info "############################# SETUP #############################"
create_role_and_schema cdb_testmember_1
create_role_and_schema cdb_testmember_2
create_table cdb_testmember_1 foo
sql cdb_testmember_1 'INSERT INTO cdb_testmember_1.foo VALUES (1), (2), (3), (4), (5), (6);'
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);'
sql cdb_testmember_2 'SELECT * FROM cdb_testmember_2.bar;'
}
function tear_down() {
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');"
sql cdb_testmember_1 'DROP TABLE cdb_testmember_1.foo;'
sql cdb_testmember_2 'DROP TABLE cdb_testmember_2.bar;'
sql "DROP SCHEMA cartodb CASCADE"
log_info "########################### TEAR DOWN ###########################"
sql 'DROP SCHEMA cdb_testmember_1;'
sql 'DROP SCHEMA cdb_testmember_2;'
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM cdb_testmember_1;"
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM cdb_testmember_2;"
sql 'DROP ROLE cdb_testmember_1;'
sql 'DROP ROLE cdb_testmember_2;'
${CMD} -c "DROP DATABASE ${DATABASE}"
}
function run_tests() {
local FAILED_TESTS=()
local TESTS
if [[ $# -ge 1 ]]
then
TESTS="$@"
else
TESTS=`cat $0 | perl -n -e'/function (test.*)\(\)/ && print "$1\n"'`
fi
for t in ${TESTS}
do
echo "####################################################################"
echo "#"
echo "# Running: ${t}"
echo "#"
echo "####################################################################"
clear_partial_result
setup
eval ${t}
if [[ ${PARTIALOK} -ne 0 ]]
then
FAILED_TESTS+=(${t})
fi
tear_down
done
if [[ ${OK} -ne 0 ]]
then
echo
log_error "The following tests are failing:"
printf -- '\t%s\n' "${FAILED_TESTS[@]}"
fi
}
#################################################### TESTS GO HERE ####################################################
# Tests quota checking taking into account both geom and raster tables
function test_quota_for_each_user() {
# Normal tables add 4096 bytes
# Raster tables no longer add anything so also count as 4096
sql cdb_testmember_1 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_1'::TEXT);" should 4096
sql cdb_testmember_2 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_2'::TEXT);" should 4096
create_raster_table cdb_testmember_1 raster_1
create_raster_table cdb_testmember_2 raster_2
sql cdb_testmember_1 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_1'::TEXT);" should 20480
sql cdb_testmember_2 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_2'::TEXT);" should 20480
create_raster_table cdb_testmember_1 raster_3
sql cdb_testmember_1 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_1'::TEXT);" should 36864
sql cdb_testmember_2 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_2'::TEXT);" should 20480
drop_raster_table cdb_testmember_1 raster_1
drop_raster_table cdb_testmember_2 raster_2
drop_raster_table cdb_testmember_1 raster_3
sql cdb_testmember_1 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_1'::TEXT);" should 4096
sql cdb_testmember_2 "SELECT cartodb.CDB_UserDataSize('cdb_testmember_2'::TEXT);" should 4096
}
function test_cdb_tablemetadatatouch() {
sql "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 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 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\"');"
# Works with OID
sql postgres "SELECT tabname from CDB_TableMetadata;" should 'touch_example'
sql postgres "SELECT count(*) from CDB_TableMetadata;" should 1
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 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 tabname from CDB_TableMetadata;" should 'touch_example'
sql postgres "SELECT count(*) from CDB_TableMetadata;" should 1
#### test tear down
sql 'DROP TABLE touch_example;'
}
function test_cdb_tablemetadatatouch_fails_for_unexistent_table() {
sql postgres "SELECT CDB_TableMetadataTouch('unexistent_example');" fails
}
function test_cdb_tablemetadatatouch_fails_from_user_without_permission() {
sql "CREATE TABLE touch_example (a int);"
sql postgres "SELECT CDB_TableMetadataTouch('touch_example');"
sql cdb_testmember_1 "SELECT CDB_TableMetadataTouch('touch_example');" fails
sql postgres "GRANT ALL ON CDB_TableMetadata TO cdb_testmember_1;"
sql cdb_testmember_1 "SELECT CDB_TableMetadataTouch('touch_example');"
sql postgres "REVOKE ALL ON CDB_TableMetadata FROM cdb_testmember_1;"
}
#################################################### TESTS END HERE ####################################################
run_tests $@
exit ${OK}

378
test/organization/test.sh Normal file
View File

@@ -0,0 +1,378 @@
#!/bin/sh
#
# It is expected that you run this script
# as a PostgreSQL superuser, for example:
#
# PGUSER=postgres bash ./test.sh
#
DATABASE=test_organizations
CMD='echo psql'
CMD=psql
OK=0
PARTIALOK=0
function set_failed() {
OK=1
PARTIALOK=1
}
function clear_partial_result() {
PARTIALOK=0
}
function sql() {
local ROLE
local QUERY
if [[ $# -ge 2 ]]
then
ROLE="$1"
QUERY="$2"
else
QUERY="$1"
fi
if [ -n "${ROLE}" ]; then
log_debug "Executing query '${QUERY}' as ${ROLE}"
RESULT=`${CMD} -U "${ROLE}" ${DATABASE} -c "${QUERY}" -A -t`
else
log_debug "Executing query '${QUERY}'"
RESULT=`${CMD} ${DATABASE} -c "${QUERY}" -A -t`
fi
CODERESULT=$?
echo ${RESULT}
echo
if [[ ${CODERESULT} -ne 0 ]]
then
echo -n "FAILED TO EXECUTE QUERY: "
log_warning "${QUERY}"
if [[ "$3" != "fails" ]]
then
log_error "${QUERY}"
set_failed
fi
else
if [[ "$3" == "fails" ]]
then
log_error "QUERY: '${QUERY}' was expected to fail and it did not fail"
set_failed
fi
fi
if [[ "$3" == "should" ]]
then
if [[ "${RESULT}" != "$4" ]]
then
log_error "QUERY '${QUERY}' expected result '${4}' but got '${RESULT}'"
set_failed
fi
fi
}
function log_info()
{
echo
echo
echo
_log "1;34m" "$1"
}
function log_error() {
_log "1;31m" "$1"
}
function log_debug() {
_log "1;32m" "> $1"
}
function log_warning() {
_log "0;33m" "$1"
}
function _log() {
echo -e "\033[$1$2\033[0m"
}
# '############################ HELPERS #############################'
function create_role_and_schema() {
local ROLE=$1
sql "CREATE ROLE ${ROLE} LOGIN;"
sql "GRANT CONNECT ON DATABASE \"${DATABASE}\" TO ${ROLE};"
sql "CREATE SCHEMA ${ROLE} AUTHORIZATION ${ROLE};"
sql "SELECT cartodb.CDB_Organization_Create_Member('${ROLE}')"
}
function drop_role_and_schema() {
local ROLE=$1
sql "DROP SCHEMA \"${ROLE}\";"
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM \"${ROLE}\";"
sql "DROP ROLE \"${ROLE}\";"
}
function create_table() {
if [[ $# -ne 2 ]]
then
log_error "create_table requires two arguments: role and table_name"
exit 1
fi
local ROLE="$1"
local TABLENAME="$2"
sql ${ROLE} "CREATE TABLE ${ROLE}.${TABLENAME} ( a int );"
}
function setup() {
${CMD} -c "CREATE DATABASE ${DATABASE}"
sql "CREATE SCHEMA cartodb;"
sql "GRANT USAGE ON SCHEMA cartodb TO public;"
log_info "########################### BOOTSTRAP ###########################"
${CMD} -d ${DATABASE} -f scripts-available/CDB_Organizations.sql
log_info "############################# SETUP #############################"
create_role_and_schema cdb_testmember_1
create_role_and_schema cdb_testmember_2
sql "CREATE ROLE publicuser LOGIN;"
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;'
}
function tear_down() {
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');"
sql cdb_testmember_1 'DROP TABLE cdb_testmember_1.foo;'
sql cdb_testmember_2 'DROP TABLE cdb_testmember_2.bar;'
sql "DROP SCHEMA cartodb CASCADE"
log_info "########################### TEAR DOWN ###########################"
sql 'DROP SCHEMA cdb_testmember_1;'
sql 'DROP SCHEMA cdb_testmember_2;'
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM cdb_testmember_1;"
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM cdb_testmember_2;"
sql "REVOKE CONNECT ON DATABASE \"${DATABASE}\" FROM publicuser;"
sql 'DROP ROLE cdb_testmember_1;'
sql 'DROP ROLE cdb_testmember_2;'
sql 'DROP ROLE publicuser;'
${CMD} -c "DROP DATABASE ${DATABASE}"
}
function run_tests() {
local FAILED_TESTS=()
local TESTS
if [[ $# -ge 1 ]]
then
TESTS="$@"
else
TESTS=`cat $0 | perl -n -e'/function (test.*)\(\)/ && print "$1\n"'`
fi
for t in ${TESTS}
do
echo "####################################################################"
echo "#"
echo "# Running: ${t}"
echo "#"
echo "####################################################################"
clear_partial_result
setup
eval ${t}
if [[ ${PARTIALOK} -ne 0 ]]
then
FAILED_TESTS+=(${t})
fi
tear_down
done
if [[ ${OK} -ne 0 ]]
then
echo
log_error "The following tests are failing:"
printf -- '\t%s\n' "${FAILED_TESTS[@]}"
fi
}
#################################################### TESTS GO HERE ####################################################
function test_member_2_cannot_read_without_permission() {
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' fails
}
function test_member_1_cannot_grant_read_permission_to_other_schema_than_its_one() {
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_2', 'foo', 'cdb_testmember_2')" fails
}
function test_member_1_grants_read_permission_and_member_2_can_read() {
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
}
function test_member_2_cannot_add_table_to_member_1_schema_after_table_permission_added() {
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
sql cdb_testmember_2 "CREATE TABLE cdb_testmember_1.bar ( a int );" fails
}
function test_grant_read_permission_between_two_members() {
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
}
function test_member_2_cannot_write_to_member_1_table() {
sql cdb_testmember_2 'INSERT INTO cdb_testmember_1.foo VALUES (5), (6), (7), (8), (9);' fails
}
function test_member_1_cannot_grant_read_write_permission_to_other_schema_than_its_one() {
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Write_Permission('cdb_testmember_2', 'foo', 'cdb_testmember_2')" fails
}
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_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
}
function test_member_1_removes_access_and_member_2_can_no_longer_query_the_table() {
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 * 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
}
function test_member_1_removes_access_and_member_2_can_no_longer_write_to_the_table() {
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 * 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
}
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_2 VALUES (1), (2), (3), (4), (5);'
sql cdb_testmember_1 'SELECT * FROM cdb_testmember_1.foo_2;'
# gives read permission to both tables
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Add_Table_Read_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
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_2;' should 5
# cdb_testmember_1 removes access to foo table
sql cdb_testmember_1 "SELECT * FROM cartodb.CDB_Organization_Remove_Access_Permission('cdb_testmember_1', 'foo', 'cdb_testmember_2')"
# cdb_testmember_2 should have access to foo_2 table but not to table foo
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo;' fails
sql cdb_testmember_2 'SELECT count(*) FROM cdb_testmember_1.foo_2;' should 5
#### test tear down
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_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
}
function test_user_can_read_when_it_has_permission_after_organization_permission_is_removed() {
create_role_and_schema cdb_testmember_3
# 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_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
# 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_3 'SELECT count(*) FROM cdb_testmember_1.foo;' fails
drop_role_and_schema cdb_testmember_3
}
function test_cdb_querytables_returns_schema_and_table_name() {
sql "CREATE EXTENSION plpythonu;"
${CMD} -d ${DATABASE} -f scripts-available/CDB_QueryStatements.sql
${CMD} -d ${DATABASE} -f 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() {
sql "CREATE EXTENSION plpythonu;"
${CMD} -d ${DATABASE} -f scripts-available/CDB_QueryStatements.sql
${CMD} -d ${DATABASE} -f 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() {
sql "CREATE EXTENSION plpythonu;"
${CMD} -d ${DATABASE} -f scripts-available/CDB_QueryStatements.sql
${CMD} -d ${DATABASE} -f 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}"
}
function test_cdb_usertables_should_work_with_orgusers() {
sql "GRANT USAGE ON SCHEMA cartodb TO publicuser;"
${CMD} -d ${DATABASE} -f scripts-available/CDB_UserTables.sql
sql cdb_testmember_1 "CREATE TABLE test_perms_pub (a int)"
sql cdb_testmember_1 "CREATE TABLE test_perms_priv (a int)"
sql cdb_testmember_1 "GRANT SELECT ON TABLE test_perms_pub TO publicuser"
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
# the following tests are for https://github.com/CartoDB/cartodb-postgresql/issues/98
#sql cdb_testmember_2 "SELECT count(*) FROM CDB_UserTables('all')" should 1
#sql cdb_testmember_2 "SELECT count(*) FROM CDB_UserTables('public')" should 1
#sql cdb_testmember_2 "SELECT count(*) FROM CDB_UserTables('private')" should 0
sql cdb_testmember_1 "DROP TABLE test_perms_pub"
sql cdb_testmember_1 "DROP TABLE test_perms_priv"
}
#################################################### TESTS END HERE ####################################################
run_tests $@
exit ${OK}