schema_reaper 1.0.0 → 1.0.3
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +26 -0
- data/PRO.md +98 -0
- data/README.md +15 -0
- data/lib/schema_reaper/analyzers/base.rb +25 -7
- data/lib/schema_reaper/analyzers/dead_table.rb +3 -1
- data/lib/schema_reaper/analyzers/registry.rb +3 -1
- data/lib/schema_reaper/cli.rb +12 -4
- data/lib/schema_reaper/config.rb +35 -9
- data/lib/schema_reaper/finding.rb +3 -1
- data/lib/schema_reaper/gem_awareness.rb +2 -0
- data/lib/schema_reaper/migration_generator.rb +7 -2
- data/lib/schema_reaper/reporters/markdown.rb +3 -1
- data/lib/schema_reaper/reporters/sarif.rb +3 -1
- data/lib/schema_reaper/reporters/table.rb +3 -1
- data/lib/schema_reaper/runner.rb +2 -0
- data/lib/schema_reaper/runtime.rb +14 -4
- data/lib/schema_reaper/schema.rb +23 -7
- data/lib/schema_reaper/static/scanner.rb +1 -1
- data/lib/schema_reaper/version.rb +1 -1
- data/lib/schema_reaper.rb +3 -1
- data/schema_reaper.gemspec +39 -13
- metadata +38 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 22245618558de174144cf3732fbced500e89726cafdec2ab14bdefe35d535287
|
|
4
|
+
data.tar.gz: 95a26a72863e7f603d1d6894aed3f89948f34d85959359863db83e9d01cc5d1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f98dcbc99cf5a8527ecdfbce06dfce99f6f678971c9d60ee96a030ba0b71eeac731e39f0e8dc87e697a9d74dcfec95d1fb1cfe06549a5b3db55a36580da42a7c
|
|
7
|
+
data.tar.gz: dbcb412469b0bb09c2676706b5a11a6fb085649411d9af3c29d8e633fd208a9e83315905886cf9bc065af4d85a148cdab11101b9dc6ff09380887effbee17493
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.3] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Richer gem metadata for the RubyGems page: expanded `description` (analyzer
|
|
7
|
+
list, safety model, gem-awareness, reporters), and added `bug_tracker_uri`,
|
|
8
|
+
`documentation_uri`, `wiki_uri` and `funding_uri` (GitHub Sponsors) link
|
|
9
|
+
metadata. No code change.
|
|
10
|
+
|
|
11
|
+
## [1.0.2] - 2026-09-04
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Lowered `required_ruby_version` to `>= 2.7.0` (was `>= 3.1.0`).
|
|
15
|
+
- Rewrote all 40 endless method definitions (`def x = ...`, a Ruby 3.0
|
|
16
|
+
feature) as classic `def ... end`.
|
|
17
|
+
- Added explicit `require "set"` where `Set` / `to_set` are used (autoloaded
|
|
18
|
+
only on Ruby 3.2+).
|
|
19
|
+
- `rubocop` `TargetRubyVersion` set to 2.7; CI matrix now 2.7–3.3.
|
|
20
|
+
- Every `lib/`, `spec/` and `exe/` file verified to parse under Ruby 2.7.0;
|
|
21
|
+
dependency-free modules exercised on a real 2.7 runtime. Runtime deps
|
|
22
|
+
(`prism` >= 2.7, `thor` >= 2.6, `pg`) all support 2.7.
|
|
23
|
+
- No behaviour change; 29 specs unchanged.
|
|
24
|
+
|
|
25
|
+
## [1.0.1] - 2026-09-04
|
|
26
|
+
|
|
27
|
+
Superseded by 1.0.2 before release. Lowered the floor to `>= 3.0.0` only.
|
|
28
|
+
|
|
3
29
|
## [1.0.0] - 2026-09-04
|
|
4
30
|
|
|
5
31
|
First stable release.
|
data/PRO.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# schema_reaper Pro — open-core plan
|
|
2
|
+
|
|
3
|
+
The `schema_reaper` gem stays **MIT, free, and complete for its job**: schema
|
|
4
|
+
introspection, the seven analyzers, static + runtime signal, staged migrations,
|
|
5
|
+
`table`/`json`/`markdown`/`sarif` output, the CI baseline gate. Everything a
|
|
6
|
+
solo developer or single app needs.
|
|
7
|
+
|
|
8
|
+
**Pro** is a separate paid layer for *teams and multi-database estates* — the
|
|
9
|
+
work that only pays off at scale and that individuals rarely need.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Free vs Pro
|
|
14
|
+
|
|
15
|
+
| Capability | Free (`schema_reaper`) | Pro (`schema_reaper-pro`) |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| PostgreSQL adapter | ✅ | ✅ |
|
|
18
|
+
| MySQL / MariaDB adapter | — | ✅ |
|
|
19
|
+
| 7 core analyzers | ✅ | ✅ |
|
|
20
|
+
| Orphan-row + schema-drift analyzers | — | ✅ |
|
|
21
|
+
| Runtime attribute tracker | ✅ (file sink) | ✅ + Redis / StatsD / OTLP sinks |
|
|
22
|
+
| Reporters | table / json / markdown / sarif | + HTML, Slack, Jira, PR review comments |
|
|
23
|
+
| Reclaimable estimate | bytes/row × est. rows | real `pg_total_relation_size`, index bloat, $ per cloud plan |
|
|
24
|
+
| History / trend | local JSONL + `trend` | hosted timeline, burndown charts, per-team dashboards |
|
|
25
|
+
| Baseline gate | one file | per-branch baselines, ownership routing (CODEOWNERS) |
|
|
26
|
+
| Multi-database / shards | one DB per run | fan-out across N databases, divergence report |
|
|
27
|
+
| Scheduled scans + alerts | — | ✅ (cron runner, "queue breached", "new dead column in `billing`") |
|
|
28
|
+
| Mountable dashboard engine | — | ✅ `mount SchemaReaper::Pro::Engine` |
|
|
29
|
+
| SSO, audit log, RBAC | — | ✅ (Enterprise) |
|
|
30
|
+
| Support | GitHub issues, best effort | private issues, SLA |
|
|
31
|
+
|
|
32
|
+
Pro depends on the OSS gem and reuses its analyzer registry — no fork, no
|
|
33
|
+
divergence. New analyzers land in Free first unless they are inherently
|
|
34
|
+
team-scale.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Delivery & licensing
|
|
39
|
+
|
|
40
|
+
- **Distribution**: private gem on a Gemfury / GitHub Packages source, installed
|
|
41
|
+
with a license-key-scoped token.
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
source "https://pro.schema-reaper.dev" do
|
|
45
|
+
gem "schema_reaper-pro"
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **License check**: offline-friendly signed key (Ed25519), 30-day grace if the
|
|
50
|
+
license server is unreachable. No phone-home of schema content — ever.
|
|
51
|
+
- **License**: commercial EULA, per-organization. Source-available to
|
|
52
|
+
subscribers (read + patch), not redistributable.
|
|
53
|
+
- **Trial**: 21-day full-feature key, no card.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Pricing (draft)
|
|
58
|
+
|
|
59
|
+
| Tier | Who | Price | Limits |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| **Solo Pro** | 1 dev, unlimited personal/side projects | $9 / mo ($90 / yr) | 3 databases |
|
|
62
|
+
| **Team** | up to 10 devs | $49 / mo ($490 / yr) | 15 databases, Slack/Jira, scheduled scans |
|
|
63
|
+
| **Business** | up to 50 devs | $199 / mo | unlimited databases, dashboard engine, priority support |
|
|
64
|
+
| **Enterprise** | 50+ / regulated | custom (from ~$12k / yr) | SSO, audit log, RBAC, on-prem license server, SLA |
|
|
65
|
+
|
|
66
|
+
Free forever for: OSS projects, students, and any single non-commercial
|
|
67
|
+
database. Charity/edu 50% off.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Hosted option (later — biggest ceiling)
|
|
72
|
+
|
|
73
|
+
`schema-reaper.dev` SaaS: point it at a read-replica, get weekly reports, trend
|
|
74
|
+
dashboards, PR comments, and alerts with zero gem install. Same tiers, +$X for
|
|
75
|
+
the managed runner. Ship the self-hosted Pro gem first; the SaaS is Pro + a
|
|
76
|
+
scheduler + a web app.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Rollout order
|
|
81
|
+
|
|
82
|
+
1. **Now**: Sponsor button, `PRO.md`, "Pro" section in README (waitlist link).
|
|
83
|
+
2. **v1.1 (OSS)**: MySQL adapter groundwork, real size math — proves demand,
|
|
84
|
+
some lands free.
|
|
85
|
+
3. **Pro 0.1**: private gem = MySQL + Slack reporter + scheduled scans + license
|
|
86
|
+
key. Sell to the first 10 teams at half price for feedback.
|
|
87
|
+
4. **Pro 0.2**: dashboard engine, multi-DB fan-out, per-branch baselines.
|
|
88
|
+
5. **SaaS beta**: once ~25 paying teams self-host.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Principles
|
|
93
|
+
|
|
94
|
+
- Free tier never gets *worse* to sell Pro. No crippling, no nag screens.
|
|
95
|
+
- Anything a single app genuinely needs stays free.
|
|
96
|
+
- Never transmit schema, data, or query text without explicit opt-in.
|
|
97
|
+
- Pro features are the ones a 2-person team can't be bothered to build and a
|
|
98
|
+
50-person org will happily expense.
|
data/README.md
CHANGED
|
@@ -124,6 +124,21 @@ require:
|
|
|
124
124
|
- Mountable dashboard engine, trend charts
|
|
125
125
|
- MySQL adapter
|
|
126
126
|
|
|
127
|
+
## Pro (for teams)
|
|
128
|
+
|
|
129
|
+
The gem is free and complete for a single app. **schema_reaper Pro** adds the
|
|
130
|
+
team-scale layer: MySQL adapter, multi-database fan-out, orphan-row and
|
|
131
|
+
schema-drift analyzers, Slack/Jira/PR-comment reporters, real
|
|
132
|
+
`pg_total_relation_size` + $ estimates, scheduled scans with alerts, and a
|
|
133
|
+
mountable dashboard engine. See [PRO.md](PRO.md). Waitlist / early access:
|
|
134
|
+
open an issue tagged `pro`.
|
|
135
|
+
|
|
136
|
+
## Sponsor
|
|
137
|
+
|
|
138
|
+
`schema_reaper` is MIT and maintained in the open. If it saved you disk, money,
|
|
139
|
+
or a nasty migration, sponsor its development via the **Sponsor** button on the
|
|
140
|
+
repo.
|
|
141
|
+
|
|
127
142
|
## Development
|
|
128
143
|
|
|
129
144
|
```
|
|
@@ -9,8 +9,13 @@ module SchemaReaper
|
|
|
9
9
|
# gem_columns - Hash{table_name => Set<column_name>} reserved by gems
|
|
10
10
|
# config - Config
|
|
11
11
|
Context = Struct.new(:schema, :used_tokens, :runtime, :gem_columns, :config, keyword_init: true) do
|
|
12
|
-
def runtime
|
|
13
|
-
|
|
12
|
+
def runtime
|
|
13
|
+
self[:runtime] || Runtime::Report.empty
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def gem_columns
|
|
17
|
+
self[:gem_columns] || {}
|
|
18
|
+
end
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
# Shared plumbing for analyzers: schema access and token lookup helpers.
|
|
@@ -24,16 +29,29 @@ module SchemaReaper
|
|
|
24
29
|
end
|
|
25
30
|
|
|
26
31
|
# @return [Array<Finding>]
|
|
27
|
-
def call
|
|
32
|
+
def call
|
|
33
|
+
raise(NotImplementedError)
|
|
34
|
+
end
|
|
28
35
|
|
|
29
36
|
private
|
|
30
37
|
|
|
31
38
|
attr_reader :ctx
|
|
32
39
|
|
|
33
|
-
def schema
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
def schema
|
|
41
|
+
ctx.schema
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def config
|
|
45
|
+
ctx.config
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def runtime
|
|
49
|
+
ctx.runtime
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def used?(token)
|
|
53
|
+
ctx.used_tokens.include?(token.to_s.downcase)
|
|
54
|
+
end
|
|
37
55
|
|
|
38
56
|
def gem_reserved?(table, column)
|
|
39
57
|
ctx.gem_columns.fetch(table, []).include?(column)
|
data/lib/schema_reaper/cli.rb
CHANGED
|
@@ -6,7 +6,9 @@ require_relative "../schema_reaper"
|
|
|
6
6
|
module SchemaReaper
|
|
7
7
|
# Command-line entry point. See `exe/schema_reaper`.
|
|
8
8
|
class CLI < Thor
|
|
9
|
-
def self.exit_on_failure?
|
|
9
|
+
def self.exit_on_failure?
|
|
10
|
+
true
|
|
11
|
+
end
|
|
10
12
|
|
|
11
13
|
class_option :config, type: :string, default: ".schema_reaper.yml",
|
|
12
14
|
desc: "path to config file"
|
|
@@ -48,13 +50,19 @@ module SchemaReaper
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
desc "version", "Print version"
|
|
51
|
-
def version
|
|
53
|
+
def version
|
|
54
|
+
say(SchemaReaper::VERSION)
|
|
55
|
+
end
|
|
52
56
|
|
|
53
57
|
private
|
|
54
58
|
|
|
55
|
-
def config
|
|
59
|
+
def config
|
|
60
|
+
@config ||= Config.load(options[:config])
|
|
61
|
+
end
|
|
56
62
|
|
|
57
|
-
def run
|
|
63
|
+
def run
|
|
64
|
+
Runner.new(config: config).run
|
|
65
|
+
end
|
|
58
66
|
|
|
59
67
|
def enforce_baseline(findings)
|
|
60
68
|
new_ones = Baseline.new(config.baseline_path).new_among(findings)
|
data/lib/schema_reaper/config.rb
CHANGED
|
@@ -44,15 +44,41 @@ module SchemaReaper
|
|
|
44
44
|
@data["database_url"] || ENV.fetch("DATABASE_URL", nil)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def scan_paths
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def
|
|
47
|
+
def scan_paths
|
|
48
|
+
@data["scan_paths"]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def view_globs
|
|
52
|
+
@data["view_globs"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ignore_tables
|
|
56
|
+
@data.dig("ignore", "tables").to_a
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def min_age_days
|
|
60
|
+
@data["min_age_days"]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def baseline_path
|
|
64
|
+
@data["baseline"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def runtime_log
|
|
68
|
+
@data["runtime_log"]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def history_log
|
|
72
|
+
@data["history_log"]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def gem_awareness?
|
|
76
|
+
@data["gem_awareness"] != false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def require_paths
|
|
80
|
+
@data["require"].to_a
|
|
81
|
+
end
|
|
56
82
|
|
|
57
83
|
def always_keep_columns
|
|
58
84
|
@data["always_keep_columns"].to_a
|
|
@@ -19,7 +19,9 @@ module SchemaReaper
|
|
|
19
19
|
[type, table, column, index].compact.join("/")
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def reclaimable_bytes
|
|
22
|
+
def reclaimable_bytes
|
|
23
|
+
self[:reclaimable_bytes] || 0
|
|
24
|
+
end
|
|
23
25
|
|
|
24
26
|
def to_h
|
|
25
27
|
super.merge(id: id, reclaimable_bytes: reclaimable_bytes)
|
|
@@ -61,7 +61,12 @@ module SchemaReaper
|
|
|
61
61
|
path
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
def camel
|
|
65
|
-
|
|
64
|
+
def camel
|
|
65
|
+
"#{@table}_#{@column}".split("_").map(&:capitalize).join
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def model
|
|
69
|
+
@table.split("_").map(&:capitalize).join.sub(/s$/, "")
|
|
70
|
+
end
|
|
66
71
|
end
|
|
67
72
|
end
|
|
@@ -39,7 +39,9 @@ module SchemaReaper
|
|
|
39
39
|
@findings.sort_by { |f| [-f.confidence, SEV_ORDER.fetch(f.severity, 9)] }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def total_reclaimable
|
|
42
|
+
def total_reclaimable
|
|
43
|
+
@findings.sum(&:reclaimable_bytes)
|
|
44
|
+
end
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
47
|
end
|
data/lib/schema_reaper/runner.rb
CHANGED
|
@@ -11,7 +11,9 @@ module SchemaReaper
|
|
|
11
11
|
module Runtime
|
|
12
12
|
# Aggregated view of a usage log, consumed by analyzers.
|
|
13
13
|
Report = Struct.new(:accessed, :observed_days, keyword_init: true) do
|
|
14
|
-
def self.empty
|
|
14
|
+
def self.empty
|
|
15
|
+
new(accessed: Set.new, observed_days: 0)
|
|
16
|
+
end
|
|
15
17
|
|
|
16
18
|
def self.load(path)
|
|
17
19
|
return empty unless path && File.exist?(path)
|
|
@@ -36,9 +38,17 @@ module SchemaReaper
|
|
|
36
38
|
((Time.parse(last) - Time.parse(first)) / 86_400).ceil
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
def empty?
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
def empty?
|
|
42
|
+
accessed.empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def present?
|
|
46
|
+
!empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read?(table, column)
|
|
50
|
+
accessed.include?("#{table}.#{column}")
|
|
51
|
+
end
|
|
42
52
|
end
|
|
43
53
|
|
|
44
54
|
# Buffered writer for the usage log. Thread-safe append of JSON lines.
|
data/lib/schema_reaper/schema.rb
CHANGED
|
@@ -9,26 +9,42 @@ module SchemaReaper
|
|
|
9
9
|
keyword_init: true
|
|
10
10
|
) do
|
|
11
11
|
# true when every row holds NULL (pg_stats null_frac == 1)
|
|
12
|
-
def always_null?
|
|
12
|
+
def always_null?
|
|
13
|
+
null_fraction && null_fraction >= 1.0
|
|
14
|
+
end
|
|
13
15
|
|
|
14
16
|
# true when the column holds exactly one distinct non-null value
|
|
15
|
-
def single_value?
|
|
17
|
+
def single_value?
|
|
18
|
+
distinct_values == 1
|
|
19
|
+
end
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
Index = Struct.new(:name, :columns, :unique, :primary, :scans, keyword_init: true) do
|
|
19
|
-
def covers?(other)
|
|
23
|
+
def covers?(other)
|
|
24
|
+
columns.first(other.columns.length) == other.columns
|
|
25
|
+
end
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
Table = Struct.new(
|
|
23
29
|
:name, :columns, :indexes, :primary_key, :foreign_keys, :row_count,
|
|
24
30
|
keyword_init: true
|
|
25
31
|
) do
|
|
26
|
-
def column_names
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
def column_names
|
|
33
|
+
columns.map(&:name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def column(name)
|
|
37
|
+
columns.find { |c| c.name == name }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def index_on(cols)
|
|
41
|
+
indexes.find { |i| i.columns == Array(cols) }
|
|
42
|
+
end
|
|
29
43
|
end
|
|
30
44
|
|
|
31
45
|
DatabaseSchema = Struct.new(:tables, keyword_init: true) do
|
|
32
|
-
def table(name)
|
|
46
|
+
def table(name)
|
|
47
|
+
tables.find { |t| t.name == name }
|
|
48
|
+
end
|
|
33
49
|
end
|
|
34
50
|
end
|
|
@@ -10,7 +10,7 @@ module SchemaReaper
|
|
|
10
10
|
# safe, a false "dead" is not.
|
|
11
11
|
class Scanner
|
|
12
12
|
RUBY_GLOB = "**/*.rb"
|
|
13
|
-
WORD_RE = /[a-z_][a-z0-9_]*/i
|
|
13
|
+
WORD_RE = /[a-z_][a-z0-9_]*/i.freeze
|
|
14
14
|
|
|
15
15
|
# Node classes whose #name (or #unescaped) is a bare identifier we treat
|
|
16
16
|
# as a possible column/table reference.
|
data/lib/schema_reaper.rb
CHANGED
data/schema_reaper.gemspec
CHANGED
|
@@ -8,25 +8,51 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["aksshatt"]
|
|
9
9
|
spec.email = ["akshatpegwar5@gmail.com"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "Find dead columns, unused indexes and schema dead-weight
|
|
11
|
+
spec.summary = "Find dead columns, dead tables, unused indexes and other schema dead-weight " \
|
|
12
|
+
"in Rails/ActiveRecord apps -- then remove it safely."
|
|
12
13
|
spec.description = <<~DESC
|
|
13
|
-
schema_reaper
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
schema_reaper reads your live PostgreSQL schema and planner statistics and
|
|
15
|
+
cross-references them against a Prism-AST static scan of your codebase
|
|
16
|
+
(models, views, SQL string literals). With an optional runtime signal -- a
|
|
17
|
+
sampled log of which columns are actually read in production -- it fuses
|
|
18
|
+
static and runtime evidence into a confidence score per finding.
|
|
19
|
+
|
|
20
|
+
Analyzers:
|
|
21
|
+
* dead_column -- column no code path references
|
|
22
|
+
* dead_table -- table with no model/query reference, zero rows
|
|
23
|
+
* unused_index -- non-unique index, idx_scan = 0 in pg_stat
|
|
24
|
+
* duplicate_index -- index that is a prefix of a wider index
|
|
25
|
+
* missing_fk_index -- foreign-key / *_id column with no covering index
|
|
26
|
+
* always_null_column -- pg_stats null_frac = 1.0, carries no data
|
|
27
|
+
* single_value_column -- one distinct value across a large table
|
|
28
|
+
|
|
29
|
+
Every finding carries a reclaimable-bytes estimate (from row counts) and a
|
|
30
|
+
concrete fix. `generate-migration` emits a staged, reversible pair:
|
|
31
|
+
ignored_columns first, remove_column after a soak. Nothing is dropped
|
|
32
|
+
automatically. Columns owned by common gems (devise, paper_trail,
|
|
33
|
+
activestorage, actiontext, friendly_id, audited, pg_search, ahoy_matey,
|
|
34
|
+
paranoia) are whitelisted when the gem is in your bundle.
|
|
35
|
+
|
|
36
|
+
Reporters: table, json, markdown (PR comments), SARIF 2.1.0 (GitHub code
|
|
37
|
+
scanning). `scan --ci` gates only findings absent from a committed baseline.
|
|
38
|
+
`trend` keeps an append-only history log for cleanup burndown. A Rails
|
|
39
|
+
railtie adds rake tasks and an opt-in runtime tracker. Custom analyzers
|
|
40
|
+
load through the `require:` config key.
|
|
41
|
+
|
|
42
|
+
PostgreSQL only for now (MySQL adapter on the roadmap). Ruby >= 2.7.
|
|
21
43
|
DESC
|
|
22
44
|
spec.homepage = "https://github.com/aksshatt/schema_reaper"
|
|
23
45
|
spec.license = "MIT"
|
|
24
|
-
spec.required_ruby_version = ">=
|
|
46
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
25
47
|
|
|
26
48
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
27
|
-
spec.metadata["homepage_uri"]
|
|
28
|
-
spec.metadata["source_code_uri"]
|
|
29
|
-
spec.metadata["changelog_uri"]
|
|
49
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
50
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
51
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
52
|
+
spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
|
|
53
|
+
spec.metadata["documentation_uri"] = "#{spec.homepage}#readme"
|
|
54
|
+
spec.metadata["wiki_uri"] = "#{spec.homepage}/wiki"
|
|
55
|
+
spec.metadata["funding_uri"] = "https://github.com/sponsors/aksshatt"
|
|
30
56
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
31
57
|
|
|
32
58
|
spec.files = Dir.chdir(__dir__) do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schema_reaper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- aksshatt
|
|
@@ -79,14 +79,35 @@ dependencies:
|
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
80
|
version: '1.5'
|
|
81
81
|
description: |
|
|
82
|
-
schema_reaper
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
schema_reaper reads your live PostgreSQL schema and planner statistics and
|
|
83
|
+
cross-references them against a Prism-AST static scan of your codebase
|
|
84
|
+
(models, views, SQL string literals). With an optional runtime signal -- a
|
|
85
|
+
sampled log of which columns are actually read in production -- it fuses
|
|
86
|
+
static and runtime evidence into a confidence score per finding.
|
|
87
|
+
|
|
88
|
+
Analyzers:
|
|
89
|
+
* dead_column -- column no code path references
|
|
90
|
+
* dead_table -- table with no model/query reference, zero rows
|
|
91
|
+
* unused_index -- non-unique index, idx_scan = 0 in pg_stat
|
|
92
|
+
* duplicate_index -- index that is a prefix of a wider index
|
|
93
|
+
* missing_fk_index -- foreign-key / *_id column with no covering index
|
|
94
|
+
* always_null_column -- pg_stats null_frac = 1.0, carries no data
|
|
95
|
+
* single_value_column -- one distinct value across a large table
|
|
96
|
+
|
|
97
|
+
Every finding carries a reclaimable-bytes estimate (from row counts) and a
|
|
98
|
+
concrete fix. `generate-migration` emits a staged, reversible pair:
|
|
99
|
+
ignored_columns first, remove_column after a soak. Nothing is dropped
|
|
100
|
+
automatically. Columns owned by common gems (devise, paper_trail,
|
|
101
|
+
activestorage, actiontext, friendly_id, audited, pg_search, ahoy_matey,
|
|
102
|
+
paranoia) are whitelisted when the gem is in your bundle.
|
|
103
|
+
|
|
104
|
+
Reporters: table, json, markdown (PR comments), SARIF 2.1.0 (GitHub code
|
|
105
|
+
scanning). `scan --ci` gates only findings absent from a committed baseline.
|
|
106
|
+
`trend` keeps an append-only history log for cleanup burndown. A Rails
|
|
107
|
+
railtie adds rake tasks and an opt-in runtime tracker. Custom analyzers
|
|
108
|
+
load through the `require:` config key.
|
|
109
|
+
|
|
110
|
+
PostgreSQL only for now (MySQL adapter on the roadmap). Ruby >= 2.7.
|
|
90
111
|
email:
|
|
91
112
|
- akshatpegwar5@gmail.com
|
|
92
113
|
executables:
|
|
@@ -100,6 +121,7 @@ files:
|
|
|
100
121
|
- CHANGELOG.md
|
|
101
122
|
- CODE_OF_CONDUCT.md
|
|
102
123
|
- LICENSE.txt
|
|
124
|
+
- PRO.md
|
|
103
125
|
- README.md
|
|
104
126
|
- Rakefile
|
|
105
127
|
- exe/schema_reaper
|
|
@@ -143,6 +165,10 @@ metadata:
|
|
|
143
165
|
homepage_uri: https://github.com/aksshatt/schema_reaper
|
|
144
166
|
source_code_uri: https://github.com/aksshatt/schema_reaper
|
|
145
167
|
changelog_uri: https://github.com/aksshatt/schema_reaper/blob/main/CHANGELOG.md
|
|
168
|
+
bug_tracker_uri: https://github.com/aksshatt/schema_reaper/issues
|
|
169
|
+
documentation_uri: https://github.com/aksshatt/schema_reaper#readme
|
|
170
|
+
wiki_uri: https://github.com/aksshatt/schema_reaper/wiki
|
|
171
|
+
funding_uri: https://github.com/sponsors/aksshatt
|
|
146
172
|
rubygems_mfa_required: 'true'
|
|
147
173
|
post_install_message:
|
|
148
174
|
rdoc_options: []
|
|
@@ -152,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
152
178
|
requirements:
|
|
153
179
|
- - ">="
|
|
154
180
|
- !ruby/object:Gem::Version
|
|
155
|
-
version:
|
|
181
|
+
version: 2.7.0
|
|
156
182
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
183
|
requirements:
|
|
158
184
|
- - ">="
|
|
@@ -162,6 +188,6 @@ requirements: []
|
|
|
162
188
|
rubygems_version: 3.4.10
|
|
163
189
|
signing_key:
|
|
164
190
|
specification_version: 4
|
|
165
|
-
summary: Find dead columns, unused indexes and schema dead-weight
|
|
166
|
-
apps.
|
|
191
|
+
summary: Find dead columns, dead tables, unused indexes and other schema dead-weight
|
|
192
|
+
in Rails/ActiveRecord apps -- then remove it safely.
|
|
167
193
|
test_files: []
|