activerecord-analyze 0.9.1 → 0.10.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09f3c691a676a55f45059cbfd0e12161e42f7ab87acdc834c1f496a2ceae1c1f'
4
- data.tar.gz: c83a3d1bb42bdbbb82ca80f00acc55d420de1c7589660f8fd72f103f19aca0c4
3
+ metadata.gz: 8871bdc597d0ba5c601a8b93b17aa037430297554ba074fa7ed448f7fce3f9d3
4
+ data.tar.gz: 846ed95e10f289793e75c17bbae7ee0cd274a93c5af263777da59d66c1006acd
5
5
  SHA512:
6
- metadata.gz: 96e5ba8e107b9d005f59e6ffe3ff0925b872c753b51ef9deee505919d415e3165a67d5bfe1cffa6f29dbd34eb6726e65f93f14d52389bcdd736edc636c25d0e5
7
- data.tar.gz: 957325a0f295e28c76689b504a92a4d0aa636965f6a71326004c3662256aef769ca24bf75bd81a823d2fd3281b9f49d805e0a63dedda3bc5c392f7790cfd0cde
6
+ metadata.gz: 5699bbab0ed4d533d1c62ce5e02d1e54f5868db437cffee62a1be5fb5a34ad7c91fbb758d26ee6f227cb222e67e391564afbd53eb1a6cf1b197d1dfd3cf18b80
7
+ data.tar.gz: b4d2a4ab6b79780d3f55584353381904a8a86c7cfe0f311646326b33f229c01097e250fd02f6569e69ecc8fe6ba89767aa39eac7f44b38f869e5a2e99479f3e0
data/.circleci/config.yml CHANGED
@@ -16,7 +16,7 @@ jobs:
16
16
  - run: gem update --system
17
17
  - run: gem install bundler
18
18
  - run: bundle install --path vendor/bundle
19
- - run: sudo apt-get update
19
+ - run: sudo apt-get update --allow-releaseinfo-change
20
20
  - run: sudo apt install postgresql-client-11
21
21
  - run: dockerize -wait tcp://localhost:5432 -timeout 1m
22
22
  - run:
data/README.md CHANGED
@@ -109,6 +109,43 @@ User.all.analyze(analyze: false)
109
109
 
110
110
  ```
111
111
 
112
+ ### Analyzing raw SQL queries
113
+
114
+ You can also use a raw SQL query string to generate an EXPLAIN ANALYZE output:
115
+
116
+ ```ruby
117
+ query = "SELECT * FROM users WHERE email = 'email@example.com'"
118
+
119
+ puts ActiveRecordAnalyze.analyze_sql(query, { format: :json })
120
+
121
+ # [
122
+ # {
123
+ # "Plan": {
124
+ # "Node Type": "Seq Scan",
125
+ # "Parallel Aware": false,
126
+ # "Relation Name": "users",
127
+ # "Alias": "users",
128
+ # "Startup Cost": 0.00,
129
+ # "Total Cost": 18.75,
130
+ # "Plan Rows": 4,
131
+ # "Plan Width": 88,
132
+ # "Actual Startup Time": 0.010,
133
+ # "Actual Total Time": 0.018,
134
+ # "Actual Rows": 0,
135
+ # "Actual Loops": 1,
136
+ # "Filter": "((email)::text = 'email@example.com'::text)",
137
+ # "Rows Removed by Filter": 0
138
+ # },
139
+ # "Planning Time": 0.052,
140
+ # "Triggers": [
141
+ # ],
142
+ # "Execution Time": 0.062
143
+ # }
144
+ # ]
145
+ ```
146
+
147
+ This feature is helpful in analyzing SQL queries extracted from the logs.
148
+
112
149
  ### Disclaimer
113
150
 
114
151
  It is a bit experimental and can break with new Rails release.
@@ -15,7 +15,8 @@ Gem::Specification.new do |gem|
15
15
  gem.test_files = gem.files.grep(%r{^(spec)/})
16
16
  gem.require_paths = ["lib"]
17
17
  gem.license = "MIT"
18
- gem.add_dependency "rails", ">= 5.1.0"
18
+ gem.add_dependency "activerecord"
19
+ gem.add_dependency "railties"
19
20
  gem.add_development_dependency "rake"
20
21
  gem.add_development_dependency "pg"
21
22
  gem.add_development_dependency "rspec"
@@ -3,60 +3,7 @@ module ActiveRecord
3
3
  module PostgreSQL
4
4
  module DatabaseStatements
5
5
  def analyze(arel, binds = [], opts = {})
6
- format_sql = if fmt = opts[:format].presence
7
- case fmt
8
- when :json
9
- "FORMAT JSON, "
10
- when :hash
11
- "FORMAT JSON, "
12
- when :pretty_json
13
- "FORMAT JSON, "
14
- when :yaml
15
- "FORMAT YAML, "
16
- when :text
17
- "FORMAT TEXT, "
18
- when :xml
19
- "FORMAT XML, "
20
- else
21
- ""
22
- end
23
- end
24
-
25
- verbose_sql = if opts[:verbose] == true
26
- ", VERBOSE"
27
- end
28
-
29
- costs_sql = if opts[:costs] == true
30
- ", COSTS"
31
- end
32
-
33
- settings_sql = if opts[:settings] == true
34
- ", SETTINGS"
35
- end
36
-
37
- buffers_sql = if opts[:buffers] == true
38
- ", BUFFERS"
39
- end
40
-
41
- timing_sql = if opts[:timing] == true
42
- ", TIMING"
43
- end
44
-
45
- summary_sql = if opts[:summary] == true
46
- ", SUMMARY"
47
- end
48
-
49
- analyze_sql = if opts[:analyze] == false
50
- ""
51
- else
52
- "ANALYZE"
53
- end
54
-
55
- opts_sql = "(#{format_sql}#{analyze_sql}#{verbose_sql}#{costs_sql}#{settings_sql}#{buffers_sql}#{timing_sql}#{summary_sql})"
56
- .strip.gsub(/\s+/, " ")
57
- .gsub(/\(\s?\s?\s?,/, "(")
58
- .gsub(/\s,\s/, " ")
59
- .gsub(/\(\s?\)/, "")
6
+ opts_sql = ActiveRecordAnalyze.build_prefix(opts)
60
7
 
61
8
  sql = "EXPLAIN #{opts_sql} #{to_sql(arel, binds)}"
62
9
  PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN #{opts_sql}".strip, binds))
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordAnalyze
2
- VERSION = "0.9.1"
2
+ VERSION = "0.10.2"
3
3
  end
@@ -1,4 +1,84 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'activerecord-analyze/main'
2
4
 
3
5
  module ActiveRecordAnalyze
6
+ def self.analyze_sql(raw_sql, opts = {})
7
+ prefix = "EXPLAIN #{build_prefix(opts)}"
8
+
9
+ result = ActiveRecord::Base.connection.execute("#{prefix} #{raw_sql}").to_a
10
+
11
+
12
+ if [:json, :hash, :pretty_json].include?(opts[:format])
13
+ raw_json = result[0].fetch("QUERY PLAN")
14
+ if opts[:format] == :json
15
+ raw_json
16
+ elsif opts[:format] == :hash
17
+ JSON.parse(raw_json)
18
+ elsif opts[:format] == :pretty_json
19
+ JSON.pretty_generate(JSON.parse(raw_json))
20
+ end
21
+ else
22
+ result.map do |el|
23
+ el.fetch("QUERY PLAN")
24
+ end.join("\n")
25
+ end
26
+ end
27
+
28
+ def self.build_prefix(opts = {})
29
+ format_sql = if fmt = opts[:format].presence
30
+ case fmt
31
+ when :json
32
+ "FORMAT JSON, "
33
+ when :hash
34
+ "FORMAT JSON, "
35
+ when :pretty_json
36
+ "FORMAT JSON, "
37
+ when :yaml
38
+ "FORMAT YAML, "
39
+ when :text
40
+ "FORMAT TEXT, "
41
+ when :xml
42
+ "FORMAT XML, "
43
+ else
44
+ ""
45
+ end
46
+ end
47
+
48
+ verbose_sql = if opts[:verbose] == true
49
+ ", VERBOSE"
50
+ end
51
+
52
+ costs_sql = if opts[:costs] == true
53
+ ", COSTS"
54
+ end
55
+
56
+ settings_sql = if opts[:settings] == true
57
+ ", SETTINGS"
58
+ end
59
+
60
+ buffers_sql = if opts[:buffers] == true
61
+ ", BUFFERS"
62
+ end
63
+
64
+ timing_sql = if opts[:timing] == true
65
+ ", TIMING"
66
+ end
67
+
68
+ summary_sql = if opts[:summary] == true
69
+ ", SUMMARY"
70
+ end
71
+
72
+ analyze_sql = if opts[:analyze] == false
73
+ ""
74
+ else
75
+ "ANALYZE"
76
+ end
77
+
78
+ opts_sql = "(#{format_sql}#{analyze_sql}#{verbose_sql}#{costs_sql}#{settings_sql}#{buffers_sql}#{timing_sql}#{summary_sql})"
79
+ .strip.gsub(/\s+/, " ")
80
+ .gsub(/\(\s?\s?\s?,/, "(")
81
+ .gsub(/\s,\s/, " ")
82
+ .gsub(/\(\s?\)/, "")
83
+ end
4
84
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "ActiveRecord analyze" do
6
+ let(:raw_sql) do
7
+ "SELECT * FROM users WHERE email = 'email@example.com'"
8
+ end
9
+
10
+ let(:result) do
11
+ ActiveRecordAnalyze.analyze_sql(raw_sql, opts)
12
+ end
13
+
14
+ describe "default opts" do
15
+ let(:opts) do
16
+ {}
17
+ end
18
+
19
+ it "works" do
20
+ expect do
21
+ result
22
+ end.not_to raise_error
23
+ end
24
+ end
25
+
26
+ describe "format json" do
27
+ let(:opts) do
28
+ { format: :json }
29
+ end
30
+
31
+ it "works" do
32
+ puts result
33
+ expect(JSON.parse(result)[0].keys.sort).to eq [
34
+ "Execution Time", "Plan", "Planning Time", "Triggers"
35
+ ]
36
+ end
37
+ end
38
+
39
+ describe "format hash" do
40
+ let(:opts) do
41
+ { format: :hash }
42
+ end
43
+
44
+ it "works" do
45
+ expect(result[0].keys.sort).to eq [
46
+ "Execution Time", "Plan", "Planning Time", "Triggers"
47
+ ]
48
+ end
49
+ end
50
+
51
+ describe "format pretty" do
52
+ let(:opts) do
53
+ { format: :pretty_json }
54
+ end
55
+
56
+ it "works" do
57
+ expect(JSON.parse(result)[0].keys.sort).to eq [
58
+ "Execution Time", "Plan", "Planning Time", "Triggers"
59
+ ]
60
+ end
61
+ end
62
+
63
+ describe "supports options" do
64
+ let(:raw_sql) do
65
+ "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"email\" IS NOT NULL LIMIT 10"
66
+ end
67
+
68
+ let(:opts) do
69
+ {
70
+ format: :hash,
71
+ costs: true,
72
+ timing: true,
73
+ summary: true
74
+ }
75
+ end
76
+
77
+ it "works" do
78
+ expect(result[0].keys.sort).to eq [
79
+ "Execution Time", "Plan", "Planning Time", "Triggers"
80
+ ]
81
+ end
82
+ end
83
+ end
data/spec/main_spec.rb CHANGED
@@ -15,7 +15,7 @@ describe "ActiveRecord analyze" do
15
15
  ActiveRecord::Migrator.new(:up, [CreateUsers.new], @schema_migration).migrate
16
16
  end
17
17
 
18
- describe "no opts" do
18
+ describe "default opts" do
19
19
  it "works" do
20
20
  expect do
21
21
  User.all.analyze
data/spec/spec_helper.rb CHANGED
@@ -3,6 +3,18 @@
3
3
  require 'rubygems'
4
4
  require "active_record/railtie"
5
5
  require 'bundler/setup'
6
+ require "migrations/create_users_migration.rb"
6
7
  require_relative '../lib/activerecord-analyze'
7
8
 
8
9
  ENV["DATABASE_URL"] ||= "postgresql://postgres:secret@localhost:5432/activerecord-analyze-test"
10
+
11
+ RSpec.configure do |config|
12
+ config.before(:suite) do
13
+ ActiveRecord::Base.establish_connection(
14
+ ENV.fetch("DATABASE_URL")
15
+ )
16
+
17
+ @schema_migration = ActiveRecord::Base.connection.schema_migration
18
+ ActiveRecord::Migrator.new(:up, [CreateUsers.new], @schema_migration).migrate
19
+ end
20
+ end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-analyze
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - pawurb
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-11 00:00:00.000000000 Z
11
+ date: 2022-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rails
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.1.0
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 5.1.0
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: railties
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,6 +100,7 @@ files:
86
100
  - lib/activerecord-analyze/main.rb
87
101
  - lib/activerecord-analyze/version.rb
88
102
  - query-plan.png
103
+ - spec/analyze_sql_spec.rb
89
104
  - spec/main_spec.rb
90
105
  - spec/migrations/create_users_migration.rb
91
106
  - spec/spec_helper.rb
@@ -93,7 +108,7 @@ homepage: http://github.com/pawurb/activerecord-analyze
93
108
  licenses:
94
109
  - MIT
95
110
  metadata: {}
96
- post_install_message:
111
+ post_install_message:
97
112
  rdoc_options: []
98
113
  require_paths:
99
114
  - lib
@@ -108,11 +123,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
123
  - !ruby/object:Gem::Version
109
124
  version: '0'
110
125
  requirements: []
111
- rubygems_version: 3.1.4
112
- signing_key:
126
+ rubygems_version: 3.1.6
127
+ signing_key:
113
128
  specification_version: 4
114
129
  summary: Add EXPLAIN ANALYZE to Active Record query objects
115
130
  test_files:
131
+ - spec/analyze_sql_spec.rb
116
132
  - spec/main_spec.rb
117
133
  - spec/migrations/create_users_migration.rb
118
134
  - spec/spec_helper.rb