activerecord-analyze 0.1.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2c1961ed7400cf716abcc28764176bdf02b4c3eb
4
- data.tar.gz: c982bed6eb37c9413bbec9785416f96b9c55490c
2
+ SHA256:
3
+ metadata.gz: 1f3e8c5511121ff7c9a7e2aab56d1638e1f42286477bfb97a72ae6d284f719b1
4
+ data.tar.gz: 0a6eba3afdf108a616ad837d9520148a2a387ab0ab3441afbbe2e9851e974e1e
5
5
  SHA512:
6
- metadata.gz: 5f57e1be57c643f06a0ba3036a0e2709ce9ac799c3a02ca7a0c1cce99d92340b9b0a7d574f8c8652d52ee522a3c5edbccec8ea5f8d1d8fbd21468c1c20372ec7
7
- data.tar.gz: 432321449fdff40d5f410cb635b4f20448437c471526c87de4eaa25782cae474a816abf4f64a871804a8009d8d80e68989cac75fa09d342b7fa590ac89d59163
6
+ metadata.gz: 94223f7a2f15534a7b1d648cb5a019f4aeab0efef044a104ff70e4ee876cdc332de67dfdc86fb325aac5b847a515ae6b02846fed2a19c1b342df4e43b14c7e3d
7
+ data.tar.gz: 14aa9186a5e14d00dc6b6475d9d7052e00942d82bb5deafcb14f7c170a80282216c05597e102e9ce55c934807ceee145462d6f416ece2030ae21cfbbd495f3d8
data/README.md CHANGED
@@ -1,2 +1,112 @@
1
1
  # ActiveRecord Analyze
2
2
 
3
+ This gem adds an `analyze` method to Active Record query objects. It executes `EXPLAIN ANALYZE` on a query SQL.
4
+
5
+ You can check out this blog post for more info on how to [debug and fix slow queries in Rails apps](https://pawelurbanek.com/slow-rails-queries).
6
+
7
+ ## Installation
8
+
9
+ In your Gemfile:
10
+
11
+ ```ruby
12
+
13
+ gem 'activerecord-analyze'
14
+
15
+ ```
16
+
17
+ ## Options
18
+
19
+ The `analyze` method supports the following EXPLAIN query options ([PostgreSQL docs reference](https://www.postgresql.org/docs/12/sql-explain.html)):
20
+
21
+ ```
22
+ VERBOSE [ boolean ]
23
+ COSTS [ boolean ]
24
+ SETTINGS [ boolean ]
25
+ BUFFERS [ boolean ]
26
+ TIMING [ boolean ]
27
+ SUMMARY [ boolean ]
28
+ FORMAT { TEXT | XML | JSON | YAML }
29
+ ```
30
+
31
+ You can execute it like that:
32
+
33
+ ```ruby
34
+
35
+ User.all.analyze(
36
+ format: :json,
37
+ verbose: true,
38
+ costs: true,
39
+ settings: true,
40
+ buffers: true,
41
+ timing: true,
42
+ summary: true
43
+ )
44
+
45
+ # EXPLAIN (FORMAT JSON, ANALYZE, VERBOSE, COSTS, SETTINGS, BUFFERS, TIMING, SUMMARY)
46
+ # SELECT "users".* FROM "users"
47
+ # [
48
+ # {
49
+ # "Plan": {
50
+ # "Node Type": "Seq Scan",
51
+ # "Parallel Aware": false,
52
+ # "Relation Name": "users",
53
+ # "Schema": "public",
54
+ # "Alias": "users",
55
+ # "Startup Cost": 0.00,
56
+ # "Total Cost": 11.56,
57
+ # "Plan Rows": 520,
58
+ # "Plan Width": 127,
59
+ # "Actual Startup Time": 0.006,
60
+ # "Actual Total Time": 0.007,
61
+ # "Actual Rows": 2,
62
+ # "Actual Loops": 1,
63
+ # "Output": ["id", "team_id", "email"],
64
+ # "Shared Hit Blocks": 1,
65
+ # "Shared Read Blocks": 0,
66
+ # "Shared Dirtied Blocks": 0,
67
+ # "Shared Written Blocks": 0,
68
+ # "Local Hit Blocks": 0,
69
+ # "Local Read Blocks": 0,
70
+ # "Local Dirtied Blocks": 0,
71
+ # "Local Written Blocks": 0,
72
+ # "Temp Read Blocks": 0,
73
+ # "Temp Written Blocks": 0,
74
+ # "I/O Read Time": 0.000,
75
+ # "I/O Write Time": 0.000
76
+ # },
77
+ # "Settings": {
78
+ # "cpu_index_tuple_cost": "0.001",
79
+ # "cpu_operator_cost": "0.0005",
80
+ # "cpu_tuple_cost": "0.003",
81
+ # "effective_cache_size": "10800000kB",
82
+ # "max_parallel_workers_per_gather": "1",
83
+ # "random_page_cost": "2",
84
+ # "work_mem": "100MB"
85
+ # },
86
+ # "Planning Time": 0.033,
87
+ # "Triggers": [
88
+ # ],
89
+ # "Execution Time": 0.018
90
+ # }
91
+ # ]
92
+
93
+ ```
94
+
95
+ The following `format` options are supported `:json, :hash, :yaml, :text, :xml`. Especially the `:json` format is useful because it allows you to visualize a query plan using [a visualizer tool](https://tatiyants.com/pev/#/plans/new).
96
+
97
+ Optionally you can disable running the `ANALYZE` query and only generate the plan:
98
+
99
+ ```ruby
100
+
101
+ User.all.analyze(analyze: false)
102
+
103
+ # EXPLAIN ANALYZE for: SELECT "users".* FROM "users"
104
+ # QUERY PLAN
105
+ # ----------------------------------------------------------
106
+ # Seq Scan on users (cost=0.00..15.20 rows=520 width=127)
107
+
108
+ ```
109
+
110
+ ### Disclaimer
111
+
112
+ It is a bit experimental and can break with new Rails release.
@@ -15,5 +15,5 @@ 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 "rails", ">= 5.1.0"
19
19
  end
@@ -1,2 +1,4 @@
1
+ require 'activerecord-analyze/main'
2
+
1
3
  module ActiveRecordAnalyze
2
4
  end
@@ -2,9 +2,60 @@ module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  module PostgreSQL
4
4
  module DatabaseStatements
5
- def analyze(arel, binds = [])
6
- sql = "EXPLAIN ANALYZE #{to_sql(arel, binds)}"
7
- PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN ANALYZE", binds))
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 :yaml
13
+ "FORMAT YAML,"
14
+ when :text
15
+ "FORMAT TEXT,"
16
+ when :xml
17
+ "FORMAT XML,"
18
+ end
19
+ end
20
+
21
+ verbose_sql = if opts[:verbose] == true
22
+ ", VERBOSE"
23
+ end
24
+
25
+ costs_sql = if opts[:costs] == true
26
+ ", COSTS"
27
+ end
28
+
29
+ settings_sql = if opts[:settings] == true
30
+ ", SETTINGS"
31
+ end
32
+
33
+ buffers_sql = if opts[:buffers] == true
34
+ ", BUFFERS"
35
+ end
36
+
37
+ timing_sql = if opts[:timing] == true
38
+ ", TIMING"
39
+ end
40
+
41
+ summary_sql = if opts[:summary] == true
42
+ ", SUMMARY"
43
+ end
44
+
45
+ analyze_sql = if opts[:analyze] == false
46
+ ""
47
+ else
48
+ "ANALYZE"
49
+ end
50
+
51
+ opts_sql = "(#{format_sql} #{analyze_sql}#{verbose_sql}#{costs_sql}#{settings_sql}#{buffers_sql}#{timing_sql}#{summary_sql})"
52
+ .strip.gsub(/\s+/, " ")
53
+ .gsub(/\(\s?\s?\s?,/, "(")
54
+ .gsub(/\s,\s/, " ")
55
+ .gsub(/\(\s?\)/, "")
56
+
57
+ sql = "EXPLAIN #{opts_sql} #{to_sql(arel, binds)}"
58
+ PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN #{opts_sql}".strip, binds))
8
59
  end
9
60
  end
10
61
  end
@@ -13,23 +64,40 @@ end
13
64
 
14
65
  module ActiveRecord
15
66
  class Relation
16
- def analyze
17
- exec_analyze(collecting_queries_for_explain { exec_queries })
67
+ def analyze(opts = {})
68
+ res = exec_analyze(collecting_queries_for_explain { exec_queries }, opts)
69
+ if [:json, :hash].include?(opts[:format])
70
+ raw_json = "[" + res[/\[(.*?)(\(\d row)/m, 1]
71
+
72
+ if opts[:format] == :json
73
+ JSON.parse(raw_json).to_json
74
+ elsif opts[:format] == :hash
75
+ JSON.parse(raw_json)
76
+ end
77
+ else
78
+ res
79
+ end
18
80
  end
19
81
  end
20
82
  end
21
83
 
22
84
  module ActiveRecord
23
85
  module Explain
24
- def exec_analyze(queries) # :nodoc:
86
+ def exec_analyze(queries, opts = {}) # :nodoc:
25
87
  str = queries.map do |sql, binds|
26
- msg = "EXPLAIN ANALYZE for: #{sql}".dup
88
+ analyze_msg = if opts[:analyze] == false
89
+ ""
90
+ else
91
+ " ANALYZE"
92
+ end
93
+
94
+ msg = "EXPLAIN#{analyze_msg} for: #{sql}".dup
27
95
  unless binds.empty?
28
96
  msg << " "
29
97
  msg << binds.map { |attr| render_bind(attr) }.inspect
30
98
  end
31
99
  msg << "\n"
32
- msg << connection.analyze(sql, binds)
100
+ msg << connection.analyze(sql, binds, opts)
33
101
  end.join("\n")
34
102
 
35
103
  # Overriding inspect to be more human readable, especially in the console.
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordAnalyze
2
- VERSION = "0.1.0"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-analyze
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - pawurb
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-11 00:00:00.000000000 Z
11
+ date: 2021-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 5.1.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
26
  version: 5.1.0
27
27
  description: ' Gem adds an "analyze" method to all Active Record query objects. Compatible
@@ -45,7 +45,7 @@ homepage: http://github.com/pawurb/activerecord-analyze
45
45
  licenses:
46
46
  - MIT
47
47
  metadata: {}
48
- post_install_message:
48
+ post_install_message:
49
49
  rdoc_options: []
50
50
  require_paths:
51
51
  - lib
@@ -60,9 +60,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  requirements: []
63
- rubyforge_project:
64
- rubygems_version: 2.6.14
65
- signing_key:
63
+ rubygems_version: 3.1.4
64
+ signing_key:
66
65
  specification_version: 4
67
66
  summary: Add EXPLAIN ANALYZE to Active Record query objects
68
67
  test_files: []