This can be done with the recursive function below. The function uses intarray extension.
create extension intarray;
The first element of int array arr
is an id
. The rest of the array contains consecutive references source -> target.
If the second and the last elements of the array are equal, then a circular reference was found. (1)
We must look for inner circular references and eliminate them (or we'll finish with stack overflow). (2)
create or replace function find_cref(arr int[])
returns setof int[] language plpgsql
as $$
declare
vlen int = #arr;
vtarget int;
begin
if arr[2] = arr[vlen] then -- (1)
return query select arr;
else
if #uniq(sort(subarray(arr, 2)))+ 1 = vlen then -- (2)
for vtarget in
select target from the_table where source = arr[vlen]
loop
return query select find_cref (arr+ vtarget);
end loop;
end if;
end if;
end $$;
select c[1] id, subarray(c, 2) cref
from (
select find_cref(array[id, source, target]) c
from the_table) x