diff --git a/tsl/src/continuous_aggs/common.c b/tsl/src/continuous_aggs/common.c index dd75061ef3b..14975cb039b 100644 --- a/tsl/src/continuous_aggs/common.c +++ b/tsl/src/continuous_aggs/common.c @@ -667,17 +667,12 @@ cagg_validate_query(const Query *query, const bool finalized, const char *cagg_s CAggTimebucketInfo bucket_info = { 0 }; CAggTimebucketInfo bucket_info_parent = { 0 }; Hypertable *ht = NULL, *ht_parent = NULL; - RangeTblRef *rtref = NULL, *rtref_other = NULL; - RangeTblEntry *rte = NULL, *rte_other = NULL; - JoinType jointype = JOIN_FULL; - OpExpr *op = NULL; - List *fromList = NIL; + RangeTblEntry *rte = NULL; StringInfo hint = makeStringInfo(); StringInfo detail = makeStringInfo(); bool is_hierarchical = false; Query *prev_query = NULL; ContinuousAgg *cagg_parent = NULL; - Oid normal_table_id = InvalidOid; if (!cagg_query_supported(query, hint, detail, finalized)) { @@ -688,146 +683,71 @@ cagg_validate_query(const Query *query, const bool finalized, const char *cagg_s detail->len > 0 ? errdetail("%s", detail->data) : 0)); } - /* Check if there are only two tables in the from list. */ - fromList = query->jointree->fromlist; - if (list_length(fromList) > CONTINUOUS_AGG_MAX_JOIN_RELATIONS) - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only two tables with one hypertable and one normal table " - "are allowed in continuous aggregate view"))); - } - /* Extra checks for joins in Caggs. */ - if (list_length(fromList) == CONTINUOUS_AGG_MAX_JOIN_RELATIONS || - !IsA(linitial(query->jointree->fromlist), RangeTblRef)) + int num_tables = 0, num_hypertables = 0; + ListCell *lc; + foreach (lc, query->rtable) { - if (list_length(fromList) == CONTINUOUS_AGG_MAX_JOIN_RELATIONS) + RangeTblEntry *inner_rte = lfirst_node(RangeTblEntry, lc); + + if (inner_rte->rtekind == RTE_RELATION) { - if (!IsA(linitial(fromList), RangeTblRef) || !IsA(lsecond(fromList), RangeTblRef)) + bool is_hypertable = ts_is_hypertable(inner_rte->relid) || + ts_continuous_agg_find_by_relid(inner_rte->relid); + + if (is_hypertable) + { + num_hypertables++; + if (rte == NULL) + rte = copyObject(inner_rte); + } + else + { + num_tables++; + } + + if (is_hypertable && inner_rte->inh == false) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("invalid continuous aggregate view"), errdetail( - "From clause can only have one hypertable and one normal table."))); - - rtref = linitial_node(RangeTblRef, query->jointree->fromlist); - rte = list_nth(query->rtable, rtref->rtindex - 1); - rtref_other = lsecond_node(RangeTblRef, query->jointree->fromlist); - rte_other = list_nth(query->rtable, rtref_other->rtindex - 1); - jointype = rte->jointype || rte_other->jointype; - - if (query->jointree->quals != NULL && IsA(query->jointree->quals, OpExpr)) - op = (OpExpr *) query->jointree->quals; - } - else - { - ListCell *l; - foreach (l, query->jointree->fromlist) - { - Node *jtnode = (Node *) lfirst(l); - JoinExpr *join = NULL; - if (IsA(jtnode, JoinExpr)) - { - join = castNode(JoinExpr, jtnode); - jointype = join->jointype; - op = (OpExpr *) join->quals; - rte = list_nth(query->rtable, ((RangeTblRef *) join->larg)->rtindex - 1); - rte_other = list_nth(query->rtable, ((RangeTblRef *) join->rarg)->rtindex - 1); - if (rte->subquery != NULL || rte_other->subquery != NULL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid continuous aggregate view"), - errdetail("Sub-queries are not supported in FROM clause."))); - RangeTblEntry *jrte = rt_fetch(join->rtindex, query->rtable); - if (jrte->joinaliasvars == NIL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid continuous aggregate view"))); - } - } + "FROM ONLY on hypertables is not allowed in continuous aggregate."))); } - /* - * Error out if there is aynthing else than one normal table and one hypertable - * in the from clause, e.g. sub-query, lateral, two hypertables, etc. - */ - if (rte->lateral || rte_other->lateral) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid continuous aggregate view"), - errdetail("Lateral joins are not supported in FROM clause."))); - if ((rte->relkind == RELKIND_VIEW && ts_is_hypertable(rte_other->relid)) || - (rte_other->relkind == RELKIND_VIEW && ts_is_hypertable(rte->relid))) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid continuous aggregate view"), - errdetail("Views are not supported in FROM clause."))); - if (rte->relkind != RELKIND_VIEW && rte_other->relkind != RELKIND_VIEW && - (ts_is_hypertable(rte->relid) == ts_is_hypertable(rte_other->relid))) + /* Only inner joins are allowed. */ + if (inner_rte->jointype != JOIN_INNER && inner_rte->jointype != JOIN_LEFT) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid continuous aggregate view"), - errdetail("Multiple hypertables or normal tables are not supported in FROM " - "clause."))); + errmsg("only INNER or LEFT joins are supported in continuous aggregates"))); - /* Only inner joins are allowed. */ - if (jointype != JOIN_INNER) + /* Subquery only using LATERAL */ + if (inner_rte->subquery && !inner_rte->lateral) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only inner joins are supported in continuous aggregates"))); + errmsg("invalid continuous aggregate view"), + errdetail("Sub-queries are not supported in FROM clause."))); - /* Only equality conditions are permitted on joins. */ - if (op && IsA(op, OpExpr) && - list_length(castNode(OpExpr, op)->args) == CONTINUOUS_AGG_MAX_JOIN_RELATIONS) - { - Oid left_type = exprType(linitial(op->args)); - Oid right_type = exprType(lsecond(op->args)); - if (!ts_is_equality_operator(op->opno, left_type, right_type)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid continuous aggregate view"), - errdetail( - "Only equality conditions are supported in continuous aggregates."))); - } - else + /* TABLESAMPLE not allowed */ + if (inner_rte->tablesample) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("invalid continuous aggregate view"), - errdetail("Unsupported expression in join clause."), - errhint("Only equality conditions are supported in continuous aggregates."))); - /* - * Record the table oid of the normal table. This is required so - * that we know which one is hypertable to carry out the related - * processing in later parts of code. - */ - if (rte->relkind == RELKIND_VIEW) - normal_table_id = rte_other->relid; - else if (rte_other->relkind == RELKIND_VIEW) - normal_table_id = rte->relid; - else - normal_table_id = ts_is_hypertable(rte->relid) ? rte_other->relid : rte->relid; - if (normal_table_id == rte->relid) - rte = rte_other; + errdetail("TABLESAMPLE is not supported in continuous aggregate."))); } - else - { - /* Check if we have a hypertable in the FROM clause. */ - rtref = linitial_node(RangeTblRef, query->jointree->fromlist); - rte = list_nth(query->rtable, rtref->rtindex - 1); - } - /* FROM only sets rte->inh to false. */ - if (rte->rtekind != RTE_JOIN) + + if (num_hypertables > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("invalid continuous aggregate view"), + errdetail("Only one hypertable is allowed in continuous aggregate view."))); + + if (rte == NULL) { - if ((rte->relkind != RELKIND_RELATION && rte->relkind != RELKIND_VIEW) || - rte->tablesample || rte->inh == false) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("invalid continuous aggregate view"))); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("invalid continuous aggregate view"), + errdetail("At least one hypertable should be used in the view definition."))); } - Ensure(rte->relkind == RELKIND_RELATION || rte->relkind == RELKIND_VIEW, - "invalid continuous aggregate view"); - const Dimension *part_dimension = NULL; int32 parent_mat_hypertable_id = INVALID_HYPERTABLE_ID; Cache *hcache = ts_hypertable_cache_pin(); diff --git a/tsl/test/expected/cagg_errors.out b/tsl/test/expected/cagg_errors.out index 5fdef687437..6e3eb5f324a 100644 --- a/tsl/test/expected/cagg_errors.out +++ b/tsl/test/expected/cagg_errors.out @@ -37,7 +37,8 @@ CREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materi as select a, count(*) from mat_t1 group by a WITH NO DATA; -ERROR: table "mat_t1" is not a hypertable +ERROR: invalid continuous aggregate view +DETAIL: At least one hypertable should be used in the view definition. -- no group by CREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) as @@ -119,6 +120,7 @@ from from conditions ) q group by time_bucket('1week', timec) , location WITH NO DATA; ERROR: invalid continuous aggregate view +DETAIL: Sub-queries are not supported in FROM clause. CREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS select * from @@ -194,6 +196,7 @@ from conditions tablesample bernoulli(0.2) group by time_bucket('1week', timec) , location WITH NO DATA; ERROR: invalid continuous aggregate view +DETAIL: TABLESAMPLE is not supported in continuous aggregate. -- ONLY in from clause CREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS @@ -201,6 +204,7 @@ Select sum(humidity), avg(temperature::int4) from ONLY conditions group by time_bucket('1week', timec) , location WITH NO DATA; ERROR: invalid continuous aggregate view +DETAIL: FROM ONLY on hypertables is not allowed in continuous aggregate. --grouping sets and variants CREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS @@ -645,7 +649,7 @@ ERROR: compression not enabled on "i2980" -- cagg on normal view should error out CREATE VIEW v1 AS SELECT now() AS time; CREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS SELECT time_bucket('1h',time) FROM v1 GROUP BY 1; -ERROR: invalid continuous aggregate query +ERROR: invalid continuous aggregate view -- cagg on normal view should error out CREATE MATERIALIZED VIEW matv1 AS SELECT now() AS time; CREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS SELECT time_bucket('1h',time) FROM matv1 GROUP BY 1; diff --git a/tsl/test/expected/cagg_joins.out b/tsl/test/expected/cagg_joins.out index 5f097fbcb7f..2fcc19d0f93 100644 --- a/tsl/test/expected/cagg_joins.out +++ b/tsl/test/expected/cagg_joins.out @@ -1,20 +1,17 @@ -- 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. -\set ON_ERROR_STOP 0 \set VERBOSITY default CREATE TABLE conditions( day TIMESTAMPTZ NOT NULL, city text NOT NULL, temperature INT NOT NULL, -device_id int NOT NULL); -SELECT create_hypertable( - 'conditions', 'day', - chunk_time_interval => INTERVAL '1 day' + device_id int NOT NULL ); - create_hypertable -------------------------- - (1,public,conditions,t) +SELECT table_name FROM create_hypertable('conditions', 'day', chunk_time_interval => INTERVAL '1 day'); + table_name +------------ + conditions (1 row) INSERT INTO conditions (day, city, temperature, device_id) VALUES @@ -33,25 +30,22 @@ INSERT INTO conditions (day, city, temperature, device_id) VALUES ('2021-06-26', 'Moscow', 32,3), ('2021-06-27', 'Moscow', 31,3); CREATE TABLE conditions_dup AS SELECT * FROM conditions; -SELECT create_hypertable( - 'conditions_dup', 'day', - chunk_time_interval => INTERVAL '1 day', - migrate_data => true -); +SELECT table_name FROM create_hypertable('conditions_dup', 'day', chunk_time_interval => INTERVAL '1 day', migrate_data => true); NOTICE: adding not-null constraint to column "day" DETAIL: Dimensions cannot have NULL values. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. - create_hypertable ------------------------------ - (2,public,conditions_dup,t) + table_name +---------------- + conditions_dup (1 row) CREATE TABLE devices ( device_id int not null, name text, location text); INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm'); +CREATE TABLE location (location_id INTEGER, name TEXT); +INSERT INTO location VALUES (1, 'Moscow'), (2, 'Berlin'), (3, 'London'), (4, 'Stockholm'); CREATE TABLE devices_dup AS SELECT * FROM devices; CREATE VIEW devices_view AS SELECT * FROM devices; --- Working cases -- Cagg with inner join + realtime aggregate CREATE MATERIALIZED VIEW cagg_realtime WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS @@ -289,7 +283,7 @@ SELECT * FROM cagg_realtime_using ORDER BY bucket, name; (15 rows) -- Reorder tables in FROM clause --- Cagg with inner join + realtime aggregate +-- Cagg with inner join + realtime aggregate CREATE MATERIALIZED VIEW cagg_realtime_reorder WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -730,7 +724,39 @@ SELECT * FROM cagg_reorder_using; Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 (16 rows) ---Create CAgg with join and additional WHERE conditions +-- Cagg join with another table using FROM ONLY +CREATE MATERIALIZED VIEW cagg_from_only +WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS +SELECT time_bucket(INTERVAL '1 day', day) AS bucket, + AVG(temperature), + name +FROM ONLY devices JOIN conditions USING (device_id) +GROUP BY name, bucket +ORDER BY bucket; +NOTICE: refreshing continuous aggregate "cagg_from_only" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +SELECT * FROM cagg_from_only; + bucket | avg | name +------------------------------+---------------------+---------- + Sun Jun 13 17:00:00 2021 PDT | 26.0000000000000000 | thermo_1 + Mon Jun 14 17:00:00 2021 PDT | 22.0000000000000000 | thermo_2 + Tue Jun 15 17:00:00 2021 PDT | 24.0000000000000000 | thermo_3 + Wed Jun 16 17:00:00 2021 PDT | 24.0000000000000000 | thermo_4 + Thu Jun 17 17:00:00 2021 PDT | 27.0000000000000000 | thermo_4 + Fri Jun 18 17:00:00 2021 PDT | 28.0000000000000000 | thermo_4 + Sat Jun 19 17:00:00 2021 PDT | 30.0000000000000000 | thermo_1 + Sun Jun 20 17:00:00 2021 PDT | 31.0000000000000000 | thermo_1 + Mon Jun 21 17:00:00 2021 PDT | 34.0000000000000000 | thermo_1 + Tue Jun 22 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 + Wed Jun 23 17:00:00 2021 PDT | 34.0000000000000000 | thermo_2 + Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 + Fri Jun 25 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 + Sat Jun 26 17:00:00 2021 PDT | 31.0000000000000000 | thermo_3 + Tue Jun 29 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 + Wed Jun 30 17:00:00 2021 PDT | 28.0000000000000000 | thermo_3 +(16 rows) + +-- Create CAgg with join and additional WHERE conditions CREATE MATERIALIZED VIEW cagg_more_conds WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -749,7 +775,7 @@ SELECT * FROM cagg_more_conds ORDER BY bucket; Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 (2 rows) ---Cagg with more conditions and USING clause +-- Cagg with more conditions and USING clause CREATE MATERIALIZED VIEW cagg_more_conds_using WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -768,7 +794,7 @@ SELECT * FROM cagg_more_conds_using ORDER BY bucket; Thu Jun 24 17:00:00 2021 PDT | 32.0000000000000000 | thermo_3 (2 rows) --- Nested CAgg over a CAgg with join +-- Hierarchical CAgg with join CREATE MATERIALIZED VIEW cagg_on_cagg WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS SELECT time_bucket(INTERVAL '1 day', bucket) AS bucket, @@ -801,8 +827,8 @@ SELECT * FROM cagg_on_cagg; DROP MATERIALIZED VIEW cagg_on_cagg CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table _timescaledb_internal._hyper_17_59_chunk -drop cascades to table _timescaledb_internal._hyper_17_60_chunk +DETAIL: drop cascades to table _timescaledb_internal._hyper_18_61_chunk +drop cascades to table _timescaledb_internal._hyper_18_62_chunk -- Nested CAgg over a CAgg with JOIN clause CREATE MATERIALIZED VIEW cagg_on_cagg_join WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS @@ -836,9 +862,9 @@ SELECT * FROM cagg_on_cagg_join; DROP MATERIALIZED VIEW cagg_on_cagg_join CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table _timescaledb_internal._hyper_18_61_chunk -drop cascades to table _timescaledb_internal._hyper_18_62_chunk ---Create CAgg with join and ORDER BY +DETAIL: drop cascades to table _timescaledb_internal._hyper_19_63_chunk +drop cascades to table _timescaledb_internal._hyper_19_64_chunk +-- Create CAgg with join and ORDER BY CREATE MATERIALIZED VIEW cagg_ordered WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -919,10 +945,9 @@ NOTICE: refreshing continuous aggregate "cagg_nested" HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. DROP MATERIALIZED VIEW cagg_nested CASCADE; NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table _timescaledb_internal._hyper_23_71_chunk -drop cascades to table _timescaledb_internal._hyper_23_72_chunk ---Error cases ---CAgg with multiple join conditions without JOIN clause +DETAIL: drop cascades to table _timescaledb_internal._hyper_24_73_chunk +drop cascades to table _timescaledb_internal._hyper_24_74_chunk +-- CAgg with multiple join conditions without JOIN clause CREATE MATERIALIZED VIEW cagg_more_joins_conds WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -934,19 +959,18 @@ FROM conditions JOIN devices ON conditions.device_id = devices.device_id AND conditions.city = devices.location AND conditions.temperature > 28 GROUP BY name, bucket; -ERROR: invalid continuous aggregate view -DETAIL: Unsupported expression in join clause. -HINT: Only equality conditions are supported in continuous aggregates. -CREATE TABLE mat_t1( a integer, b integer,c TEXT); ---With LATERAL multiple tables in new format +NOTICE: refreshing continuous aggregate "cagg_more_joins_conds" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +-- With LATERAL multiple tables in new format +CREATE TABLE mat_t1(a integer, b integer, c TEXT); CREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) -as -select temperature, count(*) from conditions, -LATERAL (Select * from mat_t1 where a = conditions.temperature) q -group by temperature WITH NO DATA; -ERROR: invalid continuous aggregate view -DETAIL: Lateral joins are not supported in FROM clause. ---With FROM clause has view +AS +SELECT time_bucket(INTERVAL '1 day', day) AS bucket, temperature, count(*) from conditions, +LATERAL (SELECT * FROM mat_t1 WHERE mat_t1.a = conditions.temperature) q +GROUP BY bucket, temperature; +NOTICE: refreshing continuous aggregate "mat_m1" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +-- Joining a hypertable and view CREATE MATERIALIZED VIEW cagg_view WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -956,11 +980,12 @@ SELECT time_bucket(INTERVAL '1 day', day) AS bucket, FROM conditions, devices_view WHERE conditions.device_id = devices_view.device_id GROUP BY name, bucket, devices_view.device_id; -ERROR: invalid continuous aggregate view -DETAIL: Views are not supported in FROM clause. +NOTICE: refreshing continuous aggregate "cagg_view" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. CREATE TABLE cities(name text, currency text); INSERT INTO cities VALUES ('Berlin', 'EUR'), ('London', 'PND'); --Error out when FROM clause has sub selects +\set ON_ERROR_STOP 0 CREATE MATERIALIZED VIEW conditions_summary_subselect WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -1036,7 +1061,7 @@ FROM conditions, conditions_dup WHERE conditions.device_id = conditions_dup.device_id GROUP BY bucket; ERROR: invalid continuous aggregate view -DETAIL: Multiple hypertables or normal tables are not supported in FROM clause. +DETAIL: Only one hypertable is allowed in continuous aggregate view. --Error out when join is between two normal tables CREATE MATERIALIZED VIEW cagg_nt WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS @@ -1047,9 +1072,10 @@ FROM devices, devices_dup WHERE devices.device_id = devices_dup.device_id GROUP BY devices.name, devices.location; ERROR: invalid continuous aggregate view -DETAIL: Multiple hypertables or normal tables are not supported in FROM clause. ---Error out when join is on non-equality condition -CREATE MATERIALIZED VIEW cagg_unequal +DETAIL: At least one hypertable should be used in the view definition. +\set ON_ERROR_STOP 1 +-- JOIN with non-equality condition +CREATE MATERIALIZED VIEW cagg_unequal1 WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), @@ -1057,10 +1083,10 @@ SELECT time_bucket(INTERVAL '1 day', day) AS bucket, FROM conditions, devices WHERE conditions.device_id <> devices.device_id GROUP BY name, bucket; -ERROR: invalid continuous aggregate view -DETAIL: Only equality conditions are supported in continuous aggregates. ---Unsupported join condition -CREATE MATERIALIZED VIEW cagg_unequal +NOTICE: refreshing continuous aggregate "cagg_unequal1" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +-- JOIN with non-equality condition +CREATE MATERIALIZED VIEW cagg_unequal2 WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), @@ -1069,23 +1095,21 @@ FROM conditions, devices WHERE conditions.device_id = devices.device_id AND conditions.city like '%cow*' GROUP BY name, bucket; -ERROR: invalid continuous aggregate view -DETAIL: Unsupported expression in join clause. -HINT: Only equality conditions are supported in continuous aggregates. ---Unsupported join condition -CREATE MATERIALIZED VIEW cagg_unequal +NOTICE: refreshing continuous aggregate "cagg_unequal2" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +-- JOIN with non-equality condition +CREATE MATERIALIZED VIEW cagg_unequal3 WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), name -FROM conditions, devices -WHERE conditions.device_id = devices.device_id OR - conditions.city like '%cow*' +FROM conditions +JOIN devices ON conditions.device_id = devices.device_id OR conditions.city LIKE '%cow*' GROUP BY name, bucket; -ERROR: invalid continuous aggregate view -DETAIL: Unsupported expression in join clause. -HINT: Only equality conditions are supported in continuous aggregates. ---Error out when join type is not inner +NOTICE: refreshing continuous aggregate "cagg_unequal3" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +-- Error out when join type is not INNER or LEFT +\set ON_ERROR_STOP 0 CREATE MATERIALIZED VIEW cagg_outer WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -1094,8 +1118,19 @@ SELECT time_bucket(INTERVAL '1 day', day) AS bucket, FROM conditions FULL JOIN devices ON conditions.device_id = devices.device_id GROUP BY name, bucket; -ERROR: only inner joins are supported in continuous aggregates -CREATE MATERIALIZED VIEW cagg_outer +ERROR: only INNER or LEFT joins are supported in continuous aggregates +CREATE MATERIALIZED VIEW cagg_right +WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS +SELECT time_bucket(INTERVAL '1 day', day) AS bucket, + AVG(temperature), + name +FROM conditions RIGHT JOIN devices +ON conditions.device_id = devices.device_id +GROUP BY name, bucket; +ERROR: only INNER or LEFT joins are supported in continuous aggregates +\set ON_ERROR_STOP 1 +-- LEFT JOIN is allowed +CREATE MATERIALIZED VIEW cagg_left_join WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), @@ -1103,8 +1138,10 @@ SELECT time_bucket(INTERVAL '1 day', day) AS bucket, FROM conditions LEFT JOIN devices ON conditions.device_id = devices.device_id GROUP BY name, bucket; -ERROR: only inner joins are supported in continuous aggregates ---Error out for join between cagg and hypertable +NOTICE: refreshing continuous aggregate "cagg_left_join" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +-- Error out for join between cagg and hypertable +\set ON_ERROR_STOP 0 CREATE MATERIALIZED VIEW cagg_nested_ht WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', cagg.bucket) AS bucket, @@ -1114,32 +1151,56 @@ FROM cagg_cagg cagg, conditions WHERE cagg.device_id = conditions.device_id GROUP BY 1,2,3; ERROR: invalid continuous aggregate view -DETAIL: Views are not supported in FROM clause. +DETAIL: Only one hypertable is allowed in continuous aggregate view. +\set ON_ERROR_STOP 1 +-- Multiple JOINS are supported +CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS +SELECT time_bucket(INTERVAL '1 day', conditions.day) AS bucket, + AVG(conditions.temperature), + devices.name AS device, + location.name AS location +FROM conditions +JOIN devices ON conditions.device_id = devices.device_id +JOIN location ON location.name = devices.location +GROUP BY bucket, devices.name, location.name; +NOTICE: refreshing continuous aggregate "conditions_by_day" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. +-- JOIN with a foreign table +\c :TEST_DBNAME :ROLE_SUPERUSER +SELECT current_setting('port') AS "PGPORT", current_database() AS "PGDATABASE" \gset +CREATE EXTENSION postgres_fdw; +CREATE SERVER loopback + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (host 'localhost', dbname :'PGDATABASE', port :'PGPORT'); +CREATE USER MAPPING FOR :ROLE_DEFAULT_PERM_USER + SERVER loopback + OPTIONS (user :'ROLE_DEFAULT_PERM_USER', password 'nopassword'); +ALTER USER MAPPING FOR :ROLE_DEFAULT_PERM_USER + SERVER loopback + OPTIONS (ADD password_required 'false'); +GRANT USAGE ON FOREIGN SERVER loopback TO :ROLE_DEFAULT_PERM_USER; +\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER; +CREATE FOREIGN TABLE devices_fdw ( + device_id int not null, + name text, + location text +) SERVER loopback OPTIONS (table_name 'devices'); +CREATE MATERIALIZED VIEW conditions_fdw WITH (timescaledb.continuous) AS +SELECT time_bucket(INTERVAL '1 day', conditions.day) AS bucket, + AVG(conditions.temperature), + devices.name AS device +FROM conditions +JOIN devices_fdw AS devices ON conditions.device_id = devices.device_id +GROUP BY bucket, devices.name; +NOTICE: refreshing continuous aggregate "conditions_fdw" +HINT: Use WITH NO DATA if you do not want to refresh the continuous aggregate on creation. \set VERBOSITY terse +SET client_min_messages TO WARNING; DROP TABLE conditions CASCADE; -NOTICE: drop cascades to 51 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects -NOTICE: drop cascades to 2 other objects DROP TABLE devices CASCADE; -NOTICE: drop cascades to view devices_view DROP TABLE conditions_dup CASCADE; DROP TABLE devices_dup CASCADE; +RESET client_min_messages; \set VERBOSITY default -- SDC #1859 CREATE TABLE conditions( @@ -1147,10 +1208,10 @@ CREATE TABLE conditions( value FLOAT8 NOT NULL, device_id int NOT NULL ); -SELECT create_hypertable('conditions', 'time', chunk_time_interval => INTERVAL '1 day'); - create_hypertable --------------------------- - (24,public,conditions,t) +SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => INTERVAL '1 day'); + table_name +------------ + conditions (1 row) INSERT INTO conditions (time, value, device_id) @@ -1180,6 +1241,7 @@ SELECT a.* FROM cagg_realtime a WHERE a.location = 'Moscow' ORDER BY bucket LIMI (2 rows) \set VERBOSITY terse +SET client_min_messages TO WARNING; DROP TABLE conditions CASCADE; -NOTICE: drop cascades to 3 other objects DROP TABLE devices CASCADE; +RESET client_min_messages; diff --git a/tsl/test/expected/cagg_utils.out b/tsl/test/expected/cagg_utils.out index 5b75ad0c16e..e4f15702aac 100644 --- a/tsl/test/expected/cagg_utils.out +++ b/tsl/test/expected/cagg_utils.out @@ -123,9 +123,9 @@ SELECT * FROM cagg_validate_query($$ SELECT 1 FROM pg_catalog.pg_class $$); (1 row) SELECT * FROM cagg_validate_query($$ SELECT relkind, count(*) FROM pg_catalog.pg_class GROUP BY 1 $$); - is_valid | error_level | error_code | error_message | error_detail | error_hint -----------+-------------+------------+--------------------------------------+--------------+------------ - f | ERROR | TS001 | table "pg_class" is not a hypertable | | + is_valid | error_level | error_code | error_message | error_detail | error_hint +----------+-------------+------------+-----------------------------------+----------------------------------------------------------------+------------ + f | ERROR | 0A000 | invalid continuous aggregate view | At least one hypertable should be used in the view definition. | (1 row) -- time_bucket with offset is not allowed @@ -158,33 +158,33 @@ SELECT * FROM cagg_validate_query($$ SELECT time_bucket_gapfill('1 hour', "time" -- invalid join queries SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', a."time"), count(*) FROM metrics a, metrics b GROUP BY 1 $$); - is_valid | error_level | error_code | error_message | error_detail | error_hint -----------+-------------+------------+-----------------------------------+-------------------------------------------------------------------------+------------ - f | ERROR | 0A000 | invalid continuous aggregate view | Multiple hypertables or normal tables are not supported in FROM clause. | + is_valid | error_level | error_code | error_message | error_detail | error_hint +----------+-------------+------------+-----------------------------------+--------------------------------------------------------------+------------ + f | ERROR | 0A000 | invalid continuous aggregate view | Only one hypertable is allowed in continuous aggregate view. | (1 row) SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), count(*) FROM metrics, devices a, devices b GROUP BY 1 $$); - is_valid | error_level | error_code | error_message | error_detail | error_hint -----------+-------------+------------+---------------------------------------------------------------------------------------------------+--------------+------------ - f | ERROR | 0A000 | only two tables with one hypertable and one normal table are allowed in continuous aggregate view | | + is_valid | error_level | error_code | error_message | error_detail | error_hint +----------+-------------+------------+---------------+--------------+------------ + t | | | | | (1 row) SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics LEFT JOIN devices ON id = device_id GROUP BY 1, 2 $$); - is_valid | error_level | error_code | error_message | error_detail | error_hint -----------+-------------+------------+---------------------------------------------------------+--------------+------------ - f | ERROR | 0A000 | only inner joins are supported in continuous aggregates | | + is_valid | error_level | error_code | error_message | error_detail | error_hint +----------+-------------+------------+---------------+--------------+------------ + t | | | | | (1 row) SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics JOIN devices ON id = device_id AND name = 'foo' GROUP BY 1, 2 $$); - is_valid | error_level | error_code | error_message | error_detail | error_hint -----------+-------------+------------+-----------------------------------+----------------------------------------+------------------------------------------------------------------ - f | ERROR | 0A000 | invalid continuous aggregate view | Unsupported expression in join clause. | Only equality conditions are supported in continuous aggregates. + is_valid | error_level | error_code | error_message | error_detail | error_hint +----------+-------------+------------+---------------+--------------+------------ + t | | | | | (1 row) SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics JOIN devices ON id < device_id GROUP BY 1, 2 $$); - is_valid | error_level | error_code | error_message | error_detail | error_hint -----------+-------------+------------+-----------------------------------+------------------------------------------------------------------+------------ - f | ERROR | 0A000 | invalid continuous aggregate view | Only equality conditions are supported in continuous aggregates. | + is_valid | error_level | error_code | error_message | error_detail | error_hint +----------+-------------+------------+---------------+--------------+------------ + t | | | | | (1 row) -- invalid caggs on caggs diff --git a/tsl/test/sql/cagg_joins.sql b/tsl/test/sql/cagg_joins.sql index 40598528046..4d0e2ffc8f4 100644 --- a/tsl/test/sql/cagg_joins.sql +++ b/tsl/test/sql/cagg_joins.sql @@ -2,18 +2,15 @@ -- Please see the included NOTICE for copyright information and -- LICENSE-TIMESCALE for a copy of the license. -\set ON_ERROR_STOP 0 \set VERBOSITY default CREATE TABLE conditions( day TIMESTAMPTZ NOT NULL, city text NOT NULL, temperature INT NOT NULL, -device_id int NOT NULL); -SELECT create_hypertable( - 'conditions', 'day', - chunk_time_interval => INTERVAL '1 day' + device_id int NOT NULL ); +SELECT table_name FROM create_hypertable('conditions', 'day', chunk_time_interval => INTERVAL '1 day'); INSERT INTO conditions (day, city, temperature, device_id) VALUES ('2021-06-14', 'Moscow', 26,1), ('2021-06-15', 'Berlin', 22,2), @@ -31,20 +28,18 @@ INSERT INTO conditions (day, city, temperature, device_id) VALUES ('2021-06-27', 'Moscow', 31,3); CREATE TABLE conditions_dup AS SELECT * FROM conditions; -SELECT create_hypertable( - 'conditions_dup', 'day', - chunk_time_interval => INTERVAL '1 day', - migrate_data => true -); +SELECT table_name FROM create_hypertable('conditions_dup', 'day', chunk_time_interval => INTERVAL '1 day', migrate_data => true); + CREATE TABLE devices ( device_id int not null, name text, location text); INSERT INTO devices values (1, 'thermo_1', 'Moscow'), (2, 'thermo_2', 'Berlin'),(3, 'thermo_3', 'London'),(4, 'thermo_4', 'Stockholm'); +CREATE TABLE location (location_id INTEGER, name TEXT); +INSERT INTO location VALUES (1, 'Moscow'), (2, 'Berlin'), (3, 'London'), (4, 'Stockholm'); + CREATE TABLE devices_dup AS SELECT * FROM devices; CREATE VIEW devices_view AS SELECT * FROM devices; --- Working cases -- Cagg with inner join + realtime aggregate - CREATE MATERIALIZED VIEW cagg_realtime WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -64,7 +59,6 @@ INSERT INTO conditions (day, city, temperature, device_id) VALUES SELECT * FROM cagg_realtime ORDER BY bucket, name; -- Cagg with inner join + realtime aggregate + JOIN clause - CREATE MATERIALIZED VIEW cagg_realtime_join WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -84,7 +78,6 @@ INSERT INTO conditions (day, city, temperature, device_id) VALUES SELECT * FROM cagg_realtime_join ORDER BY bucket, name; -- Cagg with inner join + realtime aggregate + USING clause - CREATE MATERIALIZED VIEW cagg_realtime_using WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -104,8 +97,7 @@ INSERT INTO conditions (day, city, temperature, device_id) VALUES SELECT * FROM cagg_realtime_using ORDER BY bucket, name; -- Reorder tables in FROM clause --- Cagg with inner join + realtime aggregate - +-- Cagg with inner join + realtime aggregate CREATE MATERIALIZED VIEW cagg_realtime_reorder WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -126,7 +118,6 @@ SELECT * FROM cagg_realtime_reorder ORDER BY bucket, name; -- Reorder tables in FROM clause -- Cagg with inner join + realtime aggregate + JOIN clause - CREATE MATERIALIZED VIEW cagg_realtime_reorder_join WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -148,7 +139,6 @@ FROM cagg_realtime_reorder_join; -- Reorder tables in FROM clause -- Cagg with inner join + realtime aggregate + USING clause - CREATE MATERIALIZED VIEW cagg_realtime_reorder_using WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -168,7 +158,6 @@ INSERT INTO conditions (day, city, temperature, device_id) VALUES SELECT * FROM cagg_realtime_reorder_using ORDER BY bucket, name; -- Cagg with inner joins - realtime aggregate - CREATE MATERIALIZED VIEW cagg WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -183,7 +172,6 @@ ORDER BY bucket; SELECT * FROM cagg ORDER BY bucket, name, thermo_id; -- Cagg with inner joins - realtime aggregate + JOIN clause - CREATE MATERIALIZED VIEW cagg_join WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -196,7 +184,6 @@ ORDER BY bucket; SELECT * FROM cagg_join ORDER BY bucket, name; -- Cagg with inner joins - realtime aggregate + USING clause - CREATE MATERIALIZED VIEW cagg_using WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -210,7 +197,6 @@ SELECT * FROM cagg_using; -- Reorder tables in FROM clause -- Cagg with inner joins - realtime aggregate - CREATE MATERIALIZED VIEW cagg_reorder WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -224,7 +210,6 @@ ORDER BY bucket; SELECT * FROM cagg_reorder ORDER BY bucket, name; -- Cagg with inner joins - realtime aggregate + JOIN clause - CREATE MATERIALIZED VIEW cagg_reorder_join WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -237,7 +222,6 @@ ORDER BY bucket; SELECT * FROM cagg_reorder_join; -- Cagg with inner joins - realtime aggregate + USING clause - CREATE MATERIALIZED VIEW cagg_reorder_using WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -249,8 +233,19 @@ ORDER BY bucket; SELECT * FROM cagg_reorder_using; ---Create CAgg with join and additional WHERE conditions +-- Cagg join with another table using FROM ONLY +CREATE MATERIALIZED VIEW cagg_from_only +WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS +SELECT time_bucket(INTERVAL '1 day', day) AS bucket, + AVG(temperature), + name +FROM ONLY devices JOIN conditions USING (device_id) +GROUP BY name, bucket +ORDER BY bucket; + +SELECT * FROM cagg_from_only; +-- Create CAgg with join and additional WHERE conditions CREATE MATERIALIZED VIEW cagg_more_conds WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -263,8 +258,7 @@ GROUP BY name, bucket; SELECT * FROM cagg_more_conds ORDER BY bucket; ---Cagg with more conditions and USING clause - +-- Cagg with more conditions and USING clause CREATE MATERIALIZED VIEW cagg_more_conds_using WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -277,8 +271,7 @@ GROUP BY name, bucket; SELECT * FROM cagg_more_conds_using ORDER BY bucket; --- Nested CAgg over a CAgg with join - +-- Hierarchical CAgg with join CREATE MATERIALIZED VIEW cagg_on_cagg WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS SELECT time_bucket(INTERVAL '1 day', bucket) AS bucket, @@ -292,7 +285,6 @@ SELECT * FROM cagg_on_cagg; DROP MATERIALIZED VIEW cagg_on_cagg CASCADE; -- Nested CAgg over a CAgg with JOIN clause - CREATE MATERIALIZED VIEW cagg_on_cagg_join WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS SELECT time_bucket(INTERVAL '1 day', bucket) AS bucket, @@ -305,7 +297,7 @@ SELECT * FROM cagg_on_cagg_join; DROP MATERIALIZED VIEW cagg_on_cagg_join CASCADE; ---Create CAgg with join and ORDER BY +-- Create CAgg with join and ORDER BY CREATE MATERIALIZED VIEW cagg_ordered WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -366,8 +358,7 @@ GROUP BY 1,2; DROP MATERIALIZED VIEW cagg_nested CASCADE; ---Error cases ---CAgg with multiple join conditions without JOIN clause +-- CAgg with multiple join conditions without JOIN clause CREATE MATERIALIZED VIEW cagg_more_joins_conds WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -380,16 +371,16 @@ AND conditions.city = devices.location AND conditions.temperature > 28 GROUP BY name, bucket; -CREATE TABLE mat_t1( a integer, b integer,c TEXT); +-- With LATERAL multiple tables in new format +CREATE TABLE mat_t1(a integer, b integer, c TEXT); ---With LATERAL multiple tables in new format CREATE MATERIALIZED VIEW mat_m1 WITH (timescaledb.continuous, timescaledb.materialized_only = FALSE) -as -select temperature, count(*) from conditions, -LATERAL (Select * from mat_t1 where a = conditions.temperature) q -group by temperature WITH NO DATA; +AS +SELECT time_bucket(INTERVAL '1 day', day) AS bucket, temperature, count(*) from conditions, +LATERAL (SELECT * FROM mat_t1 WHERE mat_t1.a = conditions.temperature) q +GROUP BY bucket, temperature; ---With FROM clause has view +-- Joining a hypertable and view CREATE MATERIALIZED VIEW cagg_view WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -404,6 +395,7 @@ CREATE TABLE cities(name text, currency text); INSERT INTO cities VALUES ('Berlin', 'EUR'), ('London', 'PND'); --Error out when FROM clause has sub selects +\set ON_ERROR_STOP 0 CREATE MATERIALIZED VIEW conditions_summary_subselect WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -485,9 +477,10 @@ SELECT AVG(devices.device_id), FROM devices, devices_dup WHERE devices.device_id = devices_dup.device_id GROUP BY devices.name, devices.location; +\set ON_ERROR_STOP 1 ---Error out when join is on non-equality condition -CREATE MATERIALIZED VIEW cagg_unequal +-- JOIN with non-equality condition +CREATE MATERIALIZED VIEW cagg_unequal1 WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), @@ -496,8 +489,8 @@ FROM conditions, devices WHERE conditions.device_id <> devices.device_id GROUP BY name, bucket; ---Unsupported join condition -CREATE MATERIALIZED VIEW cagg_unequal +-- JOIN with non-equality condition +CREATE MATERIALIZED VIEW cagg_unequal2 WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), @@ -507,18 +500,18 @@ WHERE conditions.device_id = devices.device_id AND conditions.city like '%cow*' GROUP BY name, bucket; ---Unsupported join condition -CREATE MATERIALIZED VIEW cagg_unequal +-- JOIN with non-equality condition +CREATE MATERIALIZED VIEW cagg_unequal3 WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), name -FROM conditions, devices -WHERE conditions.device_id = devices.device_id OR - conditions.city like '%cow*' +FROM conditions +JOIN devices ON conditions.device_id = devices.device_id OR conditions.city LIKE '%cow*' GROUP BY name, bucket; ---Error out when join type is not inner +-- Error out when join type is not INNER or LEFT +\set ON_ERROR_STOP 0 CREATE MATERIALIZED VIEW cagg_outer WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, @@ -528,7 +521,18 @@ FROM conditions FULL JOIN devices ON conditions.device_id = devices.device_id GROUP BY name, bucket; -CREATE MATERIALIZED VIEW cagg_outer +CREATE MATERIALIZED VIEW cagg_right +WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS +SELECT time_bucket(INTERVAL '1 day', day) AS bucket, + AVG(temperature), + name +FROM conditions RIGHT JOIN devices +ON conditions.device_id = devices.device_id +GROUP BY name, bucket; +\set ON_ERROR_STOP 1 + +-- LEFT JOIN is allowed +CREATE MATERIALIZED VIEW cagg_left_join WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', day) AS bucket, AVG(temperature), @@ -537,7 +541,8 @@ FROM conditions LEFT JOIN devices ON conditions.device_id = devices.device_id GROUP BY name, bucket; ---Error out for join between cagg and hypertable +-- Error out for join between cagg and hypertable +\set ON_ERROR_STOP 0 CREATE MATERIALIZED VIEW cagg_nested_ht WITH (timescaledb.continuous, timescaledb.materialized_only = TRUE) AS SELECT time_bucket(INTERVAL '1 day', cagg.bucket) AS bucket, @@ -546,12 +551,56 @@ SELECT time_bucket(INTERVAL '1 day', cagg.bucket) AS bucket, FROM cagg_cagg cagg, conditions WHERE cagg.device_id = conditions.device_id GROUP BY 1,2,3; +\set ON_ERROR_STOP 1 + +-- Multiple JOINS are supported +CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS +SELECT time_bucket(INTERVAL '1 day', conditions.day) AS bucket, + AVG(conditions.temperature), + devices.name AS device, + location.name AS location +FROM conditions +JOIN devices ON conditions.device_id = devices.device_id +JOIN location ON location.name = devices.location +GROUP BY bucket, devices.name, location.name; + +-- JOIN with a foreign table +\c :TEST_DBNAME :ROLE_SUPERUSER +SELECT current_setting('port') AS "PGPORT", current_database() AS "PGDATABASE" \gset +CREATE EXTENSION postgres_fdw; +CREATE SERVER loopback + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (host 'localhost', dbname :'PGDATABASE', port :'PGPORT'); +CREATE USER MAPPING FOR :ROLE_DEFAULT_PERM_USER + SERVER loopback + OPTIONS (user :'ROLE_DEFAULT_PERM_USER', password 'nopassword'); +ALTER USER MAPPING FOR :ROLE_DEFAULT_PERM_USER + SERVER loopback + OPTIONS (ADD password_required 'false'); +GRANT USAGE ON FOREIGN SERVER loopback TO :ROLE_DEFAULT_PERM_USER; +\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER; + +CREATE FOREIGN TABLE devices_fdw ( + device_id int not null, + name text, + location text +) SERVER loopback OPTIONS (table_name 'devices'); + +CREATE MATERIALIZED VIEW conditions_fdw WITH (timescaledb.continuous) AS +SELECT time_bucket(INTERVAL '1 day', conditions.day) AS bucket, + AVG(conditions.temperature), + devices.name AS device +FROM conditions +JOIN devices_fdw AS devices ON conditions.device_id = devices.device_id +GROUP BY bucket, devices.name; \set VERBOSITY terse +SET client_min_messages TO WARNING; DROP TABLE conditions CASCADE; DROP TABLE devices CASCADE; DROP TABLE conditions_dup CASCADE; DROP TABLE devices_dup CASCADE; +RESET client_min_messages; \set VERBOSITY default @@ -562,7 +611,7 @@ CREATE TABLE conditions( device_id int NOT NULL ); -SELECT create_hypertable('conditions', 'time', chunk_time_interval => INTERVAL '1 day'); +SELECT table_name FROM create_hypertable('conditions', 'time', chunk_time_interval => INTERVAL '1 day'); INSERT INTO conditions (time, value, device_id) SELECT t, 1, 1 FROM generate_series('2024-01-01 00:00:00-00'::timestamptz, '2024-12-31 00:00:00-00'::timestamptz, '1 hour'::interval) AS t; @@ -590,5 +639,7 @@ VACUUM ANALYZE; SELECT a.* FROM cagg_realtime a WHERE a.location = 'Moscow' ORDER BY bucket LIMIT 2; \set VERBOSITY terse +SET client_min_messages TO WARNING; DROP TABLE conditions CASCADE; DROP TABLE devices CASCADE; +RESET client_min_messages;