Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .unreleased/pr_9119
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implements: #9119 Support calendar-based chunking
18 changes: 12 additions & 6 deletions sql/ddl_api.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ CREATE OR REPLACE FUNCTION @[email protected]_hypertable(
migrate_data BOOLEAN = FALSE,
chunk_target_size TEXT = NULL,
chunk_sizing_func REGPROC = '_timescaledb_functions.calculate_chunk_interval'::regproc,
time_partitioning_func REGPROC = NULL
time_partitioning_func REGPROC = NULL,
chunk_time_origin "any" = NULL::timestamptz
) RETURNS TABLE(hypertable_id INT, schema_name NAME, table_name NAME, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_create' LANGUAGE C VOLATILE;

-- A generalized hypertable creation API that can be used to convert a PostgreSQL table
Expand All @@ -53,7 +54,6 @@ CREATE OR REPLACE FUNCTION @[email protected]_hypertable(
migrate_data BOOLEAN = FALSE
) RETURNS TABLE(hypertable_id INT, created BOOL) AS '@MODULE_PATHNAME@', 'ts_hypertable_create_general' LANGUAGE C VOLATILE;


-- Set adaptive chunking. To disable, set chunk_target_size => 'off'.
CREATE OR REPLACE FUNCTION @[email protected]_adaptive_chunking(
hypertable REGCLASS,
Expand All @@ -73,7 +73,9 @@ CREATE OR REPLACE FUNCTION @[email protected]_adaptive_chunking(
CREATE OR REPLACE FUNCTION @[email protected]_chunk_time_interval(
hypertable REGCLASS,
chunk_time_interval ANYELEMENT,
dimension_name NAME = NULL
dimension_name NAME = NULL,
chunk_time_origin "any" = NULL::TIMESTAMPTZ,
calendar_chunking BOOL = NULL
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_dimension_set_interval' LANGUAGE C VOLATILE;

-- Update partition_interval for a hypertable.
Expand All @@ -87,7 +89,9 @@ CREATE OR REPLACE FUNCTION @[email protected]_chunk_time_interval(
CREATE OR REPLACE FUNCTION @[email protected]_partitioning_interval(
hypertable REGCLASS,
partition_interval ANYELEMENT,
dimension_name NAME = NULL
dimension_name NAME = NULL,
partition_origin "any" = NULL::TIMESTAMPTZ,
calendar_chunking BOOL = NULL
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_dimension_set_interval' LANGUAGE C VOLATILE;

CREATE OR REPLACE FUNCTION @[email protected]_number_partitions(
Expand Down Expand Up @@ -133,7 +137,8 @@ CREATE OR REPLACE FUNCTION @[email protected]_dimension(
number_partitions INTEGER = NULL,
chunk_time_interval ANYELEMENT = NULL::BIGINT,
partitioning_func REGPROC = NULL,
if_not_exists BOOLEAN = FALSE
if_not_exists BOOLEAN = FALSE,
chunk_time_origin "any" = NULL::timestamptz
) RETURNS TABLE(dimension_id INT, schema_name NAME, table_name NAME, column_name NAME, created BOOL)
AS '@MODULE_PATHNAME@', 'ts_dimension_add' LANGUAGE C VOLATILE;

Expand Down Expand Up @@ -184,7 +189,8 @@ CREATE OR REPLACE FUNCTION @[email protected]_hash(column_name NAME, number_partitio

CREATE OR REPLACE FUNCTION @[email protected]_range(column_name NAME,
partition_interval ANYELEMENT = NULL::bigint,
partition_func regproc = NULL)
partition_func regproc = NULL,
partition_origin "any" = NULL::TIMESTAMPTZ)
RETURNS _timescaledb_internal.dimension_info LANGUAGE C
AS '@MODULE_PATHNAME@', 'ts_range_dimension';

Expand Down
12 changes: 9 additions & 3 deletions sql/pre_install/tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,14 @@ CREATE TABLE _timescaledb_catalog.dimension (
partitioning_func_schema name NULL,
partitioning_func name NULL,
-- open dimensions (e.g., time)
interval_length bigint NULL,
-- compress interval is used by rollup procedure during compression
-- Origin for chunk alignment, stored as Unix epoch microseconds (not PostgreSQL epoch).
-- For timestamp types: microseconds since 1970-01-01 00:00:00 UTC.
-- For integer types: the raw integer value (currently not supported).
-- NULL means use the default origin (0 for legacy chunking, 2001-01-01 for calendar chunking).
interval_origin bigint NULL,
interval interval NULL, -- calendar-based interval (variable-length, e.g., '1 month').
interval_length bigint NULL, -- fixed-size interval in microseconds for timestamp types, or raw value for integer types.
-- compress_interval_length is used by rollup procedure during compression
-- in order to merge multiple chunks into a single one
compress_interval_length bigint NULL,
integer_now_func_schema name NULL,
Expand All @@ -104,7 +110,7 @@ CREATE TABLE _timescaledb_catalog.dimension (
CONSTRAINT dimension_pkey PRIMARY KEY (id),
CONSTRAINT dimension_hypertable_id_column_name_key UNIQUE (hypertable_id, column_name),
CONSTRAINT dimension_check CHECK ((partitioning_func_schema IS NULL AND partitioning_func IS NULL) OR (partitioning_func_schema IS NOT NULL AND partitioning_func IS NOT NULL)),
CONSTRAINT dimension_check1 CHECK ((num_slices IS NULL AND interval_length IS NOT NULL) OR (num_slices IS NOT NULL AND interval_length IS NULL)),
CONSTRAINT dimension_check1 CHECK ((num_slices IS NULL AND (interval_length IS NOT NULL OR interval IS NOT NULL)) OR (num_slices IS NOT NULL AND interval_length IS NULL AND interval IS NULL)),
CONSTRAINT dimension_check2 CHECK ((integer_now_func_schema IS NULL AND integer_now_func IS NULL) OR (integer_now_func_schema IS NOT NULL AND integer_now_func IS NOT NULL)),
CONSTRAINT dimension_interval_length_check CHECK (interval_length IS NULL OR interval_length > 0),
CONSTRAINT dimension_compress_interval_length_check CHECK (compress_interval_length IS NULL OR compress_interval_length > 0),
Expand Down
125 changes: 125 additions & 0 deletions sql/updates/latest-dev.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
DROP VIEW IF EXISTS timescaledb_information.dimensions;

-- Drop old function signatures that are being replaced with new signatures
-- that include partition_origin parameter
DROP FUNCTION IF EXISTS @[email protected]_hypertable(
regclass, name, name, integer, name, name, anyelement,
boolean, boolean, regproc, boolean, text, regproc, regproc
);
DROP FUNCTION IF EXISTS @[email protected]_chunk_time_interval(regclass, anyelement, name);
DROP FUNCTION IF EXISTS @[email protected]_partitioning_interval(regclass, anyelement, name);
DROP FUNCTION IF EXISTS @[email protected]_dimension(regclass, name, integer, anyelement, regproc, boolean);
DROP FUNCTION IF EXISTS @[email protected]_range(name, anyelement, regproc);

-- Drop old function signatures that are being replaced with new signatures
-- that include calendar_chunking parameter
DROP FUNCTION IF EXISTS @[email protected]_chunk_time_interval(regclass, anyelement, name, "any");
DROP FUNCTION IF EXISTS @[email protected]_partitioning_interval(regclass, anyelement, name, "any");

-- Block update if CAggs in old format are found
DO
$$
Expand Down Expand Up @@ -246,3 +263,111 @@ DROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts
DROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ);

DROP FUNCTION timescaledb_experimental.time_bucket_ng(bucket_width INTERVAL, ts TIMESTAMPTZ, origin TIMESTAMPTZ);

--
-- Rebuild the catalog table `_timescaledb_catalog.dimension` to add calendar-based chunking columns
--
-- New columns:
-- interval_origin: origin timestamp for chunk alignment (stored as bigint microseconds)
-- interval: calendar-based interval (e.g., '1 month', '1 year')
--

-- Drop views that depend on the dimension table
DROP VIEW IF EXISTS timescaledb_information.hypertable_columnstore_settings;
DROP VIEW IF EXISTS timescaledb_information.hypertable_compression_settings;
DROP VIEW IF EXISTS timescaledb_information.chunks;

-- Drop foreign key constraints referencing dimension table
ALTER TABLE _timescaledb_catalog.dimension_slice
DROP CONSTRAINT dimension_slice_dimension_id_fkey;

-- Drop the dimension table and its sequence from the extension so we can rebuild it
ALTER EXTENSION timescaledb
DROP TABLE _timescaledb_catalog.dimension;
ALTER EXTENSION timescaledb
DROP SEQUENCE _timescaledb_catalog.dimension_id_seq;

-- Save existing data
CREATE TABLE _timescaledb_catalog._tmp_dimension AS
SELECT
id,
hypertable_id,
column_name,
column_type,
aligned,
num_slices,
partitioning_func_schema,
partitioning_func,
NULL::bigint AS interval_origin,
NULL::interval AS interval,
interval_length,
compress_interval_length,
integer_now_func_schema,
integer_now_func
FROM
_timescaledb_catalog.dimension
ORDER BY
id;

-- Drop old table
DROP TABLE _timescaledb_catalog.dimension;

-- Create new table with correct column order
CREATE TABLE _timescaledb_catalog.dimension (
id serial NOT NULL,
hypertable_id integer NOT NULL,
column_name name NOT NULL,
column_type REGTYPE NOT NULL,
aligned boolean NOT NULL,
-- closed dimensions
num_slices smallint NULL,
partitioning_func_schema name NULL,
partitioning_func name NULL,
-- open dimensions (e.g., time)
interval_origin bigint NULL,
interval interval NULL,
interval_length bigint NULL,
-- compress_interval_length for rollup during compression
compress_interval_length bigint NULL,
integer_now_func_schema name NULL,
integer_now_func name NULL,
-- table constraints
CONSTRAINT dimension_pkey PRIMARY KEY (id),
CONSTRAINT dimension_hypertable_id_column_name_key UNIQUE (hypertable_id, column_name),
CONSTRAINT dimension_check CHECK ((partitioning_func_schema IS NULL AND partitioning_func IS NULL) OR (partitioning_func_schema IS NOT NULL AND partitioning_func IS NOT NULL)),
CONSTRAINT dimension_check1 CHECK ((num_slices IS NULL AND (interval_length IS NOT NULL OR interval IS NOT NULL)) OR (num_slices IS NOT NULL AND interval_length IS NULL AND interval IS NULL)),
CONSTRAINT dimension_check2 CHECK ((integer_now_func_schema IS NULL AND integer_now_func IS NULL) OR (integer_now_func_schema IS NOT NULL AND integer_now_func IS NOT NULL)),
CONSTRAINT dimension_interval_length_check CHECK (interval_length IS NULL OR interval_length > 0),
CONSTRAINT dimension_compress_interval_length_check CHECK (compress_interval_length IS NULL OR compress_interval_length > 0),
CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE
);

-- Copy data from temp table
INSERT INTO _timescaledb_catalog.dimension
SELECT * FROM _timescaledb_catalog._tmp_dimension;

-- Drop temp table
DROP TABLE _timescaledb_catalog._tmp_dimension;

-- Restore sequence value
SELECT setval(pg_get_serial_sequence('_timescaledb_catalog.dimension', 'id'),
max(id), true)
FROM _timescaledb_catalog.dimension;

-- Re-add foreign key constraint
ALTER TABLE _timescaledb_catalog.dimension_slice
ADD CONSTRAINT dimension_slice_dimension_id_fkey
FOREIGN KEY (dimension_id) REFERENCES _timescaledb_catalog.dimension (id) ON DELETE CASCADE;

-- Register for pg_dump
SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.dimension', '');
SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.dimension', 'id'), '');

GRANT SELECT ON TABLE _timescaledb_catalog.dimension TO PUBLIC;
GRANT SELECT ON SEQUENCE _timescaledb_catalog.dimension_id_seq TO PUBLIC;

ANALYZE _timescaledb_catalog.dimension;

--
-- END Rebuild the catalog table `_timescaledb_catalog.dimension`
--
115 changes: 114 additions & 1 deletion sql/updates/reverse-dev.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,118 @@
DROP VIEW IF EXISTS timescaledb_information.dimensions;

-- Drop new function signatures that include origin parameter
DROP FUNCTION IF EXISTS @[email protected]_hypertable(
regclass, name, name, integer, name, name, anyelement,
boolean, boolean, regproc, boolean, text, regproc, regproc, "any"
);
DROP FUNCTION IF EXISTS @[email protected]_dimension(regclass, name, integer, anyelement, regproc, boolean, "any");
DROP FUNCTION IF EXISTS @[email protected]_range(name, anyelement, regproc, "any");

-- Drop new function signatures that include calendar_chunking parameter
DROP FUNCTION IF EXISTS @[email protected]_chunk_time_interval(regclass, anyelement, name, "any", bool);
DROP FUNCTION IF EXISTS @[email protected]_partitioning_interval(regclass, anyelement, name, "any", bool);

--
-- Rebuild the catalog table `_timescaledb_catalog.dimension` to remove interval_origin column
--

-- Drop views that depend on the dimension table
DROP VIEW IF EXISTS timescaledb_information.hypertables;
DROP VIEW IF EXISTS timescaledb_information.hypertable_columnstore_settings;
DROP VIEW IF EXISTS timescaledb_information.hypertable_compression_settings;
DROP VIEW IF EXISTS timescaledb_information.chunks;

-- Drop foreign key constraints referencing dimension table
ALTER TABLE _timescaledb_catalog.dimension_slice
DROP CONSTRAINT dimension_slice_dimension_id_fkey;

-- Drop the dimension table and its sequence from the extension so we can rebuild it
ALTER EXTENSION timescaledb
DROP TABLE _timescaledb_catalog.dimension;
ALTER EXTENSION timescaledb
DROP SEQUENCE _timescaledb_catalog.dimension_id_seq;

-- Save existing data without interval_origin column
CREATE TABLE _timescaledb_catalog._tmp_dimension AS
SELECT
id,
hypertable_id,
column_name,
column_type,
aligned,
num_slices,
partitioning_func_schema,
partitioning_func,
interval_length,
compress_interval_length,
integer_now_func_schema,
integer_now_func
FROM
_timescaledb_catalog.dimension
ORDER BY
id;

-- Drop old table
DROP TABLE _timescaledb_catalog.dimension;

-- Create table without interval_origin column
CREATE TABLE _timescaledb_catalog.dimension (
id serial NOT NULL,
hypertable_id integer NOT NULL,
column_name name NOT NULL,
column_type REGTYPE NOT NULL,
aligned boolean NOT NULL,
-- closed dimensions
num_slices smallint NULL,
partitioning_func_schema name NULL,
partitioning_func name NULL,
-- open dimensions (e.g., time)
interval_length bigint NULL,
-- compress interval for rollup during compression
compress_interval_length bigint NULL,
integer_now_func_schema name NULL,
integer_now_func name NULL,
-- table constraints
CONSTRAINT dimension_pkey PRIMARY KEY (id),
CONSTRAINT dimension_hypertable_id_column_name_key UNIQUE (hypertable_id, column_name),
CONSTRAINT dimension_check CHECK ((partitioning_func_schema IS NULL AND partitioning_func IS NULL) OR (partitioning_func_schema IS NOT NULL AND partitioning_func IS NOT NULL)),
CONSTRAINT dimension_check1 CHECK ((num_slices IS NULL AND interval_length IS NOT NULL) OR (num_slices IS NOT NULL AND interval_length IS NULL)),
CONSTRAINT dimension_check2 CHECK ((integer_now_func_schema IS NULL AND integer_now_func IS NULL) OR (integer_now_func_schema IS NOT NULL AND integer_now_func IS NOT NULL)),
CONSTRAINT dimension_interval_length_check CHECK (interval_length IS NULL OR interval_length > 0),
CONSTRAINT dimension_compress_interval_length_check CHECK (compress_interval_length IS NULL OR compress_interval_length > 0),
CONSTRAINT dimension_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE
);

-- Copy data from temp table
INSERT INTO _timescaledb_catalog.dimension
SELECT * FROM _timescaledb_catalog._tmp_dimension;

-- Drop temp table
DROP TABLE _timescaledb_catalog._tmp_dimension;

-- Restore sequence value
SELECT setval(pg_get_serial_sequence('_timescaledb_catalog.dimension', 'id'),
max(id), true)
FROM _timescaledb_catalog.dimension;

-- Re-add foreign key constraint
ALTER TABLE _timescaledb_catalog.dimension_slice
ADD CONSTRAINT dimension_slice_dimension_id_fkey
FOREIGN KEY (dimension_id) REFERENCES _timescaledb_catalog.dimension (id) ON DELETE CASCADE;

-- Register for pg_dump
SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.dimension', '');
SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.dimension', 'id'), '');

GRANT SELECT ON TABLE _timescaledb_catalog.dimension TO PUBLIC;
GRANT SELECT ON SEQUENCE _timescaledb_catalog.dimension_id_seq TO PUBLIC;

ANALYZE _timescaledb_catalog.dimension;

--
-- END Rebuild the catalog table `_timescaledb_catalog.dimension`
--

--
-- Rebuild the catalog table `_timescaledb_catalog.continuous_agg` to add `finalized` column
--
Expand Down Expand Up @@ -113,4 +227,3 @@ DROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_to_array(_timesca
DROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_column_size(_timescaledb_internal.compressed_data, ANYELEMENT);

DROP FUNCTION IF EXISTS _timescaledb_functions.estimate_uncompressed_size;

30 changes: 27 additions & 3 deletions sql/views.sql
Original file line number Diff line number Diff line change
Expand Up @@ -232,17 +232,21 @@ SELECT ht.schema_name AS hypertable_schema,
rank() OVER (PARTITION BY hypertable_id ORDER BY dim.id) AS dimension_number,
dim.column_name,
dim.column_type,
CASE WHEN dim.interval_length IS NULL THEN
CASE WHEN dim.interval_length IS NULL AND dim.interval IS NULL THEN
'Space'
ELSE
'Time'
END AS dimension_type,
CASE WHEN dim.interval_length IS NOT NULL THEN
CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN
CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN
CASE WHEN dim.interval IS NOT NULL THEN
dim.interval
WHEN dim.interval_length IS NOT NULL THEN
_timescaledb_functions.to_interval(dim.interval_length)
ELSE
NULL
END
ELSE
NULL
END AS time_interval,
CASE WHEN dim.interval_length IS NOT NULL THEN
CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date', 'uuid']::regtype[]) THEN
Expand All @@ -251,6 +255,26 @@ SELECT ht.schema_name AS hypertable_schema,
dim.interval_length
END
END AS integer_interval,
CASE WHEN dim.interval_origin IS NOT NULL THEN
CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','uuid']::regtype[]) THEN
_timescaledb_functions.to_timestamp(dim.interval_origin)
WHEN dim.column_type = 'date'::regtype THEN
_timescaledb_functions.to_timestamp(dim.interval_origin)::date::timestamptz
ELSE
NULL
END
ELSE
NULL
END AS time_origin,
CASE WHEN dim.interval_origin IS NOT NULL THEN
CASE WHEN dim.column_type = ANY(ARRAY['timestamp','timestamptz','date','uuid']::regtype[]) THEN
NULL
ELSE
dim.interval_origin
END
ELSE
NULL
END AS integer_origin,
dim.integer_now_func,
dim.num_slices AS num_partitions
FROM _timescaledb_catalog.hypertable ht,
Expand Down
Loading
Loading