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 Below are a few current limitations of our database, which we are
actively working to resolve: actively working to resolve:
- Anyone using TimescaleDB currently needs superuser privileges (this is - Any user has full read/write access to the metadata tables for hypertables.
getting fixed very soon) - Permission changes on hypertables are not correctly propagated.
- `create_hypertable()` can only be run on an empty table - `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 - `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 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 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. We provide `scripts/migrate_data.sh` to help with this.
- Custom user-created triggers on hypertables currently will not work (i.e., - Custom user-created triggers on hypertables currently will not work (i.e.,
fire) 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.sql
sql/main/ddl_triggers.sql sql/main/ddl_triggers.sql
sql/main/setup_main.sql sql/main/setup_main.sql
sql/common/permissions.sql

View File

@ -43,10 +43,22 @@ DECLARE
remote_node _timescaledb_catalog.node; remote_node _timescaledb_catalog.node;
BEGIN BEGIN
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
DECLARE
cnt INTEGER;
BEGIN
EXECUTE format( EXECUTE format(
$$ $$
CREATE SCHEMA IF NOT EXISTS %I CREATE SCHEMA IF NOT EXISTS %I
$$, NEW.associated_schema_name); $$, 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); PERFORM _timescaledb_internal.create_table(NEW.root_schema_name, NEW.root_table_name);

View File

@ -66,21 +66,11 @@ static Catalog catalog = {
Catalog * Catalog *
catalog_get(void) catalog_get(void)
{ {
AclResult aclresult;
int i; int i;
if (MyDatabaseId == InvalidOid) if (MyDatabaseId == InvalidOid)
elog(ERROR, "Invalid database ID"); 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) if (MyDatabaseId == catalog.database_id)
return &catalog; 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