-- This file contains table definitions for various abstractions and data -- structures for representing hypertables and lower level concepts. -- Data node information for the cluster. database_name is the postgres database -- located on at hostname. server_name is used to identify the connection. -- schema_name is the name of the schema used to represent the node on the meta -- node (it stores remote wrappers to update meta tables on data nodes). CREATE TABLE IF NOT EXISTS _timescaledb_catalog.node ( database_name NAME NOT NULL PRIMARY KEY, schema_name NAME NOT NULL UNIQUE, --the schema name with remote tables to _timescaledb_catalog schema of the node server_name NAME NOT NULL UNIQUE, hostname TEXT NOT NULL, port INT NOT NULL CONSTRAINT valid_port CHECK (port >= 0 AND port <= 65535), active BOOLEAN NOT NULL DEFAULT TRUE, id SERIAL NOT NULL UNIQUE -- id for node. used in naming ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.node', ''); SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.node','id'), ''); -- Singleton (i.e. should only contain one row) holding info about meta db. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.meta ( database_name NAME NOT NULL PRIMARY KEY, hostname TEXT NOT NULL, port INT NOT NULL, server_name NAME NOT NULL ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.meta', ''); CREATE UNIQUE INDEX there_can_be_only_one_meta ON _timescaledb_catalog.meta ((1)); -- Users should exist an all nodes+meta in the cluster. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.cluster_user ( username TEXT NOT NULL PRIMARY KEY, password TEXT NULL --not any more of a security hole than usual since stored in pg_user_mapping anyway ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.cluster_user', ''); -- The hypertable is an abstraction that represents a replicated table that is -- partitioned on 2 dimensions: time and another (user-)chosen one. -- -- Each row, representing a hypertable, creates 3 tables: -- 1) main table - an alias to the 0'th replica for now. Represents the -- hypertable to the user for insertion and modification. -- 2) root table - ancesstor of all the data tables (across replicas). -- Should not be queryable for data (TODO). -- -- Additionally, a schema for associated tables (partitioned, replicated data -- tables) is created. -- -- The name and type of the time column (used to partition on time) are defined -- in `time_column_name` and `time_column_type`. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable ( id SERIAL PRIMARY KEY, schema_name NAME NOT NULL CHECK (schema_name != '_timescaledb_catalog'), table_name NAME NOT NULL, associated_schema_name NAME NOT NULL, associated_table_prefix NAME NOT NULL, root_schema_name NAME NOT NULL, root_table_name NAME NOT NULL, replication_factor SMALLINT NOT NULL CHECK (replication_factor > 0), placement _timescaledb_catalog.chunk_placement_type NOT NULL, time_column_name NAME NOT NULL, time_column_type REGTYPE NOT NULL, created_on NAME NOT NULL REFERENCES _timescaledb_catalog.node(database_name), chunk_size_bytes BIGINT NOT NULL CHECK (chunk_size_bytes > 0), UNIQUE (schema_name, table_name), UNIQUE (associated_schema_name, associated_table_prefix), UNIQUE (root_schema_name, root_table_name) ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', ''); SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.hypertable','id'), ''); -- deleted_hypertable is used to avoid deadlocks when doing multinode drops. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.deleted_hypertable ( LIKE _timescaledb_catalog.hypertable, deleted_on NAME ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.deleted_hypertable', ''); -- hypertable_replica contains information on how a hypertable's data replicas -- are stored. A replica of the data is across all partitions and time. -- -- Each row identifies a table for each hypertable + replica_id combination: -- - data replica table (schema_name.table_name) - -- All the data for a hypertable. -- Parent: hypertable's `root table` -- Children: hypertable's `partition_replica` tables CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable_replica ( hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE, replica_id SMALLINT NOT NULL CHECK (replica_id >= 0), schema_name NAME NOT NULL, table_name NAME NOT NULL, PRIMARY KEY (hypertable_id, replica_id), UNIQUE (schema_name, table_name) ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_replica', ''); -- Mapping that shows which replica is pointed to by the main table on -- each node. The translation from main table to replica should happen -- in C tranformation right after the parsing step. -- (Postgres RULES cannot be used, unfortunately) CREATE TABLE IF NOT EXISTS _timescaledb_catalog.default_replica_node ( database_name NAME NOT NULL REFERENCES _timescaledb_catalog.node(database_name), hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE, replica_id SMALLINT NOT NULL CHECK (replica_id >= 0), PRIMARY KEY (database_name, hypertable_id), FOREIGN KEY (hypertable_id, replica_id) REFERENCES _timescaledb_catalog.hypertable_replica(hypertable_id, replica_id) ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.default_replica_node', ''); -- A partition_epoch represents a different partitioning of the data. -- It has a start and end time (data time). Data needs to be placed in the correct epoch by time. -- Partitionings are defined by a function, column, and modulo: -- 1) partitioning_func - Takes the partitioning_column and returns a number -- which is modulo'd to place the data correctly -- 2) partitioning_mod - Number used in modulo operation -- 3) partitioning_column - column in data to partition by (input to partitioning_func) -- -- Changing a data's partitioning, and thus creating a new epoch, should be done -- INFREQUENTLY as it's expensive operation. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.partition_epoch ( id SERIAL NOT NULL PRIMARY KEY, hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE, start_time BIGINT NULL CHECK (start_time >= 0), end_time BIGINT NULL CHECK (end_time >= 0), num_partitions SMALLINT NOT NULL CHECK (num_partitions >= 0), partitioning_func_schema NAME NULL, partitioning_func NAME NULL, --function name of a function of the form func(data_value, partitioning_mod) -> [0, partitioning_mod) partitioning_mod INT NOT NULL CHECK (partitioning_mod < 65536), partitioning_column NAME NULL, UNIQUE (hypertable_id, start_time), UNIQUE (hypertable_id, end_time), CHECK (start_time <= end_time), CHECK (num_partitions <= partitioning_mod), CHECK ((partitioning_func_schema IS NULL AND partitioning_func IS NULL) OR (partitioning_func_schema IS NOT NULL AND partitioning_func IS NOT NULL)) ); CREATE INDEX ON _timescaledb_catalog.partition_epoch(hypertable_id, start_time, end_time); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.partition_epoch', ''); SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.partition_epoch','id'), ''); -- A partition defines a partition witin a partition_epoch. -- For any partition the keyspace is defined as [keyspace_start, keyspace_end]. -- For any epoch, there must be a partition that covers every element in the -- keyspace, i.e. from [0, partition_epoch.partitioning_mod]. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.partition ( id SERIAL NOT NULL PRIMARY KEY, epoch_id INT NOT NULL REFERENCES _timescaledb_catalog.partition_epoch (id) ON DELETE CASCADE, keyspace_start SMALLINT NOT NULL CHECK (keyspace_start >= 0), --start inclusive keyspace_end SMALLINT NOT NULL CHECK (keyspace_end > 0), --end inclusive; compatible with between operator tablespace NAME NULL, UNIQUE (epoch_id, keyspace_start), CHECK (keyspace_end > keyspace_start) ); CREATE INDEX ON _timescaledb_catalog.partition(epoch_id); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.partition', ''); SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.partition','id'), ''); --Represents a replica for a partition. --Each row creates a table: -- Parent: "hypertable_replica.schema_name"."hypertable_replica.table_name" -- Children: "chunk_replica_node.schema_name"."chunk_replica_node.table_name" --TODO: trigger to verify partition_epoch hypertable id matches this hypertable_id CREATE TABLE IF NOT EXISTS _timescaledb_catalog.partition_replica ( id SERIAL NOT NULL PRIMARY KEY, partition_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.partition(id) ON DELETE CASCADE, hypertable_id INTEGER NOT NULL, replica_id SMALLINT NOT NULL, schema_name NAME NOT NULL, table_name NAME NOT NULL, UNIQUE (schema_name, table_name), UNIQUE (partition_id, replica_id), FOREIGN KEY (hypertable_id, replica_id) REFERENCES _timescaledb_catalog.hypertable_replica(hypertable_id, replica_id) ON DELETE CASCADE ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.partition_replica', ''); SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.partition_replica','id'), ''); -- Represent a (replicated) chunk of data, which is data in a hypertable that is -- both partitioned by both the partition_column and time. -- -- For each partition, there can be 0 or more chunks, which are replicated. -- At most two chunks per partition are "open-ended", i.e. having a NULL -- start_time or a NULL end_time. A NULL start_time means the chunk has -- data from the beginning of time until end_time. A NULL end_time means the -- chunk has data from start_time until the end of time. Only when there is -- one chunk for a partition can it be open-ended on BOTH start_time and end_time. -- -- TODO(erik) - Describe conditions of closure. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.chunk ( id SERIAL NOT NULL PRIMARY KEY, partition_id INT NOT NULL REFERENCES _timescaledb_catalog.partition (id) ON DELETE CASCADE, start_time BIGINT NULL CHECK (start_time >= 0), end_time BIGINT NULL CHECK (end_time >= 0), UNIQUE (partition_id, start_time), UNIQUE (partition_id, end_time), CHECK (start_time <= end_time) ); CREATE INDEX ON _timescaledb_catalog.chunk(partition_id, start_time, end_time); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk', ''); SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.chunk','id'), ''); -- A mapping between chunks, partition_replica, and nodes representing where -- actual data is stored. That is, a chunk_replica_node is a particular -- replication instance of a chunk. -- -- Each row represents a table: -- Parent table: "partition_replica.schema_name"."partition_replica.table_name" CREATE TABLE IF NOT EXISTS _timescaledb_catalog.chunk_replica_node ( chunk_id INT NOT NULL REFERENCES _timescaledb_catalog.chunk(id) ON DELETE CASCADE, partition_replica_id INT NOT NULL REFERENCES _timescaledb_catalog.partition_replica(id) ON DELETE CASCADE, database_name NAME NOT NULL REFERENCES _timescaledb_catalog.node(database_name), schema_name NAME NOT NULL, table_name NAME NOT NULL, PRIMARY KEY (chunk_id, partition_replica_id), --a single chunk, replica tuple UNIQUE (chunk_id, database_name), --no two chunk replicas on same node UNIQUE (schema_name, table_name) ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_replica_node', ''); -- Represents a hypertable column. CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable_column ( hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE, name NAME NOT NULL, attnum INT2 NOT NULL, --MUST match pg_attribute.attnum on main table. SHOULD match on root/hierarchy table as well. data_type REGTYPE NOT NULL, default_value TEXT NULL, not_null BOOLEAN NOT NULL, created_on NAME NOT NULL REFERENCES _timescaledb_catalog.node(database_name), modified_on NAME NOT NULL REFERENCES _timescaledb_catalog.node(database_name), PRIMARY KEY (hypertable_id, name), UNIQUE(hypertable_id, attnum) ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_column', ''); -- TODO(mat) - Description? CREATE TABLE IF NOT EXISTS _timescaledb_catalog.deleted_hypertable_column ( LIKE _timescaledb_catalog.hypertable_column, deleted_on NAME ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.deleted_hypertable_column', ''); CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable_index ( hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE, main_schema_name NAME NOT NULL, --schema name of main table (needed for a uniqueness constraint) main_index_name NAME NOT NULL, --index name on main table definition TEXT NOT NULL, --def with /*INDEX_NAME*/ and /*TABLE_NAME*/ placeholders created_on NAME NOT NULL REFERENCES _timescaledb_catalog.node(database_name), PRIMARY KEY (hypertable_id, main_index_name), UNIQUE(main_schema_name, main_index_name) --globally unique since index names globally unique ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable_index', ''); -- TODO(mat) - Description? CREATE TABLE IF NOT EXISTS _timescaledb_catalog.deleted_hypertable_index ( LIKE _timescaledb_catalog.hypertable_index, deleted_on NAME ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.deleted_hypertable_index', '');