timescaledb/sql/util_internal_table_ddl.sql
Olof Rensfelt 201a948452 Check that time dimensions are set as NOT NULL.
Add check that time dimensions are set as NOT NULL in the
main table that a hypertable is created from. If it is not
set, the constraint will be added.
2017-11-02 09:12:15 +01:00

224 lines
7.2 KiB
PL/PgSQL

-- This file contains functions associated with creating new
-- hypertables.
-- Creates a new schema if it does not exist.
CREATE OR REPLACE FUNCTION _timescaledb_internal.create_schema(
schema_name NAME
)
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
$BODY$
BEGIN
EXECUTE format(
$$
CREATE SCHEMA IF NOT EXISTS %I
$$, schema_name);
END
$BODY$
SET client_min_messages = WARNING -- suppress NOTICE on IF EXISTS
;
CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_create_table(
chunk_id INT,
tablespace_name NAME
)
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
chunk_row _timescaledb_catalog.chunk;
hypertable_row _timescaledb_catalog.hypertable;
tablespace_clause TEXT := '';
table_owner NAME;
tablespace_oid OID;
BEGIN
SELECT * INTO STRICT chunk_row
FROM _timescaledb_catalog.chunk
WHERE id = chunk_id;
SELECT * INTO STRICT hypertable_row
FROM _timescaledb_catalog.hypertable
WHERE id = chunk_row.hypertable_id;
SELECT t.oid
INTO tablespace_oid
FROM pg_catalog.pg_tablespace t
WHERE t.spcname = tablespace_name;
SELECT tableowner
INTO STRICT table_owner
FROM pg_catalog.pg_tables
WHERE schemaname = hypertable_row.schema_name
AND tablename = hypertable_row.table_name;
IF tablespace_oid IS NOT NULL THEN
tablespace_clause := format('TABLESPACE %s', tablespace_name);
ELSIF tablespace_name IS NOT NULL THEN
RAISE EXCEPTION 'No tablespace % in database %', tablespace_name, current_database()
USING ERRCODE = 'IO501';
END IF;
EXECUTE format(
$$
CREATE TABLE IF NOT EXISTS %1$I.%2$I () INHERITS(%3$I.%4$I) %5$s;
$$,
chunk_row.schema_name, chunk_row.table_name,
hypertable_row.schema_name, hypertable_row.table_name, tablespace_clause
);
EXECUTE format(
$$
ALTER TABLE %1$I.%2$I OWNER TO %3$I
$$,
chunk_row.schema_name, chunk_row.table_name,
table_owner
);
END
$BODY$;
CREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_slice_get_constraint_sql(
dimension_slice_id INTEGER
)
RETURNS TEXT LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
dimension_slice_row _timescaledb_catalog.dimension_slice;
dimension_row _timescaledb_catalog.dimension;
proargtype OID;
typecast TEXT = '';
BEGIN
SELECT * INTO STRICT dimension_slice_row
FROM _timescaledb_catalog.dimension_slice
WHERE id = dimension_slice_id;
SELECT * INTO STRICT dimension_row
FROM _timescaledb_catalog.dimension
WHERE id = dimension_slice_row.dimension_id;
IF dimension_row.partitioning_func IS NOT NULL THEN
SELECT proargtypes[0] INTO STRICT proargtype
FROM pg_proc p, pg_namespace n
WHERE n.nspname = dimension_row.partitioning_func_schema
AND p.proname = dimension_row.partitioning_func
AND n.oid = p.pronamespace;
-- Check if we are using a legacy partitioning function
-- that only takes text input
IF proargtype = 'text'::regtype THEN
typecast := '::text';
END IF;
return format(
$$
%1$I.%2$s(%3$I%4$s) >= %5$L AND %1$I.%2$s(%3$I%4$s) < %6$L
$$,
dimension_row.partitioning_func_schema,
dimension_row.partitioning_func,
dimension_row.column_name,
typecast,
dimension_slice_row.range_start,
dimension_slice_row.range_end);
ELSE
--TODO: only works with time for now
IF _timescaledb_internal.time_literal_sql(dimension_slice_row.range_start, dimension_row.column_type) =
_timescaledb_internal.time_literal_sql(dimension_slice_row.range_end, dimension_row.column_type) THEN
RAISE 'Time based constraints have the same start and end values for column "%": %',
dimension_row.column_name,
_timescaledb_internal.time_literal_sql(dimension_slice_row.range_end, dimension_row.column_type);
END IF;
return format(
$$
%1$I >= %2$s AND %1$I < %3$s
$$,
dimension_row.column_name,
_timescaledb_internal.time_literal_sql(dimension_slice_row.range_start, dimension_row.column_type),
_timescaledb_internal.time_literal_sql(dimension_slice_row.range_end, dimension_row.column_type));
END IF;
END
$BODY$;
-- Outputs the create_hypertable command to recreate the given hypertable.
--
-- This is currently used internally for our single hypertable backup tool
-- so that it knows how to restore the hypertable without user intervention.
--
-- It only works for hypertables with up to 2 dimensions.
CREATE OR REPLACE FUNCTION _timescaledb_internal.get_create_command(
table_name NAME
)
RETURNS TEXT LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
h_id INTEGER;
schema_name NAME;
time_column NAME;
time_interval BIGINT;
space_column NAME;
space_partitions INTEGER;
dimension_cnt INTEGER;
dimension_row record;
ret TEXT;
BEGIN
SELECT h.id, h.schema_name
FROM _timescaledb_catalog.hypertable AS h
WHERE h.table_name = get_create_command.table_name
INTO h_id, schema_name;
IF h_id IS NULL THEN
RAISE EXCEPTION 'hypertable % not found', table_name
USING ERRCODE = 'IO101';
END IF;
SELECT COUNT(*)
FROM _timescaledb_catalog.dimension d
WHERE d.hypertable_id = h_id
INTO STRICT dimension_cnt;
IF dimension_cnt > 2 THEN
RAISE EXCEPTION 'get_create_command only supports hypertables with up to 2 dimensions'
USING ERRCODE = 'IO101';
END IF;
FOR dimension_row IN
SELECT *
FROM _timescaledb_catalog.dimension d
WHERE d.hypertable_id = h_id
LOOP
IF dimension_row.interval_length IS NOT NULL THEN
time_column := dimension_row.column_name;
time_interval := dimension_row.interval_length;
ELSIF dimension_row.num_slices IS NOT NULL THEN
space_column := dimension_row.column_name;
space_partitions := dimension_row.num_slices;
END IF;
END LOOP;
ret := format($$SELECT create_hypertable('%I.%I', '%I'$$, schema_name, table_name, time_column);
IF space_column IS NOT NULL THEN
ret := ret || format($$, '%I', %s$$, space_column, space_partitions);
END IF;
ret := ret || format($$, chunk_time_interval => %s, create_default_indexes=>FALSE);$$, time_interval);
RETURN ret;
END
$BODY$;
-- Used to make sure all time dimension columns are set as NOT NULL.
CREATE OR REPLACE FUNCTION _timescaledb_internal.set_time_columns_not_null()
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
ht_time_column RECORD;
BEGIN
FOR ht_time_column IN
SELECT ht.schema_name, ht.table_name, d.column_name
FROM _timescaledb_catalog.hypertable ht, _timescaledb_catalog.dimension d
WHERE ht.id = d.hypertable_id AND d.partitioning_func IS NULL
LOOP
EXECUTE format(
$$
ALTER TABLE %I.%I ALTER %I SET NOT NULL
$$, ht_time_column.schema_name, ht_time_column.table_name, ht_time_column.column_name);
END LOOP;
END
$BODY$;