Skip to content

Commit c9baf4b

Browse files
committed
Support RETURNING clause with direct compress
The direct compress insert path added each tuple to the compressor and moved on to the next row without ever evaluating the RETURNING clause, so an INSERT with RETURNING returned no rows even though the data was inserted. Project and return the RETURNING result for each inserted row in the direct compress path, matching the normal insert path. Fixes #10243
1 parent df450e2 commit c9baf4b

4 files changed

Lines changed: 85 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes: #10280 RETURNING clause returned no rows for INSERT using Direct Compress

src/nodes/modify_hypertable_exec.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2523,6 +2523,20 @@ ExecModifyTable(CustomScanState *cs_node, PlanState *pstate)
25232523
ts_cm_functions->compressor_add_slot(ht_state->compressor, ht_state->bulk_writer, chunk_slot);
25242524
if (node->canSetTag)
25252525
estate->es_processed++;
2526+
2527+
/*
2528+
* Project the RETURNING result. Direct compress skips
2529+
* ExecInsert, which normally handles this.
2530+
*/
2531+
if (chunk_rri->ri_projectReturning)
2532+
{
2533+
chunk_slot->tts_tableOid = RelationGetRelid(ctr->cis->rel);
2534+
return ExecProcessReturning(chunk_rri,
2535+
CMD_INSERT,
2536+
NULL,
2537+
chunk_slot,
2538+
context.planSlot);
2539+
}
25262540
continue;
25272541
}
25282542

tsl/test/expected/direct_compress_insert.out

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,47 @@ SELECT count(*) FROM dc_excl;
961961

962962
RESET timescaledb.enable_direct_compress_insert;
963963
DROP TABLE dc_excl;
964+
-- Direct compress must return rows for a RETURNING clause
965+
CREATE TABLE dc_ret(time timestamptz NOT NULL, device text NOT NULL, val int NOT NULL)
966+
WITH (tsdb.hypertable, tsdb.partition_column='time');
967+
ALTER TABLE dc_ret SET (timescaledb.compress, timescaledb.compress_segmentby='device');
968+
NOTICE: updated compression settings will only apply to future compressions
969+
SET timescaledb.enable_direct_compress_insert = true;
970+
-- confirm the direct compress path is used for this insert
971+
EXPLAIN (COSTS OFF, SUMMARY OFF, TIMING OFF)
972+
INSERT INTO dc_ret SELECT '2025-01-01'::timestamptz + (i * INTERVAL '1 minute'), 'd' || (i % 2)::text, i
973+
FROM generate_series(1, 20) i RETURNING val;
974+
--- QUERY PLAN ---
975+
Custom Scan (ModifyHypertable)
976+
Direct Compress: true
977+
-> Insert on dc_ret
978+
-> Function Scan on generate_series i
979+
980+
-- RETURNING should return one row per inserted tuple
981+
WITH inserted AS (
982+
INSERT INTO dc_ret SELECT '2025-01-01'::timestamptz + (i * INTERVAL '1 minute'), 'd' || (i % 2)::text, i
983+
FROM generate_series(1, 20) i RETURNING val, device
984+
)
985+
SELECT count(*), min(val), max(val), sum(val), count(DISTINCT device) FROM inserted;
986+
count | min | max | sum | count
987+
-------+-----+-----+-----+-------
988+
20 | 1 | 20 | 210 | 2
989+
990+
-- RETURNING with an expression referencing multiple columns
991+
WITH inserted AS (
992+
INSERT INTO dc_ret SELECT '2025-01-01'::timestamptz + (i * INTERVAL '1 hour'), 'd' || (i % 2)::text, i
993+
FROM generate_series(1, 20) i RETURNING device || ':' || val AS label
994+
)
995+
SELECT count(*), min(label), max(label) FROM inserted;
996+
count | min | max
997+
-------+-------+------
998+
20 | d0:10 | d1:9
999+
1000+
-- rows were actually inserted
1001+
SELECT count(*) FROM dc_ret;
1002+
count
1003+
-------
1004+
40
1005+
1006+
RESET timescaledb.enable_direct_compress_insert;
1007+
DROP TABLE dc_ret;

tsl/test/sql/direct_compress_insert.sql

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,3 +569,29 @@ SELECT count(*) FROM dc_excl;
569569
RESET timescaledb.enable_direct_compress_insert;
570570
DROP TABLE dc_excl;
571571

572+
-- Direct compress must return rows for a RETURNING clause
573+
CREATE TABLE dc_ret(time timestamptz NOT NULL, device text NOT NULL, val int NOT NULL)
574+
WITH (tsdb.hypertable, tsdb.partition_column='time');
575+
ALTER TABLE dc_ret SET (timescaledb.compress, timescaledb.compress_segmentby='device');
576+
SET timescaledb.enable_direct_compress_insert = true;
577+
-- confirm the direct compress path is used for this insert
578+
EXPLAIN (COSTS OFF, SUMMARY OFF, TIMING OFF)
579+
INSERT INTO dc_ret SELECT '2025-01-01'::timestamptz + (i * INTERVAL '1 minute'), 'd' || (i % 2)::text, i
580+
FROM generate_series(1, 20) i RETURNING val;
581+
-- RETURNING should return one row per inserted tuple
582+
WITH inserted AS (
583+
INSERT INTO dc_ret SELECT '2025-01-01'::timestamptz + (i * INTERVAL '1 minute'), 'd' || (i % 2)::text, i
584+
FROM generate_series(1, 20) i RETURNING val, device
585+
)
586+
SELECT count(*), min(val), max(val), sum(val), count(DISTINCT device) FROM inserted;
587+
-- RETURNING with an expression referencing multiple columns
588+
WITH inserted AS (
589+
INSERT INTO dc_ret SELECT '2025-01-01'::timestamptz + (i * INTERVAL '1 hour'), 'd' || (i % 2)::text, i
590+
FROM generate_series(1, 20) i RETURNING device || ':' || val AS label
591+
)
592+
SELECT count(*), min(label), max(label) FROM inserted;
593+
-- rows were actually inserted
594+
SELECT count(*) FROM dc_ret;
595+
RESET timescaledb.enable_direct_compress_insert;
596+
DROP TABLE dc_ret;
597+

0 commit comments

Comments
 (0)