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 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,2 @@
1
+ require_relative 'rspec/matchers/use_index'
2
+ require_relative 'rspec/matchers/have_used_index'
@@ -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: []