full_table_scan_matchers 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 461eb2bcbdf13c351343b98640ee0e110fcd2ff6
4
+ data.tar.gz: 0fc519dd6462d5befb9cbe595882d50c4850cebc
5
+ SHA512:
6
+ metadata.gz: e7379fddc8b2b57e61fa4200ae2a005d7be85f8836dfba1f4c75f8bb3612da8a8d635e7ce7710d6b444a649806356ffd82a66e1dff7697beba4a1a2dc77daecd
7
+ data.tar.gz: 14f459f90f4ed869c77d3620f30aa22e0d80cc9a088feebaeb6e1364eb5881d5434764da724953cf8665a965ae3fa8a35cbbd5abb6263e3c56d1765528c419f9
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ config/database.yml
11
+
12
+ coverage/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,27 @@
1
+ services:
2
+ - mysql
3
+ sudo: false
4
+ cache: bundler
5
+ language: ruby
6
+ rvm:
7
+ - 2.0.0
8
+ - 2.1.0
9
+ - 2.2.0
10
+ - 2.3.0
11
+ - 2.3.1
12
+ env:
13
+ - RAILS_VERSION=4
14
+ - RAILS_VERSION=5
15
+ matrix:
16
+ exclude:
17
+ - rvm: 2.0.0
18
+ env: RAILS_VERSION=5
19
+ - rvm: 2.1.0
20
+ env: RAILS_VERSION=5
21
+ - rvm: 2.2.0
22
+ env: RAILS_VERSION=5
23
+ before_install: gem install bundler -v 1.12.5
24
+ before_script:
25
+ - cp config/database.travis.yml config/database.yml
26
+ - bin/setup
27
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in full_table_scan_matchers.gemspec
4
+ gemspec
5
+
6
+ if ENV['RAILS_VERSION']
7
+ gem 'activesupport', "~> #{ENV['RAILS_VERSION']}"
8
+ gem 'activerecord', "~> #{ENV['RAILS_VERSION']}"
9
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Justin Aiken
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,88 @@
1
+ [![Gem Version](http://img.shields.io/gem/v/full_table_scan_matchers.svg)](https://rubygems.org/gems/full_table_scan_matchers)[![Build Status](http://img.shields.io/travis/JustinAiken/full_table_scan_matchers/master.svg)](http://travis-ci.org/JustinAiken/full_table_scan_matchers)[![Coveralls branch](http://img.shields.io/coveralls/JustinAiken/full_table_scan_matchers/master.svg)](https://coveralls.io/r/JustinAiken/full_table_scan_matchers?branch=master)
2
+
3
+ # full_table_scan_matchers
4
+
5
+ Detect full table scans in your unit tests! As opposed to just doing basic regexes on the `db/schema.rb` like [rails-best-practices](https://github.com/railsbp/rails_best_practices) does, this actually checks queries made against the database, to ensure and assert that there are indexes for it to use.
6
+
7
+ ## Requirements/Support
8
+
9
+ - Ruby 2.0+
10
+ - ActiveRecord 4+
11
+ - ActiveSupport 4+
12
+ - mysql2 adapter
13
+
14
+ ## Installation
15
+
16
+ Add to your `Gemfile`, in the test group:
17
+
18
+ ```ruby
19
+ group :test do
20
+ gem 'full_table_scan_matchers', github: 'JustinAiken/full_table_scan_matchers'
21
+ end
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```ruby
27
+ # No full table scans at all please:
28
+ expect { user.posts }.not_to full_table_scan
29
+
30
+ # Or on just a specific thing:
31
+ expect {
32
+ user.posts.joins(:comments).first.comments
33
+ }.not_to full_table_scan.on :posts
34
+ ```
35
+
36
+ ## How's it work?
37
+
38
+ - It logs all SQL queries made inside the `expect` block
39
+ - ..optionally filtering them to table(s) you care about
40
+ - Afterwards, it runs all those through `EXPLAIN`
41
+ - Instead of checking `type` for `ALL`, it checks the keys instead
42
+ - Because in test mode, mysql may ignore the index and scan the whole table because it's so small
43
+ - But your production database is much, much larger - we just want to make sure that indexes are available
44
+
45
+ ## Configuration
46
+
47
+ You can optionally configure a few things:
48
+
49
+ ```ruby
50
+ FullTableScanMatchers.configure do |config|
51
+ # Tables to ignore - defaults to none
52
+ # Default: none
53
+ config.ignores = []
54
+
55
+ # Database adapter to use - only one for now
56
+ # Default (and only): mysql
57
+ config.adapter = FullTableScanMatchers::DBAdapters::MySql
58
+
59
+ # Includes a backtrace in the fail output if this is set to true
60
+ # Default: false
61
+ config.log_backtrace = false
62
+
63
+ # Add a proc to strip things from the logged backtraces
64
+ # Default: None
65
+ config.backtrace_filter = Proc.new { |backtrace| backtrace }
66
+ end
67
+ ```
68
+
69
+ ## Postgres?
70
+
71
+ Currently, this just support mySql. However, I tried to keep the part that's most-tied to that database (parsing the `EXPLAIN` output) silo'd in an adapter - I'd happily merge any clean PRs that add Postgres (or others)!
72
+
73
+ ## Development
74
+
75
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
76
+
77
+ ## Credits
78
+
79
+ - Primary author: [@JustinAiken](https://github.com/JustinAiken)
80
+ - A **lot** of inspiration comes from the excellent [db-query-matchers](https://github.com/brigade/db-query-matchers) by [@brigade](https://github.com/brigade)
81
+
82
+ ## Contributing
83
+
84
+ Bug reports and pull requests are welcome on GitHub at https://github.com/JustinAiken/full_table_scan_matchers.
85
+
86
+ ## License
87
+
88
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,31 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
7
+
8
+ require 'active_record'
9
+ require 'yaml'
10
+
11
+ namespace :db do
12
+ db_config = YAML::load(File.open('config/database.yml'))['test']
13
+ db_config_admin = db_config.merge('database' => 'mysql')
14
+
15
+ desc "Create the database"
16
+ task :create do
17
+ ActiveRecord::Base.establish_connection(db_config_admin)
18
+ ActiveRecord::Base.connection.create_database(db_config["database"])
19
+ puts "Database created."
20
+ end
21
+
22
+ desc "Drop the database"
23
+ task :drop do
24
+ ActiveRecord::Base.establish_connection(db_config_admin)
25
+ ActiveRecord::Base.connection.drop_database(db_config["database"])
26
+ puts "Database deleted."
27
+ end
28
+
29
+ desc "Reset the database"
30
+ task reset: [:drop, :create]
31
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+ bundle exec rake db:drop
8
+ bundle exec rake db:create
@@ -0,0 +1,5 @@
1
+ test:
2
+ adapter: mysql2
3
+ database: full_table_scan_matchers_test
4
+ username: travis
5
+ encoding: utf8
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'full_table_scan_matchers/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "full_table_scan_matchers"
8
+ spec.version = FullTableScanMatchers::VERSION
9
+ spec.authors = ["Justin Aiken"]
10
+ spec.email = ["60tonangel@gmail.com"]
11
+
12
+ spec.summary = %q{Matchers for rspec to detect full table scans}
13
+ spec.description = %q{Matchers for rspec to detect full table scans}
14
+ spec.homepage = "https://github.com/JustinAiken/full_table_scan_matchers"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "activesupport", ">= 3.0"
21
+ spec.add_dependency "activerecord", ">= 3.0"
22
+
23
+ spec.add_development_dependency 'mysql2'
24
+ spec.add_development_dependency "bundler", "~> 1.12"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency 'coveralls'
28
+ end
@@ -0,0 +1,39 @@
1
+ require 'full_table_scan_matchers/version'
2
+ require 'full_table_scan_matchers/db_adapters'
3
+ require 'full_table_scan_matchers/db_adapters/mysql'
4
+ require 'full_table_scan_matchers/db_adapters/mysql/explain_result'
5
+ require 'full_table_scan_matchers/sql_watcher'
6
+ require 'full_table_scan_matchers/matchers/full_table_scan'
7
+ require 'full_table_scan_matchers/configuration'
8
+
9
+ require 'active_record'
10
+ require 'active_support'
11
+
12
+ # Main module that holds global configuration.
13
+ module FullTableScanMatchers
14
+ class << self
15
+ attr_writer :configuration
16
+ end
17
+
18
+ # Gets the current configuration
19
+ # @return [FullTableScanMatchers::Configuration] the active configuration
20
+ def self.configuration
21
+ @configuration ||= Configuration.new
22
+ end
23
+
24
+ # Resets the current configuration.
25
+ # @return [FullTableScanMatchers::Configuration] the active configuration
26
+ def self.reset_configuration
27
+ @configuration = Configuration.new
28
+ end
29
+
30
+ # Updates the current configuration.
31
+ # @example
32
+ # FullTableScanMatchers.configure do |config|
33
+ # config.ignores = [/SELECT.*FROM.*users/]
34
+ # end
35
+ #
36
+ def self.configure
37
+ yield configuration
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ module FullTableScanMatchers
2
+
3
+ # Configuration for the FullTableScanMatchers module.
4
+ class Configuration
5
+ attr_accessor :ignores, :adapter, :log_backtrace, :backtrace_filter
6
+
7
+ def initialize
8
+ @ignores = []
9
+ @adapter = DBAdapters::MySql
10
+ @log_backtrace = false
11
+ @backtrace_filter = Proc.new { |backtrace| backtrace }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ module FullTableScanMatchers
2
+ module DBAdapters
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module FullTableScanMatchers
2
+ module DBAdapters
3
+ module MySql
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,61 @@
1
+ require 'ostruct'
2
+
3
+ module FullTableScanMatchers
4
+ module DBAdapters
5
+ module MySql
6
+ class ExplainResult
7
+ attr_accessor :sql_statement, :structs, :backtrace, :tables
8
+
9
+ def initialize(sql_statement, backtrace: nil, tables: nil)
10
+ @sql_statement = sql_statement
11
+ @backtrace = backtrace
12
+ @tables = tables
13
+ end
14
+
15
+ def full_table_scan?
16
+ offending_structs.any?
17
+ end
18
+
19
+ def structs
20
+ @structs ||= as_hashes.map { |hash| OpenStruct.new hash }
21
+ end
22
+
23
+ def to_s
24
+ [
25
+ "SQL: #{sql_statement}",
26
+ structs.map do |struct|
27
+ (offending_structs.include?(struct) ? "FAIL: " : "INFO: ") +
28
+ struct.to_h.map { |k, v| "#{k}: #{v}" }.join(", ")
29
+ end,
30
+ (backtrace.present? ? "BACKTRACE: #{backtrace}" : nil)
31
+ ].flatten.compact.join "\n"
32
+ end
33
+
34
+ private
35
+
36
+ def offending_structs
37
+ structs
38
+ .reject { |struct| struct.table == "NULL" }
39
+ .select { |struct| tables.nil? || Array(tables).map(&:to_s).include?(struct.table) }
40
+ .select { |struct| struct.key == "NULL" && struct.possible_keys == "NULL" }
41
+ end
42
+
43
+ def as_hashes
44
+ keys = explain_rows.shift.map(&:downcase)
45
+ explain_rows.map { |values| Hash[keys.zip(values)].symbolize_keys! }
46
+ end
47
+
48
+ def explain_rows
49
+ @explain_rows ||= explained_result
50
+ .reject { |row| row =~ /^\s*\+/ } # Reject table frames, like: +----+-------------+
51
+ .reject { |row| row =~ / rows? in set/ } # Reject "1 row in set" type things after result
52
+ .map { |row| row.split("|").map(&:strip).reject &:blank? }
53
+ end
54
+
55
+ def explained_result
56
+ @explained_result ||= ActiveRecord::Base.connection.explain(sql_statement).split("\n")
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,79 @@
1
+ require 'rspec/core'
2
+ require 'rspec/expectations'
3
+ require 'rspec/mocks'
4
+
5
+ RSpec::Matchers.define :full_table_scan do |options = {}|
6
+ if RSpec::Core::Version::STRING =~ /^2/
7
+ def self.failure_message_when_negated(&block)
8
+ failure_message_for_should_not(&block)
9
+ end
10
+
11
+ def self.failure_message(&block)
12
+ failure_message_for_should(&block)
13
+ end
14
+
15
+ def supports_block_expectations?
16
+ true
17
+ end
18
+ else
19
+ supports_block_expectations
20
+ end
21
+
22
+ # Taken from ActionView::Helpers::TextHelper
23
+ def pluralize(count, singular, plural = nil)
24
+ word = if count == 1 || count =~ /^1(\.0+)?$/
25
+ singular
26
+ else
27
+ plural || singular.pluralize
28
+ end
29
+
30
+ "#{count || 0} #{word}"
31
+ end
32
+
33
+ chain :on do |tables|
34
+ @tables = tables
35
+ end
36
+
37
+ define_method :matches? do |block|
38
+ watcher_option = {}
39
+ watcher_option[:tables] = Array(@tables) if defined?(@tables)
40
+ @watcher = FullTableScanMatchers::SQLWatcher.new(watcher_option)
41
+ ActiveSupport::Notifications.subscribed @watcher.to_proc, 'sql.active_record', &block
42
+
43
+ replay_logged_with_explain!
44
+
45
+ @watcher.count > 0
46
+ end
47
+
48
+ def replay_logged_with_explain!
49
+ @watcher.log
50
+ .map! { |logged| FullTableScanMatchers.configuration.adapter::ExplainResult.new(logged[:sql], backtrace: logged[:backtrace], tables: @tables) }
51
+ .reject! { |logged| !logged.full_table_scan? }
52
+ end
53
+
54
+ def output_offenders
55
+ @watcher.log.map(&:to_s).join("\n")
56
+ end
57
+
58
+ failure_message_when_negated do |_|
59
+ <<-EOS
60
+ expected no full table scans, but #{@watcher.log.count} were made:
61
+ #{output_offenders}
62
+ EOS
63
+ end
64
+
65
+ failure_message do |_|
66
+ if options[:count]
67
+ expected = pluralize(options[:count], 'full table scan')
68
+ actual = pluralize(@watcher.count, 'was', 'were')
69
+
70
+ output = "expected #{expected}, but #{actual} made"
71
+ if @watcher.count > 0
72
+ output += ":\n#{output_offenders}"
73
+ end
74
+ output
75
+ else
76
+ 'expected full table scans, but none were made'
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,63 @@
1
+ module FullTableScanMatchers
2
+ class SQLWatcher
3
+ attr_reader :log, :options
4
+
5
+ def initialize(options = {})
6
+ @log = []
7
+ @options = options
8
+ end
9
+
10
+ # Turns a SQLWatcher instance into a lambda. Designed to be used when
11
+ # subscribing to events through the ActiveSupport::Notifications module.
12
+ #
13
+ # @return [Proc]
14
+ def to_proc
15
+ lambda &method(:callback)
16
+ end
17
+
18
+ # Method called from the ActiveSupport::Notifications module (through the
19
+ # lambda created by `to_proc`) when an SQL query is made.
20
+ #
21
+ # @param _name [String] name of the event
22
+ # @param _start [Time] when the instrumented block started execution
23
+ # @param _finish [Time] when the instrumented block ended execution
24
+ # @param _message_id [String] unique ID for this notification
25
+ # @param payload [Hash] the payload
26
+ #
27
+ def callback(_name, _start, _finish, _message_id, payload)
28
+ sql_statement = payload[:sql]
29
+ return if sql_statement =~ /EXPLAIN /i # That's from us, don't EXPLAIN the EXPLAINS!
30
+ return unless sql_statement =~ /SELECT / # Only selects for now
31
+ return if any_match? ignores, sql_statement
32
+ return unless any_match? tables, sql_statement if options[:tables]
33
+
34
+ backtrace = if FullTableScanMatchers.configuration.log_backtrace
35
+ raw_backtrace = caller
36
+ filtered_backtrace = FullTableScanMatchers.configuration.backtrace_filter.call(raw_backtrace)
37
+ "#{filtered_backtrace.join("\n")}\n"
38
+ else
39
+ nil
40
+ end
41
+
42
+ @log << {sql: sql_statement.strip, backtrace: backtrace}
43
+ end
44
+
45
+ def count
46
+ log.count
47
+ end
48
+
49
+ private
50
+
51
+ def tables
52
+ Array(options[:tables]).map { |table_name| /(FROM|JOIN)\s+`?#{table_name.to_s}`?/i }
53
+ end
54
+
55
+ def ignores
56
+ FullTableScanMatchers.configuration.ignores
57
+ end
58
+
59
+ def any_match?(patterns, sql)
60
+ patterns.any? { |pattern| sql =~ pattern }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module FullTableScanMatchers
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: full_table_scan_matchers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Aiken
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mysql2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Matchers for rspec to detect full table scans
112
+ email:
113
+ - 60tonangel@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/setup
126
+ - config/database.travis.yml
127
+ - full_table_scan_matchers.gemspec
128
+ - lib/full_table_scan_matchers.rb
129
+ - lib/full_table_scan_matchers/configuration.rb
130
+ - lib/full_table_scan_matchers/db_adapters.rb
131
+ - lib/full_table_scan_matchers/db_adapters/mysql.rb
132
+ - lib/full_table_scan_matchers/db_adapters/mysql/explain_result.rb
133
+ - lib/full_table_scan_matchers/matchers/full_table_scan.rb
134
+ - lib/full_table_scan_matchers/sql_watcher.rb
135
+ - lib/full_table_scan_matchers/version.rb
136
+ homepage: https://github.com/JustinAiken/full_table_scan_matchers
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.6.6
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Matchers for rspec to detect full table scans
160
+ test_files: []