sequel_query_limit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e683803757e1d70033db910195911a1595bba93
4
+ data.tar.gz: 74d998162745cf681bc2c942b2ec9ef32f46c626
5
+ SHA512:
6
+ metadata.gz: e8768b642efdfda37b6bfbf0320e10ca04303c3792bd2759dd1407a23b305e0844eb01687411cc55a7703dd4df8f0652a112d47f315ae55f5f0d7478dc5d7a33
7
+ data.tar.gz: 9120fe3ae3f92746fc6d031dc83ee059e0052fc15b017200990751be46e922380402c1804b70365712b93dfab1f3268a65e1fc49284475d874c2170976bce9bd
data/.gitignore ADDED
@@ -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
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sequel_query_limit.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Kondratenko Denis (dikond)
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.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ ## What this is about?
2
+
3
+ You have probably heard of N+1 queries problem. In the rails community, there are a [bullet](https://github.com/flyerhzm/bullet) and [rspec-sqlimit](https://github.com/nepalez/rspec-sqlimit) gems that help to detect this kind of queries. `sequel_query_limit` is a [Sequel](https://github.com/jeremyevans/sequel) extension that pursuits the very much similar goal as `rspec-sqlimit` does. Maybe a little more :)
4
+
5
+
6
+ ## Usage
7
+
8
+ #### With RSpec
9
+
10
+ ```ruby
11
+ module SqlSpecHelper
12
+ def self.included(base)
13
+ base.before(:each) { QueryLimit::Listener::Global.watch }
14
+ base.after(:each) { QueryLimit::Listener::Global.analyze(np1: true, reset: true) }
15
+ base.after(:all) { QueryLimit::Listener::Global.die }
16
+ end
17
+ end
18
+
19
+ RSpec.configure do |config|
20
+ config.include SqlSpecHelper, watch: :np1
21
+ end
22
+
23
+ RSpec.describe 'analyzer', watch: :np1 do
24
+ it 'analyzes queries that has been run' do
25
+ collection = Models::Company.limit(10).all
26
+ App::Companies::Serializer.for_collection.new(collection).to_hash
27
+ expect(true).to eq true
28
+ end
29
+ end
30
+ ```
31
+
32
+ Using this technique you can check individual examples for N+1 quries withouth wrapping everything into `DB.with_query_limit` block.
33
+
34
+
35
+ #### `#with_query_limit`
36
+
37
+ Maybe useful for ad-hoc testing via console. This also can be used in tests.
38
+
39
+ ```ruby
40
+ DB.with_query_limit(/SELECT/, max: 3) do
41
+ collection = Models::Company.limit(10).all
42
+ App::Companies::Serializer.for_collection.new(collection).to_hash
43
+ end
44
+ ```
45
+
46
+ This will return result of your query if it's not exceeding specified `max` limit. Otherwise, it will raise an exception with captured sql queries.
47
+
48
+ ## TODO
49
+
50
+ - Add configuration option for what to do when query exceeds a limit (raise exception or run a callback)
51
+ - Add configuration option for what to do when N+1 problem found
52
+ - Add RSpec matchers
53
+ - Add option to limit by query execution time
54
+ - Add ability to pass custom sql backrace formatter
55
+ - Add ability to analyze on the fly
56
+
57
+
58
+ ## Contributing
59
+
60
+ Bug reports and pull requests are welcome!
61
+
62
+
63
+ ## License
64
+
65
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
66
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,30 @@
1
+ module QueryLimit
2
+ class Analyzer
3
+ MATCHER = %r{\A(SELECT\s.*\sFROM\s.*\sWHERE\s)(.*)\z}
4
+
5
+ def initialize(stack)
6
+ @stack = stack
7
+ end
8
+
9
+ attr_reader :stack
10
+
11
+ def analyze_np1
12
+ grp = stack.group_by { |entry| entry.sql.match(MATCHER)&.captures&.at(1) }
13
+ diff = grp.values.find { |entries| entries.size > 1 }
14
+
15
+ tell_the_story(diff.first.sql, diff.first.stacktrace) if diff
16
+ end
17
+
18
+ private
19
+
20
+ def tell_the_story(query, stacktrace)
21
+ puts
22
+ puts 'Possible N+1 query has been detected'
23
+ puts
24
+ puts "#{query}"
25
+ puts
26
+ puts stacktrace.reject { |s| s.include? 'gem' }.map { |s| " #{s}"}.join("\n")
27
+ puts
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module QueryLimit
2
+ module Errors
3
+ class ExceedingMaxError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'analyzer'
2
+
3
+ module QueryLimit
4
+ class Listener
5
+ class Entry < Struct.new(:sql, :stacktrace); end
6
+
7
+ attr_reader :stack
8
+
9
+ def initialize
10
+ @stack = []
11
+ end
12
+
13
+ def sequel_query(sql, stacktrace)
14
+ @stack << Entry.new(sql, stacktrace)
15
+ end
16
+
17
+ class Global
18
+ VAR_NAME = 'query_limit_spy'.freeze
19
+
20
+ class << self
21
+ def watch
22
+ self.spy = Listener.new if spy.nil?
23
+ Wisper.subscribe(spy, on: :sequel_query)
24
+ end
25
+
26
+ def sleep
27
+ Wisper.unsubscribe(spy)
28
+ end
29
+
30
+ def die
31
+ Wisper.unsubscribe(spy)
32
+ self.spy = nil
33
+ end
34
+
35
+ def analyze(np1: true, reset: false)
36
+ Analyzer.new(spy.stack).analyze_np1 if np1
37
+ self.spy = Listener.new if reset
38
+ end
39
+
40
+ private
41
+
42
+ def spy=(value)
43
+ Thread.current.thread_variable_set(VAR_NAME, value)
44
+ end
45
+
46
+ def spy
47
+ Thread.current.thread_variable_get(VAR_NAME)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ require 'wisper'
2
+ require_relative 'listener'
3
+ require_relative 'errors'
4
+
5
+ module QueryLimit
6
+ module SequelExtension
7
+ include Wisper::Publisher
8
+
9
+ def log_connection_yield(sql, conn, args = nil)
10
+ broadcast(:sequel_query, sql, caller)
11
+ super
12
+ end
13
+
14
+ def with_query_limit(pattern, max:)
15
+ listener = QueryLimit::Listener.new
16
+
17
+ Wisper.subscribe(listener, on: :sequel_query) { yield }
18
+
19
+ raise QueryLimit::Errors::ExceedingMaxError if listener.stack.grep(pattern).size > max
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module QueryLimit
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'query_limit/version'
2
+ require 'sequel/extensions/query_limit'
@@ -0,0 +1,7 @@
1
+ require_relative '../../query_limit/sequel_extension'
2
+
3
+ module Sequel
4
+ module Extensions
5
+ Sequel::Database.register_extension(:query_limit, QueryLimit::SequelExtension)
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ require './lib/query_limit/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'sequel_query_limit'
5
+ spec.version = QueryLimit::VERSION
6
+ spec.authors = 'dikond'
7
+ spec.email = 'di.kondratenko@gmail.com'
8
+
9
+ spec.summary = 'Helps to watch for N+1 queries in Sequel'
10
+ spec.homepage = 'https://github.com/dikond/sequel_query_limit'
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.required_ruby_version = '>= 2.3.0'
18
+
19
+ spec.add_runtime_dependency 'sequel', '~> 4.0', '>= 4.0.0'
20
+ spec.add_runtime_dependency 'wisper', '~> 2.0', '>= 2.0.0'
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.14'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.0'
25
+ end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe SequelQueryLimit do
4
+ it "has a version number" do
5
+ expect(SequelQueryLimit::VERSION).not_to be nil
6
+ end
7
+
8
+ it "does something useful" do
9
+ expect(false).to eq(true)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require "bundler/setup"
2
+ require "sequel_query_limit"
3
+
4
+ RSpec.configure do |config|
5
+ # Enable flags like --only-failures and --next-failure
6
+ config.example_status_persistence_file_path = ".rspec_status"
7
+
8
+ config.expect_with :rspec do |c|
9
+ c.syntax = :expect
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel_query_limit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - dikond
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sequel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 4.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 4.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: wisper
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 2.0.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '2.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.0.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: bundler
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.14'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.14'
67
+ - !ruby/object:Gem::Dependency
68
+ name: rake
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '10.0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '10.0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: rspec
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '3.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '3.0'
95
+ description:
96
+ email: di.kondratenko@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - ".gitignore"
102
+ - ".rspec"
103
+ - Gemfile
104
+ - LICENSE.txt
105
+ - README.md
106
+ - Rakefile
107
+ - lib/query_limit.rb
108
+ - lib/query_limit/analyzer.rb
109
+ - lib/query_limit/errors.rb
110
+ - lib/query_limit/listener.rb
111
+ - lib/query_limit/sequel_extension.rb
112
+ - lib/query_limit/version.rb
113
+ - lib/sequel/extensions/query_limit.rb
114
+ - sequel_query_limit.gemspec
115
+ - spec/sequel_query_limit_spec.rb
116
+ - spec/spec_helper.rb
117
+ homepage: https://github.com/dikond/sequel_query_limit
118
+ licenses:
119
+ - MIT
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 2.3.0
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.5.2
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Helps to watch for N+1 queries in Sequel
141
+ test_files:
142
+ - spec/sequel_query_limit_spec.rb
143
+ - spec/spec_helper.rb