Skip to content

Commit

Permalink
Fix bug with negative dimension values
Browse files Browse the repository at this point in the history
Previously, negative dimension values had an off-by-one bug where the
wrong chunk was created for points on chunk borders. This PR fixes that.
  • Loading branch information
cevian committed Aug 28, 2017
1 parent 3c69e4f commit c3b6fb9
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 48 deletions.
78 changes: 57 additions & 21 deletions sql/chunk.sql
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,58 @@ BEGIN
END
$BODY$;

CREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_calculate_default_range_closed(
dimension_value BIGINT,
num_slices SMALLINT,
range_max BIGINT = 2147483647,
OUT range_start BIGINT,
OUT range_end BIGINT)
LANGUAGE PLPGSQL STABLE AS
$BODY$
DECLARE
inter BIGINT;
BEGIN
IF dimension_value < 0 THEN
RAISE 'Dimension values for closed dimensions should be positive. Got: %', dimension_value;
END IF;
inter := ( range_max / num_slices);
IF dimension_value >= inter * (num_slices - 1) THEN
--put overflow from integer-division errors in last range
range_start = inter * (num_slices - 1);
range_end = range_max;
ELSE
range_start = (dimension_value / inter) * inter;
range_end := range_start + inter;
END IF;
END
$BODY$;

CREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_calculate_default_range_open(
dimension_value BIGINT,
interval_length BIGINT,
OUT range_start BIGINT,
OUT range_end BIGINT)
LANGUAGE PLPGSQL STABLE AS
$BODY$
DECLARE
BEGIN
-- For positive values, integer division finds a lower bound which is BEFORE
-- the value we want. For negative values, integer division finds a upper bound which
-- is AFTER the value we want. Therefore for positive numbers we find the
-- range_start via integer division, while for negative we find the range_end.
IF dimension_value >= 0 THEN
range_start := (dimension_value / interval_length) * interval_length;
range_end := range_start + interval_length;
ELSE
--the +1 in (dimension_value + 1) makes this work with inclusive range_start exclusive range_end
range_end := ((dimension_value + 1) / interval_length) * interval_length;
range_start := range_end - interval_length;
END IF;
END
$BODY$;



--todo: unit test
CREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_calculate_default_range(
dimension_id INTEGER,
Expand All @@ -75,35 +127,19 @@ LANGUAGE PLPGSQL STABLE AS
$BODY$
DECLARE
dimension_row _timescaledb_catalog.dimension;
inter BIGINT;
BEGIN
SELECT *
FROM _timescaledb_catalog.dimension
INTO STRICT dimension_row
WHERE id = dimension_id;

IF dimension_row.interval_length IS NOT NULL THEN
-- For positive values, integer division finds a lower bound which is BEFORE
-- the value we want. For negative values, integer divsion finds a upper bound which
-- is AFTER the value we want. Therefore for positive numbers we find the
-- range_start via integer division, while for negative we find the range_end.
IF dimension_value >= 0 THEN
range_start := (dimension_value / dimension_row.interval_length) * dimension_row.interval_length;
range_end := range_start + dimension_row.interval_length;
ELSE
range_end := (dimension_value / dimension_row.interval_length) * dimension_row.interval_length;
range_start := range_end - dimension_row.interval_length;
END IF;
SELECT * INTO STRICT range_start, range_end
FROM _timescaledb_internal.dimension_calculate_default_range_open(dimension_value, dimension_row.interval_length);
ELSE
inter := (2147483647 / dimension_row.num_slices);
IF dimension_value >= inter * (dimension_row.num_slices - 1) THEN
--put overflow from integer-division errors in last range
range_start = inter * (dimension_row.num_slices - 1);
range_end = 2147483647;
ELSE
range_start = (dimension_value / inter) * inter;
range_end := range_start + inter;
END IF;
SELECT * INTO STRICT range_start, range_end
FROM _timescaledb_internal.dimension_calculate_default_range_closed(dimension_value,
dimension_row.num_slices);
END IF;
END
$BODY$;
Expand Down
1 change: 1 addition & 0 deletions test/expected/chunks.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
\unset ECHO
77 changes: 53 additions & 24 deletions test/expected/insert_single.out
Original file line number Diff line number Diff line change
Expand Up @@ -309,31 +309,61 @@ SELECT create_hypertable('"1dim_pre1970"', 'time');

(1 row)

INSERT INTO "1dim_pre1970" VALUES('1969-11-20T09:00:00', 21.2);
INSERT INTO "1dim_pre1970" VALUES('1969-12-01T19:00:00', 21.2);
INSERT INTO "1dim_pre1970" VALUES('1969-12-20T09:00:00', 25.1);
INSERT INTO "1dim_pre1970" VALUES('1970-01-20T09:00:00', 26.6);
INSERT INTO "1dim_pre1970" VALUES('1969-02-20T09:00:00', 29.9);
CREATE TABLE "1dim_neg"(time INTEGER, temp float);
SELECT create_hypertable('"1dim_neg"', 'time', chunk_time_interval=>10);
create_hypertable
-------------------

(1 row)

INSERT INTO "1dim_neg" VALUES (-20, 21.2);
INSERT INTO "1dim_neg" VALUES (-19, 21.2);
INSERT INTO "1dim_neg" VALUES (-1, 21.2);
INSERT INTO "1dim_neg" VALUES (0, 21.2);
INSERT INTO "1dim_neg" VALUES (1, 21.2);
INSERT INTO "1dim_neg" VALUES (19, 21.2);
INSERT INTO "1dim_neg" VALUES (20, 21.2);
SELECT * FROM "1dim_pre1970";
time | temp
--------------------------+------
Thu Nov 20 09:00:00 1969 | 21.2
Mon Dec 01 19:00:00 1969 | 21.2
Sat Dec 20 09:00:00 1969 | 25.1
Tue Jan 20 09:00:00 1970 | 26.6
Thu Feb 20 09:00:00 1969 | 29.9
(4 rows)

SELECT * FROM "1dim_neg";
time | temp
------+------
-20 | 21.2
-19 | 21.2
-1 | 21.2
0 | 21.2
1 | 21.2
19 | 21.2
20 | 21.2
(7 rows)

SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name
----+---------------+-----------------------+------------------
id | hypertable_id | schema_name | table_name
----+---------------+-----------------------+-------------------
1 | 1 | one_Partition | _hyper_1_1_chunk
2 | 1 | one_Partition | _hyper_1_2_chunk
3 | 1 | one_Partition | _hyper_1_3_chunk
4 | 2 | _timescaledb_internal | _hyper_2_4_chunk
5 | 3 | _timescaledb_internal | _hyper_3_5_chunk
6 | 3 | _timescaledb_internal | _hyper_3_6_chunk
7 | 3 | _timescaledb_internal | _hyper_3_7_chunk
8 | 3 | _timescaledb_internal | _hyper_3_8_chunk
(8 rows)
8 | 4 | _timescaledb_internal | _hyper_4_8_chunk
9 | 4 | _timescaledb_internal | _hyper_4_9_chunk
10 | 4 | _timescaledb_internal | _hyper_4_10_chunk
11 | 4 | _timescaledb_internal | _hyper_4_11_chunk
12 | 4 | _timescaledb_internal | _hyper_4_12_chunk
(12 rows)

SELECT * FROM _timescaledb_catalog.dimension_slice;
id | dimension_id | range_start | range_end
Expand All @@ -342,11 +372,15 @@ SELECT * FROM _timescaledb_catalog.dimension_slice;
2 | 1 | 1257897600000000000 | 1257900192000000000
3 | 1 | 1257985728000000000 | 1257988320000000000
4 | 2 | 1482624000000000 | 1485216000000000
5 | 3 | -5184000000000 | -2592000000000
6 | 3 | -2592000000000 | 0
7 | 3 | 0 | 2592000000000
8 | 3 | -28512000000000 | -25920000000000
(8 rows)
5 | 3 | -2592000000000 | 0
6 | 3 | 0 | 2592000000000
7 | 3 | -28512000000000 | -25920000000000
8 | 4 | -20 | -10
9 | 4 | -10 | 0
10 | 4 | 0 | 10
11 | 4 | 10 | 20
12 | 4 | 20 | 30
(12 rows)

-- Create a three-dimensional table
CREATE TABLE "3dim" (time timestamp, temp float, device text, location text);
Expand All @@ -367,21 +401,16 @@ INSERT INTO "3dim" VALUES('2017-01-20T09:00:21', 21.2, 'brown', 'sthlm');
INSERT INTO "3dim" VALUES('2017-01-20T09:00:47', 25.1, 'yellow', 'la');
--show the constraints on the three-dimensional chunk
\d+ _timescaledb_internal._hyper_4_10_chunk
Table "_timescaledb_internal._hyper_4_10_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
----------+-----------------------------+-----------+----------+--------------+-------------
time | timestamp without time zone | | plain | |
temp | double precision | | plain | |
device | text | | extended | |
location | text | | extended | |
Table "_timescaledb_internal._hyper_4_10_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
--------+------------------+-----------+---------+--------------+-------------
time | integer | | plain | |
temp | double precision | | plain | |
Indexes:
"26-3dim_time_idx" btree ("time" DESC)
"27-3dim_device_time_idx" btree (device, "time" DESC)
"25-1dim_neg_time_idx" btree ("time" DESC)
Check constraints:
"constraint_13" CHECK (_timescaledb_internal.get_partition_for_key(device) >= 1073741823 AND _timescaledb_internal.get_partition_for_key(device) < 2147483647)
"constraint_14" CHECK (_timescaledb_internal.get_partition_for_key(location) >= 0 AND _timescaledb_internal.get_partition_for_key(location) < 1073741823)
"constraint_9" CHECK ("time" >= 'Sat Dec 24 16:00:00 2016'::timestamp without time zone AND "time" < 'Mon Jan 23 16:00:00 2017'::timestamp without time zone)
Inherits: "3dim"
"constraint_10" CHECK ("time" >= 0 AND "time" < 10)
Inherits: "1dim_neg"

--queries should work in three dimensions
SELECT * FROM "3dim";
Expand Down
4 changes: 2 additions & 2 deletions test/expected/pg_dump.out
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
126
128
(1 row)

\c postgres
Expand All @@ -67,7 +67,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
126
128
(1 row)

\c single
Expand Down
40 changes: 40 additions & 0 deletions test/sql/chunks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
\unset ECHO
\o /dev/null
\ir include/create_single_db.sql
\ir include/test_utils.sql
\o
\set ECHO errors
\set VERBOSITY default

\o /dev/null

--open
SELECT assert_equal(0::bigint, actual_range_start), assert_equal(10::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_open(0,10) AS res(actual_range_start, actual_range_end);

SELECT assert_equal(0::bigint, actual_range_start), assert_equal(10::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_open(9,10) AS res(actual_range_start, actual_range_end);

SELECT assert_equal(10::bigint, actual_range_start), assert_equal(20::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_open(10,10) AS res(actual_range_start, actual_range_end);

SELECT assert_equal(-10::bigint, actual_range_start), assert_equal(0::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_open(-1,10) AS res(actual_range_start, actual_range_end);

SELECT assert_equal(-10::bigint, actual_range_start), assert_equal(0::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_open(-10,10) AS res(actual_range_start, actual_range_end);

SELECT assert_equal(-20::bigint, actual_range_start), assert_equal(-10::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_open(-11,10) AS res(actual_range_start, actual_range_end);

--closed
SELECT assert_equal(0::bigint, actual_range_start), assert_equal(1073741823::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_closed(0,2::smallint) AS res(actual_range_start, actual_range_end);

SELECT assert_equal(1073741823::bigint, actual_range_start), assert_equal(2147483647::bigint, actual_range_end)
FROM _timescaledb_internal.dimension_calculate_default_range_closed(1073741823,2::smallint) AS res(actual_range_start, actual_range_end);





25 changes: 25 additions & 0 deletions test/sql/include/test_utils.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
CREATE OR REPLACE FUNCTION assert_true(
val boolean
)
RETURNS VOID LANGUAGE PLPGSQL IMMUTABLE AS
$BODY$
BEGIN
IF !val THEN
RAISE 'Assert failed';
END IF;
END
$BODY$;


CREATE OR REPLACE FUNCTION assert_equal(
val1 anyelement,
val2 anyelement
)
RETURNS VOID LANGUAGE PLPGSQL IMMUTABLE AS
$BODY$
BEGIN
IF val1 != val2 THEN
RAISE 'Assert failed';
END IF;
END
$BODY$;
13 changes: 12 additions & 1 deletion test/sql/insert_single.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,22 @@ SELECT "1dim" FROM "1dim";
--test that we can insert pre-1970 dates
CREATE TABLE "1dim_pre1970"(time timestamp PRIMARY KEY, temp float);
SELECT create_hypertable('"1dim_pre1970"', 'time');
INSERT INTO "1dim_pre1970" VALUES('1969-11-20T09:00:00', 21.2);
INSERT INTO "1dim_pre1970" VALUES('1969-12-01T19:00:00', 21.2);
INSERT INTO "1dim_pre1970" VALUES('1969-12-20T09:00:00', 25.1);
INSERT INTO "1dim_pre1970" VALUES('1970-01-20T09:00:00', 26.6);
INSERT INTO "1dim_pre1970" VALUES('1969-02-20T09:00:00', 29.9);

CREATE TABLE "1dim_neg"(time INTEGER, temp float);
SELECT create_hypertable('"1dim_neg"', 'time', chunk_time_interval=>10);
INSERT INTO "1dim_neg" VALUES (-20, 21.2);
INSERT INTO "1dim_neg" VALUES (-19, 21.2);
INSERT INTO "1dim_neg" VALUES (-1, 21.2);
INSERT INTO "1dim_neg" VALUES (0, 21.2);
INSERT INTO "1dim_neg" VALUES (1, 21.2);
INSERT INTO "1dim_neg" VALUES (19, 21.2);
INSERT INTO "1dim_neg" VALUES (20, 21.2);
SELECT * FROM "1dim_pre1970";
SELECT * FROM "1dim_neg";
SELECT * FROM _timescaledb_catalog.chunk;
SELECT * FROM _timescaledb_catalog.dimension_slice;

Expand Down

0 comments on commit c3b6fb9

Please sign in to comment.