pg_canary 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +1 -8
  4. data/lib/pg_canary/configuration.rb +1 -14
  5. data/lib/pg_canary/rules/base.rb +1 -10
  6. data/lib/pg_canary/rules/definitions/array_search_without_gin.rb +0 -4
  7. data/lib/pg_canary/rules/definitions/cartesian_join.rb +0 -4
  8. data/lib/pg_canary/rules/definitions/correlated_subquery_in_select.rb +0 -4
  9. data/lib/pg_canary/rules/definitions/count_star_without_where.rb +0 -4
  10. data/lib/pg_canary/rules/definitions/deep_offset.rb +0 -4
  11. data/lib/pg_canary/rules/definitions/distinct_with_join.rb +0 -4
  12. data/lib/pg_canary/rules/definitions/function_on_column.rb +0 -4
  13. data/lib/pg_canary/rules/definitions/huge_in_list.rb +0 -4
  14. data/lib/pg_canary/rules/definitions/implicit_cast.rb +0 -4
  15. data/lib/pg_canary/rules/definitions/jsonb_search_without_gin.rb +0 -4
  16. data/lib/pg_canary/rules/definitions/leading_wildcard_like.rb +0 -4
  17. data/lib/pg_canary/rules/definitions/not_in_subquery.rb +0 -4
  18. data/lib/pg_canary/rules/definitions/or_across_columns.rb +0 -4
  19. data/lib/pg_canary/rules/definitions/order_by_random.rb +0 -4
  20. data/lib/pg_canary/rules/definitions/query_complexity.rb +0 -4
  21. data/lib/pg_canary/rules/definitions/regex_without_trgm.rb +0 -4
  22. data/lib/pg_canary/rules/definitions/select_star_with_heavy_columns.rb +0 -4
  23. data/lib/pg_canary/rules/definitions/unindexed_join.rb +0 -4
  24. data/lib/pg_canary/rules/definitions/unindexed_order_by_with_limit.rb +1 -5
  25. data/lib/pg_canary/rules/definitions/unindexed_where.rb +1 -5
  26. data/lib/pg_canary/rules/definitions/union_instead_of_union_all.rb +0 -4
  27. data/lib/pg_canary/version.rb +1 -1
  28. data/lib/pg_canary.rb +0 -1
  29. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d97dd5cbefd1e0f736b3800993118237cb01c427e55d7c9dbc44a4296d63627
4
- data.tar.gz: 1ebaec08d05be5f91fd6275b0236a4b5b6fbd53d826bfd319789cf8ee727f22d
3
+ metadata.gz: 3117b64abbdaf73701f98780b6dd794822d6bf19919365e3a7c4ffffa62b69a5
4
+ data.tar.gz: c1c73a9c77af75bdcbc7bd3b3685654387728986a5d1d7a880c69c7263dc4ca6
5
5
  SHA512:
6
- metadata.gz: 539edfe1fe783b0d4619d331e527447054ffd44affc964c514fb6d09005c2f8ffc8b48478816ad1ca088a8ef53b0827165b4895892d50d2222b7f207e46fe29e
7
- data.tar.gz: 3bae6b432546feb624e0f898f4d8a23e1a3050a25f47e20bbfff89a81e34bd3c4b37acc65fbeb9574fa35e40fd48aab6f23f9d4fa7d99e9593079054149720e6
6
+ metadata.gz: 17df21da5a3d3a6f221db4aa087d4627fac79de5e8a9d4c7cd96c77849df96a47a63ea4d681a9404bd0b52274cbf7424e926cb7a84769532b8e7b81dd0daa089
7
+ data.tar.gz: '08302281e57605688fd0eaded8f1a2d031ebae2b5e9499d67c564aecd81e420de5d8f015b495e3a5153297e0e3113366874fe13563c78eb403db7bd3a3eb40c5'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0] - 2026-07-18
4
+
5
+ - Remove `config.table_size_hints` / `config.size_rule_threshold`. Tier 2 rules are already opt-in per rule; use `config.ignore_tables` to exclude small tables.
6
+
3
7
  ## [0.1.0] - 2026-07-14
4
8
 
5
9
  Initial release.
data/README.md CHANGED
@@ -62,10 +62,7 @@ config.rules.query_complexity.max_joins = 12
62
62
  config.rules.select_star_with_heavy_columns.heavy_types = %w[bytea jsonb]
63
63
  ```
64
64
 
65
- Once opted in, you can keep false positives down two ways:
66
-
67
- 1. List small tables in `config.ignore_tables`
68
- 2. Provide approximate production row counts in `config.table_size_hints` — when hints are present, Tier 2 rules apply **only** to tables hinted at `size_rule_threshold` (default 10,000) or above. When hints are empty, Tier 2 rules apply to every table — which is precisely why they are off by default.
65
+ Once opted in, keep false positives down by listing small tables in `config.ignore_tables`.
69
66
 
70
67
  ## Configuration
71
68
 
@@ -82,10 +79,6 @@ PgCanary.configure do |config|
82
79
 
83
80
  # Excluding tables
84
81
  config.ignore_tables = %w[prefectures plans schema_migrations ar_internal_metadata]
85
-
86
- # For size-dependent (Tier 2) rules: approximate production row counts
87
- config.table_size_hints = { "users" => 1_000_000, "orders" => 10_000_000 }
88
- config.size_rule_threshold = 10_000 # apply Tier 2 rules only to tables hinted at or above this
89
82
  end
90
83
  ```
91
84
 
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgCanary
4
- # Global settings, exposed through PgCanary.configure.
5
4
  class Configuration
6
5
  # nil means "not explicitly configured" — the Railtie then enables
7
6
  # pg_canary in development/test only.
8
7
  attr_accessor :enabled
9
8
 
10
9
  # False-positive controls
11
- attr_accessor :ignore_tables, :table_size_hints, :size_rule_threshold
10
+ attr_accessor :ignore_tables
12
11
 
13
12
  attr_accessor :logger, :app_root
14
13
  attr_reader :rules
@@ -16,8 +15,6 @@ module PgCanary
16
15
  def initialize
17
16
  @enabled = nil
18
17
  @ignore_tables = %w[schema_migrations ar_internal_metadata]
19
- @table_size_hints = {}
20
- @size_rule_threshold = 10_000
21
18
  @rules = RulesConfig.new
22
19
  @logger = nil
23
20
  @app_root = nil
@@ -26,16 +23,6 @@ module PgCanary
26
23
  def ignore_table?(table)
27
24
  table && ignore_tables.map(&:to_s).include?(table.to_s)
28
25
  end
29
-
30
- # Whether a size-dependent (Tier 2) rule should look at this table.
31
- # With no hints configured, every table qualifies (which is exactly why
32
- # Tier 2 rules are opt-in).
33
- def size_hint_allows?(table)
34
- hints = table_size_hints || {}
35
- return true if hints.empty?
36
-
37
- hints[table.to_s].to_i >= size_rule_threshold
38
- end
39
26
  end
40
27
 
41
28
  # Exposes one RuleConfig per built-in rule, both as methods and via [].
@@ -42,23 +42,14 @@ module PgCanary
42
42
  raise NotImplementedError, "#{self.class} must declare #default_enabled (true for Tier 1, false for Tier 2)"
43
43
  end
44
44
 
45
- def size_dependent?
46
- raise NotImplementedError, "#{self.class} must declare #size_dependent?"
47
- end
48
-
49
45
  private
50
46
 
51
47
  def rule_config(query)
52
48
  query.config.rules[self.class.rule_name]
53
49
  end
54
50
 
55
- # Tier 2 gate: with size hints configured, only tables hinted as large
56
- # are checked.
57
51
  def applicable_table?(query, table)
58
- return false if query.config.ignore_table?(table)
59
- return true unless size_dependent?
60
-
61
- query.config.size_hint_allows?(table)
52
+ !query.config.ignore_table?(table)
62
53
  end
63
54
 
64
55
  def detection(query, message:, suggestion: nil, table: nil, columns: nil)
@@ -11,10 +11,6 @@ module PgCanary
11
11
  true
12
12
  end
13
13
 
14
- def size_dependent?
15
- false
16
- end
17
-
18
14
  ARRAY_OPS = %w[@> <@ &&].freeze
19
15
 
20
16
  def check(query)
@@ -10,10 +10,6 @@ module PgCanary
10
10
  true
11
11
  end
12
12
 
13
- def size_dependent?
14
- false
15
- end
16
-
17
13
  def check(query)
18
14
  detections = []
19
15
  query.each_scope do |scope|
@@ -9,10 +9,6 @@ module PgCanary
9
9
  true
10
10
  end
11
11
 
12
- def size_dependent?
13
- false
14
- end
15
-
16
12
  def check(query)
17
13
  detections = []
18
14
  query.each_scope do |scope|
@@ -9,10 +9,6 @@ module PgCanary
9
9
  false
10
10
  end
11
11
 
12
- def size_dependent?
13
- true
14
- end
15
-
16
12
  def check(query)
17
13
  detections = []
18
14
  query.each_scope do |scope|
@@ -11,10 +11,6 @@ module PgCanary
11
11
  true
12
12
  end
13
13
 
14
- def size_dependent?
15
- false
16
- end
17
-
18
14
  def self.options
19
15
  { threshold: 1000 }
20
16
  end
@@ -11,10 +11,6 @@ module PgCanary
11
11
  false
12
12
  end
13
13
 
14
- def size_dependent?
15
- false
16
- end
17
-
18
14
  def check(query)
19
15
  detections = []
20
16
  query.each_scope do |scope|
@@ -11,10 +11,6 @@ module PgCanary
11
11
  true
12
12
  end
13
13
 
14
- def size_dependent?
15
- false
16
- end
17
-
18
14
  CHECKED_KINDS = %i[AEXPR_OP AEXPR_LIKE AEXPR_ILIKE AEXPR_IN AEXPR_BETWEEN].freeze
19
15
 
20
16
  def check(query)
@@ -12,10 +12,6 @@ module PgCanary
12
12
  true
13
13
  end
14
14
 
15
- def size_dependent?
16
- false
17
- end
18
-
19
15
  def self.options
20
16
  { threshold: 500 }
21
17
  end
@@ -10,10 +10,6 @@ module PgCanary
10
10
  true
11
11
  end
12
12
 
13
- def size_dependent?
14
- false
15
- end
16
-
17
13
  INTEGER_TYPES = %w[smallint integer bigint].freeze
18
14
  NUMERIC_TYPE_NAMES = %w[numeric decimal float4 float8].freeze
19
15
 
@@ -14,10 +14,6 @@ module PgCanary
14
14
  true
15
15
  end
16
16
 
17
- def size_dependent?
18
- false
19
- end
20
-
21
17
  CONTAINMENT_OPS = %w[@> <@ ? ?| ?&].freeze
22
18
  EXTRACTION_OPS = %w[-> ->> #> #>>].freeze
23
19
 
@@ -13,10 +13,6 @@ module PgCanary
13
13
  true
14
14
  end
15
15
 
16
- def size_dependent?
17
- false
18
- end
19
-
20
16
  LIKE_KINDS = %i[AEXPR_LIKE AEXPR_ILIKE].freeze
21
17
 
22
18
  def check(query)
@@ -10,10 +10,6 @@ module PgCanary
10
10
  true
11
11
  end
12
12
 
13
- def size_dependent?
14
- false
15
- end
16
-
17
13
  def check(query)
18
14
  detections = []
19
15
  query.each_scope do |scope|
@@ -10,10 +10,6 @@ module PgCanary
10
10
  false
11
11
  end
12
12
 
13
- def size_dependent?
14
- true
15
- end
16
-
17
13
  def check(query)
18
14
  detections = []
19
15
  query.each_scope do |scope|
@@ -9,10 +9,6 @@ module PgCanary
9
9
  true
10
10
  end
11
11
 
12
- def size_dependent?
13
- false
14
- end
15
-
16
12
  def check(query)
17
13
  detections = []
18
14
  query.each_scope do |scope|
@@ -11,10 +11,6 @@ module PgCanary
11
11
  false
12
12
  end
13
13
 
14
- def size_dependent?
15
- false
16
- end
17
-
18
14
  def self.options
19
15
  { max_joins: 8, max_depth: 4 }
20
16
  end
@@ -12,10 +12,6 @@ module PgCanary
12
12
  true
13
13
  end
14
14
 
15
- def size_dependent?
16
- false
17
- end
18
-
19
15
  REGEX_OPS = %w[~ ~* !~ !~*].freeze
20
16
 
21
17
  def check(query)
@@ -11,10 +11,6 @@ module PgCanary
11
11
  false
12
12
  end
13
13
 
14
- def size_dependent?
15
- false
16
- end
17
-
18
14
  def self.options
19
15
  { heavy_types: %w[bytea jsonb text].freeze }
20
16
  end
@@ -12,10 +12,6 @@ module PgCanary
12
12
  false
13
13
  end
14
14
 
15
- def size_dependent?
16
- true
17
- end
18
-
19
15
  def check(query)
20
16
  detections = []
21
17
  query.each_scope do |scope|
@@ -4,7 +4,7 @@ module PgCanary
4
4
  module Rules
5
5
  # Tier 2 (opt-in): ORDER BY x LIMIT n without an index led by x forces a
6
6
  # full sort before the limit can apply. Size-dependent, so disabled by
7
- # default and gated by config.table_size_hints.
7
+ # default.
8
8
  class UnindexedOrderByWithLimit < Base
9
9
  include IndexPredicates
10
10
 
@@ -12,10 +12,6 @@ module PgCanary
12
12
  false
13
13
  end
14
14
 
15
- def size_dependent?
16
- true
17
- end
18
-
19
15
  def check(query)
20
16
  detections = []
21
17
  query.each_scope do |scope|
@@ -5,16 +5,12 @@ module PgCanary
5
5
  # Tier 2 (opt-in): equality/range predicates on columns with no index
6
6
  # whose leading column could serve them. Depends on production table
7
7
  # size — a 30-row lookup table is fine without indexes — hence disabled
8
- # by default and gated by config.table_size_hints.
8
+ # by default.
9
9
  class UnindexedWhere < Base
10
10
  def default_enabled
11
11
  false
12
12
  end
13
13
 
14
- def size_dependent?
15
- true
16
- end
17
-
18
14
  def check(query)
19
15
  query.each_scope.with_object([]) do |scope, detections|
20
16
  next unless scope.where_clause
@@ -11,10 +11,6 @@ module PgCanary
11
11
  false
12
12
  end
13
13
 
14
- def size_dependent?
15
- false
16
- end
17
-
18
14
  def check(query)
19
15
  union = query.scopes.find { |scope| scope.stmt.op == :SETOP_UNION && !scope.stmt.all }
20
16
  return [] unless union
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgCanary
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/pg_canary.rb CHANGED
@@ -56,7 +56,6 @@ module PgCanary
56
56
  end
57
57
 
58
58
  if defined?(Rails::Railtie)
59
- # Middleware insertion and automatic enabling in development/test.
60
59
  class Railtie < Rails::Railtie
61
60
  initializer "pg_canary.app_root", before: :load_config_initializers do
62
61
  PgCanary.config.app_root ||= Rails.root.to_s
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_canary
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kyuuri1791
@@ -119,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
119
  - !ruby/object:Gem::Version
120
120
  version: '0'
121
121
  requirements: []
122
- rubygems_version: 3.6.7
122
+ rubygems_version: 3.6.9
123
123
  specification_version: 4
124
124
  summary: Detects SQL anti-patterns that can become slow queries in production, while
125
125
  you develop.