-- 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. \ir include/setup_hypercore.sql -- To generate plans consistently. set max_parallel_workers_per_gather to 0; -- Create a function that uses a cursor to scan the the Hypercore -- table. This should work equivalent to a query on the same table. create function location_humidity_for( in p_owner integer, out p_location integer, out p_humidity float) returns setof record as $$ declare location_record record; location_cursor cursor for select location_id, avg(humidity) as avg_humidity from readings where owner_id = p_owner group by location_id; begin open location_cursor; loop fetch next from location_cursor into location_record; exit when not found; p_location = location_record.location_id; p_humidity = location_record.avg_humidity; return next; end loop; close location_cursor; end; $$ language plpgsql; select compress_chunk(show_chunks(:'hypertable'), hypercore_use_access_method => true); -- Compare executing the function with a cursor with a query fetching -- the same data directly from the hypertable. select p_location, lhs.p_humidity, rhs.p_humidity from (select * from location_humidity_for(1)) lhs join (select location_id as p_location, avg(humidity) as p_humidity from :hypertable where owner_id = 1 group by location_id) rhs using (p_location) where lhs.p_humidity != rhs.p_humidity order by p_location; -- Create a function that will use a cursor to iterate through a table -- and update the humidity for a location using a cursor. create function update_location_humidity( in p_location integer, in p_humidity float) returns setof record as $$ declare location_record record; location_cursor cursor for select location_id, humidity from readings where location_id = p_location; begin open location_cursor; loop move next in location_cursor; exit when not found; update readings set humidity = p_humidity where current of location_cursor; end loop; close location_cursor; end; $$ language plpgsql; set timescaledb.max_tuples_decompressed_per_dml_transaction to 0; create table saved as select * from :hypertable; -- These two should generate the same result update saved set humidity = 100.0 where location_id = 10; select update_location_humidity(10, 100.0); -- This should show no rows, but if there are differences we limit -- this to 10 rows to not waste electrons. -- -- Note that update of compressed tables through a cursor does not -- work for all compressed tables right now because of the way the -- local ExecModifyTable is implemented, so this will show rows. select metric_id, lhs.humidity, rhs.humidity from saved lhs full join :hypertable rhs using (metric_id) where lhs.humidity != rhs.humidity order by metric_id limit 10; drop function location_humidity_for; drop function update_location_humidity; -- Test cursor going backwards create table backward_cursor (time timestamptz, location_id bigint, temp float8); select create_hypertable('backward_cursor', 'time', create_default_indexes=>false); alter table backward_cursor set (timescaledb.compress, timescaledb.compress_segmentby='location_id', timescaledb.compress_orderby='time asc'); insert into backward_cursor values ('2024-01-01 01:00', 1, 1.0), ('2024-01-01 02:00', 1, 2.0), ('2024-01-01 03:00', 2, 3.0), ('2024-01-01 04:00', 2, 4.0); select compress_chunk(ch, hypercore_use_access_method => true) from show_chunks('backward_cursor') ch; insert into backward_cursor values ('2024-01-01 05:00', 3, 5.0), ('2024-01-01 06:00', 3, 6.0); begin; -- This needs to be a simple scan on top of the baserel, without a -- materialization. For scan nodes that don't support backwards scans, -- or where a sort or similar happens, the query is typically -- materialized first, thus not really testing the TAMs ability to do -- backwards scanning. explain (costs off) declare curs1 cursor for select _timescaledb_debug.is_compressed_tid(ctid), * from backward_cursor; declare curs1 cursor for select _timescaledb_debug.is_compressed_tid(ctid), * from backward_cursor; -- Immediately fetching backward should return nothing fetch backward 1 from curs1; -- Now read some values forward fetch forward 1 from curs1; fetch forward 1 from curs1; -- The next fetch should move into a new segment with location_id=2 fetch forward 1 from curs1; -- Last compressed entry fetch forward 1 from curs1; -- Now should move into non-compressed fetch forward 1 from curs1; -- Last entry in non-compressed fetch forward 1 from curs1; -- Should return nothing since at end fetch forward 1 from curs1; -- Now move backwards fetch backward 1 from curs1; -- Now backwards into the old segment fetch backward 5 from curs1; -- Next fetch should return nothing since at start fetch backward 1 from curs1; -- Fetch first value again fetch forward 1 from curs1; -- Jump to last value fetch last from curs1; -- Back to first fetch first from curs1; -- Get the values at position 2 and 5 from the start fetch absolute 2 from curs1; fetch absolute 5 from curs1; -- Get the value at position 3 from the end (which should be 4 from -- the start) fetch absolute -3 from curs1; commit;