-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.

\set PREFIX 'EXPLAIN (BUFFERS OFF, COSTS OFF) '

CREATE TABLE order_test(time int NOT NULL, device_id int, value float);
CREATE INDEX ON order_test(time,device_id);
CREATE INDEX ON order_test(device_id,time);

SELECT create_hypertable('order_test','time',chunk_time_interval:=1000);

INSERT INTO order_test SELECT 0,10,0.5;
INSERT INTO order_test SELECT 1,9,0.5;
INSERT INTO order_test SELECT 2,8,0.5;

-- we want to see here that index scans are possible for the chosen expressions
-- so we disable seqscan so we dont need to worry about other factors which would
-- make PostgreSQL prefer seqscan over index scan
SET enable_seqscan TO off;

-- test sort optimization with single member order by
SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1;
-- should use index scan
:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1;

-- test sort optimization with ordering by multiple columns and time_bucket not last
SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1,2;
SET enable_seqscan TO default;
-- must not use index scan
:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 1,2;
SET enable_seqscan TO off;

-- test sort optimization with ordering by multiple columns and time_bucket as last member
SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 2,1;
-- should use index scan
:PREFIX SELECT time_bucket(10,time),device_id,value FROM order_test ORDER BY 2,1;

-- test sort optimization with interval calculation with non-fixed interval
-- #7097
CREATE TABLE i7097_1(time timestamptz NOT NULL, quantity float, "isText" boolean);
CREATE TABLE i7097_2(time timestamptz NOT NULL, quantity float, "isText" boolean);

CREATE INDEX ON i7097_1(time) WHERE "isText" IS NULL;
CREATE INDEX ON i7097_2(time) WHERE "isText" IS NULL;

SELECT table_name FROM create_hypertable('i7097_1', 'time', create_default_indexes => false);
SELECT table_name FROM create_hypertable('i7097_2', 'time', create_default_indexes => false);

INSERT INTO i7097_1(time, quantity)
SELECT time, round((random() * (100-3) + 3)::NUMERIC) AS quantity
FROM generate_series('2023-01-01T00:00:00+01:00', '2023-05-01T00:00:00+01:00', interval 'PT10M') AS t(time);

INSERT INTO i7097_2(time, quantity)
SELECT time, round((random() * (100-3) + 3)::NUMERIC) AS quantity
FROM generate_series('2023-01-01T00:00:00+01:00', '2023-05-01T00:00:00+01:00', interval 'PT10M') AS t(time);

VACUUM ANALYZE i7097_1, i7097_2;

SET TIME ZONE 'Europe/Paris';
WITH
"cte1" AS (SELECT time + interval 'P1Y' AS time, avg(quantity) AS quantity FROM i7097_1 WHERE time >= '2024-03-31T00:00:00+01:00'::timestamptz - interval 'P1Y' AND time < '2024-03-31T23:59:59+02:00'::timestamptz + (- interval 'P1Y') AND "isText" IS NULL GROUP BY 1 ORDER BY 1 ASC),
"cte2" AS (SELECT time + interval 'P1Y' AS time, avg(quantity) AS quantity FROM i7097_2 WHERE time >= '2024-03-31T00:00:00+01:00'::timestamptz - interval 'P1Y' AND time < '2024-03-31T23:59:59+02:00'::timestamptz + (- interval 'P1Y') AND "isText" IS NULL GROUP BY 1 ORDER BY 1 ASC)
SELECT count(*) FROM (SELECT time, cte1.quantity + cte2.quantity FROM cte1 FULL OUTER JOIN cte2 USING (time)) j;

-- github issue 9214
-- test off-by one error in sort optimization
CREATE TABLE i9214(time timestamptz NOT NULL, machine_id INT NOT NULL, name TEXT NOT NULL, value FLOAT NOT NULL) WITH (tsdb.hypertable);

INSERT INTO i9214
VALUES
('2026-01-30 10:00:00+00', 1, 'tag1', 10.5),
('2026-01-30 10:00:00+00', 1, 'tag2', 20.5),
('2026-01-30 10:01:00+00', 1, 'tag1', 11.0),
('2026-01-30 10:01:00+00', 1, 'tag2', 21.0);

WITH rule1 AS (
  SELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag1' AND value > 5
), row_numbered AS (
  SELECT time, machine_id, row_number() OVER (ORDER BY time) AS seqnum FROM rule1
)
SELECT min(time) AS start_time, machine_id, count(*) AS duration_minutes
FROM row_numbered
GROUP BY machine_id, (time - (seqnum * interval '1 minute'))
ORDER BY min(time);

WITH rule1 AS (
	SELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag1' AND value > 5
), rule2 AS (
  SELECT date_trunc('minute', time) AS time, machine_id FROM i9214 WHERE machine_id = 1 AND name = 'tag2' AND value > 5
), joined_rules AS (
  SELECT r1.time, r1.machine_id FROM rule1 r1 INNER JOIN rule2 r2 USING(time,machine_id)
), row_numbered AS (
  SELECT time, machine_id, row_number() OVER (ORDER BY time) AS seqnum FROM joined_rules
)
SELECT min(time) AS start_time, machine_id, count(*) AS duration_minutes
FROM row_numbered
GROUP BY machine_id, (time - (seqnum * interval '1 minute'))
ORDER BY min(time);

-- Crash in sort_transform_ec when a hypertable sits on the nullable side of an
-- outer join and the pathkey expression references that nullable column.
-- pull_varnos pulls outer-join nullingrels into em_relids on PG16+, and those
-- relids have no entry in simple_rel_array.
CREATE TABLE sort_oj_plain(v int);
CREATE TABLE sort_oj_ht(time timestamptz NOT NULL, v int) WITH (tsdb.hypertable);
INSERT INTO sort_oj_ht VALUES ('2026-01-01', 1);
SELECT DISTINCT time_bucket('1 day', b.time)
FROM sort_oj_plain a LEFT JOIN sort_oj_ht b ON a.v = b.v;
DROP TABLE sort_oj_ht;
DROP TABLE sort_oj_plain;

-- Test issue #10130: sort_transform applied incorrectly to negative consts
-- We should not apply sort_transform to expressions like (1-x), x*-1, x/-1 as it changes sort direction

-- int8 OpExpr
-------------
CREATE TABLE t10130(time bigint NOT NULL, val int);
SELECT create_hypertable('t10130', 'time', chunk_time_interval => 1000::bigint);
INSERT INTO t10130 SELECT i, i * 10 FROM generate_series(1, 2000) i;

-- Make sure indexscan is not selected even when the cost pushes for it
SET enable_seqscan=0;

-- Baseline: correct descending order
SET timescaledb.enable_optimizations = off;
SELECT time, val FROM t10130 ORDER BY time * (-1)::bigint LIMIT 3;

-- Should return correct result, should not apply sort transform
SET timescaledb.enable_optimizations = on;
SELECT time, val FROM t10130 ORDER BY time * (-1)::bigint LIMIT 3;
SELECT time, val FROM t10130 ORDER BY (-1)::bigint * time LIMIT 3;

-- Same for const - var
SELECT time, val FROM t10130 ORDER BY 5000::bigint - time LIMIT 3;

-- Same for var / negative const
SELECT time, val FROM t10130 ORDER BY time / (-1)::bigint LIMIT 3;

-- Non-commutative operation nested in commutative operation: should not apply sort transform
SELECT time, val FROM t10130 ORDER BY (time * (-1)::bigint) + 2::bigint LIMIT 3;

drop table t10130 cascade;

-- int4 OpExpr
------------
CREATE TABLE t10130(time int NOT NULL, val int);
SELECT create_hypertable('t10130', 'time', chunk_time_interval => 1000::int);
INSERT INTO t10130 SELECT i, i * 10 FROM generate_series(1, 2000) i;

-- Baseline: correct descending order
SET timescaledb.enable_optimizations = off;
SELECT time, val FROM t10130 ORDER BY time * (-1)::int LIMIT 3;

-- Should return correct result, should not apply sort transform
SET timescaledb.enable_optimizations = on;
SELECT time, val FROM t10130 ORDER BY time * (-1)::int LIMIT 3;
SELECT time, val FROM t10130 ORDER BY (-1)::int * time LIMIT 3;

-- Same for const - var
SELECT time, val FROM t10130 ORDER BY 5000::int - time LIMIT 3;

-- Same for var / negative const
SELECT time, val FROM t10130 ORDER BY time / (-1)::int LIMIT 3;

-- Non-commutative operation nested in commutative operation: should not apply sort transform
SELECT time, val FROM t10130 ORDER BY (time * (-1)::int) + 2::int LIMIT 3;

drop table t10130 cascade;

-- int2 OpExpr
------------
CREATE TABLE t10130(time smallint NOT NULL, val int);
SELECT create_hypertable('t10130', 'time', chunk_time_interval => 1000::smallint);
INSERT INTO t10130 SELECT i, i * 10 FROM generate_series(1, 2000) i;

-- Baseline: correct descending order
SET timescaledb.enable_optimizations = off;
SELECT time, val FROM t10130 ORDER BY time * (-1)::smallint LIMIT 3;

-- Should return correct result, should not apply sort transform
SET timescaledb.enable_optimizations = on;
SELECT time, val FROM t10130 ORDER BY time * (-1)::smallint LIMIT 3;
SELECT time, val FROM t10130 ORDER BY (-1)::smallint * time LIMIT 3;

-- Same for const - var
SELECT time, val FROM t10130 ORDER BY 5000::smallint - time LIMIT 3;

-- Same for var / negative const
SELECT time, val FROM t10130 ORDER BY time / (-1)::smallint LIMIT 3;

-- Non-commutative operation nested in commutative operation: should not apply sort transform
SELECT time, val FROM t10130 ORDER BY (time * (-1)::smallint) + 2::smallint LIMIT 3;

-- Custom "*" operator which does not preserve sort order: should not apply sort transform to custom ops
\c :TEST_DBNAME :ROLE_SUPERUSER
CREATE SCHEMA test_ops;
CREATE FUNCTION test_ops.custom_mult(a smallint, b smallint) RETURNS smallint AS $$
BEGIN
    RETURN a * coalesce(b, -1);
END;
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE OPERATOR test_ops.* (
    LEFTARG = smallint,
    RIGHTARG = smallint,
    FUNCTION = test_ops.custom_mult,
    COMMUTATOR = OPERATOR(test_ops.*)
);

-- Should use seqscan as we sort by custom operator
:PREFIX SELECT time, val FROM t10130 ORDER BY time OPERATOR(test_ops.*) NULL::smallint LIMIT 3;
:PREFIX SELECT time, val FROM t10130 ORDER BY time OPERATOR(test_ops.*) 1::smallint LIMIT 3;

drop schema test_ops cascade;
drop table t10130 cascade;

RESET enable_seqscan;


-- Sort transform must not create a wrong join EC.
create table a(ts timestamptz not null, k int);
create table b(ts timestamptz not null, k int);
select create_hypertable('a','ts', chunk_time_interval => interval '1 day');
select create_hypertable('b','ts', chunk_time_interval => interval '1 day');
insert into a select '2024-08-01'::timestamptz + (g * interval '1 hour'),   1 from generate_series(0, 23) g;
insert into b select '2024-08-01'::timestamptz + (g * interval '30 minutes'), 1 from generate_series(0, 47) g;


select sum(c)
from
  (select time_bucket('1 hour', a.ts) bb, count(*) c
   from a
   join b on time_bucket('1 hour', a.ts) = time_bucket('1 hour', b.ts)
       and a.k = b.k
   group by bb) z
;

drop table a;
drop table b;
