Class: PgCanary::Rules::DeepOffset

Inherits:
Base
  • Object
show all
Defined in:
lib/pg_canary/rules/definitions/deep_offset.rb

Overview

OFFSET-based pagination reads and throws away every skipped row, so deep pages degrade linearly. The offset value is read from the runtime bind ($n), which static SQL linters cannot do. Threshold: config.rules.deep_offset.threshold (default 1000).

Constant Summary

Constants included from PgQuerySupport

PgQuerySupport::COMPARISON_OPS

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

all, #enabled?, rule_name

Class Method Details

.optionsObject



14
15
16
# File 'lib/pg_canary/rules/definitions/deep_offset.rb', line 14

def self.options
  { threshold: 1000 }
end

Instance Method Details

#check(query) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/pg_canary/rules/definitions/deep_offset.rb', line 18

def check(query)
  threshold = rule_config(query).threshold
  detections = []
  query.each_scope do |scope|
    next unless scope.stmt.limit_offset

    value = numeric_value(query, scope.stmt.limit_offset)
    next unless value && value >= threshold

    detections << detection(
      query,
      table: scope.tables.length == 1 ? scope.tables.first : nil,
      message: "OFFSET #{value} reads and discards #{value} rows before returning anything — " \
               "offset pagination degrades linearly with page depth.",
      suggestion: <<~SUGGESTION.chomp
        Consider keyset pagination instead:
          WHERE (created_at, id) < (:last_seen_created_at, :last_seen_id) ORDER BY created_at DESC, id DESC LIMIT n
      SUGGESTION
    )
  end
  detections
end

#default_enabledObject



10
11
12
# File 'lib/pg_canary/rules/definitions/deep_offset.rb', line 10

def default_enabled
  true
end