Skip to content

Commit

Permalink
Simplify cached
Browse files Browse the repository at this point in the history
  • Loading branch information
ddnexus committed Dec 7, 2024
1 parent ad67aaf commit 59523fd
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 33 deletions.
4 changes: 2 additions & 2 deletions gem/lib/pagy/keyset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ def assign_page
def assign_filter_params
return unless @cutoff

@filter_params = cutoff_to_filter_params(@cutoff)
@filter_params = cutoff_to_params(@cutoff)
end

# Decode a cutoff, check its consistency and returns the filter params
def cutoff_to_filter_params(cutoff, prefix = nil)
def cutoff_to_params(cutoff, prefix = nil)
identifier = JSON.parse(B64.urlsafe_decode(cutoff)).transform_keys(&:to_sym)
raise InternalError, 'cutoff and keyset are not consistent' \
unless identifier.keys == @keyset.keys
Expand Down
56 changes: 25 additions & 31 deletions gem/lib/pagy/keyset/cached.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ class Sequel < Cached
include SequelAdapter
end
# Avoid params conflicts for composite filters
ON_PREFIX = 'on_' # Prefix for ON filter filter_params
OFF_PREFIX = 'off_' # Prefix for OFF filter filter_params
NOT_PREFIX = 'not_' # Prefix for NOT filter filter_params

include SharedMethodsForUI

Expand All @@ -41,53 +40,48 @@ def assign_page
assign_and_check(page: 1)
end

# Override the assignation of the filter params when the next_cutoff is cached
# Generate different params when the next_cutoff is cached
def assign_filter_params
@filter = {} # may contain :on and :off entries
return super unless (next_cutoff = @cutoffs[@page + 1]) # return super only when it's the last page

# The ON flag indicates that the filter query contains a filter identifying where
# the page records bregin. It is missing for page 1 (which starts from the first record).
if @cutoff
@filter_params = cutoff_to_filter_params(@cutoff, ON_PREFIX)
@filter[:on] = true # the filter is ON beyond the cutoff
end
# The OFF flag indicates that the filter query contains a filter identifying where
# the page records end.
# The off filter is used as a repacement of the LIMIT, which may become inaccurate for cached cutoffs.
filter_params = cutoff_to_filter_params(next_cutoff, OFF_PREFIX)
return super unless @next_cached_cutoff ||= @cutoffs[@page + 1] # return super only when it's the last page

# Regular cutoff filter: it is missing for page 1 that doesn't have a cutoff.
@filter_params = cutoff_to_params(@cutoff) if @cutoff

# The NOT filter (exclude the records after the next cutoff) is used as a repacement of the LIMIT, which may become inaccurate for cached cutoffs.

Check failure on line 50 in gem/lib/pagy/keyset/cached.rb

View workflow job for this annotation

GitHub Actions / Ruby 3.2 Test

Layout/LineLength: Line is too long. [154/130]

Check failure on line 50 in gem/lib/pagy/keyset/cached.rb

View workflow job for this annotation

GitHub Actions / Ruby 3.3 Test

Layout/LineLength: Line is too long. [154/130]
filter_params = cutoff_to_params(@next_cached_cutoff, NOT_PREFIX)
(@filter_params ||= {}).merge!(filter_params)
@filter[:off] = true # the filter is OFF beyond the next_cutoff
end

# Add the default variables required by the UI
def default
{ **super, **DEFAULT.slice(:ends, :page, :size) }
end

# Override the fetching when the next_cutoff is cached
# Remove the LIMIT when the next_cutoff is cached
def fetch_records
return super unless @filter[:off] # return super only when it's the last page
return super unless @next_cached_cutoff # super for the last page

# The LIMIT is replaced by the :off filter.
# The LIMIT is replaced by the NOT filter.
# That keeps the fetching accurate also when records are added or removed from a page alredy visited
@more = true
@set.limit(nil).to_a
end

# Override the query when the next_cutoff is cached
# Generate a filter between cutoffs when the next_cutoff is cached
def filter_records_query
return super unless @filter[:off] # return super only when it's the last page

# Generate a possibly composite filter between
# - page == 1: the start of the set and the next_cursor (something like: NOT OFF_FILTER)
# - page > 1: the current cursor and the next_cursor (something like: ON_FILTER AND NOT OFF_FILTER)
# The fetch_records will execute without the LIMIT
return super unless @next_cached_cutoff # super for the last page

# Generate a composite filter between:
# - The start of the set and the next cutoff (page == 1)
# Filter logic: NOT BEYOND_NEXT_CUTOFF
# - The current cutoff and the next cutoff (page > 1)
# Filter logic: BEYOND_CUTOFF AND NOT BEYOND_NEXT_CUTOFF
# Notice: the fetch_records will execute without the LIMIT
sql = +''
# Generate the :on filter if flagged
sql << "(#{super(ON_PREFIX)}) AND " if @filter[:on]
# Add the :off filter
sql << "NOT (#{super(OFF_PREFIX)})"
sql << "(#{super}) AND " if @cutoff
# Add the NOT filter
sql << "NOT (#{super(NOT_PREFIX)})"
end

# Return the next page number and cache the next_cutoff if it's missing
Expand All @@ -96,7 +90,7 @@ def next
return if !@more || (@vars[:max_pages] && @page >= @vars[:max_pages])

@next ||= (@page + 1).tap do |next_page|
@cutoffs[next_page] ||= next_cutoff unless @filter[:off] # only for non cached next_cutoff
@cutoffs[next_page] = next_cutoff unless @next_cached_cutoff
end
end

Expand Down

0 comments on commit 59523fd

Please sign in to comment.