From 5ed1208a500e5e1b6c27d5880d43fa89880189f1 Mon Sep 17 00:00:00 2001
From: Domizio Demichelis
Date: Sat, 7 Dec 2024 08:15:00 +0700
Subject: [PATCH] Improve cached filtering
---
gem/lib/pagy/keyset/cached.rb | 64 +++++++++++++++++------------------
1 file changed, 31 insertions(+), 33 deletions(-)
diff --git a/gem/lib/pagy/keyset/cached.rb b/gem/lib/pagy/keyset/cached.rb
index 84d4f7cce..090544707 100644
--- a/gem/lib/pagy/keyset/cached.rb
+++ b/gem/lib/pagy/keyset/cached.rb
@@ -15,6 +15,7 @@ class ActiveRecord < Cached
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
@@ -35,32 +36,28 @@ def assign_cutoff
@cutoff = @cutoffs[@page]
end
- # Assign a numeric page param
+ # Assign a numeric page
def assign_page
assign_and_check(page: 1)
end
- # Assign the filter_params and define the :on and :off filter flags referring to
- # the SQL conditions of the filter_record_query, i.e. the page record selected
- # are included between the :on and :off filters
- #
- # The ON flag indicates that the filter query contains a SQL statement identifying where
- # the page records bregin. It is missing for page 1 (which starts from the first record).
- #
- # The OFF flag indicates that the filter query contains a SQL statement identifying where
- # the page records end. It is present when there is a cached cutoff pointing to the last page record.
- # That is used as a repacement of the LIMIT, which may become inaccurate for cached cutoffs.
+ # Override the assignation of the filter params when the next_cutoff is cached
def assign_filter_params
- @filter = {}
+ @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
- if (next_cutoff = @cutoffs[@page + 1])
- filter_params = cutoff_to_filter_params(next_cutoff, OFF_PREFIX)
- (@filter_params ||= {}).merge!(filter_params)
- @filter[:off] = true # the filter is OFF beyond the next_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)
+ (@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
@@ -68,28 +65,29 @@ def default
{ **super, **DEFAULT.slice(:ends, :page, :size) }
end
- # Get the records and set the @more (different way for cached next_cutoff)
+ # Override the fetching when the next_cutoff is cached
def fetch_records
- # When a cached next_cutoff is present, use it to replace the LIMIT with an OFF filter condition
+ return super unless @filter[:off] # return super only when it's the last page
+
+ # The LIMIT is replaced by the :off filter.
# That keeps the fetching accurate also when records are added or removed from a page alredy visited
- if @filter[:off]
- @more = true
- @set.limit(nil).to_a
- else
- super
- end
+ @more = true
+ @set.limit(nil).to_a
end
- # Generate a single ON_FILTER if there is no cached next_cutoff (execute with LIMIT)
- # Generate a composite filter with ON_FILTER AND NOT OFF_FILTER if there is a cached next_cutoff (execute without LIMIT)
+ # Override the query 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
sql = +''
- sql << "(#{super(ON_PREFIX)})" if @filter[:on]
- if @filter[:off]
- sql << ' AND ' if @filter[:on]
- sql << "NOT (#{super(OFF_PREFIX)})"
- end
- sql
+ # Generate the :on filter if flagged
+ sql << "(#{super(ON_PREFIX)}) AND " if @filter[:on]
+ # Add the :off filter
+ sql << "NOT (#{super(OFF_PREFIX)})"
end
# Return the next page number and cache the next_cutoff if it's missing