Allow non-superusers to work with the db (but also mess up the catalog)

Previous to this commit non-superusers could not do anything inside
a database with the timescale extension loaded. Now, non-superuser
can create their own hypertables and work inside the db. There are
two big caveats:
      1) All users have read/write permissions to the timescaledb
      catalog.
      2) Permission changes applied to the main tables are not
      propagated to the associated tables.
This commit is contained in:
Matvey Arye 2017-03-08 12:40:49 -05:00
parent 21d4ebb23b
commit fbdcab186f
7 changed files with 307 additions and 18 deletions

View File

@ -193,13 +193,13 @@ this creates a more compact, and thus efficient, index.
Below are a few current limitations of our database, which we are
actively working to resolve:
- Anyone using TimescaleDB currently needs superuser privileges (this is
getting fixed very soon)
- Any user has full read/write access to the metadata tables for hypertables.
- Permission changes on hypertables are not correctly propagated.
- `create_hypertable()` can only be run on an empty table
- `COPY`ing a dataset will currently put all data in the same chunk, even if
chunk size goes over max size. For now we recommend breaking down large
files for `COPY` (e.g., large CSVs) into smaller files that are slightly
larger than max_chunk size (currently 1GB).
larger than max_chunk size (currently 1GB by default).
We provide `scripts/migrate_data.sh` to help with this.
- Custom user-created triggers on hypertables currently will not work (i.e.,
fire)

View File

@ -0,0 +1,37 @@
--schema permisions
GRANT USAGE ON SCHEMA _timescaledb_catalog, _timescaledb_meta, _timescaledb_meta_api, _timescaledb_data_api, _timescaledb_cache
TO PUBLIC;
GRANT USAGE, CREATE ON SCHEMA _timescaledb_internal TO PUBLIC;
--needed for working with hypertables
GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_catalog TO PUBLIC;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA _timescaledb_catalog TO PUBLIC;
--Needed but dangerous. Anybody can mess up the _timescaledb_catalog.
--MUST DOCUMENT TODO: remove these permissions. Have c-based workaround.
--Everything below this line is suspect.
GRANT INSERT ON TABLE _timescaledb_catalog.hypertable,
_timescaledb_catalog.default_replica_node, _timescaledb_catalog.hypertable_replica, _timescaledb_catalog.partition_epoch,
_timescaledb_catalog.partition, _timescaledb_catalog.partition_replica,
_timescaledb_catalog.hypertable_column,
_timescaledb_catalog.chunk, _timescaledb_catalog.chunk_replica_node
TO PUBLIC;
--needed for inserts to hypertable
GRANT UPDATE ON TABLE _timescaledb_catalog.hypertable, _timescaledb_catalog.chunk,
_timescaledb_catalog.partition, _timescaledb_catalog.partition_epoch --needed for lock
TO PUBLIC;
--needed for ddl
GRANT INSERT, DELETE ON TABLE _timescaledb_catalog.hypertable_index, _timescaledb_catalog.chunk_replica_node_index
TO PUBLIC;
GRANT DELETE, UPDATE ON TABLE _timescaledb_catalog.hypertable_column
TO PUBLIC;
GRANT INSERT ON TABLE _timescaledb_catalog.deleted_hypertable_index, _timescaledb_catalog.deleted_hypertable_column
TO PUBLIC;

View File

@ -47,3 +47,4 @@ sql/main/ddl_util.sql
sql/main/ddl.sql
sql/main/ddl_triggers.sql
sql/main/setup_main.sql
sql/common/permissions.sql

View File

@ -43,10 +43,22 @@ DECLARE
remote_node _timescaledb_catalog.node;
BEGIN
IF TG_OP = 'INSERT' THEN
EXECUTE format(
$$
CREATE SCHEMA IF NOT EXISTS %I
$$, NEW.associated_schema_name);
DECLARE
cnt INTEGER;
BEGIN
EXECUTE format(
$$
CREATE SCHEMA IF NOT EXISTS %I
$$, NEW.associated_schema_name);
EXCEPTION
WHEN insufficient_privilege THEN
SELECT COUNT(*) INTO cnt
FROM pg_namespace
WHERE nspname = NEW.associated_schema_name;
IF cnt = 0 THEN
RAISE;
END IF;
END;
PERFORM _timescaledb_internal.create_table(NEW.root_schema_name, NEW.root_table_name);

View File

@ -66,22 +66,12 @@ static Catalog catalog = {
Catalog *
catalog_get(void)
{
AclResult aclresult;
int i;
if (MyDatabaseId == InvalidOid)
elog(ERROR, "Invalid database ID");
/*
* Check that the user has CREATE permissions on the database, since the
* operation may involve creating chunks and inserting into them.
*/
aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_DATABASE,
get_database_name(MyDatabaseId));
if (MyDatabaseId == catalog.database_id)
return &catalog;

View File

@ -0,0 +1,213 @@
\set ON_ERROR_STOP 1
\set VERBOSITY verbose
\set SHOW_CONTEXT never
\set ECHO ALL
\ir include/insert_single.sql
\set ON_ERROR_STOP 1
\ir create_single_db.sql
\set VERBOSITY default
SET client_min_messages = WARNING;
DROP DATABASE IF EXISTS single;
SET client_min_messages = NOTICE;
CREATE DATABASE single;
\c single
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
psql:include/create_single_db.sql:8: NOTICE: installing required extension "dblink"
psql:include/create_single_db.sql:8: NOTICE: installing required extension "postgres_fdw"
psql:include/create_single_db.sql:8: NOTICE: installing required extension "hstore"
SELECT setup_db(hostname => 'fakehost'); -- fakehost makes sure there is no network connection
setup_db
----------
(1 row)
\set VERBOSITY verbose
\c single
\set ECHO ALL
CREATE TABLE PUBLIC."testNs" (
"timeCustom" BIGINT NOT NULL,
device_id TEXT NOT NULL,
series_0 DOUBLE PRECISION NULL,
series_1 DOUBLE PRECISION NULL,
series_2 DOUBLE PRECISION NULL,
series_bool BOOLEAN NULL
);
CREATE INDEX ON PUBLIC."testNs" (device_id, "timeCustom" DESC NULLS LAST) WHERE device_id IS NOT NULL;
CREATE INDEX ON PUBLIC."testNs" ("timeCustom" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL;
CREATE INDEX ON PUBLIC."testNs" ("timeCustom" DESC NULLS LAST, series_1) WHERE series_1 IS NOT NULL;
CREATE INDEX ON PUBLIC."testNs" ("timeCustom" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL;
CREATE INDEX ON PUBLIC."testNs" ("timeCustom" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL;
SELECT * FROM create_hypertable('"public"."testNs"', 'timeCustom', associated_schema_name=>'testNs' );
create_hypertable
-------------------
(1 row)
BEGIN;
\COPY "testNs" FROM 'data/ds1_dev1_1.tsv' NULL AS '';
COMMIT;
SELECT _timescaledb_meta_api.close_chunk_end_immediate(c.id)
FROM get_open_partition_for_key((SELECT id FROM _timescaledb_catalog.hypertable WHERE table_name = 'testNs'), 'dev1') part
INNER JOIN _timescaledb_catalog.chunk c ON (c.partition_id = part.id);
close_chunk_end_immediate
---------------------------
(1 row)
INSERT INTO "testNs"("timeCustom", device_id, series_0, series_1) VALUES
(1257987600000000000, 'dev1', 1.5, 1),
(1257987600000000000, 'dev1', 1.5, 2),
(1257894000000000000, 'dev20', 1.5, 1),
(1257894002000000000, 'dev1', 2.5, 3);
INSERT INTO "testNs"("timeCustom", device_id, series_0, series_1) VALUES
(1257894000000000000, 'dev20', 1.5, 2);
CREATE TABLE chunk_closing_test(
time BIGINT,
metric INTEGER,
device_id TEXT
);
-- Test chunk closing/creation
SELECT * FROM create_hypertable('chunk_closing_test', 'time', chunk_size_bytes => 10000);
create_hypertable
-------------------
(1 row)
INSERT INTO chunk_closing_test VALUES(1, 1, 'dev1');
INSERT INTO chunk_closing_test VALUES(2, 2, 'dev2');
INSERT INTO chunk_closing_test VALUES(3, 3, 'dev3');
SELECT * FROM chunk_closing_test;
time | metric | device_id
------+--------+-----------
1 | 1 | dev1
2 | 2 | dev2
3 | 3 | dev3
(3 rows)
SELECT * FROM _timescaledb_catalog.chunk c
LEFT JOIN _timescaledb_catalog.chunk_replica_node crn ON (c.id = crn.chunk_id)
LEFT JOIN _timescaledb_catalog.partition_replica pr ON (crn.partition_replica_id = pr.id)
LEFT JOIN _timescaledb_catalog.hypertable h ON (pr.hypertable_id = h.id)
WHERE h.schema_name = 'public' AND h.table_name = 'chunk_closing_test';
id | partition_id | start_time | end_time | chunk_id | partition_replica_id | database_name | schema_name | table_name | id | partition_id | hypertable_id | replica_id | schema_name | table_name | id | schema_name | table_name | associated_schema_name | associated_table_prefix | root_schema_name | root_table_name | replication_factor | placement | time_column_name | time_column_type | created_on | chunk_size_bytes
----+--------------+------------+----------+----------+----------------------+---------------+-----------------------+---------------------+----+--------------+---------------+------------+-----------------------+------------------------+----+-------------+--------------------+------------------------+-------------------------+-----------------------+-----------------+--------------------+-----------+------------------+------------------+------------+------------------
3 | 2 | | 1 | 3 | 2 | single | _timescaledb_internal | _hyper_2_2_0_3_data | 2 | 2 | 2 | 0 | _timescaledb_internal | _hyper_2_2_0_partition | 2 | public | chunk_closing_test | _timescaledb_internal | _hyper_2 | _timescaledb_internal | _hyper_2_root | 1 | STICKY | time | bigint | single | 10000
4 | 2 | 2 | 2 | 4 | 2 | single | _timescaledb_internal | _hyper_2_2_0_4_data | 2 | 2 | 2 | 0 | _timescaledb_internal | _hyper_2_2_0_partition | 2 | public | chunk_closing_test | _timescaledb_internal | _hyper_2 | _timescaledb_internal | _hyper_2_root | 1 | STICKY | time | bigint | single | 10000
5 | 2 | 3 | | 5 | 2 | single | _timescaledb_internal | _hyper_2_2_0_5_data | 2 | 2 | 2 | 0 | _timescaledb_internal | _hyper_2_2_0_partition | 2 | public | chunk_closing_test | _timescaledb_internal | _hyper_2 | _timescaledb_internal | _hyper_2_root | 1 | STICKY | time | bigint | single | 10000
(3 rows)
\set VERBOSITY default
DO $$
BEGIN
CREATE ROLE alt_usr LOGIN;
EXCEPTION
WHEN duplicate_object THEN
--mute error
END$$;
\c single alt_usr
\dt
List of relations
Schema | Name | Type | Owner
--------+--------------------+-------+----------
public | chunk_closing_test | table | postgres
public | testNs | table | postgres
(2 rows)
\set ON_ERROR_STOP 0
SELECT * FROM chunk_closing_test;
ERROR: permission denied for relation _hyper_2_0_replica
--todo fix error message here:
SELECT * FROM "testNs";
ERROR: permission denied for schema testNs
\set ON_ERROR_STOP 1
CREATE TABLE "1dim"(time timestamp, temp float);
SELECT create_hypertable('"1dim"', 'time');
create_hypertable
-------------------
(1 row)
INSERT INTO "1dim" VALUES('2017-01-20T09:00:01', 22.5);
INSERT INTO "1dim" VALUES('2017-01-20T09:00:21', 21.2);
INSERT INTO "1dim" VALUES('2017-01-20T09:00:47', 25.1);
SELECT * FROM "1dim";
time | temp
--------------------------+------
Fri Jan 20 09:00:01 2017 | 22.5
Fri Jan 20 09:00:21 2017 | 21.2
Fri Jan 20 09:00:47 2017 | 25.1
(3 rows)
\ir include/ddl_ops_1.sql
CREATE TABLE PUBLIC."Hypertable_1" (
time BIGINT NOT NULL,
"Device_id" TEXT NOT NULL,
temp_c int NOT NULL DEFAULT -1,
humidity numeric NULL DEFAULT 0,
sensor_1 NUMERIC NULL DEFAULT 1,
sensor_2 NUMERIC NOT NULL DEFAULT 1,
sensor_3 NUMERIC NOT NULL DEFAULT 1,
sensor_4 NUMERIC NOT NULL DEFAULT 1
);
CREATE INDEX ON PUBLIC."Hypertable_1" (time, "Device_id");
SELECT * FROM create_hypertable('"public"."Hypertable_1"', 'time', 'Device_id', 1);
create_hypertable
-------------------
(1 row)
SELECT * FROM _timescaledb_catalog.hypertable;
id | schema_name | table_name | associated_schema_name | associated_table_prefix | root_schema_name | root_table_name | replication_factor | placement | time_column_name | time_column_type | created_on | chunk_size_bytes
----+-------------+--------------------+------------------------+-------------------------+-----------------------+-----------------+--------------------+-----------+------------------+-----------------------------+------------+------------------
1 | public | testNs | testNs | _hyper_1 | testNs | _hyper_1_root | 1 | STICKY | timeCustom | bigint | single | 1073741824
2 | public | chunk_closing_test | _timescaledb_internal | _hyper_2 | _timescaledb_internal | _hyper_2_root | 1 | STICKY | time | bigint | single | 10000
3 | public | 1dim | _timescaledb_internal | _hyper_3 | _timescaledb_internal | _hyper_3_root | 1 | STICKY | time | timestamp without time zone | single | 1073741824
4 | public | Hypertable_1 | _timescaledb_internal | _hyper_4 | _timescaledb_internal | _hyper_4_root | 1 | STICKY | time | bigint | single | 1073741824
(4 rows)
SELECT * FROM _timescaledb_catalog.hypertable_index;
hypertable_id | main_schema_name | main_index_name | definition | created_on
---------------+------------------+-----------------------------------+---------------------------------------------------------------------------------------------------------------------------------------+------------
1 | public | testNs_device_id_timeCustom_idx | CREATE INDEX /*INDEX_NAME*/ ON /*TABLE_NAME*/ USING btree (device_id, "timeCustom" DESC NULLS LAST) WHERE (device_id IS NOT NULL) | single
1 | public | testNs_timeCustom_series_0_idx | CREATE INDEX /*INDEX_NAME*/ ON /*TABLE_NAME*/ USING btree ("timeCustom" DESC NULLS LAST, series_0) WHERE (series_0 IS NOT NULL) | single
1 | public | testNs_timeCustom_series_1_idx | CREATE INDEX /*INDEX_NAME*/ ON /*TABLE_NAME*/ USING btree ("timeCustom" DESC NULLS LAST, series_1) WHERE (series_1 IS NOT NULL) | single
1 | public | testNs_timeCustom_series_2_idx | CREATE INDEX /*INDEX_NAME*/ ON /*TABLE_NAME*/ USING btree ("timeCustom" DESC NULLS LAST, series_2) WHERE (series_2 IS NOT NULL) | single
1 | public | testNs_timeCustom_series_bool_idx | CREATE INDEX /*INDEX_NAME*/ ON /*TABLE_NAME*/ USING btree ("timeCustom" DESC NULLS LAST, series_bool) WHERE (series_bool IS NOT NULL) | single
4 | public | Hypertable_1_time_Device_id_idx | CREATE INDEX /*INDEX_NAME*/ ON /*TABLE_NAME*/ USING btree ("time", "Device_id") | single
(6 rows)
CREATE INDEX ON PUBLIC."Hypertable_1" (time, "temp_c");
CREATE INDEX "ind_humidity" ON PUBLIC."Hypertable_1" (time, "humidity");
CREATE INDEX "ind_sensor_1" ON PUBLIC."Hypertable_1" (time, "sensor_1");
INSERT INTO PUBLIC."Hypertable_1"(time, "Device_id", temp_c, humidity, sensor_1, sensor_2, sensor_3, sensor_4)
VALUES(1257894000000000000, 'dev1', 30, 70, 1, 2, 3, 100);
--expect error cases
\set ON_ERROR_STOP 0
UPDATE ONLY PUBLIC."Hypertable_1" SET time = 0 WHERE TRUE;
psql:include/ddl_ops_1.sql:25: ERROR: UPDATE ONLY not supported on hypertables
DELETE FROM ONLY PUBLIC."Hypertable_1" WHERE "Device_id" = 'dev1';
psql:include/ddl_ops_1.sql:26: ERROR: DELETE ONLY not currently supported on hypertables
\set ON_ERROR_STOP 1
\ir include/ddl_ops_2.sql
ALTER TABLE PUBLIC."Hypertable_1" ADD COLUMN temp_f INTEGER NOT NULL DEFAULT 31;
ALTER TABLE PUBLIC."Hypertable_1" DROP COLUMN temp_c;
ALTER TABLE PUBLIC."Hypertable_1" DROP COLUMN sensor_4;
ALTER TABLE PUBLIC."Hypertable_1" ALTER COLUMN humidity SET DEFAULT 100;
ALTER TABLE PUBLIC."Hypertable_1" ALTER COLUMN sensor_1 DROP DEFAULT;
ALTER TABLE PUBLIC."Hypertable_1" ALTER COLUMN sensor_2 SET DEFAULT NULL;
ALTER TABLE PUBLIC."Hypertable_1" ALTER COLUMN sensor_1 SET NOT NULL;
ALTER TABLE PUBLIC."Hypertable_1" ALTER COLUMN sensor_2 DROP NOT NULL;
ALTER TABLE PUBLIC."Hypertable_1" RENAME COLUMN sensor_2 TO sensor_2_renamed;
ALTER TABLE PUBLIC."Hypertable_1" RENAME COLUMN sensor_3 TO sensor_3_renamed;
DROP INDEX "ind_sensor_1";
--expect error cases
\set ON_ERROR_STOP 0
ALTER TABLE PUBLIC."Hypertable_1" ALTER COLUMN sensor_2_renamed SET DATA TYPE int;
psql:include/ddl_ops_2.sql:15: ERROR: ALTER TABLE ... ALTER COLUMN SET DATA TYPE not supported on hypertable "Hypertable_1"
ALTER INDEX "ind_humidity" RENAME TO "ind_humdity2";
psql:include/ddl_ops_2.sql:16: ERROR: ALTER INDEX not supported on hypertable "Hypertable_1"
\set ON_ERROR_STOP 1
--create column with same name as previously renamed one
ALTER TABLE PUBLIC."Hypertable_1" ADD COLUMN sensor_3 BIGINT NOT NULL DEFAULT 131;
--create column with same name as previously dropped one
ALTER TABLE PUBLIC."Hypertable_1" ADD COLUMN sensor_4 BIGINT NOT NULL DEFAULT 131;

View File

@ -0,0 +1,36 @@
\set ON_ERROR_STOP 1
\set VERBOSITY verbose
\set SHOW_CONTEXT never
\set ECHO ALL
\ir include/insert_single.sql
\set VERBOSITY default
DO $$
BEGIN
CREATE ROLE alt_usr LOGIN;
EXCEPTION
WHEN duplicate_object THEN
--mute error
END$$;
\c single alt_usr
\dt
\set ON_ERROR_STOP 0
SELECT * FROM chunk_closing_test;
--todo fix error message here:
SELECT * FROM "testNs";
\set ON_ERROR_STOP 1
CREATE TABLE "1dim"(time timestamp, temp float);
SELECT create_hypertable('"1dim"', 'time');
INSERT INTO "1dim" VALUES('2017-01-20T09:00:01', 22.5);
INSERT INTO "1dim" VALUES('2017-01-20T09:00:21', 21.2);
INSERT INTO "1dim" VALUES('2017-01-20T09:00:47', 25.1);
SELECT * FROM "1dim";
\ir include/ddl_ops_1.sql
\ir include/ddl_ops_2.sql