Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aff7ae3e2e | ||
|
|
01757a1b6d | ||
|
|
f946bfe4fe | ||
|
|
a098713bfa | ||
|
|
217ff4ffb9 | ||
|
|
a0bb7b1b03 | ||
|
|
4074173c05 | ||
|
|
603b1ceed8 | ||
|
|
0caf6c50dd | ||
|
|
224b2ce395 | ||
|
|
70eeab5748 | ||
|
|
06c05a1d67 | ||
|
|
6567d5441b | ||
|
|
e10d7c3a27 | ||
|
|
b89a752548 | ||
|
|
90fa45b59d | ||
|
|
eb48e26eec | ||
|
|
85e1b92199 | ||
|
|
2dc6ec618f | ||
|
|
3127b9d398 | ||
|
|
91158fafb0 | ||
|
|
16cf70bb4a | ||
|
|
6ea0013343 | ||
|
|
80bd7f8f10 | ||
|
|
bb061981ad | ||
|
|
e289b4725f | ||
|
|
0b8bada553 | ||
|
|
7b48c2765e | ||
|
|
3d0f580fc2 | ||
|
|
d495bd45ba | ||
|
|
f4b51807a1 | ||
|
|
33b4f3718b | ||
|
|
2e186c8565 | ||
|
|
5bc725c8ab | ||
|
|
73906b60da | ||
|
|
03ff0365fd | ||
|
|
76a2cb9132 | ||
|
|
90c16fdb13 | ||
|
|
a80664e72a | ||
|
|
7e2193d3db | ||
|
|
a18cbeb2cd | ||
|
|
0d5f83b3c4 | ||
|
|
7559257d49 | ||
|
|
1198454046 | ||
|
|
32307ceef0 | ||
|
|
54973c01f2 | ||
|
|
eb4564ecee | ||
|
|
abdc39f122 | ||
|
|
d2450ff361 | ||
|
|
a0fe55bd5d | ||
|
|
56fed12392 | ||
|
|
2f26b44142 | ||
|
|
9596e8d9bc | ||
|
|
06036e2fe8 | ||
|
|
e870cf96e0 | ||
|
|
2bce771488 | ||
|
|
cd4ad29e39 | ||
|
|
d59b826d37 | ||
|
|
1c637f8689 | ||
|
|
ecbdb4a430 | ||
|
|
eb84dd04c9 | ||
|
|
276b5cf9ea | ||
|
|
ec34b8ee28 | ||
|
|
0f21db51b6 | ||
|
|
a074f4df5d | ||
|
|
1a12fd3b69 | ||
|
|
78a75cf22d | ||
|
|
1e3c7ace99 | ||
|
|
c210008184 | ||
|
|
0c43fe2731 | ||
|
|
fdbad0f93c | ||
|
|
131aee1503 | ||
|
|
e858ddfa0b | ||
|
|
edf79d9368 | ||
|
|
c26ab0fdd1 | ||
|
|
8dedd2b3f4 | ||
|
|
11834dfdab | ||
|
|
b957635e78 | ||
|
|
a9b9f1ff6c | ||
|
|
d7b560324a | ||
|
|
7b52058265 | ||
|
|
40a163f885 | ||
|
|
01d432c22c | ||
|
|
5285943dbf | ||
|
|
8414bdff31 | ||
|
|
5f96908df4 | ||
|
|
06dd31f4ad | ||
|
|
6a11698a16 | ||
|
|
00bd302f01 | ||
|
|
93d4a6ead0 | ||
|
|
66387c2d44 | ||
|
|
26c95347cd | ||
|
|
6a5e4b0460 | ||
|
|
b8d50204dd | ||
|
|
845ac6dc9a | ||
|
|
350f101c3d | ||
|
|
474de01757 | ||
|
|
1ebaeb76ac | ||
|
|
eb1222729d | ||
|
|
7033a8d9ac | ||
|
|
46bc774d38 | ||
|
|
a9e2d19918 | ||
|
|
d352e1c463 | ||
|
|
5b47c51221 | ||
|
|
ebaded0c7a | ||
|
|
ccdf8de59e | ||
|
|
a2f6cb4c04 | ||
|
|
02f386be33 | ||
|
|
a7c70fe497 | ||
|
|
0a066e0126 | ||
|
|
913640e2dc | ||
|
|
ef7e613d41 | ||
|
|
fd7a8cff71 | ||
|
|
6ea63af974 | ||
|
|
496f079b1c | ||
|
|
4580c9cd5c | ||
|
|
08828b8b7d | ||
|
|
cecba655eb | ||
|
|
b34a752172 | ||
|
|
8ba9e74c4a | ||
|
|
045ede6908 | ||
|
|
cd5c8f2904 | ||
|
|
172ca45ea5 | ||
|
|
6a6a5bc96a | ||
|
|
2ff686de27 | ||
|
|
07326626b7 | ||
|
|
4afc427008 | ||
|
|
2ab059460b | ||
|
|
2a2a7d534a | ||
|
|
8d1bbc63fa | ||
|
|
06ca4f74ee | ||
|
|
a8a2c04d71 | ||
|
|
a5bca7d715 | ||
|
|
552206464e | ||
|
|
2af0b9a57f | ||
|
|
1b5de84c9a | ||
|
|
3b48acf60c | ||
|
|
efb319074a | ||
|
|
c36a5d35c3 | ||
|
|
5a78ee2896 | ||
|
|
480e2d0979 | ||
|
|
c8a1ef6f68 | ||
|
|
ea7c16fbaf | ||
|
|
564ab75d2d | ||
|
|
5010109c7d | ||
|
|
a9f6e26fed | ||
|
|
e60f73a31b | ||
|
|
415a09392f | ||
|
|
e5cc9ef0bd | ||
|
|
f7857945c1 | ||
|
|
554464e43e | ||
|
|
d7c8f3d7e8 | ||
|
|
4c85d7f3ad | ||
|
|
d0e66910a0 | ||
|
|
dcf0b684e2 | ||
|
|
a88bfc51a3 | ||
|
|
21d5ee5813 | ||
|
|
d39a032024 | ||
|
|
4cceb2a21f | ||
|
|
2a8d467949 | ||
|
|
8895d9f3af | ||
|
|
ef376fd243 |
@@ -9,14 +9,6 @@ before_install:
|
||||
- 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
|
||||
- hg clone https://bitbucket.org/malloclabs/pg_schema_triggers &&
|
||||
cd pg_schema_triggers && make && sudo make install && cd -
|
||||
# Preload schema_triggers module
|
||||
# NOTE: might change if we make it part of the installcheck instead
|
||||
- echo "shared_preload_libraries = 'schema_triggers.so'" |
|
||||
sudo tee -a /etc/postgresql/9.3/main/postgresql.conf &&
|
||||
sudo service postgresql restart
|
||||
|
||||
script:
|
||||
- make
|
||||
|
||||
31
Makefile
31
Makefile
@@ -1,14 +1,13 @@
|
||||
# cartodb/Makefile
|
||||
|
||||
EXTENSION = cartodb
|
||||
EXTVERSION = 0.11.4
|
||||
EXTVERSION = 0.14.4
|
||||
|
||||
SED = sed
|
||||
|
||||
CDBSCRIPTS = \
|
||||
scripts-enabled/*.sql \
|
||||
scripts-available/CDB_SearchPath.sql \
|
||||
scripts-available/CDB_DDLTriggers.sql \
|
||||
scripts-available/CDB_ExtensionPost.sql \
|
||||
scripts-available/CDB_ExtensionUtils.sql \
|
||||
scripts-available/CDB_Helper.sql \
|
||||
@@ -56,6 +55,15 @@ UPGRADABLE = \
|
||||
0.11.2 \
|
||||
0.11.3 \
|
||||
0.11.4 \
|
||||
0.11.5 \
|
||||
0.12.0 \
|
||||
0.13.0 \
|
||||
0.13.1 \
|
||||
0.14.0 \
|
||||
0.14.1 \
|
||||
0.14.2 \
|
||||
0.14.3 \
|
||||
0.14.4 \
|
||||
$(EXTVERSION)dev \
|
||||
$(EXTVERSION)next \
|
||||
$(END)
|
||||
@@ -77,10 +85,9 @@ DATA_built = \
|
||||
EXTRA_CLEAN = cartodb_version.sql
|
||||
|
||||
DOCS = README.md
|
||||
REGRESS_NEW = test_ddl_triggers
|
||||
REGRESS_OLD = $(wildcard test/*.sql)
|
||||
REGRESS_LEGACY = $(REGRESS_OLD:.sql=)
|
||||
REGRESS = test_setup $(REGRESS_NEW) $(REGRESS_LEGACY)
|
||||
REGRESS = test_setup $(REGRESS_LEGACY)
|
||||
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
@@ -90,7 +97,8 @@ $(EXTENSION)--$(EXTVERSION).sql: $(CDBSCRIPTS) cartodb_version.sql Makefile
|
||||
echo '\echo Use "CREATE EXTENSION $(EXTENSION)" to load this file. \quit' > $@
|
||||
cat $(CDBSCRIPTS) | \
|
||||
$(SED) -e 's/public\./cartodb./g' \
|
||||
-e 's/:DATABASE_USERNAME/cdb_org_admin/g' >> $@
|
||||
-e 's/:DATABASE_USERNAME/cdb_org_admin/g' \
|
||||
-e "s/''public''/''cartodb''/g" >> $@
|
||||
echo "GRANT USAGE ON SCHEMA cartodb TO public;" >> $@
|
||||
cat cartodb_version.sql >> $@
|
||||
|
||||
@@ -109,6 +117,9 @@ $(EXTENSION).control: $(EXTENSION).control.in Makefile
|
||||
cartodb_version.sql: cartodb_version.sql.in Makefile $(GITDIR)/index
|
||||
$(SED) -e 's/@@VERSION@@/$(EXTVERSION)/' $< > $@
|
||||
|
||||
# Needed for consistent `echo` results with backslashes
|
||||
SHELL = bash
|
||||
|
||||
legacy_regress: $(REGRESS_OLD) Makefile
|
||||
mkdir -p sql/test/
|
||||
mkdir -p expected/test/
|
||||
@@ -116,14 +127,14 @@ legacy_regress: $(REGRESS_OLD) Makefile
|
||||
for f in $(REGRESS_OLD); do \
|
||||
tn=`basename $${f} .sql`; \
|
||||
of=sql/test/$${tn}.sql; \
|
||||
echo '\\set ECHO none' > $${of}; \
|
||||
echo '\\a' >> $${of}; \
|
||||
echo '\\t' >> $${of}; \
|
||||
echo '\\set QUIET off' >> $${of}; \
|
||||
echo '\set ECHO none' > $${of}; \
|
||||
echo '\a' >> $${of}; \
|
||||
echo '\t' >> $${of}; \
|
||||
echo '\set QUIET off' >> $${of}; \
|
||||
cat $${f} | \
|
||||
$(SED) -e 's/public\./cartodb./g' >> $${of}; \
|
||||
exp=expected/test/$${tn}.out; \
|
||||
echo '\\set ECHO none' > $${exp}; \
|
||||
echo '\set ECHO none' > $${exp}; \
|
||||
cat test/$${tn}_expect >> $${exp}; \
|
||||
done
|
||||
|
||||
|
||||
40
NEWS.md
40
NEWS.md
@@ -1,8 +1,46 @@
|
||||
0.14.4 (2016-03-29)
|
||||
* Fix creating overviews for tables with boolean columns
|
||||
[#214](https://github.com/CartoDB/cartodb-postgresql/pull/214)
|
||||
* Fix tests for some systems [#215](https://github.com/CartoDB/cartodb-postgresql/pull/215)
|
||||
|
||||
0.14.3 (2016-03-17)
|
||||
-------------------
|
||||
* Fix for `cartodb_id` bigint casting hardcoded in 0.14.2 to support `cartodb_id` text columns [#210](https://github.com/CartoDB/cartodb-postgresql/pull/210)
|
||||
|
||||
0.14.2 (2016-03-15)
|
||||
-------------------
|
||||
* Support text `cartodb_id` columns in `_CDB_Has_Usable_Primary_ID` [#202](https://github.com/CartoDB/cartodb-postgresql/pull/202)
|
||||
|
||||
0.14.1 (2016-03-07)
|
||||
-------------------
|
||||
* Fully qualify table names in cache cdb_invalidate_varnish calls [#198](https://github.com/CartoDB/cartodb-postgresql/issues/198)
|
||||
|
||||
0.14.0 (2016-02-14)
|
||||
-------------------
|
||||
* Add CDB_ForeignTable.sql to support FDW's [#199](https://github.com/CartoDB/cartodb-postgresql/pull/199)
|
||||
|
||||
0.13.1 (2016-02-01)
|
||||
-------------------
|
||||
* Fix migration fron unpackaged. [193](https://github.com/CartoDB/cartodb-postgresql/pull/193)
|
||||
|
||||
0.13.0 (2016-01-29)
|
||||
-------------------
|
||||
* Add CDB_CreateOverviews, CDB_DropOverviews and CDB_Overviews for vector overviews support. [185](https://github.com/CartoDB/cartodb-postgresql/pull/185)
|
||||
* Convert some simple functions from plpgsql to sql. [188](https://github.com/CartoDB/cartodb-postgresql/pull/188)
|
||||
|
||||
0.12.0 (2016-01-27)
|
||||
-------------------
|
||||
* Remove schema_triggers extension dependency, to ensure compatibility with PostgreSQL 9.5. [#190](https://github.com/CartoDB/cartodb-postgresql/pull/190)
|
||||
* Remove DDL trigger functions (unused by CartoDB).
|
||||
|
||||
0.11.5 (2015-11-27)
|
||||
-------------------
|
||||
* Disable log invalidation time [#178](https://github.com/CartoDB/cartodb-postgresql/pull/178)
|
||||
|
||||
0.11.4 (2015-11-24)
|
||||
-------------------
|
||||
* Fix for existing PK cartodb_id problem [#174](https://github.com/CartoDB/cartodb-postgresql/issues/174)
|
||||
* Add cartodbfication support for column names with embedded points to fix [#6114](https://github.com/CartoDB/cartodb/issues/6114)
|
||||
* Disable log invalidation time [#178](https://github.com/CartoDB/cartodb-postgresql/pull/178)
|
||||
* Add CDB_GreatCircle for creating great circle routes between two points [#171](https://github.com/CartoDB/cartodb-postgresql/pull/171)
|
||||
* Fix to prevent cartodbfication problems [#155](https://github.com/CartoDB/cartodb-postgresql/issues/155)
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -1,21 +1,18 @@
|
||||
cartodb-postgresql
|
||||
==================
|
||||
|
||||
[]
|
||||
[]
|
||||
(http://travis-ci.org/CartoDB/cartodb-postgresql)
|
||||
|
||||
PostgreSQL extension for CartoDB
|
||||
|
||||
See https://github.com/CartoDB/cartodb/wiki/CartoDB-PostgreSQL-extension
|
||||
See [the cartodb-postgresql wiki](https://github.com/CartoDB/cartodb-postgresql/wiki).
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
* PostgreSQL 9.3+ (with plpythonu extension and xml support)
|
||||
* [PostGIS extension](http://postgis.net)
|
||||
* [Schema triggers extension]
|
||||
(https://bitbucket.org/malloclabs/pg_schema_triggers)
|
||||
(or [fork](https://github.com/CartoDB/pg_schema_triggers))
|
||||
|
||||
Install
|
||||
-------
|
||||
@@ -31,10 +28,6 @@ Test installation
|
||||
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
|
||||
|
||||
@@ -47,7 +40,6 @@ In a database that needs to be turned into a "cartodb" user database, run:
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION schema_triggers;
|
||||
CREATE EXTENSION cartodb;
|
||||
```
|
||||
|
||||
@@ -63,7 +55,6 @@ be in the "cartodb" schema.
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION postgis FROM unpackaged;
|
||||
CREATE EXTENSION schema_triggers;
|
||||
CREATE EXTENSION cartodb FROM unpackaged;
|
||||
```
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ comment = 'Turn a database into a cartodb user database.'
|
||||
superuser = true
|
||||
relocatable = false
|
||||
schema = cartodb
|
||||
requires = 'plpythonu, schema_triggers, postgis'
|
||||
requires = 'plpythonu, postgis'
|
||||
|
||||
105
doc/CDB_Overviews.md
Normal file
105
doc/CDB_Overviews.md
Normal file
@@ -0,0 +1,105 @@
|
||||
Overviews are tables that represent a *reduced* version of a dataset intended
|
||||
for efficient rendering at certain zoom levels while preserving the
|
||||
general visual appearance of the complete dataset.
|
||||
|
||||
The *reduction* consists in a fewer number of records
|
||||
(while each overview record may represent an aggregation of multiple records)
|
||||
and/or simplified record geometries.
|
||||
|
||||
Overviews are created through the `CDB_CreateOverviews`.
|
||||
The statement timeout may need to be adjusted before using this function,
|
||||
as overview creation for large tables is a time-consuming operation.
|
||||
|
||||
The `CDB_Overviews` function can be used determine what overview tables
|
||||
exist for a given dataset table and which zoom levels correspond to it.
|
||||
|
||||
The `CDB_DropOverviews` remove a dataset's existing overviews.
|
||||
|
||||
### CDB_CreateOverviews
|
||||
|
||||
Create overviews for vector dataset.
|
||||
|
||||
#### Using the function
|
||||
|
||||
The table for which overviews will be generated should be
|
||||
a Cartodbfied dataset with vector geometry.
|
||||
|
||||
```sql
|
||||
SELECT CDB_CreateOverviews('table_name');
|
||||
--- Generates overview tables for the dataset
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
CDB_CreateOverviews(table_name, ref_z_strategy, reduction_strategy)
|
||||
|
||||
* **table_name** regclass, table for which overviews will be generated
|
||||
* **ref_z_strategy** regproc, optional function that provides
|
||||
the Z-scale strategy.
|
||||
It returns the base Z level for the dataset.
|
||||
It should have these arguments:
|
||||
- **table_name** regclass, table to compute the reference Z scale for
|
||||
* **reduction_strategy** regproc, optional function that provides
|
||||
the reduction strategy to generate an overview table from a table
|
||||
for a smaller scale (higher Z number).
|
||||
It returns the name of the generated table.
|
||||
It should have these arguments:
|
||||
- **base_table_name** regclass, base table to be reduced.
|
||||
- **base_z** integer, base Z level assigned to the base table.
|
||||
- **overview_z** integer, Z level for which to generate the overview.
|
||||
|
||||
### CDB_Overviews
|
||||
|
||||
Obtain overview metadata for a given table (existing overviews).
|
||||
The returned relation will be empty if the table has no overviews.
|
||||
|
||||
The function can be applied to a single table:
|
||||
|
||||
```sql
|
||||
SELECT CDB_Overviews('table_name');
|
||||
--- Return existing overview Z levels and corresponding tables
|
||||
```
|
||||
|
||||
Or to multiple tables passed as an array; this can be used
|
||||
to obtain the overviews that can be applied to a query by
|
||||
combining it with `CDB_QueryTablesText`:
|
||||
|
||||
```sql
|
||||
SELECT CDB_Overviews(CDB_QueryTablesText('SELECT * FROM table1, table2'));
|
||||
--- Return existing overview Z levels and corresponding tables
|
||||
```
|
||||
|
||||
The result of `CDB_Overviews` has three columns:
|
||||
|
||||
| base_table | z | overview_table |
|
||||
|------------+---+----------------|
|
||||
| table1 | 1 | table1_ov1 |
|
||||
| table1 | 2 | table1_ov2 |
|
||||
| table1 | 4 | table1_ov4 |
|
||||
| table2 | 1 | table1_ov1 |
|
||||
| table2 | 2 | table1_ov2 |
|
||||
|
||||
#### Arguments
|
||||
|
||||
CDB_Overviews(table_name)
|
||||
|
||||
* **table_name** regclass, oid of table to obtain existing overviews for
|
||||
|
||||
CDB_Overviews(table_names)
|
||||
|
||||
* **table_names** regclass[], array of table oids
|
||||
|
||||
|
||||
### CDB_DropOverviews
|
||||
|
||||
Remove the overviews of a table, if present.
|
||||
|
||||
```sql
|
||||
SELECT CDB_DropOverviews('table_name');
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
CDB_Overviews(table_name)
|
||||
|
||||
* **table_name** regclass, table for which to drop existing overviews.
|
||||
@@ -1,29 +0,0 @@
|
||||
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
|
||||
68
doc/CartoDB-user-table.rst
Normal file
68
doc/CartoDB-user-table.rst
Normal file
@@ -0,0 +1,68 @@
|
||||
CartoDB User Table
|
||||
==================
|
||||
|
||||
Introduction
|
||||
----------
|
||||
A CartoDB user table is a table with a well-known set of columns and a well-known set of triggers attached on.
|
||||
|
||||
Columns
|
||||
----------
|
||||
The required columns of a CartoDB table are:
|
||||
|
||||
- ``cartodb_id``
|
||||
- This column will be used as the primary key of the table and it has a sequence as default value
|
||||
- Its values must be integer, non-zero, non-null and unique
|
||||
- B-Tree indexed
|
||||
- ``the_geom``
|
||||
- This column stores the main geometric features of a table
|
||||
- The type of the column in the Postgres database is ``geometry(Geometry,4326)```
|
||||
- GiST indexed
|
||||
- geometry, GiST indexed, constrained (see below)
|
||||
- ``the_geom_webmercator``
|
||||
- This column stores the geometries used for rendering purposes
|
||||
- The type of the column in the Postgres database is ``geometry(Geometry,3857)``
|
||||
- GiST indexed
|
||||
- This column is automatically updated by the system when the ``the_geom`` column is updated or when there is an insertion of a new row into the table (See triggers below)
|
||||
|
||||
The values of ``the_geom`` and ``the_geom_webmercator`` must be two-dimensional Points, MultiLineStrings or MultiPolygons. Different geometric types in a CartoDB table are not supported.
|
||||
|
||||
Described table example
|
||||
^^^^^^^^^^
|
||||
::
|
||||
|
||||
Column | Type | Modifiers
|
||||
----------------------+-------------------------+--------------------------------------------------------
|
||||
cartodb_id | bigint | not null default nextval('t_cartodb_id_seq'::regclass)
|
||||
the_geom | geometry(Geometry,4326) |
|
||||
the_geom_webmercator | geometry(Geometry,3857) |
|
||||
Indexes:
|
||||
"table_name_pkey" PRIMARY KEY, btree (cartodb_id)
|
||||
"table_name_the_geom_idx" gist (the_geom)
|
||||
"table_name_the_geom_webmercator_idx" gist (the_geom_webmercator)
|
||||
|
||||
Triggers
|
||||
----------
|
||||
The triggers generated in each CartoDB table are:
|
||||
|
||||
- ``track_updates`` after modifying statement updates ``cdb_tablemetadata``
|
||||
- ``test_quota`` before changing statement to forbid if overquota
|
||||
- ``test_quota_per_row`` before insert ot update row to forbid if overquota (checked on a probabilistic basis)
|
||||
- ``update_the_geom_webmercator`` before insert or update row to maintain the ``the_geom_webmercator`` updated with the contents in ``the_geom``
|
||||
|
||||
Described triggers example
|
||||
^^^^^^^^^^
|
||||
::
|
||||
|
||||
test_quota BEFORE INSERT OR UPDATE ON t FOR EACH STATEMENT EXECUTE PROCEDURE cdb_checkquota('0.1', '-1', 'public')
|
||||
test_quota_per_row BEFORE INSERT OR UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE cdb_checkquota('0.001', '-1', 'public')
|
||||
track_updates AFTER INSERT OR DELETE OR UPDATE OR TRUNCATE ON t FOR EACH STATEMENT EXECUTE PROCEDURE cdb_tablemetadata_trigger()
|
||||
update_the_geom_webmercator_trigger BEFORE INSERT OR UPDATE OF the_geom ON t FOR EACH ROW EXECUTE PROCEDURE _cdb_update_the_geom_webmercator()
|
||||
|
||||
|
||||
Further details
|
||||
----------
|
||||
|
||||
Some conversions will be attempted to perform upon cartodbfication when certain fields appear:
|
||||
|
||||
- ``cartodb_id``: If found type TEXT will be attempted to cast to integer. If not casteable, an eror will be raised.
|
||||
- ``the_geom``: If found type TEXT will be attempted to cast to geometry(Geometry,4326).
|
||||
@@ -20,29 +20,4 @@ The CartoDB PostgreSQL extension is a module to load into each CartoDB user data
|
||||
|
||||
# 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).
|
||||
|
||||
|
||||
@@ -1,59 +1,63 @@
|
||||
CartoDBfy Requirements
|
||||
======================
|
||||
|
||||
Introduction
|
||||
============
|
||||
------------
|
||||
|
||||
This document aims at describing what cartodbfy is and what its formal requirements are, with the following goals in mind:
|
||||
This document aims at describing what the CartoDBfication is and what its formal requirements are, with the following goals in mind:
|
||||
|
||||
- clarify what are the expectations of the "cartodbfycation process".
|
||||
- define an important part of what should be a stable, public API
|
||||
- allow for better testing, which should in turn...
|
||||
- Clarify what are the expectations of the "cartodbfycation process".
|
||||
- Define an important part of what should be a stable, public API
|
||||
- Allow for better testing, which should in turn...
|
||||
- ...ease modifications and increase quality of the code
|
||||
|
||||
|
||||
What is the CartoDBfycation
|
||||
---------------------------
|
||||
|
||||
What is the cartodbfycation
|
||||
===========================
|
||||
|
||||
The cartodbfycation is the process of converting an arbitrary postgres table into a valid CartoDB table, and register it in the system so that it can be used in the CartoDB editor and platform to generate maps and analysis.
|
||||
|
||||
The CartoDBfycation is the process of converting an arbitrary postgres table into a valid CartoDB table, and register it in the system so that it can be used in the CartoDB editor and platform to generate maps and analysis.
|
||||
|
||||
It is performed by running the function ``CDB_CartodbfyTable(reloid REGCLASS)`` over a target table.
|
||||
|
||||
Valid CartoDB tables
|
||||
====================
|
||||
--------------------
|
||||
|
||||
A valid CartoDB table shall meet the following conditions:
|
||||
|
||||
- Have a ``cartodb_id`` integer column as primary key with a sequence as default value
|
||||
- Have a ``cartodb_id`` column with integer, unique, non-zero and non-null values as primary key with a sequence as default value
|
||||
- Have a ``the_geom`` column of type ``Geometry`` with SRID 4326
|
||||
- Have a ``the_geom_webmercator`` column of type ``Geometry`` with SRID 3857
|
||||
- The columns ``the_geom`` and ``the_geom_webmercator`` shall be in sync
|
||||
- The columns ``the_geom`` and ``the_geom_webmercator`` shall be in sync (task of the ``update_the_geom_webmercator`` trigger)
|
||||
|
||||
Additionally, a CartoDB table can contain other columns.
|
||||
|
||||
See the `CartoDB User Table documentation`_
|
||||
|
||||
.. _CartoDB User Table documentation: https://github.com/CartoDB/cartodb-postgresql/blob/master/doc/CartoDB-user-table.md
|
||||
for further information.
|
||||
|
||||
High level requirements
|
||||
=======================
|
||||
-----------------------
|
||||
|
||||
Here is a list of high level requirments for the public function ``CDB_CartodbfyTable()``:
|
||||
|
||||
- A call to ``CDB_CartodbfyTable()`` shall modify/rewrite the table and produce a valid CartoDB table with the same name.
|
||||
- A call to ``CDB_CartodbfyTable()`` shall cause the registration of the table into the platform
|
||||
- It shall be idempotent, meaning that successive calls to ``CDB_CartodbfyTable()`` shall not produce any visible effect in the system.
|
||||
- A call to the function shall modify/rewrite the table and produce a valid CartoDB table with the same name.
|
||||
- A call to the function shall cause the registration of the table into the platform.
|
||||
- It shall be idempotent, meaning that successive calls to the function shall not produce any visible effect in the system.
|
||||
- If there's a column containing a geometry, it shall be used to generate ``the_geom`` and the ``the_geom_webmercator`` columns.
|
||||
- Exporting and re-importing the same table in CartoDB shall produce equivalent tables, with the same features associated to the same ``cartodb_id``'s.
|
||||
|
||||
Note that there should be only one feature per row in the source table. If there's more than one, then which one is used for ``the_geom`` and ``the_geom_webmercator`` fields is not determined.
|
||||
|
||||
Note that there should be only one geometry per row in the source table. If there's more than one, then which one is used for ``the_geom`` and ``the_geom_webmercator`` fields is not determined.
|
||||
|
||||
|
||||
Low-level requirements
|
||||
======================
|
||||
----------------------
|
||||
|
||||
- If the original table contains a valid (unique and not null) ``cartodb_id`` column, it shall be used
|
||||
- If the original table contains a ``the_geom`` column or a ``the_geom_webmercator`` column in the expected projection (EPSG 4326 and EPSG 3857, respectively) they shall be used.
|
||||
- If the original table contains a valid (integer, unique, non-zero and not null) ``cartodb_id`` column, it shall be used
|
||||
- If the original table contains a ``the_geom`` column or a ``the_geom_webmercator`` geometric column in the expected projection (EPSG 4326 and EPSG 3857, respectively) they shall be used.
|
||||
- A modification of a cartodbfy'ed table shall insert or update a row in ``CDB_TableMetadata``
|
||||
- A cartodbfy'ed table shall have a ``btree`` index on ``cartodb_id``
|
||||
- A cartodbfy'ed table shall have ``gist`` indices on ``the_geom`` and ``the_geom_webmercator``
|
||||
- Cartodbfy shall deal with text columns for imports, regarding CartoDB columns
|
||||
|
||||
- Cartodbfy shall deal with text columns for imports, regarding CartoDB columns (``cartodb_id``, ``the_geom``, ``the_geom_webmercator``)
|
||||
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
\set VERBOSITY terse
|
||||
-- Set user quota to infinite
|
||||
SELECT CDB_SetUserQuotaInBytes(0);
|
||||
cdb_setuserquotainbytes
|
||||
-------------------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
-- Enable ddl triggers
|
||||
SELECT cartodb.cdb_enable_ddl_hooks();
|
||||
NOTICE: event trigger "cdb_on_relation_create" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_drop" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_alter_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_drop_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_add_column" does not exist, skipping
|
||||
cdb_enable_ddl_hooks
|
||||
----------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
create schema c;
|
||||
SELECT CDB_SetUserQuotaInBytes('c', 0);
|
||||
cdb_setuserquotainbytes
|
||||
-------------------------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
DROP USER IF EXISTS cartodb_postgresql_unpriv_user;
|
||||
NOTICE: role "cartodb_postgresql_unpriv_user" does not exist, skipping
|
||||
CREATE USER cartodb_postgresql_unpriv_user;
|
||||
GRANT ALL ON SCHEMA c to cartodb_postgresql_unpriv_user;
|
||||
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
|
||||
--SELECT session_user, current_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
|
||||
NOTICE: trigger "test_quota" for table "c.t3" does not exist, skipping
|
||||
NOTICE: trigger "test_quota_per_row" for table "c.t3" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_create" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_drop" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_alter_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_drop_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_add_column" does not exist, skipping
|
||||
NOTICE: cdb_invalidate_varnish(c.t3) called
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)) as age
|
||||
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
|
||||
tabname | age
|
||||
---------+-----
|
||||
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;
|
||||
NOTICE: trigger "track_updates" for table "c.t4" does not exist, skipping
|
||||
NOTICE: trigger "update_the_geom_webmercator_trigger" for table "c.t4" does not exist, skipping
|
||||
NOTICE: trigger "test_quota" for table "c.t4" does not exist, skipping
|
||||
NOTICE: trigger "test_quota_per_row" for table "c.t4" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_create" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_drop" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_alter_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_drop_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_add_column" does not exist, skipping
|
||||
NOTICE: cdb_invalidate_varnish(c.t4) called
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)) as age
|
||||
FROM CDB_TableMetadata WHERE tabname = 'c.t4'::regclass;
|
||||
tabname | age
|
||||
---------+-----
|
||||
c.t4 | 0
|
||||
(1 row)
|
||||
|
||||
----------------------------
|
||||
-- ALTER TABLE RENAME COLUMN
|
||||
----------------------------
|
||||
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
|
||||
select pg_sleep(.1);
|
||||
pg_sleep
|
||||
----------
|
||||
|
||||
(1 row)
|
||||
|
||||
alter table c.t3 rename column the_geom_webmercator to webmerc;
|
||||
NOTICE: column "the_geom_webmercator" of relation "t3" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_create" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_drop" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_alter_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_drop_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_add_column" does not exist, skipping
|
||||
NOTICE: cdb_invalidate_varnish(c.t3) called
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)*10) as agecs
|
||||
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
|
||||
tabname | agecs
|
||||
---------+-------
|
||||
c.t3 | 0
|
||||
(1 row)
|
||||
|
||||
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
|
||||
select pg_sleep(.1);
|
||||
pg_sleep
|
||||
----------
|
||||
|
||||
(1 row)
|
||||
|
||||
alter table c.t3 rename column the_geom_webmercator to webmerc2;
|
||||
NOTICE: column "the_geom_webmercator" of relation "t3" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_create" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_drop" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_alter_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_drop_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_add_column" does not exist, skipping
|
||||
NOTICE: cdb_invalidate_varnish(c.t3) called
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)*10) as agecs
|
||||
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
|
||||
tabname | agecs
|
||||
---------+-------
|
||||
c.t3 | 0
|
||||
(1 row)
|
||||
|
||||
----------------------------
|
||||
-- ALTER TABLE DROP COLUMN
|
||||
----------------------------
|
||||
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
|
||||
select pg_sleep(.1);
|
||||
pg_sleep
|
||||
----------
|
||||
|
||||
(1 row)
|
||||
|
||||
alter table c.t3 drop column the_geom_webmercator;
|
||||
NOTICE: event trigger "cdb_on_relation_create" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_relation_drop" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_alter_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_drop_column" does not exist, skipping
|
||||
NOTICE: event trigger "cdb_on_add_column" does not exist, skipping
|
||||
NOTICE: cdb_invalidate_varnish(c.t3) called
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)*10) as agecs
|
||||
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
|
||||
tabname | agecs
|
||||
---------+-------
|
||||
c.t3 | 0
|
||||
(1 row)
|
||||
|
||||
----------------------------
|
||||
-- ALTER TABLE ADD COLUMN
|
||||
----------------------------
|
||||
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
|
||||
select pg_sleep(.1);
|
||||
pg_sleep
|
||||
----------
|
||||
|
||||
(1 row)
|
||||
|
||||
alter table c.t3 add column id2 int;
|
||||
NOTICE: cdb_invalidate_varnish(c.t3) called
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)*10) as agecs
|
||||
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
|
||||
tabname | agecs
|
||||
---------+-------
|
||||
c.t3 | 0
|
||||
(1 row)
|
||||
|
||||
----------------------------
|
||||
-- DROP TABLE
|
||||
----------------------------
|
||||
RESET SESSION AUTHORIZATION;
|
||||
drop schema c cascade;
|
||||
NOTICE: drop cascades to 3 other objects
|
||||
select count(*) from CDB_TableMetadata;
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
DROP OWNED BY cartodb_postgresql_unpriv_user;
|
||||
DROP ROLE cartodb_postgresql_unpriv_user;
|
||||
DROP FUNCTION _CDB_UserQuotaInBytes();
|
||||
@@ -1,5 +1,4 @@
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION schema_triggers;
|
||||
CREATE EXTENSION plpythonu;
|
||||
CREATE EXTENSION cartodb;
|
||||
CREATE FUNCTION public.cdb_invalidate_varnish(table_name text)
|
||||
|
||||
@@ -470,6 +470,7 @@ $$ LANGUAGE 'plpgsql';
|
||||
-- If the table has both a usable key and usable geometry
|
||||
-- we can no-op on the table copy and just ensure that the
|
||||
-- indexes and triggers are in place
|
||||
DROP FUNCTION IF EXISTS _CDB_Has_Usable_Primary_ID(reloid REGCLASS);
|
||||
CREATE OR REPLACE FUNCTION _CDB_Has_Usable_Primary_ID(reloid REGCLASS)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
@@ -489,114 +490,98 @@ BEGIN
|
||||
-- Do we already have a properly named column?
|
||||
SELECT a.attname, i.indisprimary, i.indisunique, a.attnotnull, a.atttypid
|
||||
INTO rec
|
||||
FROM pg_class c
|
||||
JOIN pg_attribute a ON a.attrelid = c.oid
|
||||
FROM pg_class c
|
||||
JOIN pg_attribute a ON a.attrelid = c.oid
|
||||
JOIN pg_type t ON a.atttypid = t.oid
|
||||
LEFT JOIN pg_index i ON c.oid = i.indrelid AND a.attnum = ANY(i.indkey)
|
||||
WHERE c.oid = reloid
|
||||
WHERE c.oid = reloid
|
||||
AND NOT a.attisdropped
|
||||
AND a.attname = const.pkey;
|
||||
|
||||
-- Found something named right...
|
||||
IF FOUND THEN
|
||||
|
||||
-- And it's an integer column...
|
||||
IF rec.atttypid IN (20,21,23) THEN
|
||||
|
||||
-- And it's a unique primary key! Done!
|
||||
IF (rec.indisprimary OR rec.indisunique) AND rec.attnotnull THEN
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', Format('found good ''%s''', const.pkey);
|
||||
RETURN true;
|
||||
|
||||
-- Check and see if the column values are unique and not null,
|
||||
-- if they are, we can use this column...
|
||||
-- And it's a unique primary key! Done!
|
||||
IF (rec.indisprimary OR rec.indisunique) AND rec.attnotnull THEN
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', Format('found good ''%s''', const.pkey);
|
||||
RETURN true;
|
||||
|
||||
-- Check and see if the column values are unique and not null,
|
||||
-- if they are, we can use this column...
|
||||
ELSE
|
||||
|
||||
-- Assume things are OK until proven otherwise...
|
||||
useable_key := true;
|
||||
BEGIN
|
||||
sql := Format('ALTER TABLE %s ADD CONSTRAINT %s_pk PRIMARY KEY (%s)', reloid::text, const.pkey, const.pkey);
|
||||
sql := sql || ', ' || Format('ADD CONSTRAINT %s_integer CHECK (%s::integer >=0);', const.pkey, const.pkey);
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', sql;
|
||||
EXECUTE sql;
|
||||
EXCEPTION
|
||||
-- Failed unique check...
|
||||
WHEN unique_violation THEN
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', Format('column %s is not unique', const.pkey);
|
||||
useable_key := false;
|
||||
-- Failed not null check...
|
||||
WHEN not_null_violation THEN
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', Format('column %s contains nulls', const.pkey);
|
||||
useable_key := false;
|
||||
-- Failed integer check...
|
||||
WHEN invalid_text_representation THEN
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', Format('invalid input syntax for integer %s', const.pkey);
|
||||
useable_key := false;
|
||||
-- Other fatal error
|
||||
WHEN others THEN
|
||||
PERFORM _CDB_Error(sql, Format('_CDB_Has_Usable_Primary_ID: %s', SQLERRM));
|
||||
END;
|
||||
|
||||
-- Clean up test constraint
|
||||
IF useable_key THEN
|
||||
PERFORM _CDB_SQL(Format('ALTER TABLE %s DROP CONSTRAINT %s_pk', reloid::text, const.pkey));
|
||||
PERFORM _CDB_SQL(Format('ALTER TABLE %s DROP CONSTRAINT %s_integer', reloid::text, const.pkey));
|
||||
|
||||
-- Move non-valid column out of the way
|
||||
ELSE
|
||||
|
||||
-- Assume things are OK until proven otherwise...
|
||||
useable_key := true;
|
||||
|
||||
BEGIN
|
||||
sql := Format('ALTER TABLE %s ADD CONSTRAINT %s_pk PRIMARY KEY (%s)', reloid::text, const.pkey, const.pkey);
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', sql;
|
||||
EXECUTE sql;
|
||||
EXCEPTION
|
||||
-- Failed unique check...
|
||||
WHEN unique_violation THEN
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', Format('column %s is not unique', const.pkey);
|
||||
useable_key := false;
|
||||
-- Failed not null check...
|
||||
WHEN not_null_violation THEN
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', Format('column %s contains nulls', const.pkey);
|
||||
useable_key := false;
|
||||
-- Other fatal error
|
||||
WHEN others THEN
|
||||
PERFORM _CDB_Error(sql, '_CDB_Has_Usable_Primary_ID');
|
||||
END;
|
||||
|
||||
-- Clean up test constraint
|
||||
IF useable_key THEN
|
||||
PERFORM _CDB_SQL(Format('ALTER TABLE %s DROP CONSTRAINT %s_pk', reloid::text, const.pkey));
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %',
|
||||
Format('found non-valid ''%s''', const.pkey);
|
||||
|
||||
-- Move non-unique column out of the way
|
||||
ELSE
|
||||
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %',
|
||||
Format('found non-unique ''%s'', renaming it', const.pkey);
|
||||
|
||||
PERFORM _CDB_SQL(
|
||||
Format('ALTER TABLE %s RENAME COLUMN %s TO %I',
|
||||
reloid::text, rec.attname,
|
||||
cartodb._CDB_Unique_Column_Identifier(NULL, const.pkey, NULL, reloid)),
|
||||
'_CDB_Has_Usable_Primary_ID');
|
||||
|
||||
END IF;
|
||||
|
||||
return useable_key;
|
||||
PERFORM _CDB_Error(sql, Format('_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, %s', const.pkey));
|
||||
|
||||
END IF;
|
||||
|
||||
-- It's not an integer column, we have to rename it
|
||||
ELSE
|
||||
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %',
|
||||
Format('found non-integer ''%s'', renaming it', const.pkey);
|
||||
|
||||
PERFORM _CDB_SQL(
|
||||
Format('ALTER TABLE %s RENAME COLUMN %s TO %I',
|
||||
reloid::text, rec.attname, cartodb._CDB_Unique_Column_Identifier(NULL, const.pkey, NULL, reloid)),
|
||||
'_CDB_Has_Usable_Primary_ID');
|
||||
|
||||
RETURN useable_key;
|
||||
|
||||
END IF;
|
||||
|
||||
|
||||
-- There's no column there named pkey
|
||||
ELSE
|
||||
|
||||
-- Is there another suitable primary key already?
|
||||
-- Is there another integer suitable primary key already?
|
||||
SELECT a.attname
|
||||
INTO rec
|
||||
FROM pg_class c
|
||||
JOIN pg_attribute a ON a.attrelid = c.oid
|
||||
JOIN pg_attribute a ON a.attrelid = c.oid
|
||||
JOIN pg_type t ON a.atttypid = t.oid
|
||||
LEFT JOIN pg_index i ON c.oid = i.indrelid AND a.attnum = ANY(i.indkey)
|
||||
WHERE c.oid = reloid AND NOT a.attisdropped
|
||||
AND i.indisprimary AND i.indisunique AND a.attnotnull AND a.atttypid IN (20,21,23);
|
||||
|
||||
|
||||
-- Yes! Ok, rename it.
|
||||
IF FOUND THEN
|
||||
PERFORM _CDB_SQL(Format('ALTER TABLE %s RENAME COLUMN %s TO %s', reloid::text, rec.attname, const.pkey),'_CDB_Has_Usable_Primary_ID');
|
||||
RETURN true;
|
||||
ELSE
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %',
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %',
|
||||
Format('found no useful column for ''%s''', const.pkey);
|
||||
END IF;
|
||||
|
||||
|
||||
END IF;
|
||||
|
||||
|
||||
RAISE DEBUG 'CDB(_CDB_Has_Usable_Primary_ID): %', 'function complete';
|
||||
|
||||
-- Didn't find re-usable key, so return FALSE
|
||||
RETURN false;
|
||||
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
|
||||
@@ -825,10 +810,10 @@ DECLARE
|
||||
str TEXT;
|
||||
table_srid INTEGER;
|
||||
geom_srid INTEGER;
|
||||
|
||||
|
||||
has_usable_primary_key BOOLEAN;
|
||||
has_usable_pk_sequence BOOLEAN;
|
||||
|
||||
|
||||
BEGIN
|
||||
|
||||
RAISE DEBUG 'CDB(_CDB_Rewrite_Table): %', 'entered function';
|
||||
@@ -839,7 +824,7 @@ BEGIN
|
||||
-- Save the raw schema/table names for later
|
||||
SELECT n.nspname, c.relname, c.relname
|
||||
INTO STRICT relschema, relname, destname
|
||||
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.oid = reloid;
|
||||
|
||||
-- Default the destination to current schema if unspecified
|
||||
@@ -890,7 +875,7 @@ BEGIN
|
||||
|
||||
RAISE DEBUG 'CDB(_CDB_Rewrite_Table): has_usable_geoms %', gc.has_usable_geoms;
|
||||
|
||||
-- We can only avoid a rewrite if both the key and
|
||||
-- We can only avoid a rewrite if both the key and
|
||||
-- geometry are usable
|
||||
|
||||
-- No table re-write is required, BUT a rename is required to
|
||||
@@ -934,7 +919,7 @@ BEGIN
|
||||
|
||||
-- Add cartodb ID!
|
||||
IF has_usable_primary_key THEN
|
||||
sql := sql || const.pkey;
|
||||
sql := sql || const.pkey || '::integer ';
|
||||
ELSE
|
||||
sql := sql || 'nextval(''' || destseq || ''') AS ' || const.pkey;
|
||||
END IF;
|
||||
@@ -1096,7 +1081,7 @@ BEGIN
|
||||
|
||||
-- Run it!
|
||||
PERFORM _CDB_SQL(sql, '_CDB_Rewrite_Table');
|
||||
|
||||
|
||||
-- Set up the primary key sequence
|
||||
-- If we copied the primary key from the original data, we need
|
||||
-- to set the sequence to the maximum value of that key
|
||||
|
||||
@@ -8,8 +8,9 @@ AS $$
|
||||
WHERE table_name = _tn.relname
|
||||
AND table_schema = _sn.nspname
|
||||
AND _tn.oid = $1::oid
|
||||
AND _sn.oid = _tn.relnamespace;
|
||||
|
||||
AND _sn.oid = _tn.relnamespace
|
||||
ORDER BY ordinal_position;
|
||||
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
-- This is to migrate from pre-0.2.0 version
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
-- Table creation
|
||||
-- {
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_handle_create_table ()
|
||||
RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
event_info RECORD;
|
||||
rel RECORD;
|
||||
newtable REGCLASS;
|
||||
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;
|
||||
|
||||
SELECT c.relname, c.relnamespace, c.relkind, n.nspname
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n
|
||||
ON c.relnamespace = n.oid
|
||||
WHERE c.oid = event_info.relation
|
||||
INTO rel;
|
||||
|
||||
RAISE DEBUG 'Relation % of kind % created in table % namespace % (oid %)',
|
||||
event_info.relation, rel.relkind, rel.relname, rel.nspname, rel.relnamespace;
|
||||
|
||||
-- We don't want to react to alters triggered by superuser,
|
||||
IF current_setting('is_superuser') = 'on' THEN
|
||||
RAISE DEBUG 'no ddl trigger for superuser';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
PERFORM cartodb.cdb_disable_ddl_hooks();
|
||||
|
||||
-- CDB_CartodbfyTable must not create tables, or infinite loop will happen
|
||||
newtable := cartodb.CDB_CartodbfyTable(rel.nspname, event_info.relation);
|
||||
|
||||
PERFORM cartodb.cdb_enable_ddl_hooks();
|
||||
|
||||
RAISE DEBUG 'Inserting into cartodb.CDB_TableMetadata';
|
||||
|
||||
-- Add entry to CDB_TableMetadata (should CartodbfyTable do this?)
|
||||
INSERT INTO cartodb.CDB_TableMetadata(tabname, updated_at)
|
||||
VALUES (newtable, now());
|
||||
|
||||
END; $$;
|
||||
-- }
|
||||
|
||||
-- Table drop
|
||||
-- {
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_handle_drop_table ()
|
||||
RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
event_info RECORD;
|
||||
BEGIN
|
||||
event_info := schema_triggers.get_relation_drop_eventinfo();
|
||||
|
||||
-- We're only interested in real relations
|
||||
IF (event_info.old).relkind != 'r' THEN RETURN; END IF;
|
||||
|
||||
RAISE DEBUG 'Relation % of kind % dropped from namespace oid %',
|
||||
event_info.old_relation_oid, (event_info.old).relkind, (event_info.old).relnamespace;
|
||||
|
||||
-- delete record from CDB_TableMetadata (should invalidate varnish)
|
||||
DELETE FROM cartodb.CDB_TableMetadata WHERE tabname = event_info.old_relation_oid;
|
||||
|
||||
END; $$;
|
||||
-- }
|
||||
|
||||
-- Column alter
|
||||
-- {
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_handle_alter_column ()
|
||||
RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
event_info RECORD;
|
||||
rel RECORD;
|
||||
newtable REGCLASS;
|
||||
BEGIN
|
||||
event_info := schema_triggers.get_column_alter_eventinfo();
|
||||
|
||||
SELECT c.relname, c.relnamespace, c.relkind, n.nspname
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n
|
||||
ON c.relnamespace = n.oid
|
||||
WHERE c.oid = event_info.relation
|
||||
INTO rel;
|
||||
|
||||
RAISE DEBUG 'Column % altered by % (superuser? %) in relation % of kind %',
|
||||
(event_info.old).attname, current_user, current_setting('is_superuser'), rel.relname, rel.relkind;
|
||||
|
||||
-- We're only interested in real relations
|
||||
IF rel.relkind != 'r' THEN RETURN; END IF;
|
||||
|
||||
-- We don't want to react to alters triggered by superuser,
|
||||
IF current_setting('is_superuser') = 'on' THEN
|
||||
RAISE DEBUG 'no ddl trigger for superuser';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
PERFORM cartodb.cdb_disable_ddl_hooks();
|
||||
|
||||
newtable := cartodb.CDB_CartodbfyTable(rel.nspname, event_info.relation);
|
||||
|
||||
PERFORM cartodb.cdb_enable_ddl_hooks();
|
||||
|
||||
-- update CDB_TableMetadata.updated_at (should invalidate varnish)
|
||||
UPDATE cartodb.CDB_TableMetadata SET updated_at = NOW(), tabname = newtable
|
||||
WHERE tabname = event_info.relation;
|
||||
|
||||
END; $$;
|
||||
-- }
|
||||
|
||||
-- Column drop
|
||||
-- {
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_handle_drop_column ()
|
||||
RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
event_info RECORD;
|
||||
rel RECORD;
|
||||
newtable REGCLASS;
|
||||
BEGIN
|
||||
event_info := schema_triggers.get_column_drop_eventinfo();
|
||||
|
||||
SELECT c.relname, c.relnamespace, c.relkind, n.nspname
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n
|
||||
ON c.relnamespace = n.oid
|
||||
WHERE c.oid = event_info.relation
|
||||
INTO rel;
|
||||
|
||||
RAISE DEBUG 'Column % drop by % (superuser? %) in relation % of kind %',
|
||||
(event_info.old).attname, current_user, current_setting('is_superuser'), rel.relname, rel.relkind;
|
||||
|
||||
-- We're only interested in real relations
|
||||
IF rel.relkind != 'r' THEN RETURN; END IF;
|
||||
|
||||
-- We don't want to react to drops triggered by superuser,
|
||||
IF current_setting('is_superuser') = 'on' THEN
|
||||
RAISE DEBUG 'no ddl trigger for superuser';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
|
||||
PERFORM cartodb.cdb_disable_ddl_hooks();
|
||||
|
||||
newtable := cartodb.CDB_CartodbfyTable(rel.nspname, event_info.relation);
|
||||
|
||||
PERFORM cartodb.cdb_enable_ddl_hooks();
|
||||
|
||||
-- update CDB_TableMetadata.updated_at (should invalidate varnish)
|
||||
UPDATE cartodb.CDB_TableMetadata SET updated_at = NOW(), tabname = newtable
|
||||
WHERE tabname = event_info.relation;
|
||||
|
||||
END; $$;
|
||||
-- }
|
||||
|
||||
-- Column add
|
||||
-- {
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_handle_add_column ()
|
||||
RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
event_info RECORD;
|
||||
rel RECORD;
|
||||
BEGIN
|
||||
event_info := schema_triggers.get_column_add_eventinfo();
|
||||
|
||||
SELECT c.relname, c.relnamespace, c.relkind, n.nspname
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n
|
||||
ON c.relnamespace = n.oid
|
||||
WHERE c.oid = event_info.relation
|
||||
INTO rel;
|
||||
|
||||
RAISE DEBUG 'Column % added by % (superuser? %) in relation % of kind %',
|
||||
(event_info.new).attname, current_user, current_setting('is_superuser'), rel.relname, rel.relkind;
|
||||
|
||||
-- We're only interested in real relations
|
||||
IF rel.relkind != 'r' THEN RETURN; END IF;
|
||||
|
||||
-- We don't want to react to drops triggered by superuser,
|
||||
IF current_setting('is_superuser') = 'on' THEN
|
||||
RAISE DEBUG 'no ddl trigger for superuser';
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- update CDB_TableMetadata.updated_at (should invalidate varnish)
|
||||
UPDATE cartodb.CDB_TableMetadata SET updated_at = NOW()
|
||||
WHERE tabname = event_info.relation;
|
||||
|
||||
END; $$;
|
||||
-- }
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_disable_ddl_hooks() returns void AS $$
|
||||
DROP EVENT TRIGGER IF EXISTS cdb_on_relation_create;
|
||||
DROP EVENT TRIGGER IF EXISTS cdb_on_relation_drop;
|
||||
DROP EVENT TRIGGER IF EXISTS cdb_on_alter_column;
|
||||
DROP EVENT TRIGGER IF EXISTS cdb_on_drop_column;
|
||||
DROP EVENT TRIGGER IF EXISTS cdb_on_add_column;
|
||||
$$ LANGUAGE sql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.cdb_enable_ddl_hooks() returns void AS $$
|
||||
SELECT cartodb.cdb_disable_ddl_hooks();
|
||||
CREATE EVENT TRIGGER cdb_on_relation_create
|
||||
ON "relation_create" EXECUTE PROCEDURE cartodb.cdb_handle_create_table();
|
||||
CREATE EVENT TRIGGER cdb_on_relation_drop
|
||||
ON "relation_drop" EXECUTE PROCEDURE cartodb.cdb_handle_drop_table();
|
||||
CREATE EVENT TRIGGER cdb_on_alter_column
|
||||
ON "column_alter" EXECUTE PROCEDURE cartodb.cdb_handle_alter_column();
|
||||
CREATE EVENT TRIGGER cdb_on_drop_column
|
||||
ON "column_drop" EXECUTE PROCEDURE cartodb.cdb_handle_drop_column();
|
||||
CREATE EVENT TRIGGER cdb_on_add_column
|
||||
ON "column_add" EXECUTE PROCEDURE cartodb.cdb_handle_add_column();
|
||||
$$ LANGUAGE sql;
|
||||
|
||||
-- Do not enable hooks by default
|
||||
--SELECT cartodb.cdb_enable_ddl_hooks();
|
||||
199
scripts-available/CDB_ForeignTable.sql
Normal file
199
scripts-available/CDB_ForeignTable.sql
Normal file
@@ -0,0 +1,199 @@
|
||||
---------------------------
|
||||
-- FDW MANAGEMENT FUNCTIONS
|
||||
--
|
||||
-- All the FDW settings are read from the `cdb_conf.fdws` entry json file.
|
||||
---------------------------
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Setup_FDW(fdw_name text, config json)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
row record;
|
||||
option record;
|
||||
org_role text;
|
||||
BEGIN
|
||||
-- This function tries to be as idempotent as possible, by not creating anything more than once
|
||||
-- (not even using IF NOT EXIST to avoid throwing warnings)
|
||||
IF NOT EXISTS ( SELECT * FROM pg_extension WHERE extname = 'postgres_fdw') THEN
|
||||
CREATE EXTENSION postgres_fdw;
|
||||
END IF;
|
||||
-- Create FDW first if it does not exist
|
||||
IF NOT EXISTS ( SELECT * FROM pg_foreign_server WHERE srvname = fdw_name)
|
||||
THEN
|
||||
EXECUTE FORMAT('CREATE SERVER %I FOREIGN DATA WRAPPER postgres_fdw', fdw_name);
|
||||
END IF;
|
||||
|
||||
-- Set FDW settings
|
||||
FOR row IN SELECT p.key, p.value from lateral json_each_text(config->'server') p
|
||||
LOOP
|
||||
IF NOT EXISTS (WITH a AS (select split_part(unnest(srvoptions), '=', 1) as options from pg_foreign_server where srvname=fdw_name) SELECT * from a where options = row.key)
|
||||
THEN
|
||||
EXECUTE FORMAT('ALTER SERVER %I OPTIONS (ADD %I %L)', fdw_name, row.key, row.value);
|
||||
ELSE
|
||||
EXECUTE FORMAT('ALTER SERVER %I OPTIONS (SET %I %L)', fdw_name, row.key, row.value);
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- Create user mappings
|
||||
FOR row IN SELECT p.key, p.value from lateral json_each(config->'users') p LOOP
|
||||
-- Check if entry on pg_user_mappings exists
|
||||
|
||||
IF NOT EXISTS ( SELECT * FROM pg_user_mappings WHERE srvname = fdw_name AND usename = row.key ) THEN
|
||||
EXECUTE FORMAT ('CREATE USER MAPPING FOR %I SERVER %I', row.key, fdw_name);
|
||||
END IF;
|
||||
|
||||
-- Update user mapping settings
|
||||
FOR option IN SELECT o.key, o.value from lateral json_each_text(row.value) o LOOP
|
||||
IF NOT EXISTS (WITH a AS (select split_part(unnest(umoptions), '=', 1) as options from pg_user_mappings WHERE srvname = fdw_name AND usename = row.key) SELECT * from a where options = option.key) THEN
|
||||
EXECUTE FORMAT('ALTER USER MAPPING FOR %I SERVER %I OPTIONS (ADD %I %L)', row.key, fdw_name, option.key, option.value);
|
||||
ELSE
|
||||
EXECUTE FORMAT('ALTER USER MAPPING FOR %I SERVER %I OPTIONS (SET %I %L)', row.key, fdw_name, option.key, option.value);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
|
||||
-- Create schema if it does not exist.
|
||||
IF NOT EXISTS ( SELECT * from pg_namespace WHERE nspname=fdw_name) THEN
|
||||
EXECUTE FORMAT ('CREATE SCHEMA %I', fdw_name);
|
||||
END IF;
|
||||
|
||||
-- Give the organization role usage permisions over the schema
|
||||
SELECT cartodb.CDB_Organization_Member_Group_Role_Member_Name() INTO org_role;
|
||||
EXECUTE FORMAT ('GRANT USAGE ON SCHEMA %I TO %I', fdw_name, org_role);
|
||||
|
||||
-- Bring here the remote cdb_tablemetadata
|
||||
IF NOT EXISTS ( SELECT * FROM PG_CLASS WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname=fdw_name) and relname='cdb_tablemetadata') THEN
|
||||
EXECUTE FORMAT ('CREATE FOREIGN TABLE %I.cdb_tablemetadata (tabname text, updated_at timestamp with time zone) SERVER %I OPTIONS (table_name ''cdb_tablemetadata_text'', schema_name ''public'', updatable ''false'')', fdw_name, fdw_name);
|
||||
END IF;
|
||||
EXECUTE FORMAT ('GRANT SELECT ON %I.cdb_tablemetadata TO %I', fdw_name, org_role);
|
||||
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Setup_FDWS()
|
||||
RETURNS VOID AS
|
||||
$$
|
||||
DECLARE
|
||||
row record;
|
||||
BEGIN
|
||||
FOR row IN SELECT p.key, p.value from lateral json_each(cartodb.CDB_Conf_GetConf('fdws')) p LOOP
|
||||
EXECUTE 'SELECT cartodb._CDB_Setup_FDW($1, $2)' USING row.key, row.value;
|
||||
END LOOP;
|
||||
END
|
||||
$$
|
||||
LANGUAGE PLPGSQL;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._CDB_Setup_FDW(fdw_name text)
|
||||
RETURNS void AS
|
||||
$BODY$
|
||||
DECLARE
|
||||
config json;
|
||||
BEGIN
|
||||
SELECT p.value FROM LATERAL json_each(cartodb.CDB_Conf_GetConf('fdws')) p WHERE p.key = fdw_name INTO config;
|
||||
EXECUTE 'SELECT cartodb._CDB_Setup_FDW($1, $2)' USING fdw_name, config;
|
||||
END
|
||||
$BODY$
|
||||
LANGUAGE plpgsql VOLATILE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_Add_Remote_Table(source text, table_name text)
|
||||
RETURNS void AS
|
||||
$$
|
||||
BEGIN
|
||||
PERFORM cartodb._CDB_Setup_FDW(source);
|
||||
EXECUTE FORMAT ('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I;', source, table_name, source, source);
|
||||
--- Grant SELECT to publicuser
|
||||
EXECUTE FORMAT ('GRANT SELECT ON %I.%I TO publicuser;', source, table_name);
|
||||
END
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_Get_Foreign_Updated_At(foreign_table regclass)
|
||||
RETURNS timestamp with time zone AS
|
||||
$$
|
||||
DECLARE
|
||||
remote_table_name text;
|
||||
fdw_schema_name text;
|
||||
time timestamp with time zone;
|
||||
BEGIN
|
||||
-- This will turn a local foreign table (referenced as regclass) to its fully qualified text remote table reference.
|
||||
WITH a AS (SELECT ftoptions FROM pg_foreign_table WHERE ftrelid=foreign_table LIMIT 1),
|
||||
b as (SELECT (pg_options_to_table(ftoptions)).* FROM a)
|
||||
SELECT FORMAT('%I.%I', (SELECT option_value FROM b WHERE option_name='schema_name'), (SELECT option_value FROM b WHERE option_name='table_name'))
|
||||
INTO remote_table_name;
|
||||
|
||||
-- We assume that the remote cdb_tablemetadata is called cdb_tablemetadata and is on the same schema as the queried table.
|
||||
SELECT nspname FROM pg_class c, pg_namespace n WHERE c.oid=foreign_table AND c.relnamespace = n.oid INTO fdw_schema_name;
|
||||
EXECUTE FORMAT('SELECT updated_at FROM %I.cdb_tablemetadata WHERE tabname=%L ORDER BY updated_at DESC LIMIT 1', fdw_schema_name, remote_table_name) INTO time;
|
||||
RETURN time;
|
||||
END
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION cartodb._cdb_dbname_of_foreign_table(reloid oid)
|
||||
RETURNS TEXT AS $$
|
||||
SELECT option_value FROM pg_options_to_table((
|
||||
|
||||
SELECT fs.srvoptions
|
||||
FROM pg_foreign_table ft
|
||||
LEFT JOIN pg_foreign_server fs ON ft.ftserver = fs.oid
|
||||
WHERE ft.ftrelid = reloid
|
||||
|
||||
)) WHERE option_name='dbname';
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
|
||||
-- Return a set of (dbname, schema_name, table_name, updated_at)
|
||||
-- It is aware of foreign tables
|
||||
-- It assumes the local (schema_name, table_name) map to the remote ones with the same name
|
||||
-- Note: dbname is never quoted whereas schema and table names are when needed.
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_QueryTables_Updated_At(query text)
|
||||
RETURNS TABLE(dbname text, schema_name text, table_name text, updated_at timestamptz)
|
||||
AS $$
|
||||
WITH query_tables AS (
|
||||
SELECT unnest(CDB_QueryTablesText(query)) schema_table_name
|
||||
), query_tables_oid AS (
|
||||
SELECT schema_table_name, schema_table_name::regclass::oid AS reloid
|
||||
FROM query_tables
|
||||
),
|
||||
fqtn AS (
|
||||
SELECT
|
||||
(CASE WHEN c.relkind = 'f' THEN cartodb._cdb_dbname_of_foreign_table(query_tables_oid.reloid)
|
||||
ELSE current_database()
|
||||
END)::text AS dbname,
|
||||
quote_ident(n.nspname::text) schema_name,
|
||||
quote_ident(c.relname::text) table_name,
|
||||
c.relkind,
|
||||
query_tables_oid.reloid
|
||||
FROM query_tables_oid, pg_catalog.pg_class c
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.oid = query_tables_oid.reloid
|
||||
)
|
||||
SELECT fqtn.dbname, fqtn.schema_name, fqtn.table_name,
|
||||
(CASE WHEN relkind = 'f' THEN cartodb.CDB_Get_Foreign_Updated_At(reloid)
|
||||
ELSE (SELECT md.updated_at FROM CDB_TableMetadata md WHERE md.tabname = reloid)
|
||||
END) AS updated_at
|
||||
FROM fqtn;
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
|
||||
-- Return the last updated time of a set of tables
|
||||
-- It is aware of foreign tables
|
||||
-- It assumes the local (schema_name, table_name) map to the remote ones with the same name
|
||||
CREATE OR REPLACE FUNCTION cartodb.CDB_Last_Updated_Time(tables text[])
|
||||
RETURNS timestamptz AS $$
|
||||
WITH t AS (
|
||||
SELECT unnest(tables) AS schema_table_name
|
||||
), t_oid AS (
|
||||
SELECT (t.schema_table_name)::regclass::oid as reloid FROM t
|
||||
), t_updated_at AS (
|
||||
SELECT
|
||||
(CASE WHEN relkind = 'f' THEN cartodb.CDB_Get_Foreign_Updated_At(reloid)
|
||||
ELSE (SELECT md.updated_at FROM CDB_TableMetadata md WHERE md.tabname = reloid)
|
||||
END) AS updated_at
|
||||
FROM t_oid
|
||||
LEFT JOIN pg_catalog.pg_class c ON c.oid = reloid
|
||||
) SELECT max(updated_at) FROM t_updated_at;
|
||||
$$ LANGUAGE SQL;
|
||||
@@ -8,16 +8,12 @@
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_LatLng (lat NUMERIC, lng NUMERIC) RETURNS geometry as $$
|
||||
BEGIN
|
||||
-- this function is silly
|
||||
RETURN ST_SetSRID(ST_MakePoint(lng,lat),4326);
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
SELECT ST_SetSRID(ST_MakePoint(lng,lat),4326);
|
||||
$$ language SQL IMMUTABLE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_LatLng (lat FLOAT8, lng FLOAT8) RETURNS geometry as $$
|
||||
BEGIN
|
||||
-- this function is silly
|
||||
RETURN ST_SetSRID(ST_MakePoint(lng,lat),4326);
|
||||
END;
|
||||
$$ language plpgsql IMMUTABLE;
|
||||
SELECT ST_SetSRID(ST_MakePoint(lng,lat),4326);
|
||||
$$ language SQL IMMUTABLE;
|
||||
|
||||
|
||||
685
scripts-available/CDB_Overviews.sql
Normal file
685
scripts-available/CDB_Overviews.sql
Normal file
@@ -0,0 +1,685 @@
|
||||
-- security definer
|
||||
|
||||
-- Pattern that can be used to detect overview tables and Extract
|
||||
-- the intended zoom level from the table name.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTableDiscriminator()
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN '\A_vovw_(\d+)_';
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
-- substring(tablename from _CDB_OverviewTableDiscriminator())
|
||||
|
||||
|
||||
-- Pattern matched by the overview tables of a given base table name.
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTablePattern(base_table TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN _CDB_OverviewTableDiscriminator() || base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
-- tablename SIMILAR TO _CDB_OverviewTablePattern(base_table)
|
||||
|
||||
-- Name of an overview table, given the base table name and the Z level
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTableName(base_table TEXT, z INTEGER)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN '_vovw_' || z::text || '_' || base_table;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Condition to check if a tabla is an overview table of some base table
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_IsOverviewTableOf(base_table TEXT, otable TEXT)
|
||||
RETURNS BOOLEAN
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN otable SIMILAR TO _CDB_OverviewTablePattern(base_table);
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Extract the Z level from an overview table name
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewTableZ(otable TEXT)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN substring(otable from _CDB_OverviewTableDiscriminator())::integer;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Name of the base table corresponding to an overview table
|
||||
-- Scope: private.
|
||||
CREATE OR REPLACE FUNCTION _CDB_OverviewBaseTableName(overview_table TEXT)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
IF _CDB_OverviewTableZ(overview_table) IS NULL THEN
|
||||
RETURN overview_table;
|
||||
ELSE
|
||||
RETURN regexp_replace(overview_table, _CDB_OverviewTableDiscriminator(), '');
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
|
||||
-- Remove a dataset's existing overview tables.
|
||||
-- Scope: public
|
||||
-- Parameters:
|
||||
-- reloid: oid of the table.
|
||||
CREATE OR REPLACE FUNCTION CDB_DropOverviews(reloid REGCLASS)
|
||||
RETURNS void
|
||||
AS $$
|
||||
DECLARE
|
||||
row record;
|
||||
BEGIN
|
||||
FOR row IN
|
||||
SELECT * FROM CDB_Overviews(reloid)
|
||||
LOOP
|
||||
EXECUTE Format('DROP TABLE %s;', row.overview_table);
|
||||
RAISE NOTICE 'Dropped overview for level %: %', row.z, row.overview_table;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
|
||||
|
||||
|
||||
-- Return existing overviews (if any) for a given dataset table
|
||||
-- Scope: public
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table.
|
||||
-- Return relation of overviews for the table with
|
||||
-- the base table oid,
|
||||
-- z level of the overview and overview table oid, ordered by z.
|
||||
CREATE OR REPLACE FUNCTION CDB_Overviews(reloid REGCLASS)
|
||||
RETURNS TABLE(base_table REGCLASS, z integer, overview_table REGCLASS)
|
||||
AS $$
|
||||
-- FIXME: this will fail if the overview tables
|
||||
-- require a explicit schema name
|
||||
-- possible solutions: return table names as text instead of regclass
|
||||
-- or add schema of reloid before casting to regclass
|
||||
SELECT
|
||||
reloid AS base_table,
|
||||
_CDB_OverviewTableZ(cdb_usertables) AS z,
|
||||
cdb_usertables::regclass AS overview_table
|
||||
FROM CDB_UserTables()
|
||||
WHERE _CDB_IsOverviewTableOf((SELECT relname FROM pg_class WHERE oid=reloid), cdb_usertables)
|
||||
ORDER BY z;
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
-- Return existing overviews (if any) for multiple dataset tables.
|
||||
-- Scope: public
|
||||
-- Parameters
|
||||
-- tables: Array of input tables oids
|
||||
-- Return relation of overviews for the table with
|
||||
-- the base table oid,
|
||||
-- z level of the overview and overview table oid, ordered by z.
|
||||
-- Note: CDB_Overviews can be applied to the result of CDB_QueryTablesText
|
||||
-- to obtain the overviews applicable to a query.
|
||||
CREATE OR REPLACE FUNCTION CDB_Overviews(tables regclass[])
|
||||
RETURNS TABLE(base_table REGCLASS, z integer, overview_table REGCLASS)
|
||||
AS $$
|
||||
SELECT
|
||||
base_table::regclass AS base_table,
|
||||
_CDB_OverviewTableZ(cdb_usertables) AS z,
|
||||
cdb_usertables::regclass AS overview_table
|
||||
FROM
|
||||
CDB_UserTables(), unnest(tables) base_table
|
||||
WHERE _CDB_IsOverviewTableOf((SELECT relname FROM pg_class WHERE oid=base_table), cdb_usertables)
|
||||
ORDER BY base_table, z;
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
-- Schema and relation names of a table given its reloid
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the table.
|
||||
-- Return (schema_name, table_name)
|
||||
-- note that returned names will be quoted if necessary
|
||||
CREATE OR REPLACE FUNCTION _cdb_split_table_name(reloid REGCLASS, OUT schema_name TEXT, OUT table_name TEXT)
|
||||
AS $$
|
||||
BEGIN
|
||||
SELECT n.nspname, c.relname
|
||||
INTO STRICT schema_name, table_name
|
||||
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.oid = reloid;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Calculate the estimated extent of a cartodbfy'ed table.
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table.
|
||||
-- Return value A box2d extent in 3857.
|
||||
CREATE OR REPLACE FUNCTION _cdb_estimated_extent(reloid REGCLASS)
|
||||
RETURNS box2d
|
||||
AS $$
|
||||
DECLARE
|
||||
ext box2d;
|
||||
ext_query text;
|
||||
table_id record;
|
||||
BEGIN
|
||||
|
||||
SELECT n.nspname AS schema_name, c.relname table_name INTO STRICT table_id
|
||||
FROM pg_class c JOIN pg_namespace n on n.oid = c.relnamespace WHERE c.oid = reloid::oid;
|
||||
|
||||
ext_query = format(
|
||||
'SELECT ST_EstimatedExtent(''%1$I'', ''%2$I'', ''%3$I'');',
|
||||
table_id.schema_name, table_id.table_name, 'the_geom_webmercator'
|
||||
);
|
||||
|
||||
BEGIN
|
||||
EXECUTE ext_query INTO ext;
|
||||
EXCEPTION
|
||||
-- This is the typical ERROR: stats for "mytable" do not exist
|
||||
WHEN internal_error THEN
|
||||
-- Get stats and execute again
|
||||
EXECUTE format('ANALYZE %1$I', reloid);
|
||||
EXECUTE ext_query INTO ext;
|
||||
END;
|
||||
|
||||
RETURN ext;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL VOLATILE;
|
||||
|
||||
-- Determine the max feature density of a given dataset.
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- nz: number of zoom levels to consider from z0 upward.
|
||||
-- Return value: feature density (num_features / webmercator_squared_meters).
|
||||
CREATE OR REPLACE FUNCTION _CDB_Feature_Density(reloid REGCLASS, nz integer)
|
||||
RETURNS FLOAT8
|
||||
AS $$
|
||||
DECLARE
|
||||
fd FLOAT8;
|
||||
min_features TEXT;
|
||||
n integer = 4;
|
||||
c FLOAT8;
|
||||
BEGIN
|
||||
-- TODO: for small total count or extents we could just:
|
||||
-- EXECUTE 'SELECT Count(*)/ST_Area(ST_Extent(the_geom_webmercator)) FROM ' || reloid::text || ';' INTO fd;
|
||||
|
||||
-- min_features is a SQL subexpression which can depend on z and represents
|
||||
-- the minimum number of features to recursively consider a tile.
|
||||
-- We can either use a fixed minimum number of features per tile
|
||||
-- or a minimum feature density by dividing the number of features by
|
||||
-- the area of tiles at level Z: c*c*power(2, -2*z)
|
||||
-- with c = CDB_XYZ_Resolution(-8) (earth circumference)
|
||||
min_features = '500';
|
||||
SELECT CDB_XYZ_Resolution(-8) INTO c;
|
||||
|
||||
-- We first compute a set of *seed* tiles, of the minimum Z level, z0, such that
|
||||
-- they cover the extent of the table and we have at least n of them in each
|
||||
-- linear dimension (i.e. at least n*n tiles cover the extent).
|
||||
-- We compute the number of features in these tiles, and recursively in
|
||||
-- subtiles up to level z0 + nz. Then we compute the maximum of the feature
|
||||
-- density (per tile area in webmercator squared meters) for all the
|
||||
-- considered tiles.
|
||||
EXECUTE Format('
|
||||
WITH RECURSIVE t(x, y, z, e) AS (
|
||||
WITH ext AS (SELECT _cdb_estimated_extent(%6$s) as g),
|
||||
base AS (
|
||||
SELECT (-floor(log(2, (greatest(ST_XMax(ext.g)-ST_XMin(ext.g), ST_YMax(ext.g)-ST_YMin(ext.g))/(%4$s*%5$s))::numeric)))::integer z
|
||||
FROM ext
|
||||
),
|
||||
lim AS (
|
||||
SELECT
|
||||
FLOOR((ST_XMin(ext.g)+CDB_XYZ_Resolution(0)*128)/(CDB_XYZ_Resolution(base.z)*256))::integer x0,
|
||||
FLOOR((ST_XMax(ext.g)+CDB_XYZ_Resolution(0)*128)/(CDB_XYZ_Resolution(base.z)*256))::integer x1,
|
||||
FLOOR((CDB_XYZ_Resolution(0)*128-ST_YMin(ext.g))/(CDB_XYZ_Resolution(base.z)*256))::integer y1,
|
||||
FLOOR((CDB_XYZ_Resolution(0)*128-ST_YMax(ext.g))/(CDB_XYZ_Resolution(base.z)*256))::integer y0
|
||||
FROM ext, base
|
||||
),
|
||||
seed AS (
|
||||
SELECT xt, yt, base.z, (
|
||||
SELECT count(*) FROM %1$s
|
||||
WHERE the_geom_webmercator && CDB_XYZ_Extent(xt, yt, base.z)
|
||||
) e
|
||||
FROM base, lim, generate_series(lim.x0, lim.x1) xt, generate_series(lim.y0, lim.y1) yt
|
||||
)
|
||||
SELECT * from seed
|
||||
UNION ALL
|
||||
SELECT x*2 + xx, y*2 + yy, t.z+1, (
|
||||
SELECT count(*) FROM %1$s
|
||||
WHERE the_geom_webmercator && CDB_XYZ_Extent(x*2 + xx, y*2 + yy, t.z+1)
|
||||
)
|
||||
FROM t, base, (VALUES (0, 0), (0, 1), (1, 1), (1, 0)) AS c(xx, yy)
|
||||
WHERE t.e > %2$s AND t.z < (base.z + %3$s)
|
||||
)
|
||||
SELECT MAX(e/ST_Area(CDB_XYZ_Extent(x,y,z))) FROM t where e > 0;
|
||||
', reloid::text, min_features, nz, n, c, reloid::oid)
|
||||
INTO fd;
|
||||
RETURN fd;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
|
||||
-- Experimental default strategy to assign a reference base Z level
|
||||
-- to a cartodbfied table. The resulting Z level represents the
|
||||
-- minimum scale level at which the table data can be rendered
|
||||
-- without overcrowded results or loss of detail.
|
||||
-- Parameters:
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: Z level as an integer
|
||||
CREATE OR REPLACE FUNCTION _CDB_Feature_Density_Ref_Z_Strategy(reloid REGCLASS)
|
||||
RETURNS INTEGER
|
||||
AS $$
|
||||
DECLARE
|
||||
lim FLOAT8 := 500; -- TODO: determine/parameterize this
|
||||
nz integer := 4;
|
||||
fd FLOAT8;
|
||||
c FLOAT8;
|
||||
BEGIN
|
||||
-- Compute fd as an estimation of the (maximum) number
|
||||
-- of features per unit of tile area (in webmercator squared meters)
|
||||
SELECT _CDB_Feature_Density(reloid, nz) INTO fd;
|
||||
-- lim maximum number of (desiderable) features per tile
|
||||
-- we have c = 2*Pi*R = CDB_XYZ_Resolution(-8) (earth circumference)
|
||||
-- ta(z): tile area = power(c*power(2,z), 2) = c*c*power(2,2*z)
|
||||
-- => fd*ta(z) if the average number of features per tile at level z
|
||||
-- find minimum z so that fd*ta(z) <= lim
|
||||
-- compute a rough 'feature density' value
|
||||
SELECT CDB_XYZ_Resolution(-8) INTO c;
|
||||
RETURN ceil(log(2.0, (c*c*fd/lim)::numeric)/2);
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
|
||||
-- Overview table name for a given Z level and base dataset or overview table
|
||||
-- Scope: private.
|
||||
-- Parameters:
|
||||
-- ref reference table (can be the base table of the dataset or an existing
|
||||
-- overview) from which the overview is being generated.
|
||||
-- ref_z Z level of the reference table
|
||||
-- overview_z Z level of the overview to be named, must be smaller than ref_z
|
||||
-- Return value: the name to be used for the overview. The name is always
|
||||
-- unqualified (does not include a schema name).
|
||||
CREATE OR REPLACE FUNCTION _CDB_Overview_Name(ref REGCLASS, ref_z INTEGER, overview_z INTEGER)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
schema_name TEXT;
|
||||
base TEXT;
|
||||
suffix TEXT;
|
||||
is_overview BOOLEAN;
|
||||
BEGIN
|
||||
SELECT * FROM _cdb_split_table_name(ref) INTO schema_name, base;
|
||||
SELECT _CDB_OverviewBaseTableName(base) INTO base;
|
||||
RETURN _CDB_OverviewTableName(base, overview_z);
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- Sampling reduction method.
|
||||
-- Valid for any kind of geometry.
|
||||
-- Scope: private.
|
||||
-- reloid original table (can be the base table of the dataset or an existing
|
||||
-- overview) from which the overview is being generated.
|
||||
-- ref_z Z level assigned to the original table
|
||||
-- overview_z Z level of the overview to be generated, must be smaller than ref_z
|
||||
-- Return value: Name of the generated overview table
|
||||
CREATE OR REPLACE FUNCTION _CDB_Sampling_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
overview_rel TEXT;
|
||||
fraction FLOAT8;
|
||||
base_name TEXT;
|
||||
class_info RECORD;
|
||||
num_samples INTEGER;
|
||||
BEGIN
|
||||
overview_rel := _CDB_Overview_Name(reloid, ref_z, overview_z);
|
||||
fraction := power(2, 2*(overview_z - ref_z));
|
||||
|
||||
-- FIXME: handle schema name for overview_rel if reloid requires it
|
||||
EXECUTE Format('DROP TABLE IF EXISTS %I CASCADE;', overview_rel);
|
||||
|
||||
-- Estimate number of rows
|
||||
SELECT reltuples, relpages FROM pg_class INTO STRICT class_info
|
||||
WHERE oid = reloid::oid;
|
||||
|
||||
IF class_info.relpages < 2 OR fraction > 0.5 THEN
|
||||
-- We'll avoid possible CDB_RandomTids problems
|
||||
EXECUTE Format('
|
||||
CREATE TABLE %I AS SELECT * FROM %s WHERE random() < %s;
|
||||
', overview_rel, reloid, fraction);
|
||||
ELSE
|
||||
num_samples := ceil(class_info.reltuples*fraction);
|
||||
EXECUTE Format('
|
||||
CREATE TABLE %1$I AS SELECT * FROM %2$s
|
||||
WHERE ctid = ANY (
|
||||
ARRAY[
|
||||
(SELECT CDB_RandomTids(''%2$s'', %3$s))
|
||||
]
|
||||
);
|
||||
', overview_rel, reloid, num_samples);
|
||||
END IF;
|
||||
|
||||
RETURN overview_rel;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
-- Register new overview table (post-creation chores)
|
||||
-- Scope: private
|
||||
-- Parameters:
|
||||
-- dataset: oid of the input dataset table, It must be a cartodbfy'ed table.
|
||||
-- overview_table: oid of the overview table to be registered.
|
||||
-- overview_z: intended Z level for the overview table
|
||||
-- This function is declared SECURITY DEFINER so it executes with the privileges
|
||||
-- of the function creator to have a chance to alter the privileges of the
|
||||
-- overview table to match those of the dataset. It will only perform any change
|
||||
-- if the overview table belgons to the same scheme as the dataset and it
|
||||
-- matches the scheme naming for overview tables.
|
||||
CREATE OR REPLACE FUNCTION _CDB_Register_Overview(dataset REGCLASS, overview_table REGCLASS, overview_z INTEGER)
|
||||
RETURNS VOID
|
||||
AS $$
|
||||
DECLARE
|
||||
sql TEXT;
|
||||
table_owner TEXT;
|
||||
dataset_scheme TEXT;
|
||||
dataset_name TEXT;
|
||||
overview_scheme TEXT;
|
||||
overview_name TEXT;
|
||||
BEGIN
|
||||
-- This function will only register a table as an overview table if it matches
|
||||
-- the overviews naming scheme for the dataset and z level and the table belongs
|
||||
-- to the same scheme as the the dataset
|
||||
SELECT * FROM _cdb_split_table_name(dataset) INTO dataset_scheme, dataset_name;
|
||||
SELECT * FROM _cdb_split_table_name(overview_table) INTO overview_scheme, overview_name;
|
||||
IF dataset_scheme = overview_scheme AND
|
||||
overview_name = _CDB_OverviewTableName(dataset_name, overview_z) THEN
|
||||
|
||||
-- preserve the owner of the base table
|
||||
SELECT u.usename
|
||||
FROM pg_catalog.pg_class c JOIN pg_catalog.pg_user u ON (c.relowner=u.usesysid)
|
||||
WHERE c.relname = dataset::text
|
||||
INTO table_owner;
|
||||
EXECUTE Format('ALTER TABLE IF EXISTS %s OWNER TO %I;', overview_table::text, table_owner);
|
||||
|
||||
-- preserve the table privileges
|
||||
UPDATE pg_class c_to
|
||||
SET relacl = c_from.relacl
|
||||
FROM pg_class c_from
|
||||
WHERE c_from.oid = dataset
|
||||
AND c_to.oid = overview_table;
|
||||
|
||||
PERFORM _CDB_Add_Indexes(overview_table);
|
||||
|
||||
-- TODO: If metadata about existing overviews is to be stored
|
||||
-- it should be done here (CDB_Overviews would consume such metadata)
|
||||
END IF;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL SECURITY DEFINER;
|
||||
|
||||
-- Dataset attributes (column names other than the
|
||||
-- CartoDB primary key and geometry columns) which should be aggregated
|
||||
-- in aggregated overviews.
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: set of attribute names
|
||||
CREATE OR REPLACE FUNCTION _CDB_Aggregable_Attributes(reloid REGCLASS)
|
||||
RETURNS SETOF information_schema.sql_identifier
|
||||
AS $$
|
||||
SELECT c FROM CDB_ColumnNames(reloid) c, _CDB_Columns() cdb
|
||||
WHERE c NOT IN (
|
||||
cdb.pkey, cdb.geomcol, cdb.mercgeomcol
|
||||
)
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
|
||||
-- List of dataset attributes to be aggregated in aggregated overview
|
||||
-- as a comma-separated SQL expression.
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: SQL subexpression as text
|
||||
CREATE OR REPLACE FUNCTION _CDB_Aggregable_Attributes_Expression(reloid REGCLASS)
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
attr_list TEXT;
|
||||
BEGIN
|
||||
SELECT string_agg(s.c, ',') FROM (
|
||||
SELECT * FROM _CDB_Aggregable_Attributes(reloid) c
|
||||
) AS s INTO attr_list;
|
||||
|
||||
RETURN attr_list;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
|
||||
-- SQL Aggregation expression for a datase attribute
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- column_name: column to be aggregated
|
||||
-- table_alias: (optional) table qualifier for the column to be aggregated
|
||||
-- Return SQL subexpression as text with aggregated attribute aliased
|
||||
-- with its original name.
|
||||
CREATE OR REPLACE FUNCTION _CDB_Attribute_Aggregation_Expression(reloid REGCLASS, column_name TEXT, table_alias TEXT DEFAULT '')
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
column_type TEXT;
|
||||
qualified_column TEXT;
|
||||
BEGIN
|
||||
IF table_alias <> '' THEN
|
||||
qualified_column := Format('%I.%I', table_alias, column_name);
|
||||
ELSE
|
||||
qualified_column := Format('%I', column_name);
|
||||
END IF;
|
||||
|
||||
column_type := CDB_ColumnType(reloid, column_name);
|
||||
|
||||
CASE column_type
|
||||
WHEN 'double precision', 'real', 'integer', 'bigint' THEN
|
||||
RETURN Format('AVG(%s)::' || column_type, qualified_column);
|
||||
WHEN 'text' THEN
|
||||
-- TODO: we could define a new aggregate function that returns distinct
|
||||
-- separated values with a limit, adding ellipsis if more values existed
|
||||
-- e.g. with '/' as separator and a limit of three:
|
||||
-- 'A', 'B', 'A', 'C', 'D' => 'A/B/C/...'
|
||||
-- Other ideas: if value is unique then use it, otherwise use something
|
||||
-- like '*' or '(varies)' or '(multiple values)', or NULL
|
||||
-- Using 'string_agg(' || qualified_column || ',''/'')'
|
||||
-- here causes
|
||||
RETURN 'CASE count(*) WHEN 1 THEN MIN(' || qualified_column || ') ELSE NULL END::' || column_type;
|
||||
WHEN 'boolean' THEN
|
||||
RETURN 'CASE count(*) WHEN 1 THEN BOOL_AND(' || qualified_column || ') ELSE NULL END::' || column_type;
|
||||
ELSE
|
||||
RETURN 'CASE count(*) WHEN 1 THEN MIN(' || qualified_column || ') ELSE NULL END::' || column_type;
|
||||
END CASE;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL IMMUTABLE;
|
||||
|
||||
-- List of dataset aggregated attributes as a comma-separated SQL expression.
|
||||
-- Scope: private.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- table_alias: (optional) table qualifier for the columns to be aggregated
|
||||
-- Return value: SQL subexpression as text
|
||||
CREATE OR REPLACE FUNCTION _CDB_Aggregated_Attributes_Expression(reloid REGCLASS, table_alias TEXT DEFAULT '')
|
||||
RETURNS TEXT
|
||||
AS $$
|
||||
DECLARE
|
||||
attr_list TEXT;
|
||||
BEGIN
|
||||
SELECT string_agg(_CDB_Attribute_Aggregation_Expression(reloid, s.c, table_alias) || Format(' AS %s', s.c), ',')
|
||||
FROM (
|
||||
SELECT * FROM _CDB_Aggregable_Attributes(reloid) c
|
||||
) AS s INTO attr_list;
|
||||
|
||||
RETURN attr_list;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
|
||||
-- Array of geometry types detected in a cartodbfied table
|
||||
-- For effciency only look at a limited number of rwos.
|
||||
-- Parameters
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table.
|
||||
-- Return value: array of geometry type names
|
||||
CREATE OR REPLACE FUNCTION _CDB_GeometryTypes(reloid REGCLASS)
|
||||
RETURNS TEXT[]
|
||||
AS $$
|
||||
DECLARE
|
||||
gtypes TEXT[];
|
||||
BEGIN
|
||||
EXECUTE Format('
|
||||
SELECT array_agg(DISTINCT ST_GeometryType(the_geom)) FROM (
|
||||
SELECT the_geom FROM %s
|
||||
WHERE (the_geom is not null) LIMIT 10
|
||||
) as geom_types
|
||||
', reloid)
|
||||
INTO gtypes;
|
||||
RETURN gtypes;
|
||||
END
|
||||
$$ LANGUAGE PLPGSQL STABLE;
|
||||
|
||||
-- Experimental Overview reduction method for point datasets.
|
||||
-- It clusters the points using a grid, then aggregates the point in each
|
||||
-- cluster into a point at the centroid of the clustered records.
|
||||
-- Scope: private.
|
||||
-- Parameters:
|
||||
-- reloid original table (can be the base table of the dataset or an existing
|
||||
-- overview) from which the overview is being generated.
|
||||
-- ref_z Z level assigned to the original table
|
||||
-- overview_z Z level of the overview to be generated, must be smaller than ref_z
|
||||
-- Return value: Name of the generated overview table
|
||||
CREATE OR REPLACE FUNCTION _CDB_GridCluster_Reduce_Strategy(reloid REGCLASS, ref_z INTEGER, overview_z INTEGER)
|
||||
RETURNS REGCLASS
|
||||
AS $$
|
||||
DECLARE
|
||||
overview_rel TEXT;
|
||||
reduction FLOAT8;
|
||||
base_name TEXT;
|
||||
grid_px FLOAT8 = 7.5; -- Grid size in pixels at Z level overview_z
|
||||
grid_m FLOAT8;
|
||||
aggr_attributes TEXT;
|
||||
attributes TEXT;
|
||||
columns TEXT;
|
||||
gtypes TEXT[];
|
||||
BEGIN
|
||||
SELECT _CDB_GeometryTypes(reloid) INTO gtypes;
|
||||
IF array_upper(gtypes, 1) <> 1 OR gtypes[1] <> 'ST_Point' THEN
|
||||
-- This strategy only supports datasets with point geomety
|
||||
RETURN NULL;
|
||||
RETURN 'x';
|
||||
END IF;
|
||||
|
||||
--TODO: check applicability: geometry type, minimum number of points...
|
||||
|
||||
overview_rel := _CDB_Overview_Name(reloid, ref_z, overview_z);
|
||||
|
||||
-- compute grid cell size using the overview_z dimension...
|
||||
SELECT CDB_XYZ_Resolution(overview_z)*grid_px INTO grid_m;
|
||||
|
||||
attributes := _CDB_Aggregable_Attributes_Expression(reloid);
|
||||
aggr_attributes := _CDB_Aggregated_Attributes_Expression(reloid);
|
||||
IF attributes <> '' THEN
|
||||
attributes := ', ' || attributes;
|
||||
END IF;
|
||||
IF aggr_attributes <> '' THEN
|
||||
aggr_attributes := aggr_attributes || ', ';
|
||||
END IF;
|
||||
|
||||
-- compute the resulting columns in the same order as in the base table
|
||||
-- cartodb_id,
|
||||
-- ST_Transform(ST_SetSRID(ST_MakePoint(sx/n, sy/n), 3857), 4326) AS the_geom,
|
||||
-- ST_SetSRID(ST_MakePoint(sx/n, sy/n), 3857) AS the_geom_webmercator
|
||||
-- %4$s
|
||||
WITH cols AS (
|
||||
SELECT
|
||||
CASE c
|
||||
WHEN 'cartodb_id' THEN 'cartodb_id'
|
||||
WHEN 'the_geom' THEN
|
||||
'ST_Transform(ST_SetSRID(ST_MakePoint(sx/n, sy/n), 3857), 4326) AS the_geom'
|
||||
WHEN 'the_geom_webmercator' THEN
|
||||
'ST_SetSRID(ST_MakePoint(sx/n, sy/n), 3857) AS the_geom_webmercator'
|
||||
ELSE c
|
||||
END AS column
|
||||
FROM CDB_ColumnNames(reloid) c
|
||||
)
|
||||
SELECT string_agg(s.column, ',') FROM (
|
||||
SELECT * FROM cols
|
||||
) AS s INTO columns;
|
||||
|
||||
-- FIXME: handle schema name for overview_rel if reloid requires it
|
||||
EXECUTE Format('DROP TABLE IF EXISTS %I CASCADE;', overview_rel);
|
||||
|
||||
-- Now we cluster the data using a grid of size grid_m
|
||||
-- and selecte the centroid (average coordinates) of each cluster.
|
||||
-- If we had a selected numeric attribute of interest we could use it
|
||||
-- as a weight for the average coordinates.
|
||||
EXECUTE Format('
|
||||
CREATE TABLE %3$I AS
|
||||
WITH clusters AS (
|
||||
SELECT
|
||||
%5$s
|
||||
count(*) AS n,
|
||||
SUM(ST_X(f.the_geom_webmercator)) AS sx,
|
||||
SUM(ST_Y(f.the_geom_webmercator)) AS sy,
|
||||
Floor(ST_X(f.the_geom_webmercator)/%2$s)::int AS gx,
|
||||
Floor(ST_Y(f.the_geom_webmercator)/%2$s)::int AS gy,
|
||||
MIN(cartodb_id) AS cartodb_id
|
||||
FROM %1$s f
|
||||
GROUP BY gx, gy
|
||||
)
|
||||
SELECT %6$s FROM clusters
|
||||
', reloid::text, grid_m, overview_rel, attributes, aggr_attributes, columns);
|
||||
|
||||
RETURN overview_rel;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
-- Create overview tables for a dataset.
|
||||
-- Scope: public
|
||||
-- Parameters:
|
||||
-- reloid: oid of the input table. It must be a cartodbfy'ed table with
|
||||
-- vector features.
|
||||
-- refscale_strategy: function that computes the reference Z of the dataset
|
||||
-- reduce_strategy: function that generates overviews from a base table
|
||||
-- or higher level overview. The overview tables
|
||||
-- created by the strategy must have the same columns
|
||||
-- as the base table and in the same order.
|
||||
-- Return value: Array with the names of the generated overview tables
|
||||
CREATE OR REPLACE FUNCTION CDB_CreateOverviews(reloid REGCLASS, refscale_strategy regproc DEFAULT '_CDB_Feature_Density_Ref_Z_Strategy'::regproc, reduce_strategy regproc DEFAULT '_CDB_GridCluster_Reduce_Strategy'::regproc)
|
||||
RETURNS text[]
|
||||
AS $$
|
||||
DECLARE
|
||||
ref_z integer;
|
||||
overviews_z integer[];
|
||||
base_z integer;
|
||||
base_rel REGCLASS;
|
||||
overview_z integer;
|
||||
overview_tables REGCLASS[];
|
||||
overviews_step integer := 1;
|
||||
BEGIN
|
||||
-- Determine the referece zoom level
|
||||
EXECUTE 'SELECT ' || quote_ident(refscale_strategy::text) || Format('(''%s'');', reloid) INTO ref_z;
|
||||
|
||||
-- Determine overlay zoom levels
|
||||
-- TODO: should be handled by the refscale_strategy?
|
||||
overview_z := ref_z - 1;
|
||||
WHILE overview_z >= 0 LOOP
|
||||
SELECT array_append(overviews_z, overview_z) INTO overviews_z;
|
||||
overview_z := overview_z - overviews_step;
|
||||
END LOOP;
|
||||
|
||||
-- Create overlay tables
|
||||
base_z := ref_z;
|
||||
base_rel := reloid;
|
||||
FOREACH overview_z IN ARRAY overviews_z LOOP
|
||||
EXECUTE 'SELECT ' || quote_ident(reduce_strategy::text) || Format('(''%s'', %s, %s);', base_rel, base_z, overview_z) INTO base_rel;
|
||||
IF base_rel IS NULL THEN
|
||||
EXIT;
|
||||
END IF;
|
||||
base_z := overview_z;
|
||||
PERFORM _CDB_Register_Overview(reloid, base_rel, base_z);
|
||||
SELECT array_append(overview_tables, base_rel) INTO overview_tables;
|
||||
END LOOP;
|
||||
|
||||
RETURN overview_tables;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
@@ -5,6 +5,11 @@ CREATE TABLE IF NOT EXISTS
|
||||
updated_at timestamp with time zone not null default now()
|
||||
);
|
||||
|
||||
CREATE OR REPLACE VIEW public.CDB_TableMetadata_Text AS
|
||||
SELECT FORMAT('%I.%I', n.nspname::text, c.relname::text) tabname, updated_at
|
||||
FROM public.CDB_TableMetadata, pg_catalog.pg_class c
|
||||
LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid;
|
||||
|
||||
-- No one can see this
|
||||
-- Updates are only possible trough the security definer trigger
|
||||
-- GRANT SELECT ON public.CDB_TableMetadata TO public;
|
||||
@@ -61,13 +66,11 @@ CREATE OR REPLACE FUNCTION _CDB_TableMetadata_Updated()
|
||||
RETURNS trigger AS
|
||||
$$
|
||||
DECLARE
|
||||
tabname TEXT;
|
||||
tabname regclass;
|
||||
rec RECORD;
|
||||
found BOOL;
|
||||
function_start timestamptz;
|
||||
function_end timestamptz;
|
||||
function_duration float;
|
||||
log_error_verbosity_value text;
|
||||
schema_name TEXT;
|
||||
table_name TEXT;
|
||||
BEGIN
|
||||
|
||||
IF TG_OP = 'UPDATE' or TG_OP = 'INSERT' THEN
|
||||
@@ -76,8 +79,6 @@ BEGIN
|
||||
tabname = OLD.tabname;
|
||||
END IF;
|
||||
|
||||
function_start := clock_timestamp();
|
||||
|
||||
-- Notify table data update
|
||||
-- This needs a little bit more of research regarding security issues
|
||||
-- see https://github.com/CartoDB/cartodb/pull/241
|
||||
@@ -104,31 +105,25 @@ BEGIN
|
||||
AND u.usesuper
|
||||
ORDER BY n.nspname
|
||||
LOOP
|
||||
SELECT n.nspname, c.relname FROM pg_class c, pg_namespace n WHERE c.oid=tabname AND c.relnamespace = n.oid INTO schema_name, table_name;
|
||||
EXECUTE 'SELECT ' || quote_ident(rec.nspname) || '.'
|
||||
|| quote_ident(rec.proname)
|
||||
|| '(' || quote_literal(tabname) || ')';
|
||||
|| '(' || quote_literal(quote_ident(schema_name) || '.' || quote_ident(table_name)) || ')';
|
||||
found := true;
|
||||
EXIT;
|
||||
END LOOP;
|
||||
IF NOT found THEN RAISE WARNING 'Missing cdb_invalidate_varnish()'; END IF;
|
||||
|
||||
function_end := clock_timestamp();
|
||||
SELECT extract(epoch from (function_end - function_start)) INTO function_duration;
|
||||
SELECT setting INTO log_error_verbosity_value FROM pg_settings WHERE name='log_error_verbosity';
|
||||
SET log_error_verbosity=TERSE;
|
||||
RAISE LOG 'invalidation_duration: %', function_duration::text;
|
||||
PERFORM 'SET log_error_verbosity= ' || log_error_verbosity_value;
|
||||
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
||||
|
||||
DROP TRIGGER IF EXISTS table_modified ON CDB_TableMetadata;
|
||||
DROP TRIGGER IF EXISTS table_modified ON public.CDB_TableMetadata;
|
||||
-- NOTE: on DELETE we would be unable to convert the table
|
||||
-- oid (regclass) to its name
|
||||
CREATE TRIGGER table_modified AFTER INSERT OR UPDATE
|
||||
ON CDB_TableMetadata FOR EACH ROW EXECUTE PROCEDURE
|
||||
ON public.CDB_TableMetadata FOR EACH ROW EXECUTE PROCEDURE
|
||||
_CDB_TableMetadata_Updated();
|
||||
|
||||
|
||||
|
||||
@@ -5,24 +5,9 @@
|
||||
CREATE OR REPLACE FUNCTION CDB_XYZ_Resolution(z INTEGER)
|
||||
RETURNS FLOAT8
|
||||
AS $$
|
||||
DECLARE
|
||||
earth_circumference FLOAT8;
|
||||
tile_size INTEGER;
|
||||
full_resolution FLOAT8;
|
||||
BEGIN
|
||||
|
||||
-- Earth equatorial circumference in meters (according to wikipedia)
|
||||
earth_circumference := 40075017;
|
||||
|
||||
-- Size of each tile in pixels (1:1 aspect ratio)
|
||||
tile_size := 256;
|
||||
|
||||
full_resolution := earth_circumference/tile_size;
|
||||
|
||||
RETURN full_resolution / (power(2,z));
|
||||
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
|
||||
-- circumference divided by 256 is z0 resolution, then divide by 2^z
|
||||
SELECT 40075017.0 / 256 / power(2, z);
|
||||
$$ LANGUAGE SQL IMMUTABLE STRICT;
|
||||
-- }
|
||||
|
||||
-- {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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 > 500000000 THEN RETURN 0;
|
||||
WHEN scaleDenominator <= 500000000 AND scaleDenominator > 200000000 THEN RETURN 1;
|
||||
WHEN scaleDenominator <= 200000000 AND scaleDenominator > 100000000 THEN RETURN 2;
|
||||
WHEN scaleDenominator <= 100000000 AND scaleDenominator > 50000000 THEN RETURN 3;
|
||||
WHEN scaleDenominator <= 50000000 AND scaleDenominator > 25000000 THEN RETURN 4;
|
||||
WHEN scaleDenominator <= 25000000 AND scaleDenominator > 12500000 THEN RETURN 5;
|
||||
|
||||
1
scripts-enabled/242-CDB_Overviews.sql
Symbolic link
1
scripts-enabled/242-CDB_Overviews.sql
Symbolic link
@@ -0,0 +1 @@
|
||||
../scripts-available/CDB_Overviews.sql
|
||||
1
scripts-enabled/250-CDB_ForeignTable.sql
Symbolic link
1
scripts-enabled/250-CDB_ForeignTable.sql
Symbolic link
@@ -0,0 +1 @@
|
||||
../scripts-available/CDB_ForeignTable.sql
|
||||
@@ -1,102 +0,0 @@
|
||||
\set VERBOSITY terse
|
||||
|
||||
-- Set user quota to infinite
|
||||
SELECT CDB_SetUserQuotaInBytes(0);
|
||||
|
||||
-- Enable ddl triggers
|
||||
SELECT cartodb.cdb_enable_ddl_hooks();
|
||||
|
||||
create schema c;
|
||||
|
||||
SELECT CDB_SetUserQuotaInBytes('c', 0);
|
||||
|
||||
DROP USER IF EXISTS cartodb_postgresql_unpriv_user;
|
||||
CREATE USER cartodb_postgresql_unpriv_user;
|
||||
GRANT ALL ON SCHEMA c to cartodb_postgresql_unpriv_user;
|
||||
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
|
||||
--SELECT session_user, current_user;
|
||||
|
||||
----------------------
|
||||
-- CREATE TABLE
|
||||
----------------------
|
||||
SET SESSION AUTHORIZATION 'cartodb_postgresql_unpriv_user';
|
||||
select 1 as i INTO 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;
|
||||
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)) as age
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)*10) as agecs
|
||||
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;
|
||||
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)*10) as agecs
|
||||
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;
|
||||
|
||||
RESET SESSION AUTHORIZATION;
|
||||
select
|
||||
tabname::text,
|
||||
round(extract('secs' from now() - updated_at)*10) as agecs
|
||||
FROM CDB_TableMetadata WHERE tabname = 'c.t3'::regclass;
|
||||
|
||||
----------------------------
|
||||
-- DROP TABLE
|
||||
----------------------------
|
||||
|
||||
RESET SESSION AUTHORIZATION;
|
||||
drop schema c cascade;
|
||||
select count(*) from CDB_TableMetadata;
|
||||
|
||||
DROP OWNED BY cartodb_postgresql_unpriv_user;
|
||||
DROP ROLE cartodb_postgresql_unpriv_user;
|
||||
DROP FUNCTION _CDB_UserQuotaInBytes();
|
||||
@@ -1,5 +1,4 @@
|
||||
CREATE EXTENSION postgis;
|
||||
CREATE EXTENSION schema_triggers;
|
||||
CREATE EXTENSION plpythonu;
|
||||
CREATE EXTENSION cartodb;
|
||||
CREATE FUNCTION public.cdb_invalidate_varnish(table_name text)
|
||||
@@ -7,4 +6,4 @@ RETURNS void AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'cdb_invalidate_varnish(%) called', table_name;
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY default
|
||||
\set VERBOSITY terse
|
||||
|
||||
CREATE OR REPLACE FUNCTION CDB_CartodbfyTableCheck(tabname regclass, label text)
|
||||
RETURNS text AS
|
||||
@@ -124,13 +124,21 @@ END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql';
|
||||
|
||||
-- table with single non-geometrical column
|
||||
-- check cartodbfytable idempotence
|
||||
CREATE TABLE t AS SELECT 1::int as a;
|
||||
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;
|
||||
|
||||
-- table with single non-geometrical column
|
||||
CREATE TABLE t AS SELECT ST_SetSRID(ST_MakePoint(-1,-1),4326) as the_geom, 1::int as cartodb_id, 'this is a sentence' as description;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'check function idempotence');
|
||||
SELECT * FROM t;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'check function idempotence');
|
||||
SELECT * FROM t;
|
||||
DROP TABLE t;
|
||||
|
||||
-- table with existing srid-unconstrained (but type-constrained) the_geom
|
||||
CREATE TABLE t AS SELECT ST_SetSRID(ST_MakePoint(0,0),4326)::geometry(point) as the_geom;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'srid-unconstrained the_geom');
|
||||
@@ -164,19 +172,22 @@ SELECT CDB_CartodbfyTableCheck('t', 'trigger-protected the_geom');
|
||||
SELECT 'extent',ST_Extent(ST_SnapToGrid(the_geom,0.2)) FROM t;
|
||||
DROP TABLE t;
|
||||
|
||||
-- INFO: disabled because cartodbfy does not longer consider text columns for primary ID
|
||||
-- -- table with existing cartodb_id field of type text
|
||||
-- CREATE TABLE t AS SELECT 10::text as cartodb_id;
|
||||
-- SELECT CDB_CartodbfyTableCheck('t', 'text cartodb_id');
|
||||
-- select cartodb_id/2 FROM t;
|
||||
-- DROP TABLE t;
|
||||
-- table with existing cartodb_id field of type text
|
||||
CREATE TABLE t AS SELECT 10::text as cartodb_id;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'text cartodb_id');
|
||||
select cartodb_id/2 FROM t;
|
||||
DROP TABLE t;
|
||||
|
||||
-- INFO: disabled because cartodbfy does not longer consider text columns for primary ID
|
||||
-- -- table with existing cartodb_id field of type text not casting
|
||||
-- CREATE TABLE t AS SELECT 'nan' as cartodb_id;
|
||||
-- SELECT CDB_CartodbfyTableCheck('t', 'uncasting text cartodb_id');
|
||||
-- select cartodb_id,_cartodb_id0 FROM t;
|
||||
-- DROP TABLE t;
|
||||
-- table with existing cartodb_id field of type text not casting
|
||||
CREATE TABLE t AS SELECT 'nan'::text as cartodb_id;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'uncasting text cartodb_id');
|
||||
DROP TABLE t;
|
||||
|
||||
-- table with empty cartodb_id field of type text
|
||||
CREATE TABLE t AS SELECT null::text as cartodb_id;
|
||||
SELECT CDB_CartodbfyTableCheck('t', 'empty text cartodb_id');
|
||||
SELECT cartodb_id from t;
|
||||
DROP TABLE t;
|
||||
|
||||
-- table with existing cartodb_id field of type int4 not sequenced
|
||||
CREATE TABLE t AS SELECT 1::int4 as cartodb_id;
|
||||
@@ -291,7 +302,7 @@ INSERT INTO test VALUES
|
||||
(NULL),
|
||||
(3);
|
||||
SELECT CDB_CartodbfyTableCheck('test', 'Table with null cartodb_id #148');
|
||||
SELECT cartodb_id, cartodb_id_0 from test;
|
||||
SELECT cartodb_id from test;
|
||||
DROP TABLE test;
|
||||
|
||||
-- Table with non unique cartodb_id
|
||||
@@ -303,7 +314,7 @@ INSERT INTO test VALUES
|
||||
(2),
|
||||
(2);
|
||||
SELECT CDB_CartodbfyTableCheck('test', 'Table with non unique cartodb_id #148');
|
||||
SELECT cartodb_id, cartodb_id_0 from test;
|
||||
SELECT cartodb_id from test;
|
||||
DROP TABLE test;
|
||||
|
||||
-- Table with non unique and null cartodb_id
|
||||
@@ -316,7 +327,7 @@ INSERT INTO test VALUES
|
||||
(NULL),
|
||||
(2);
|
||||
SELECT CDB_CartodbfyTableCheck('test', 'Table with non unique and null cartodb_id #148');
|
||||
SELECT cartodb_id, cartodb_id_0 from test;
|
||||
SELECT cartodb_id from test;
|
||||
DROP TABLE test;
|
||||
|
||||
CREATE TABLE test (
|
||||
|
||||
@@ -2,12 +2,16 @@ SET
|
||||
CREATE FUNCTION
|
||||
SELECT 1
|
||||
ERROR: Please set user quota before cartodbfying tables.
|
||||
CONTEXT: SQL statement "SELECT cartodb._CDB_check_prerequisites(destschema, reloid)"
|
||||
PL/pgSQL function cdb_cartodbfytable(text,regclass) line 21 at PERFORM
|
||||
0
|
||||
single non-geometrical column cartodbfied fine
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
check function idempotence cartodbfied fine
|
||||
1|0101000020E6100000000000000000F0BF000000000000F0BF|0101000020110F0000DB0B4ADA772DFBC077432E49D22DFBC0|this is a sentence
|
||||
check function idempotence cartodbfied fine
|
||||
1|0101000020E6100000000000000000F0BF000000000000F0BF|0101000020110F0000DB0B4ADA772DFBC077432E49D22DFBC0|this is a sentence
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
srid-unconstrained the_geom cartodbfied fine
|
||||
DROP TABLE
|
||||
SELECT 2
|
||||
@@ -26,6 +30,17 @@ SELECT 1
|
||||
CREATE TRIGGER
|
||||
trigger-protected the_geom cartodbfied fine
|
||||
extent|BOX(1 1,2 2)
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
text cartodb_id cartodbfied fine
|
||||
5
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
ERROR: CDB(_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, cartodb_id): ALTER TABLE t ADD CONSTRAINT cartodb_id_pk PRIMARY KEY (cartodb_id), ADD CONSTRAINT cartodb_id_integer CHECK (cartodb_id::integer >=0);
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
ERROR: CDB(_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, cartodb_id): ALTER TABLE t ADD CONSTRAINT cartodb_id_pk PRIMARY KEY (cartodb_id), ADD CONSTRAINT cartodb_id_integer CHECK (cartodb_id::integer >=0);
|
||||
|
||||
DROP TABLE
|
||||
SELECT 1
|
||||
unsequenced cartodb_id cartodbfied fine
|
||||
@@ -77,30 +92,30 @@ Table with both the_geom and wkb_geometry #141 cartodbfied fine
|
||||
DROP TABLE
|
||||
CREATE TABLE
|
||||
INSERT 0 1
|
||||
Many colliding columns #141 cartodbfied fine
|
||||
ERROR: CDB(_CDB_Has_Usable_Primary_ID: multiple primary keys for table "many_colliding_columns" are not allowed): ALTER TABLE many_colliding_columns ADD CONSTRAINT cartodb_id_pk PRIMARY KEY (cartodb_id), ADD CONSTRAINT cartodb_id_integer CHECK (cartodb_id::integer >=0);
|
||||
DROP TABLE
|
||||
CREATE TABLE
|
||||
INSERT 0 4
|
||||
Table with null cartodb_id #148 cartodbfied fine
|
||||
1|1
|
||||
2|2
|
||||
3|
|
||||
4|3
|
||||
ERROR: CDB(_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, cartodb_id): ALTER TABLE test ADD CONSTRAINT cartodb_id_pk PRIMARY KEY (cartodb_id), ADD CONSTRAINT cartodb_id_integer CHECK (cartodb_id::integer >=0);
|
||||
1
|
||||
2
|
||||
|
||||
3
|
||||
DROP TABLE
|
||||
CREATE TABLE
|
||||
INSERT 0 3
|
||||
Table with non unique cartodb_id #148 cartodbfied fine
|
||||
1|1
|
||||
2|2
|
||||
3|2
|
||||
ERROR: CDB(_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, cartodb_id): ALTER TABLE test ADD CONSTRAINT cartodb_id_pk PRIMARY KEY (cartodb_id), ADD CONSTRAINT cartodb_id_integer CHECK (cartodb_id::integer >=0);
|
||||
1
|
||||
2
|
||||
2
|
||||
DROP TABLE
|
||||
CREATE TABLE
|
||||
INSERT 0 4
|
||||
Table with non unique and null cartodb_id #148 cartodbfied fine
|
||||
1|1
|
||||
2|2
|
||||
3|
|
||||
4|2
|
||||
ERROR: CDB(_CDB_Has_Usable_Primary_ID: Error: invalid cartodb_id, cartodb_id): ALTER TABLE test ADD CONSTRAINT cartodb_id_pk PRIMARY KEY (cartodb_id), ADD CONSTRAINT cartodb_id_integer CHECK (cartodb_id::integer >=0);
|
||||
1
|
||||
2
|
||||
|
||||
2
|
||||
DROP TABLE
|
||||
CREATE TABLE
|
||||
CREATE INDEX
|
||||
|
||||
38
test/CDB_OverviewsTest.sql
Normal file
38
test/CDB_OverviewsTest.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
SET client_min_messages TO error;
|
||||
\set VERBOSITY default
|
||||
|
||||
\i test/overviews/fixtures.sql
|
||||
|
||||
SELECT _CDB_Aggregable_Attributes_Expression('base_bare_t'::regclass);
|
||||
SELECT _CDB_Aggregated_Attributes_Expression('base_bare_t'::regclass);
|
||||
SELECT _CDB_Aggregated_Attributes_Expression('base_bare_t'::regclass, 'tab');
|
||||
|
||||
SELECT CDB_CreateOverviews('base_bare_t'::regclass);
|
||||
SELECT count(*) FROM _vovw_5_base_bare_t;
|
||||
|
||||
|
||||
SELECT _CDB_Aggregable_Attributes_Expression('base_t'::regclass);
|
||||
SELECT _CDB_Aggregated_Attributes_Expression('base_t'::regclass);
|
||||
SELECT _CDB_Aggregated_Attributes_Expression('base_t'::regclass, 'tab');
|
||||
|
||||
SELECT CDB_CreateOverviews('base_t'::regclass);
|
||||
SELECT count(*) FROM _vovw_5_base_t;
|
||||
|
||||
SELECT CDB_CreateOverviews('polyg_t'::regclass);
|
||||
|
||||
SELECT CDB_CreateOverviews('column_types_t'::regclass);
|
||||
|
||||
SELECT CDB_Overviews('base_t'::regclass);
|
||||
SELECT CDB_Overviews(ARRAY['base_t'::regclass, 'base_bare_t'::regclass]);
|
||||
SELECT CDB_Overviews('polyg_t'::regclass);
|
||||
SELECT CDB_Overviews('column_types_t'::regclass);
|
||||
|
||||
SELECT CDB_DropOverviews('column_types_t'::regclass);
|
||||
SELECT CDB_DropOverviews('base_bare_t'::regclass);
|
||||
SELECT CDB_DropOverviews('base_t'::regclass);
|
||||
SELECT count(*) FROM _vovw_5_base_t;
|
||||
|
||||
DROP TABLE column_types_t;
|
||||
DROP TABLE base_bare_t;
|
||||
DROP TABLE base_t;
|
||||
DROP TABLE polyg_t;
|
||||
54
test/CDB_OverviewsTest_expect
Normal file
54
test/CDB_OverviewsTest_expect
Normal file
@@ -0,0 +1,54 @@
|
||||
SET
|
||||
CREATE TABLE
|
||||
INSERT 0 1114
|
||||
CREATE TABLE
|
||||
INSERT 0 1114
|
||||
CREATE TABLE
|
||||
INSERT 0 5
|
||||
SELECT 1114
|
||||
|
||||
|
||||
|
||||
{_vovw_5_base_bare_t,_vovw_4_base_bare_t,_vovw_3_base_bare_t,_vovw_2_base_bare_t,_vovw_1_base_bare_t,_vovw_0_base_bare_t}
|
||||
125
|
||||
number,int_number,name,start
|
||||
AVG(number)::double precision AS number,AVG(int_number)::integer AS int_number,CASE count(*) WHEN 1 THEN MIN(name) ELSE NULL END::text AS name,CASE count(*) WHEN 1 THEN MIN(start) ELSE NULL END::date AS start
|
||||
AVG(tab.number)::double precision AS number,AVG(tab.int_number)::integer AS int_number,CASE count(*) WHEN 1 THEN MIN(tab.name) ELSE NULL END::text AS name,CASE count(*) WHEN 1 THEN MIN(tab.start) ELSE NULL END::date AS start
|
||||
{_vovw_5_base_t,_vovw_4_base_t,_vovw_3_base_t,_vovw_2_base_t,_vovw_1_base_t,_vovw_0_base_t}
|
||||
125
|
||||
|
||||
{_vovw_5_column_types_t,_vovw_4_column_types_t,_vovw_3_column_types_t,_vovw_2_column_types_t,_vovw_1_column_types_t,_vovw_0_column_types_t}
|
||||
(base_t,0,_vovw_0_base_t)
|
||||
(base_t,1,_vovw_1_base_t)
|
||||
(base_t,2,_vovw_2_base_t)
|
||||
(base_t,3,_vovw_3_base_t)
|
||||
(base_t,4,_vovw_4_base_t)
|
||||
(base_t,5,_vovw_5_base_t)
|
||||
(base_bare_t,0,_vovw_0_base_bare_t)
|
||||
(base_bare_t,1,_vovw_1_base_bare_t)
|
||||
(base_bare_t,2,_vovw_2_base_bare_t)
|
||||
(base_bare_t,3,_vovw_3_base_bare_t)
|
||||
(base_bare_t,4,_vovw_4_base_bare_t)
|
||||
(base_bare_t,5,_vovw_5_base_bare_t)
|
||||
(base_t,0,_vovw_0_base_t)
|
||||
(base_t,1,_vovw_1_base_t)
|
||||
(base_t,2,_vovw_2_base_t)
|
||||
(base_t,3,_vovw_3_base_t)
|
||||
(base_t,4,_vovw_4_base_t)
|
||||
(base_t,5,_vovw_5_base_t)
|
||||
(column_types_t,0,_vovw_0_column_types_t)
|
||||
(column_types_t,1,_vovw_1_column_types_t)
|
||||
(column_types_t,2,_vovw_2_column_types_t)
|
||||
(column_types_t,3,_vovw_3_column_types_t)
|
||||
(column_types_t,4,_vovw_4_column_types_t)
|
||||
(column_types_t,5,_vovw_5_column_types_t)
|
||||
|
||||
|
||||
|
||||
ERROR: relation "_vovw_5_base_t" does not exist
|
||||
LINE 1: SELECT count(*) FROM _vovw_5_base_t;
|
||||
^
|
||||
DROP TABLE
|
||||
DROP TABLE
|
||||
DROP TABLE
|
||||
DROP TABLE
|
||||
@@ -1,4 +1,4 @@
|
||||
SELECT * FROM geometry_columns|{pg_catalog.pg_attribute,pg_catalog.pg_class,pg_catalog.pg_namespace,pg_catalog.pg_type}
|
||||
SELECT * FROM geometry_columns|{pg_catalog.pg_attribute,pg_catalog.pg_class,pg_catalog.pg_constraint,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_catalog.pg_class}
|
||||
@@ -20,4 +20,4 @@ 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}
|
||||
* FROM geometry_columns|{pg_catalog.pg_attribute,pg_catalog.pg_class,pg_catalog.pg_constraint,pg_catalog.pg_namespace,pg_catalog.pg_type}
|
||||
|
||||
@@ -4,6 +4,6 @@ range AS ( select z, generate_series(0, pow(2,z)::int-1) as r FROM zoom),
|
||||
inp AS ( select z0.z, r1.r as x, r2.r as y FROM zoom z0, range r1, range r2 WHERE z0.z = r1.z and r1.z = r2.z ),
|
||||
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)
|
||||
round(st_xmin(g)), round(st_xmax(g)), round(st_ymin(g)), round(st_ymax(g))
|
||||
from ext order by xyz;
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
0,0,0|-20037508.5|20037508.5|-20037508.5|20037508.5
|
||||
0,0,1|-20037508.5|0|0|20037508.5
|
||||
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
|
||||
2,0,2|0|10018754.25|10018754.25|20037508.5
|
||||
2,1,2|0|10018754.25|0|10018754.25
|
||||
2,2,2|0|10018754.25|-10018754.25|0
|
||||
2,3,2|0|10018754.25|-20037508.5|-10018754.25
|
||||
3,0,2|10018754.25|20037508.5|10018754.25|20037508.5
|
||||
3,1,2|10018754.25|20037508.5|0|10018754.25
|
||||
3,2,2|10018754.25|20037508.5|-10018754.25|0
|
||||
3,3,2|10018754.25|20037508.5|-20037508.5|-10018754.25
|
||||
0,0,0|-20037508|20037508|-20037508|20037508
|
||||
0,0,1|-20037508|0|0|20037508
|
||||
0,0,2|-20037508|-10018754|10018754|20037508
|
||||
0,1,1|-20037508|0|-20037508|0
|
||||
0,1,2|-20037508|-10018754|0|10018754
|
||||
0,2,2|-20037508|-10018754|-10018754|0
|
||||
0,3,2|-20037508|-10018754|-20037508|-10018754
|
||||
1,0,1|0|20037508|0|20037508
|
||||
1,0,2|-10018754|0|10018754|20037508
|
||||
1,1,1|0|20037508|-20037508|0
|
||||
1,1,2|-10018754|0|0|10018754
|
||||
1,2,2|-10018754|0|-10018754|0
|
||||
1,3,2|-10018754|0|-20037508|-10018754
|
||||
2,0,2|0|10018754|10018754|20037508
|
||||
2,1,2|0|10018754|0|10018754
|
||||
2,2,2|0|10018754|-10018754|0
|
||||
2,3,2|0|10018754|-20037508|-10018754
|
||||
3,0,2|10018754|20037508|10018754|20037508
|
||||
3,1,2|10018754|20037508|0|10018754
|
||||
3,2,2|10018754|20037508|-10018754|0
|
||||
3,3,2|10018754|20037508|-20037508|-10018754
|
||||
|
||||
@@ -172,8 +172,7 @@ function drop_raster_table() {
|
||||
sql ${ROLE} "DROP TABLE ${ROLE}.${TABLENAME};"
|
||||
}
|
||||
|
||||
|
||||
function setup() {
|
||||
function setup_database() {
|
||||
${CMD} -c "CREATE DATABASE ${DATABASE}"
|
||||
sql "CREATE SCHEMA cartodb;"
|
||||
sql "GRANT USAGE ON SCHEMA cartodb TO public;"
|
||||
@@ -184,7 +183,10 @@ function setup() {
|
||||
${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
|
||||
}
|
||||
|
||||
function setup() {
|
||||
setup_database
|
||||
|
||||
log_info "############################# SETUP #############################"
|
||||
create_role_and_schema cdb_testmember_1
|
||||
@@ -199,6 +201,10 @@ function setup() {
|
||||
sql cdb_testmember_2 'SELECT * FROM cdb_testmember_2.bar;'
|
||||
}
|
||||
|
||||
|
||||
function tear_down_database() {
|
||||
${CMD} -c "DROP DATABASE ${DATABASE}"
|
||||
}
|
||||
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');"
|
||||
@@ -219,9 +225,10 @@ function tear_down() {
|
||||
sql 'DROP ROLE cdb_testmember_1;'
|
||||
sql 'DROP ROLE cdb_testmember_2;'
|
||||
|
||||
${CMD} -c "DROP DATABASE ${DATABASE}"
|
||||
tear_down_database
|
||||
}
|
||||
|
||||
|
||||
function run_tests() {
|
||||
local FAILED_TESTS=()
|
||||
|
||||
@@ -343,6 +350,41 @@ function test_cdb_tablemetadatatouch_fails_from_user_without_permission() {
|
||||
sql postgres "REVOKE ALL ON CDB_TableMetadata FROM cdb_testmember_1;"
|
||||
}
|
||||
|
||||
function test_cdb_tablemetadatatouch_fully_qualifies_names() {
|
||||
sql postgres "CREATE TABLE touch_invalidations (table_name text);"
|
||||
sql postgres "create or replace function cartodb.cdb_invalidate_varnish(table_name text) returns void as \$\$ begin insert into touch_invalidations select table_name; end; \$\$ language 'plpgsql';"
|
||||
|
||||
#default schema
|
||||
sql "CREATE TABLE touch_example (a int);"
|
||||
sql postgres "SELECT CDB_TableMetadataTouch('touch_example');"
|
||||
sql postgres "SELECT table_name FROM touch_invalidations" should "public.touch_example"
|
||||
sql postgres "TRUNCATE TABLE touch_invalidations"
|
||||
sql postgres "DROP TABLE touch_example"
|
||||
|
||||
#setup different schema
|
||||
sql postgres "CREATE SCHEMA test_schema;"
|
||||
sql postgres "CREATE TABLE test_schema.touch_example (a int);"
|
||||
|
||||
#different schema outside search_path
|
||||
sql postgres "SELECT CDB_TableMetadataTouch('test_schema.touch_example');"
|
||||
sql postgres "SELECT table_name FROM touch_invalidations" should "test_schema.touch_example"
|
||||
sql postgres "TRUNCATE TABLE touch_invalidations"
|
||||
|
||||
#different schema in default search_path
|
||||
sql postgres "SET search_path=test_schema,public,cartodb; SELECT CDB_TableMetadataTouch('test_schema.touch_example');"
|
||||
sql postgres "SELECT table_name FROM touch_invalidations" should "test_schema.touch_example"
|
||||
sql postgres "TRUNCATE TABLE touch_invalidations"
|
||||
|
||||
#teardown different schema
|
||||
sql postgres 'DROP TABLE test_schema.touch_example;'
|
||||
sql postgres 'DROP SCHEMA test_schema;'
|
||||
|
||||
|
||||
|
||||
sql postgres 'DROP FUNCTION cartodb.cdb_invalidate_varnish(table_name text);'
|
||||
sql postgres 'DROP TABLE touch_invalidations'
|
||||
}
|
||||
|
||||
function test_cdb_column_names() {
|
||||
sql cdb_testmember_1 'CREATE TABLE cdb_testmember_1.table_cnames(c int, a int, r int, t int, o int);'
|
||||
sql cdb_testmember_2 'CREATE TABLE cdb_testmember_2.table_cnames(d int, b int);'
|
||||
@@ -429,6 +471,98 @@ function test_cdb_querytables_happy_cases() {
|
||||
sql postgres 'DROP SCHEMA foo;'
|
||||
}
|
||||
|
||||
function test_foreign_tables() {
|
||||
${CMD} -d ${DATABASE} -f scripts-available/CDB_QueryStatements.sql
|
||||
${CMD} -d ${DATABASE} -f scripts-available/CDB_QueryTables.sql
|
||||
${CMD} -d ${DATABASE} -f scripts-available/CDB_TableMetadata.sql
|
||||
${CMD} -d ${DATABASE} -f scripts-available/CDB_Conf.sql
|
||||
${CMD} -d ${DATABASE} -f scripts-available/CDB_ForeignTable.sql
|
||||
|
||||
|
||||
DATABASE=fdw_target setup_database
|
||||
${CMD} -d fdw_target -f scripts-available/CDB_QueryStatements.sql
|
||||
${CMD} -d fdw_target -f scripts-available/CDB_QueryTables.sql
|
||||
${CMD} -d fdw_target -f scripts-available/CDB_TableMetadata.sql
|
||||
|
||||
DATABASE=fdw_target sql postgres 'CREATE SCHEMA test_fdw;'
|
||||
DATABASE=fdw_target sql postgres 'CREATE TABLE test_fdw.foo (a int);'
|
||||
DATABASE=fdw_target sql postgres 'INSERT INTO test_fdw.foo (a) values (42);'
|
||||
DATABASE=fdw_target sql postgres 'CREATE TABLE test_fdw.foo2 (a int);'
|
||||
DATABASE=fdw_target sql postgres 'INSERT INTO test_fdw.foo2 (a) values (42);'
|
||||
DATABASE=fdw_target sql postgres "CREATE USER fdw_user WITH PASSWORD 'foobarino';"
|
||||
DATABASE=fdw_target sql postgres 'GRANT USAGE ON SCHEMA test_fdw TO fdw_user;'
|
||||
DATABASE=fdw_target sql postgres 'GRANT SELECT ON TABLE test_fdw.foo TO fdw_user;'
|
||||
DATABASE=fdw_target sql postgres 'GRANT SELECT ON TABLE test_fdw.foo2 TO fdw_user;'
|
||||
DATABASE=fdw_target sql postgres 'GRANT SELECT ON cdb_tablemetadata_text TO fdw_user;'
|
||||
|
||||
DATABASE=fdw_target sql postgres "SELECT cdb_tablemetadatatouch('test_fdw.foo'::regclass);"
|
||||
DATABASE=fdw_target sql postgres "SELECT cdb_tablemetadatatouch('test_fdw.foo2'::regclass);"
|
||||
|
||||
sql postgres "SELECT cartodb.CDB_Conf_SetConf('fdws', '{\"test_fdw\": {\"server\": {\"host\": \"localhost\", \"dbname\": \"fdw_target\"},
|
||||
\"users\": {\"public\": {\"user\": \"fdw_user\", \"password\": \"foobarino\"}}}}')"
|
||||
|
||||
sql postgres "SELECT cartodb._CDB_Setup_FDW('test_fdw')"
|
||||
|
||||
sql postgres "SHOW server_version_num"
|
||||
if [ "$RESULT" -gt 90499 ]
|
||||
then
|
||||
sql postgres "SELECT cartodb.CDB_Add_Remote_Table('test_fdw', 'foo')"
|
||||
sql postgres "SELECT * from test_fdw.foo;"
|
||||
else
|
||||
echo "NOTICE: PostgreSQL version is less than 9.5 ($RESULT). Skipping CDB_Add_Remote_Table."
|
||||
sql postgres "CREATE FOREIGN TABLE test_fdw.foo (a int) SERVER test_fdw OPTIONS (table_name 'foo', schema_name 'test_fdw')"
|
||||
fi
|
||||
|
||||
sql postgres "SELECT n.nspname,
|
||||
c.relname,
|
||||
s.srvname FROM pg_catalog.pg_foreign_table ft
|
||||
INNER JOIN pg_catalog.pg_class c ON c.oid = ft.ftrelid
|
||||
INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
||||
INNER JOIN pg_catalog.pg_foreign_server s ON s.oid = ft.ftserver
|
||||
ORDER BY 1, 2" should "test_fdw|cdb_tablemetadata|test_fdw
|
||||
test_fdw|foo|test_fdw"
|
||||
|
||||
sql postgres "SELECT cartodb.CDB_Get_Foreign_Updated_At('test_fdw.foo'::regclass) < NOW()" should 't'
|
||||
|
||||
sql postgres "SELECT a from test_fdw.foo LIMIT 1;" should 42
|
||||
|
||||
# Check function CDB_QueryTables_Updated_At
|
||||
sql postgres 'CREATE TABLE local (b int);'
|
||||
sql postgres 'INSERT INTO local (b) VALUES (43);'
|
||||
sql postgres "SELECT cdb_tablemetadatatouch('public.local'::regclass);"
|
||||
local query='$query$ SELECT * FROM test_fdw.foo, local $query$::text'
|
||||
sql postgres "SELECT dbname, schema_name, table_name FROM cartodb.CDB_QueryTables_Updated_At(${query}) ORDER BY dbname;" should 'fdw_target|test_fdw|foo
|
||||
test_extension|public|local'
|
||||
sql postgres "SELECT table_name FROM cartodb.CDB_QueryTables_Updated_At(${query}) order by updated_at;" should 'foo
|
||||
local'
|
||||
|
||||
# Check function CDB_Last_Updated_Time
|
||||
sql postgres "SELECT cartodb.CDB_Last_Updated_Time('{test_fdw.foo,public.local}'::text[]) < now()" should 't'
|
||||
sql postgres "SELECT cartodb.CDB_Last_Updated_Time('{test_fdw.foo,public.local}'::text[]) > (now() - interval '1 minute')" should 't'
|
||||
|
||||
# Check we quote names on output as needed (as CDB_QueryTablesText does)
|
||||
sql postgres 'CREATE TABLE "local-table-with-dashes" (c int)';
|
||||
sql postgres 'INSERT INTO "local-table-with-dashes" (c) VALUES (44)';
|
||||
sql postgres "SELECT cdb_tablemetadatatouch('public.local-table-with-dashes'::regclass);"
|
||||
query='$query$ SELECT * FROM test_fdw.foo, local, public."local-table-with-dashes" $query$::text'
|
||||
sql postgres "SELECT dbname, schema_name, table_name FROM cartodb.CDB_QueryTables_Updated_At(${query}) ORDER BY dbname, schema_name, table_name;" should 'fdw_target|test_fdw|foo
|
||||
test_extension|public|local
|
||||
test_extension|public|"local-table-with-dashes"'
|
||||
|
||||
# Check CDB_Last_Updated_Time supports quoted identifiers
|
||||
sql postgres "SELECT cartodb.CDB_Last_Updated_Time(ARRAY['test_extension.public.\"local-table-with-dashes\"']::text[]) < now()" should 't'
|
||||
sql postgres "SELECT cartodb.CDB_Last_Updated_Time(ARRAY['test_extension.public.\"local-table-with-dashes\"']::text[]) > (now() - interval '1 minute')" should 't'
|
||||
|
||||
DATABASE=fdw_target sql postgres 'REVOKE USAGE ON SCHEMA test_fdw FROM fdw_user;'
|
||||
DATABASE=fdw_target sql postgres 'REVOKE SELECT ON test_fdw.foo FROM fdw_user;'
|
||||
DATABASE=fdw_target sql postgres 'REVOKE SELECT ON test_fdw.foo2 FROM fdw_user;'
|
||||
DATABASE=fdw_target sql postgres 'REVOKE SELECT ON cdb_tablemetadata_text FROM fdw_user;'
|
||||
DATABASE=fdw_target sql postgres 'DROP ROLE fdw_user;'
|
||||
|
||||
sql postgres "select pg_terminate_backend(pid) from pg_stat_activity where datname='fdw_target';"
|
||||
DATABASE=fdw_target tear_down_database
|
||||
}
|
||||
|
||||
#################################################### TESTS END HERE ####################################################
|
||||
|
||||
run_tests $@
|
||||
|
||||
2249
test/overviews/fixtures.sql
Normal file
2249
test/overviews/fixtures.sql
Normal file
File diff suppressed because it is too large
Load Diff
43
test/overviews/gen_points.rb
Normal file
43
test/overviews/gen_points.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# Ruby script to generate test/overviews/fixtures.sql for testing overviews
|
||||
# Generated tables:
|
||||
# * base_bare_t -- points without attributes (only PK, geometries)
|
||||
|
||||
NUM_CLUSTERS = 128
|
||||
MAX_PER_CLUSTER = 16
|
||||
CLUSTER_RADIUS = 1E-3
|
||||
MIN_X = -10.0
|
||||
MAX_X = 10.0
|
||||
MIN_Y = 30.0
|
||||
MAX_Y = 40.0
|
||||
ATTRIBUTES = "number double precision, int_number integer, name text, start date"
|
||||
|
||||
id = 0
|
||||
POINTS = (0...NUM_CLUSTERS).map{
|
||||
x = MIN_X + rand()*(MAX_X - MIN_X)
|
||||
y = MIN_Y + rand()*(MAX_Y - MIN_Y)
|
||||
(0..rand(MAX_PER_CLUSTER)).map{
|
||||
id += 1
|
||||
{
|
||||
id: id,
|
||||
x: (x + rand()*CLUSTER_RADIUS).round(6),
|
||||
y: (y + rand()*CLUSTER_RADIUS).round(6)
|
||||
}
|
||||
}
|
||||
}.flatten
|
||||
|
||||
values = POINTS.map{ |point|
|
||||
"#{point[:id]}, 'SRID=4326;POINT(#{point[:x]} #{point[:y]})'::geometry, ST_Transform('SRID=4326;POINT(#{point[:x]} #{point[:y]})'::geometry, 3857)"
|
||||
}
|
||||
|
||||
File.open('fixtures.sql', 'w') do |sql|
|
||||
|
||||
sql.puts "-- bare table with no attribute columns"
|
||||
sql.puts "CREATE TABLE base_bare_t (cartodb_id integer, the_geom geometry, the_geom_webmercator geometry);"
|
||||
sql.puts "INSERT INTO base_bare_t VALUES"
|
||||
sql.puts values.map{|v| "(#{v})"}.join(",\n") + ";"
|
||||
|
||||
sql.puts "-- table with attributes"
|
||||
sql.puts "CREATE TABLE base_t (cartodb_id integer, the_geom geometry, the_geom_webmercator geometry, #{ATTRIBUTES});"
|
||||
sql.puts "INSERT INTO base_t VALUES"
|
||||
sql.puts values.map{|v| "(#{v})"}.join(",\n") + ";"
|
||||
end
|
||||
Reference in New Issue
Block a user