rspec-index-usage 0.0.1
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 +7 -0
- data/lib/rspec/matchers/have_used_index.rb +43 -0
- data/lib/rspec/matchers/use_index.rb +20 -0
- data/lib/rspec-index-usage.rb +2 -0
- data/lib/rspec_index_usage.rb +1 -0
- data/lib/uses_index/index_checkers/base.rb +23 -0
- data/lib/uses_index/index_checkers/postgresql.rb +16 -0
- data/lib/uses_index/index_checkers/sqlite.rb +16 -0
- metadata +105 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 62522b9854e0ad0cd7d84b44dfa1b019f0ebca89a1121837d885e9b5d1782bfe
|
|
4
|
+
data.tar.gz: 6aa5e60ec2a3247fc07a0a131b2e05e8902c21e589c8e442c54d73b051aba03e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d848cf6f41f25b8ba1907ae344e65dcdc93f0ebc37d035f21a9326443567e94a5ddb1b038609838d00372177d7d83e3942bc30fc824da924dc5264bf10082b1b
|
|
7
|
+
data.tar.gz: c087d3c28fa3f1e13ab05ca6137cb8a7dd4c5bd7fef8e1eaca32fa235c09863488463135be21de3de52688535b4af14c3f5e71bf5ddf5899dab94fd6f35b09cb
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'rspec/matchers'
|
|
2
|
+
require 'uses_index/index_checkers/base'
|
|
3
|
+
require 'active_support/notifications'
|
|
4
|
+
|
|
5
|
+
RSpec::Matchers.define :have_used_index do |expected_index|
|
|
6
|
+
supports_block_expectations
|
|
7
|
+
|
|
8
|
+
attr_accessor :connection
|
|
9
|
+
|
|
10
|
+
chain :on_connection do |conn|
|
|
11
|
+
@connection = conn
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
match do |block|
|
|
15
|
+
queries = []
|
|
16
|
+
subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
|
17
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
18
|
+
sql = event.payload[:sql]
|
|
19
|
+
next unless sql
|
|
20
|
+
|
|
21
|
+
queries << sql if sql.start_with?('SELECT') && !sql.match?(/\A(EXPLAIN|BEGIN|COMMIT|ROLLBACK)/i)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
block.call
|
|
25
|
+
|
|
26
|
+
ActiveSupport::Notifications.unsubscribe(subscriber)
|
|
27
|
+
|
|
28
|
+
connection = @connection || ActiveRecord::Base.connection
|
|
29
|
+
checker_class = IndexChecker.for_adapter(connection)
|
|
30
|
+
checker = checker_class.new
|
|
31
|
+
|
|
32
|
+
queries.any? { |sql| checker.check_sql(sql, expected_index, connection) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
failure_message do |block|
|
|
36
|
+
"expected the block to execute a query using index '#{expected_index}', but none did"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
failure_message_when_negated do |block|
|
|
40
|
+
"expected the block not to execute a query using index '#{expected_index}', but one did"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'rspec/matchers'
|
|
2
|
+
require 'uses_index/index_checkers/base'
|
|
3
|
+
|
|
4
|
+
RSpec::Matchers.define :use_index do |expected_index|
|
|
5
|
+
match do |query|
|
|
6
|
+
# query is an ActiveRecord::Relation
|
|
7
|
+
connection = query.connection
|
|
8
|
+
checker_class = IndexChecker.for_adapter(connection)
|
|
9
|
+
checker = checker_class.new
|
|
10
|
+
checker.check(query, expected_index, connection)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
failure_message do |query|
|
|
14
|
+
"expected query to use index '#{expected_index}', but it didn't"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
failure_message_when_negated do |query|
|
|
18
|
+
"expected query not to use index '#{expected_index}', but it did"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'rspec-index-usage'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class IndexChecker
|
|
2
|
+
def self.for_adapter(connection)
|
|
3
|
+
adapter_name = connection.adapter_name
|
|
4
|
+
case adapter_name
|
|
5
|
+
when 'SQLite'
|
|
6
|
+
require_relative 'sqlite' unless defined?(SqliteIndexChecker)
|
|
7
|
+
SqliteIndexChecker
|
|
8
|
+
when 'PostgreSQL'
|
|
9
|
+
require_relative 'postgresql' unless defined?(PostgresqlIndexChecker)
|
|
10
|
+
PostgresqlIndexChecker
|
|
11
|
+
else
|
|
12
|
+
raise "Unsupported database adapter: #{adapter_name}. Currently supports SQLite and PostgreSQL."
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check(query, expected_index, connection = ActiveRecord::Base.connection)
|
|
17
|
+
raise NotImplementedError, 'Subclasses must implement check'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check_sql(sql, expected_index, connection = ActiveRecord::Base.connection)
|
|
21
|
+
raise NotImplementedError, 'Subclasses must implement check_sql'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
class PostgresqlIndexChecker < IndexChecker
|
|
4
|
+
def check(query, expected_index, connection = ActiveRecord::Base.connection)
|
|
5
|
+
sql = query.to_sql
|
|
6
|
+
check_sql(sql, expected_index, connection)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def check_sql(sql, expected_index, connection = ActiveRecord::Base.connection)
|
|
10
|
+
explain_sql = "EXPLAIN #{sql}"
|
|
11
|
+
result = connection.execute(explain_sql)
|
|
12
|
+
plan = result.map { |row| row[0] }.join("\n")
|
|
13
|
+
plan.include?(expected_index)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
class SqliteIndexChecker < IndexChecker
|
|
4
|
+
def check(query, expected_index, connection = ActiveRecord::Base.connection)
|
|
5
|
+
sql = query.to_sql
|
|
6
|
+
check_sql(sql, expected_index, connection)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def check_sql(sql, expected_index, connection = ActiveRecord::Base.connection)
|
|
10
|
+
explain_sql = "EXPLAIN QUERY PLAN #{sql}"
|
|
11
|
+
result = connection.execute(explain_sql)
|
|
12
|
+
plan = result.map { |row| row.values.join(' ') }.join(' ')
|
|
13
|
+
plan.include?(expected_index)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
metadata
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rspec-index-usage
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pedro Sena
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: pg
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: sqlite3
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.1'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.1'
|
|
68
|
+
description: Provides RSpec matchers that verify whether ActiveRecord queries or code
|
|
69
|
+
blocks use specific database indexes by examining execution plans. Supports SQLite
|
|
70
|
+
and PostgreSQL.
|
|
71
|
+
email: sena.pedro@gmail.com
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- lib/rspec-index-usage.rb
|
|
77
|
+
- lib/rspec/matchers/have_used_index.rb
|
|
78
|
+
- lib/rspec/matchers/use_index.rb
|
|
79
|
+
- lib/rspec_index_usage.rb
|
|
80
|
+
- lib/uses_index/index_checkers/base.rb
|
|
81
|
+
- lib/uses_index/index_checkers/postgresql.rb
|
|
82
|
+
- lib/uses_index/index_checkers/sqlite.rb
|
|
83
|
+
licenses:
|
|
84
|
+
- MIT
|
|
85
|
+
metadata:
|
|
86
|
+
homepage_uri: https://github.com/PedroSena/rspec-index-usage
|
|
87
|
+
source_code_uri: https://github.com/PedroSena/rspec-index-usage
|
|
88
|
+
rdoc_options: []
|
|
89
|
+
require_paths:
|
|
90
|
+
- lib
|
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '2.7'
|
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0'
|
|
101
|
+
requirements: []
|
|
102
|
+
rubygems_version: 3.6.7
|
|
103
|
+
specification_version: 4
|
|
104
|
+
summary: RSpec matcher to check database index usage in queries
|
|
105
|
+
test_files: []
|