activerecord-analyze 0.9.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
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