-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.

----------------------------------------------------------------
-- Test version compability function
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER

CREATE OR REPLACE FUNCTION compatible_version(version CSTRING, reference CSTRING)
RETURNS BOOLEAN
AS :TSL_MODULE_PATHNAME, 'ts_test_compatible_version'
LANGUAGE C VOLATILE;

SELECT * FROM compatible_version('2.0.0-beta3.19', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('2.0.0', reference => '2.0.0');
SELECT * FROM compatible_version('1.9.9', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('1.9.9', reference => '2.0.0');
SELECT * FROM compatible_version('2.0.9', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('2.0.9', reference => '2.0.0');
SELECT * FROM compatible_version('2.1.9', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('2.1.0', reference => '2.1.19-beta3.19');

-- These should not parse and instead generate an error.
\set ON_ERROR_STOP 0
SELECT * FROM compatible_version('2.1.*', reference => '2.1.19-beta3.19');
SELECT * FROM compatible_version('2.1.0', reference => '2.1.*');
\set ON_ERROR_STOP 1

----------------------------------------------------------------
-- Create two distributed databases

CREATE DATABASE frontend_1;
CREATE DATABASE frontend_2;

\c frontend_1 :ROLE_CLUSTER_SUPERUSER
SET client_min_messages TO ERROR;
CREATE EXTENSION timescaledb;
UPDATE _timescaledb_catalog.metadata SET value = '87c235e9-d857-4f16-b59f-7fbac9b87664' WHERE key = 'uuid';
SELECT key, value FROM _timescaledb_catalog.metadata WHERE key LIKE '%uuid';
SELECT node_name, database, node_created, database_created, extension_created
FROM add_data_node('data_node_1', host => 'localhost', database => 'backend_1_1');
SELECT key, value FROM _timescaledb_catalog.metadata WHERE key LIKE '%uuid';
SET client_min_messages TO NOTICE;

-- Create a second frontend database and add a backend to it
\c frontend_2 :ROLE_CLUSTER_SUPERUSER
SET client_min_messages TO ERROR;
CREATE EXTENSION timescaledb;
UPDATE _timescaledb_catalog.metadata SET value = '77348176-09da-4a80-bc78-e31bdf5e63ec' WHERE key = 'uuid';
SELECT key, value FROM _timescaledb_catalog.metadata WHERE key LIKE '%uuid';
SELECT node_name, database, node_created, database_created, extension_created
FROM add_data_node('data_node_1', host => 'localhost', database => 'backend_2_1');
SELECT key, value FROM _timescaledb_catalog.metadata WHERE key LIKE '%uuid';
SET client_min_messages TO NOTICE;

\set ON_ERROR_STOP 0

----------------------------------------------------------------
-- Adding frontend as backend to a different frontend should fail
\c frontend_1 :ROLE_CLUSTER_SUPERUSER
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'frontend_2', bootstrap => true);
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'frontend_2', bootstrap => false);

----------------------------------------------------------------
-- Adding backend from a different group as a backend should fail
\c frontend_1 :ROLE_CLUSTER_SUPERUSER
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'backend_2_1', bootstrap => true);
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'backend_2_1', bootstrap => false);

----------------------------------------------------------------
-- Adding a valid backend target but to an existing backend should fail
\c backend_1_1 :ROLE_CLUSTER_SUPERUSER
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'backend_2_1', bootstrap => true);
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'backend_2_1', bootstrap => false);

----------------------------------------------------------------
-- Adding a frontend (frontend 1) as a backend to a nondistributed node (TEST_DBNAME) should fail
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'frontend_1', bootstrap => true);
SELECT * FROM add_data_node('invalid_data_node', host => 'localhost', database => 'frontend_1', bootstrap => false);

\set ON_ERROR_STOP 1

----------------------------------------------------------------
-- Test that a data node can be moved to a different frontend if it is
-- removed first.
\c frontend_1 :ROLE_CLUSTER_SUPERUSER
SELECT node_name, database, node_created, database_created, extension_created
FROM add_data_node('data_node_2', host => 'localhost', database => 'backend_x_2', bootstrap => true);

-- dist_uuid should be added to the metadata on the data node
\c backend_x_2 :ROLE_CLUSTER_SUPERUSER
SELECT key, value FROM _timescaledb_catalog.metadata WHERE key LIKE 'dist_uuid';

-- Now remove a backend from this distributed database to add it to the other cluster
\c frontend_1 :ROLE_CLUSTER_SUPERUSER
SELECT * FROM delete_data_node('data_node_2');

-- dist_uuid should not be removed from the metadata on the data node,
-- so we need to delete it manually before adding it to another
-- backend.
\c backend_x_2 :ROLE_CLUSTER_SUPERUSER
SELECT key FROM _timescaledb_catalog.metadata WHERE key = 'dist_uuid';
DELETE FROM _timescaledb_catalog.metadata WHERE key = 'dist_uuid';

-- Add the data node to the second frontend without bootstrapping
\c frontend_2 :ROLE_CLUSTER_SUPERUSER
SELECT node_name, database, node_created, database_created, extension_created
FROM add_data_node('data_node_2', host => 'localhost', database => 'backend_x_2', bootstrap => false);

-- dist_uuid should be added to the metadata on the data node
\c backend_x_2 :ROLE_CLUSTER_SUPERUSER
SELECT key, value FROM _timescaledb_catalog.metadata WHERE key LIKE 'dist_uuid';

-- Test space reporting functions for distributed and non-distributed tables
\c frontend_2 :ROLE_CLUSTER_SUPERUSER
CREATE TABLE nondisttable(time timestamptz, device int CHECK (device > 0), temp float);
CREATE TABLE disttable(time timestamptz, device int CHECK (device > 0), temp float);
SELECT * FROM create_hypertable('nondisttable', 'time', create_default_indexes => false);
SELECT * FROM create_distributed_hypertable('disttable', 'time', create_default_indexes => false);

SELECT node_name FROM timescaledb_information.data_nodes
ORDER BY node_name;
SELECT * FROM timescaledb_information.hypertables
ORDER BY hypertable_schema, hypertable_name;

-- Test size functions on empty distributed hypertable.
--
-- First, show the output from standard PG size functions. The
-- functions are expected to remove 0 table bytes for the distributed
-- hypertable since it doesn't have local storage.
SELECT pg_table_size('disttable'), pg_relation_size('disttable'), pg_indexes_size('disttable'), pg_total_relation_size('disttable');
SELECT pg_table_size(ch), pg_relation_size(ch), pg_indexes_size(ch), pg_total_relation_size(ch)
FROM show_chunks('disttable') ch;
SELECT * FROM _timescaledb_functions.relation_size('disttable');
SELECT * FROM show_chunks('disttable') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON TRUE;
SELECT pg_table_size('nondisttable'), pg_relation_size('nondisttable'), pg_indexes_size('nondisttable'), pg_total_relation_size('nondisttable');
SELECT pg_table_size(ch), pg_relation_size(ch), pg_indexes_size(ch), pg_total_relation_size(ch)
FROM show_chunks('nondisttable') ch;
SELECT * FROM _timescaledb_functions.relation_size('nondisttable');
SELECT * FROM show_chunks('nondisttable') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON TRUE;

SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;
SELECT * FROM chunks_detailed_size('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunks_detailed_size('nondisttable') ORDER BY  chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_compression_stats('disttable') ORDER BY node_name;
SELECT * FROM hypertable_compression_stats('nondisttable') ORDER BY node_name;
SELECT * FROM chunk_compression_stats('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunk_compression_stats('nondisttable') ORDER BY  chunk_schema, chunk_name, node_name;

-- Create primary key index and check how it affects the size of the
-- empty hypertables.
ALTER TABLE nondisttable ADD CONSTRAINT nondisttable_pkey PRIMARY KEY (time);
ALTER TABLE disttable ADD CONSTRAINT disttable_pkey PRIMARY KEY (time);

SELECT pg_table_size('disttable'), pg_relation_size('disttable'), pg_indexes_size('disttable'), pg_total_relation_size('disttable');
SELECT * FROM _timescaledb_functions.relation_size('disttable');
SELECT pg_table_size('nondisttable'), pg_relation_size('nondisttable'), pg_indexes_size('nondisttable'), pg_total_relation_size('nondisttable');
SELECT * FROM _timescaledb_functions.relation_size('nondisttable');

-- Note that the empty disttable is three times the size of the
-- nondisttable since it has primary key indexes on two data nodes in
-- addition to the access node.
SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;
SELECT * FROM chunks_detailed_size('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunks_detailed_size('nondisttable') ORDER BY  chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_compression_stats('disttable') ORDER BY node_name;
SELECT * FROM hypertable_compression_stats('nondisttable') ORDER BY node_name;
SELECT * FROM chunk_compression_stats('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunk_compression_stats('nondisttable') ORDER BY  chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_index_size('disttable_pkey');
SELECT * FROM hypertable_index_size('nondisttable_pkey');


-- Test size functions on tables with an empty chunk
INSERT INTO nondisttable VALUES ('2017-01-01 06:01', 1, 1.1);
INSERT INTO disttable SELECT * FROM nondisttable;

SELECT pg_table_size('disttable'), pg_relation_size('disttable'), pg_indexes_size('disttable'), pg_total_relation_size('disttable');
SELECT pg_table_size(ch), pg_relation_size(ch), pg_indexes_size(ch), pg_total_relation_size(ch)
FROM show_chunks('disttable') ch;
SELECT * FROM _timescaledb_functions.relation_size('disttable');
SELECT * FROM show_chunks('disttable') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON TRUE;
SELECT pg_table_size('nondisttable'), pg_relation_size('nondisttable'), pg_indexes_size('nondisttable'), pg_total_relation_size('nondisttable');
SELECT pg_table_size(ch), pg_relation_size(ch), pg_indexes_size(ch), pg_total_relation_size(ch)
FROM show_chunks('nondisttable') ch;
SELECT * FROM _timescaledb_functions.relation_size('nondisttable');
SELECT * FROM show_chunks('nondisttable') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON TRUE;

SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;

-- Delete all data, but keep chunks
DELETE FROM nondisttable;
DELETE FROM disttable;
VACUUM FULL ANALYZE nondisttable;
VACUUM FULL ANALYZE disttable;

SELECT pg_table_size('disttable'), pg_relation_size('disttable'), pg_indexes_size('disttable'), pg_total_relation_size('disttable');
SELECT pg_table_size(ch), pg_relation_size(ch), pg_indexes_size(ch)
FROM show_chunks('disttable') ch;
SELECT * FROM _timescaledb_functions.relation_size('disttable');
SELECT * FROM show_chunks('disttable') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON TRUE;
SELECT pg_table_size('nondisttable'), pg_relation_size('nondisttable'), pg_indexes_size('nondisttable'), pg_total_relation_size('nondisttable');
SELECT pg_table_size(ch), pg_relation_size(ch), pg_indexes_size(ch), pg_total_relation_size(ch)
FROM show_chunks('nondisttable') ch;
SELECT * FROM _timescaledb_functions.relation_size('nondisttable');
SELECT * FROM show_chunks('nondisttable') ch JOIN LATERAL _timescaledb_functions.relation_size(ch) ON TRUE;

SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;
SELECT * FROM chunks_detailed_size('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunks_detailed_size('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_compression_stats('disttable') ORDER BY node_name;
SELECT * FROM hypertable_compression_stats('nondisttable') ORDER BY node_name;
SELECT * FROM chunk_compression_stats('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunk_compression_stats('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_index_size('disttable_pkey');
SELECT * FROM hypertable_index_size('nondisttable_pkey');

-- Test size functions on non-empty hypertable
INSERT INTO nondisttable VALUES
       ('2017-01-01 06:01', 1, 1.1),
       ('2017-01-01 08:01', 1, 1.2),
       ('2018-01-02 08:01', 2, 1.3),
       ('2019-01-01 09:11', 3, 2.1),
       ('2017-01-01 06:05', 1, 1.4);
INSERT INTO disttable SELECT * FROM nondisttable;

SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;
SELECT * FROM chunks_detailed_size('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunks_detailed_size('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_compression_stats('disttable') ORDER BY node_name;
SELECT * FROM hypertable_compression_stats('nondisttable') ORDER BY node_name;
SELECT * FROM chunk_compression_stats('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunk_compression_stats('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_index_size('disttable_pkey');
SELECT * FROM hypertable_index_size('nondisttable_pkey');

-- Enable compression
ALTER TABLE nondisttable
SET (timescaledb.compress,
	 timescaledb.compress_segmentby='device',
	 timescaledb.compress_orderby = 'time DESC');

ALTER TABLE disttable
SET (timescaledb.compress,
	 timescaledb.compress_segmentby='device',
	 timescaledb.compress_orderby = 'time DESC');

SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;
SELECT * FROM chunks_detailed_size('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunks_detailed_size('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_compression_stats('disttable') ORDER BY node_name;
SELECT * FROM hypertable_compression_stats('nondisttable') ORDER BY node_name;
SELECT * FROM chunk_compression_stats('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunk_compression_stats('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_index_size('disttable_pkey');
SELECT * FROM hypertable_index_size('nondisttable_pkey');

-- Compress two chunks (out of three) to see effect of compression
SELECT compress_chunk(ch)
FROM show_chunks('disttable') ch
LIMIT 2;

SELECT compress_chunk(ch)
FROM show_chunks('nondisttable') ch
LIMIT 2;

SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;
SELECT * FROM chunks_detailed_size('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunks_detailed_size('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_compression_stats('disttable') ORDER BY node_name;
SELECT * FROM hypertable_compression_stats('nondisttable') ORDER BY node_name;
SELECT * FROM chunk_compression_stats('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunk_compression_stats('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_index_size('disttable_pkey');
SELECT * FROM hypertable_index_size('nondisttable_pkey');

-- Make sure functions work for non-superuser
CREATE TABLE size_test_table (value int);
INSERT INTO size_test_table SELECT * FROM generate_series(0, 10000);

SET ROLE :ROLE_1;

-- No query permissions
\set ON_ERROR_STOP 0
SELECT count(*) FROM disttable;
SELECT count(*) FROM size_test_table;
\set ON_ERROR_STOP 1

-- Size functions work anyway, similar to pg_table_size, et al.
-- pg_table_size() can vary with platform so not outputting
SELECT 1 FROM pg_table_size('size_test_table');
SELECT 1 FROM pg_table_size('disttable');
SELECT 1 FROM pg_table_size('nondisttable');

-- hypertable_size requires SELECT privilege on table
\set ON_ERROR_STOP 0
SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;
\set ON_ERROR_STOP 1

SELECT * FROM chunks_detailed_size('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunks_detailed_size('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_compression_stats('disttable') ORDER BY node_name;
SELECT * FROM hypertable_compression_stats('nondisttable') ORDER BY node_name;
SELECT * FROM chunk_compression_stats('disttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM chunk_compression_stats('nondisttable') ORDER BY chunk_schema, chunk_name, node_name;
SELECT * FROM hypertable_index_size('disttable_pkey');
SELECT * FROM hypertable_index_size('nondisttable_pkey');

RESET ROLE;
GRANT SELECT ON disttable TO :ROLE_1;
GRANT SELECT ON nondisttable TO :ROLE_1;

SET ROLE :ROLE_1;
-- Querying should now work
SELECT count(*) FROM disttable;

-- hypertable_size should work now with SELECT privilege on tables
SELECT * FROM hypertable_size('disttable');
SELECT * FROM hypertable_size('nondisttable');
SELECT * FROM hypertable_detailed_size('disttable') ORDER BY node_name;
SELECT * FROM hypertable_detailed_size('nondisttable') ORDER BY node_name;

-- Make sure timescaledb.ssl_dir and passfile gucs can be read by a non-superuser
\c :TEST_DBNAME :ROLE_1
\unset ECHO
\o /dev/null
SHOW timescaledb.ssl_dir;
SHOW timescaledb.passfile;
\o
\set ECHO all
\set ON_ERROR_STOP 0
SET timescaledb.ssl_dir TO 'ssldir';
SET timescaledb.passfile TO 'passfile';
\set ON_ERROR_STOP 1

\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER
SET client_min_messages TO ERROR;
DROP DATABASE backend_1_1 WITH (FORCE);
DROP DATABASE backend_x_2 WITH (FORCE);
DROP DATABASE backend_2_1 WITH (FORCE);
DROP DATABASE frontend_1 WITH (FORCE);
DROP DATABASE frontend_2 WITH (FORCE);
