|
|
@ -17,7 +17,6 @@ defmodule Explorer.Chain do |
|
|
|
select: 3, |
|
|
|
select: 3, |
|
|
|
subquery: 1, |
|
|
|
subquery: 1, |
|
|
|
union: 2, |
|
|
|
union: 2, |
|
|
|
union_all: 2, |
|
|
|
|
|
|
|
where: 2, |
|
|
|
where: 2, |
|
|
|
where: 3 |
|
|
|
where: 3 |
|
|
|
] |
|
|
|
] |
|
|
@ -2456,66 +2455,73 @@ defmodule Explorer.Chain do |
|
|
|
range_min = min(range_start, range_end) |
|
|
|
range_min = min(range_start, range_end) |
|
|
|
range_max = max(range_start, range_end) |
|
|
|
range_max = max(range_start, range_end) |
|
|
|
|
|
|
|
|
|
|
|
missing_prefix_query = |
|
|
|
ordered_missing_query = |
|
|
|
from(block in Block, |
|
|
|
from(b in Block, |
|
|
|
select: %{min: type(^range_min, block.number), max: min(block.number) - 1}, |
|
|
|
right_join: |
|
|
|
where: block.consensus == true, |
|
|
|
missing_range in fragment( |
|
|
|
having: ^range_min < min(block.number) and min(block.number) < ^range_max |
|
|
|
""" |
|
|
|
) |
|
|
|
(SELECT distinct b1.number |
|
|
|
|
|
|
|
FROM generate_series((?)::integer, (?)::integer) AS b1(number) |
|
|
|
missing_suffix_query = |
|
|
|
WHERE NOT EXISTS |
|
|
|
from(block in Block, |
|
|
|
(SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus)) |
|
|
|
select: %{min: max(block.number) + 1, max: type(^range_max, block.number)}, |
|
|
|
""", |
|
|
|
where: block.consensus == true, |
|
|
|
^range_min, |
|
|
|
having: ^range_min < max(block.number) and max(block.number) < ^range_max |
|
|
|
^range_max |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
on: b.number == missing_range.number, |
|
|
|
|
|
|
|
select: missing_range.number, |
|
|
|
|
|
|
|
order_by: missing_range.number, |
|
|
|
|
|
|
|
distinct: missing_range.number |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
missing_infix_query = |
|
|
|
missing_blocks = Repo.all(ordered_missing_query, timeout: :infinity) |
|
|
|
from(block in Block, |
|
|
|
|
|
|
|
select: %{min: type(^range_min, block.number), max: type(^range_max, block.number)}, |
|
|
|
|
|
|
|
where: block.consensus == true, |
|
|
|
|
|
|
|
having: |
|
|
|
|
|
|
|
(is_nil(min(block.number)) and is_nil(max(block.number))) or |
|
|
|
|
|
|
|
(^range_max < min(block.number) or max(block.number) < ^range_min) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Gaps and Islands is the term-of-art for finding the runs of missing (gaps) and existing (islands) data. If you |
|
|
|
[block_ranges, last_block_range_start, last_block_range_end] = |
|
|
|
# Google for `sql missing ranges` you won't find much, but `sql gaps and islands` will get a lot of hits. |
|
|
|
missing_blocks |
|
|
|
|
|
|
|
|> Enum.reduce([[], nil, nil], fn block_number, [block_ranges, last_block_range_start, last_block_range_end] -> |
|
|
|
|
|
|
|
cond do |
|
|
|
|
|
|
|
!last_block_range_start -> |
|
|
|
|
|
|
|
[block_ranges, block_number, block_number] |
|
|
|
|
|
|
|
|
|
|
|
land_query = |
|
|
|
block_number == last_block_range_end + 1 -> |
|
|
|
from(block in Block, |
|
|
|
[block_ranges, last_block_range_start, block_number] |
|
|
|
where: block.consensus == true and ^range_min <= block.number and block.number <= ^range_max, |
|
|
|
|
|
|
|
windows: [w: [order_by: block.number]], |
|
|
|
|
|
|
|
select: %{last_number: block.number |> lag() |> over(:w), next_number: block.number} |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gap_query = |
|
|
|
true -> |
|
|
|
from( |
|
|
|
block_ranges = block_ranges_extend(block_ranges, last_block_range_start, last_block_range_end) |
|
|
|
coastline in subquery(land_query), |
|
|
|
[block_ranges, block_number, block_number] |
|
|
|
where: coastline.last_number != coastline.next_number - 1, |
|
|
|
end |
|
|
|
select: %{min: coastline.last_number + 1, max: coastline.next_number - 1} |
|
|
|
end) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
missing_query = |
|
|
|
|
|
|
|
missing_prefix_query |
|
|
|
|
|
|
|
|> union_all(^missing_infix_query) |
|
|
|
|
|
|
|
|> union_all(^gap_query) |
|
|
|
|
|
|
|
|> union_all(^missing_suffix_query) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{first, last, direction} = |
|
|
|
final_block_ranges = |
|
|
|
if range_start <= range_end do |
|
|
|
if last_block_range_start && last_block_range_end do |
|
|
|
{:min, :max, :asc} |
|
|
|
block_ranges_extend(block_ranges, last_block_range_start, last_block_range_end) |
|
|
|
else |
|
|
|
else |
|
|
|
{:max, :min, :desc} |
|
|
|
block_ranges |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
ordered_missing_query = |
|
|
|
ordered_block_ranges = |
|
|
|
from(missing_range in subquery(missing_query), |
|
|
|
final_block_ranges |
|
|
|
select: %Range{first: field(missing_range, ^first), last: field(missing_range, ^last)}, |
|
|
|
|> Enum.sort(fn %Range{first: first1, last: _}, %Range{first: first2, last: _} -> |
|
|
|
order_by: [{^direction, field(missing_range, ^first)}] |
|
|
|
if range_start <= range_end do |
|
|
|
) |
|
|
|
first1 <= first2 |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
first1 >= first2 |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
end) |
|
|
|
|
|
|
|
|> Enum.map(fn %Range{first: first, last: last} = range -> |
|
|
|
|
|
|
|
if range_start <= range_end do |
|
|
|
|
|
|
|
range |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
%Range{first: last, last: first} |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ordered_block_ranges |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
Repo.all(ordered_missing_query, timeout: :infinity) |
|
|
|
defp block_ranges_extend(block_ranges, block_range_start, block_range_end) do |
|
|
|
|
|
|
|
# credo:disable-for-next-line |
|
|
|
|
|
|
|
block_ranges ++ [Range.new(block_range_start, block_range_end)] |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
@doc """ |
|
|
|
@doc """ |
|
|
|