-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
\set ON_ERROR_STOP 0
\c :TEST_DBNAME :ROLE_SUPERUSER
CREATE VIEW hypertable_tablespaces AS
SELECT cls.relname AS hypertable,
       (SELECT spcname FROM pg_tablespace WHERE oid = reltablespace) AS tablespace
  FROM _timescaledb_catalog.hypertable,
  LATERAL (SELECT * FROM pg_class WHERE oid = format('%I.%I', schema_name, table_name)::regclass) AS cls
  ORDER BY hypertable, tablespace;
GRANT SELECT ON hypertable_tablespaces TO PUBLIC;
--Test hypertable with tablespace. Tablespaces are cluster-wide, so we
--attach the test name as prefix to allow tests to be executed in
--parallel.
CREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
--assigning a tablespace via the main table should work
CREATE TABLE tspace_2dim(time timestamp, temp float, device text) TABLESPACE tablespace1;
SELECT create_hypertable('tspace_2dim', 'time', 'device', 2);
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
    create_hypertable     
--------------------------
 (1,public,tspace_2dim,t)

INSERT INTO tspace_2dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');
-- Tablespace for tspace_2dim should be set
SELECT * FROM hypertable_tablespaces WHERE hypertable = 'tspace_2dim';
 hypertable  | tablespace  
-------------+-------------
 tspace_2dim | tablespace1

SELECT show_tablespaces('tspace_2dim');
 show_tablespaces 
------------------
 tablespace1

--verify that the table chunk has the correct tablespace
SELECT relname, spcname FROM pg_class c
INNER JOIN pg_tablespace t ON (c.reltablespace = t.oid)
INNER JOIN _timescaledb_catalog.chunk ch ON (ch.table_name = c.relname);
     relname      |   spcname   
------------------+-------------
 _hyper_1_1_chunk | tablespace1

--check some error conditions
SELECT attach_tablespace(NULL,NULL);
ERROR:  invalid tablespace name
SELECT attach_tablespace('tablespace2', NULL);
ERROR:  invalid hypertable
SELECT attach_tablespace(NULL, 'tspace_2dim');
ERROR:  invalid tablespace name
SELECT attach_tablespace('none_existing_tablespace', 'tspace_2dim');
ERROR:  tablespace "none_existing_tablespace" does not exist
SELECT attach_tablespace('tablespace2', 'none_existing_table');
ERROR:  relation "none_existing_table" does not exist at character 41
SELECT detach_tablespace(NULL);
ERROR:  invalid tablespace name
SELECT detach_tablespaces(NULL);
ERROR:  invalid argument
SELECT show_tablespaces(NULL);
 show_tablespaces 
------------------

--attach another tablespace without first creating it --> should generate error
SELECT attach_tablespace('tablespace2', 'tspace_2dim');
ERROR:  tablespace "tablespace2" does not exist
--attach the same tablespace twice to same table should also generate error
SELECT attach_tablespace('tablespace1', 'tspace_2dim');
ERROR:  tablespace "tablespace1" is already attached to hypertable "tspace_2dim"
--no error if if_not_attached is given
SELECT attach_tablespace('tablespace1', 'tspace_2dim', if_not_attached => true);
NOTICE:  tablespace "tablespace1" is already attached to hypertable "tspace_2dim", skipping
 attach_tablespace 
-------------------
 

\c :TEST_DBNAME :ROLE_SUPERUSER
--Tablespaces are cluster-wide, so we attach the test name as prefix
--to allow tests to be executed in parallel.
CREATE TABLESPACE tablespace2 OWNER :ROLE_DEFAULT_PERM_USER_2 LOCATION :TEST_TABLESPACE2_PATH;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2
--attach without permissions on the table should fail
SELECT attach_tablespace('tablespace2', 'tspace_2dim');
ERROR:  must be owner of hypertable "tspace_2dim"
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
--attach without permissions on the tablespace should also fail
SELECT attach_tablespace('tablespace2', 'tspace_2dim');
ERROR:  permission denied for tablespace "tablespace2" by table owner "default_perm_user"
\c :TEST_DBNAME :ROLE_SUPERUSER
GRANT :ROLE_DEFAULT_PERM_USER_2 TO :ROLE_DEFAULT_PERM_USER;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
--should work with permissions on both the table and the tablespace
SELECT attach_tablespace('tablespace2', 'tspace_2dim');
 attach_tablespace 
-------------------
 

SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------
  1 |             1 | tablespace1
  2 |             1 | tablespace2

SELECT * FROM show_tablespaces('tspace_2dim');
 show_tablespaces 
------------------
 tablespace1
 tablespace2

--insert into another chunk
INSERT INTO tspace_2dim VALUES ('2017-01-20T09:00:01', 24.3, 'brown');
SELECT * FROM test.show_subtables('tspace_2dim');
                 Child                  | Tablespace  
----------------------------------------+-------------
 _timescaledb_internal._hyper_1_1_chunk | tablespace1
 _timescaledb_internal._hyper_1_2_chunk | tablespace2

--indexes should inherit the tablespace of their chunk
SELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');
                 Table                  |                               Index                                |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  
----------------------------------------+--------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------
 _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1
 _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1
 _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1
 _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1

\x
SELECT * FROM timescaledb_information.hypertables
ORDER BY hypertable_schema, hypertable_name;
-[ RECORD 1 ]----------+----------------------------
hypertable_schema      | public
hypertable_name        | tspace_2dim
owner                  | default_perm_user
num_dimensions         | 2
num_chunks             | 2
compression_enabled    | f
tablespaces            | {tablespace1,tablespace2}
primary_dimension      | time
primary_dimension_type | timestamp without time zone

SELECT hypertable_schema,
       hypertable_name,
       chunk_schema,
       chunk_name,
       chunk_tablespace
FROM timescaledb_information.chunks
ORDER BY chunk_name;
-[ RECORD 1 ]-----+----------------------
hypertable_schema | public
hypertable_name   | tspace_2dim
chunk_schema      | _timescaledb_internal
chunk_name        | _hyper_1_1_chunk
chunk_tablespace  | tablespace1
-[ RECORD 2 ]-----+----------------------
hypertable_schema | public
hypertable_name   | tspace_2dim
chunk_schema      | _timescaledb_internal
chunk_name        | _hyper_1_2_chunk
chunk_tablespace  | tablespace2

\x
--
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
CREATE TABLE tspace_1dim(time timestamp, temp float, device text);
SELECT create_hypertable('tspace_1dim', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
    create_hypertable     
--------------------------
 (2,public,tspace_1dim,t)

--user doesn't have permission on tablespace1 --> error
SELECT attach_tablespace('tablespace1', 'tspace_1dim');
ERROR:  permission denied for tablespace "tablespace1" by table owner "default_perm_user_2"
--grant permission to tablespace1
SET ROLE :ROLE_DEFAULT_PERM_USER;
GRANT CREATE ON TABLESPACE tablespace1 TO :ROLE_DEFAULT_PERM_USER_2;
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
--should work fine now. Test SELECT INTO utility statements to ensure
--internal alter table function call works with event triggers.
SELECT true INTO attached FROM attach_tablespace('tablespace1', 'tspace_1dim');
SELECT attach_tablespace('tablespace2', 'tspace_1dim');
 attach_tablespace 
-------------------
 

-- Tablespace for tspace_1dim should be set and attached
SELECT * FROM hypertable_tablespaces WHERE hypertable = 'tspace_1dim';
 hypertable  | tablespace  
-------------+-------------
 tspace_1dim | tablespace1

SELECT show_tablespaces('tspace_1dim');
 show_tablespaces 
------------------
 tablespace1
 tablespace2

--trying to revoke permissions while attached should fail
SET ROLE :ROLE_DEFAULT_PERM_USER;
REVOKE CREATE ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;
ERROR:  cannot revoke privilege while tablespace "tablespace1" is attached to hypertable "tspace_1dim"
REVOKE ALL ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;
ERROR:  cannot revoke privilege while tablespace "tablespace1" is attached to hypertable "tspace_1dim"
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------
  1 |             1 | tablespace1
  2 |             1 | tablespace2
  3 |             2 | tablespace1
  4 |             2 | tablespace2

INSERT INTO tspace_1dim VALUES ('2017-01-20T09:00:01', 24.3, 'blue');
INSERT INTO tspace_1dim VALUES ('2017-03-20T09:00:01', 24.3, 'brown');
SELECT * FROM test.show_subtablesp('tspace_%');
   Parent    |                 Child                  | Tablespace  
-------------+----------------------------------------+-------------
 tspace_2dim | _timescaledb_internal._hyper_1_1_chunk | tablespace1
 tspace_2dim | _timescaledb_internal._hyper_1_2_chunk | tablespace2
 tspace_1dim | _timescaledb_internal._hyper_2_3_chunk | tablespace1
 tspace_1dim | _timescaledb_internal._hyper_2_4_chunk | tablespace2

--indexes should inherit the tablespace of their chunk, unless the
--parent index has a tablespace set, in which case the chunks'
--corresponding indexes are pinned to the parent index's
--tablespace. The parent index can have a tablespace set in two cases:
--(1) if explicitly set in CREATE INDEX, or (2) if the main table was
--created with a tablespace, because then default indexes will be
--created in that tablespace too.
SELECT * FROM test.show_indexesp('_timescaledb_internal._hyper%_chunk');
                 Table                  |                               Index                                |    Columns    | Expr | Unique | Primary | Exclusion | Tablespace  
----------------------------------------+--------------------------------------------------------------------+---------------+------+--------+---------+-----------+-------------
 _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1
 _timescaledb_internal._hyper_1_1_chunk | _timescaledb_internal._hyper_1_1_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1
 _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1
 _timescaledb_internal._hyper_1_2_chunk | _timescaledb_internal._hyper_1_2_chunk_tspace_2dim_device_time_idx | {device,time} |      | f      | f       | f         | tablespace1
 _timescaledb_internal._hyper_2_3_chunk | _timescaledb_internal._hyper_2_3_chunk_tspace_1dim_time_idx        | {time}        |      | f      | f       | f         | tablespace2
 _timescaledb_internal._hyper_2_4_chunk | _timescaledb_internal._hyper_2_4_chunk_tspace_1dim_time_idx        | {time}        |      | f      | f       | f         | tablespace1

--detach tablespace1 from tspace_2dim should fail due to lack of permissions
SELECT detach_tablespace('tablespace1', 'tspace_2dim');
ERROR:  must be owner of hypertable "tspace_2dim"
--detach tablespace1 from all tables. Should only detach from
--'tspace_1dim' (1 tablespace) due to lack of permissions
SELECT * FROM hypertable_tablespaces;
 hypertable  | tablespace  
-------------+-------------
 tspace_1dim | tablespace1
 tspace_2dim | tablespace1

SELECT * INTO detached FROM detach_tablespace('tablespace1');
NOTICE:  tablespace "tablespace1" remains attached to 1 hypertable(s) due to lack of permissions
SELECT * FROM detached;
 detach_tablespace 
-------------------
                 1

SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------
  1 |             1 | tablespace1
  2 |             1 | tablespace2
  4 |             2 | tablespace2

SELECT * FROM show_tablespaces('tspace_1dim');
 show_tablespaces 
------------------
 tablespace2

SELECT * FROM show_tablespaces('tspace_2dim');
 show_tablespaces 
------------------
 tablespace1
 tablespace2

SELECT * FROM hypertable_tablespaces;
 hypertable  | tablespace  
-------------+-------------
 tspace_1dim | 
 tspace_2dim | tablespace1

--it should now be possible to revoke permissions on tablespace1
SET ROLE :ROLE_DEFAULT_PERM_USER;
REVOKE CREATE ON TABLESPACE tablespace1 FROM :ROLE_DEFAULT_PERM_USER_2;
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
--detach the other tablespace
SELECT detach_tablespace('tablespace2', 'tspace_1dim');
 detach_tablespace 
-------------------
                 1

SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------
  1 |             1 | tablespace1
  2 |             1 | tablespace2

SELECT * FROM show_tablespaces('tspace_1dim');
 show_tablespaces 
------------------

SELECT * FROM show_tablespaces('tspace_2dim');
 show_tablespaces 
------------------
 tablespace1
 tablespace2

SELECT * FROM hypertable_tablespaces;
 hypertable  | tablespace  
-------------+-------------
 tspace_1dim | 
 tspace_2dim | tablespace1

--detaching tablespace2 from a table without permissions should fail
SELECT detach_tablespace('tablespace2', 'tspace_2dim');
ERROR:  must be owner of hypertable "tspace_2dim"
SELECT detach_tablespaces('tspace_2dim');
ERROR:  must be owner of hypertable "tspace_2dim"
\c :TEST_DBNAME :ROLE_SUPERUSER
-- PERM_USER_2 owns tablespace2, and PERM_USER owns the table
-- 'tspace_2dim', which has tablespace2 attached. Revoking PERM_USER_2
-- FROM PERM_USER should therefore fail
REVOKE :ROLE_DEFAULT_PERM_USER_2 FROM :ROLE_DEFAULT_PERM_USER;
ERROR:  cannot revoke privilege while tablespace "tablespace2" is attached to hypertable "tspace_2dim"
SET ROLE :ROLE_DEFAULT_PERM_USER_2;
--set other user should make detach work
SET ROLE :ROLE_DEFAULT_PERM_USER;
SELECT * INTO detached_all FROM detach_tablespaces('tspace_2dim');
SELECT * FROM detached_all;
 detach_tablespaces 
--------------------
                  2

SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------

SELECT * FROM show_tablespaces('tspace_1dim');
 show_tablespaces 
------------------

SELECT * FROM show_tablespaces('tspace_2dim');
 show_tablespaces 
------------------

\c :TEST_DBNAME :ROLE_SUPERUSER
-- It should now be possible to revoke PERM_USER_2 from PERM_USER
-- since tablespace2 is no longer attched to tspace_2dim
REVOKE :ROLE_DEFAULT_PERM_USER_2 FROM :ROLE_DEFAULT_PERM_USER;
SET ROLE :ROLE_DEFAULT_PERM_USER;
--detaching twice should fail
SELECT detach_tablespace('tablespace2', 'tspace_2dim');
ERROR:  tablespace "tablespace2" is not attached to hypertable "tspace_2dim"
--adding if_attached should only generate notice
SELECT detach_tablespace('tablespace2', 'tspace_2dim', if_attached => true);
NOTICE:  tablespace "tablespace2" is not attached to hypertable "tspace_2dim", skipping
 detach_tablespace 
-------------------
                 0

--attach tablespaces again to verify that tablespaces are cleaned up
--when tables are dropped
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT attach_tablespace('tablespace2', 'tspace_1dim');
 attach_tablespace 
-------------------
 

SELECT attach_tablespace('tablespace1', 'tspace_2dim');
 attach_tablespace 
-------------------
 

SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------
  5 |             2 | tablespace2
  6 |             1 | tablespace1

DROP TABLE tspace_1dim;
SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------
  6 |             1 | tablespace1

DROP TABLE tspace_2dim;
SELECT * FROM _timescaledb_catalog.tablespace;
 id | hypertable_id | tablespace_name 
----+---------------+-----------------

-- Create two tables and attach multiple tablespaces to them. Verify
-- that dropping a tablespace from multiple tables work as expected.
CREATE TABLE tbl_1(time timestamp, temp float, device text);
SELECT create_hypertable('tbl_1', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
 create_hypertable  
--------------------
 (3,public,tbl_1,t)

CREATE TABLE tbl_2(time timestamp, temp float, device text);
SELECT create_hypertable('tbl_2', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
 create_hypertable  
--------------------
 (4,public,tbl_2,t)

CREATE TABLE tbl_3(time timestamp, temp float, device text);
SELECT create_hypertable('tbl_3', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
 create_hypertable  
--------------------
 (5,public,tbl_3,t)

SELECT * FROM hypertable_tablespaces;
 hypertable | tablespace 
------------+------------
 tbl_1      | 
 tbl_2      | 
 tbl_3      | 

SELECT * FROM show_tablespaces('tbl_1');
 show_tablespaces 
------------------

SELECT * FROM show_tablespaces('tbl_2');
 show_tablespaces 
------------------

SELECT * FROM show_tablespaces('tbl_3');
 show_tablespaces 
------------------

SELECT attach_tablespace('tablespace1', 'tbl_1');
 attach_tablespace 
-------------------
 

SELECT attach_tablespace('tablespace2', 'tbl_1');
 attach_tablespace 
-------------------
 

SELECT attach_tablespace('tablespace2', 'tbl_2');
 attach_tablespace 
-------------------
 

SELECT attach_tablespace('tablespace2', 'tbl_3');
 attach_tablespace 
-------------------
 

SELECT * FROM hypertable_tablespaces;
 hypertable | tablespace  
------------+-------------
 tbl_1      | tablespace1
 tbl_2      | tablespace2
 tbl_3      | tablespace2

SELECT * FROM show_tablespaces('tbl_1');
 show_tablespaces 
------------------
 tablespace1
 tablespace2

SELECT * FROM show_tablespaces('tbl_2');
 show_tablespaces 
------------------
 tablespace2

SELECT * FROM show_tablespaces('tbl_3');
 show_tablespaces 
------------------
 tablespace2

SELECT detach_tablespace('tablespace2');
 detach_tablespace 
-------------------
                 3

SELECT * FROM hypertable_tablespaces;
 hypertable | tablespace  
------------+-------------
 tbl_1      | tablespace1
 tbl_2      | 
 tbl_3      | 

SELECT * FROM show_tablespaces('tbl_1');
 show_tablespaces 
------------------
 tablespace1

SELECT * FROM show_tablespaces('tbl_2');
 show_tablespaces 
------------------

SELECT * FROM show_tablespaces('tbl_3');
 show_tablespaces 
------------------

DROP TABLE tbl_1;
DROP TABLE tbl_2;
DROP TABLE tbl_3;
-- verify that one cannot DROP a tablespace while it is attached to a
-- hypertable
CREATE TABLE tbl_1(time timestamp, temp float, device text);
SELECT create_hypertable('tbl_1', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
 create_hypertable  
--------------------
 (6,public,tbl_1,t)

SELECT attach_tablespace('tablespace1', 'tbl_1');
 attach_tablespace 
-------------------
 

SELECT * FROM show_tablespaces('tbl_1');
 show_tablespaces 
------------------
 tablespace1

DROP TABLESPACE tablespace1;
ERROR:  tablespace "tablespace1" is still attached to 1 hypertables
--after detaching we should now be able to drop the tablespace
SELECT detach_tablespace('tablespace1', 'tbl_1');
 detach_tablespace 
-------------------
                 1

DROP TABLESPACE tablespace1;
DROP TABLESPACE tablespace2;
