simple-sql 0.4.41 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55d19f3d2101b740d88d0e4d668ffbeb40be5ab0e853d9804d2f055e7322b6aa
4
- data.tar.gz: b3e5c916d99d64527c0e4e81c50577c4f3d6e9750510e12af93c8682950abea3
3
+ metadata.gz: f7a208873c084434a4267d43cfd76ccae14aa249e7ce2cd40de78e76fe4d0df8
4
+ data.tar.gz: c6c961737c2ebd4b57b5ab7946b872f229e63a8c22c275177825a8bcb5301cef
5
5
  SHA512:
6
- metadata.gz: dfc817694c9c76a10f25879b487c20eeff2bfbef14e206e4dcda17e157e4057542712784328b84b103704d31628a0f4d041cd90f77d8c3683d3af889e32226ee
7
- data.tar.gz: b881bf24697cc7b32c55fec07a98cacae2569355cc33cceca0bb023ca7cc19cd69746bad28eea7ee64fbaff0040b9ddea84b3e1c130bc728c953a63709768c84
6
+ metadata.gz: 2c5c053640e50ba398acf014f855fb28ea369bdd7806ae7a564f69ce87f1d7323dd58ced4ca9a77bd6e7a595266f16754a5de5d0ce6efc0f24fd29b2ea2acb39
7
+ data.tar.gz: af48f5add33e6330a526ec2d6486579f84f2e1b2fb7ee0d03aff0a0d65723da07ea8c5932978c26db6cf005aee2627c02ebb7c2b4ba273ad909c95fd0fa8b4bb
data/lib/simple/sql.rb CHANGED
@@ -21,7 +21,7 @@ module Simple
21
21
  extend self
22
22
 
23
23
  extend Forwardable
24
- delegate [:ask, :all, :each, :exec, :locked, :print, :transaction, :wait_for_notify, :costs] => :default_connection
24
+ delegate [:ask, :all, :each, :exec, :locked, :print, :transaction, :wait_for_notify] => :default_connection
25
25
 
26
26
  delegate [:logger, :logger=] => ::Simple::SQL::Logging
27
27
 
@@ -35,11 +35,6 @@ module Simple
35
35
  Connection.create(database_url)
36
36
  end
37
37
 
38
- # deprecated
39
- def configuration
40
- Config.parse_url(Config.determine_url)
41
- end
42
-
43
38
  # -- default connection ---------------------------------------------------
44
39
 
45
40
  DEFAULT_CONNECTION_KEY = :"Simple::SQL.default_connection"
@@ -90,16 +90,6 @@ module Simple::SQL::ConnectionAdapter
90
90
  end
91
91
  end
92
92
 
93
- # returns an Array [min_cost, max_cost] based on the database's estimation
94
- def costs(sql, *args)
95
- explanation_first = Simple::SQL.ask "EXPLAIN #{sql}", *args
96
- unless explanation_first =~ /cost=(\d+(\.\d+))\.+(\d+(\.\d+))/
97
- raise "Cannot determine cost"
98
- end
99
-
100
- [Float($1), Float($3)]
101
- end
102
-
103
93
  # Executes a block, usually of db insert code, while holding an
104
94
  # advisory lock.
105
95
  #
@@ -50,7 +50,7 @@ module Simple
50
50
  end
51
51
 
52
52
  def slow_query_treshold=(slow_query_treshold)
53
- expect! slow_query_treshold.nil? || slow_query_treshold > 0
53
+ expect! slow_query_treshold > 0
54
54
  @slow_query_treshold = slow_query_treshold
55
55
  end
56
56
 
@@ -1,6 +1,5 @@
1
+ # rubocop:disable Metrics/AbcSize
1
2
  # rubocop:disable Naming/AccessorMethodName
2
- # rubocop:disable Style/DoubleNegation
3
- # rubocop:disable Style/GuardClause
4
3
 
5
4
  require_relative "helpers"
6
5
 
@@ -32,67 +31,39 @@ class ::Simple::SQL::Result < Array
32
31
  replace(records)
33
32
  end
34
33
 
35
- # returns the (potentialy estimated) total count of results
34
+ # returns the total_count of search hits
36
35
  #
37
- # This is only available for paginated scopes
38
- def total_fast_count
39
- @total_fast_count ||= pagination_scope.fast_count
40
- end
41
-
42
- # returns the (potentialy estimated) total number of pages
43
- #
44
- # This is only available for paginated scopes
45
- def total_fast_pages
46
- @total_fast_pages ||= (total_fast_count * 1.0 / pagination_scope.per).ceil
47
- end
48
-
49
- # returns the (potentialy slow) exact total count of results
50
- #
51
- # This is only available for paginated scopes
52
- def total_count
53
- @total_count ||= pagination_scope.count
54
- end
36
+ # This is filled in when resolving a paginated scope.
37
+ attr_reader :total_count
55
38
 
56
- # returns the (potentialy estimated) total number of pages
39
+ # returns the total number of pages of search hits
57
40
  #
58
- # This is only available for paginated scopes
59
- def total_pages
60
- @total_pages ||= (total_count * 1.0 / pagination_scope.per).ceil
61
- end
41
+ # This is filled in when resolving a paginated scope. It takes
42
+ # into account the scope's "per" option.
43
+ attr_reader :total_pages
62
44
 
63
45
  # returns the current page number in a paginated search
64
46
  #
65
- # This is only available for paginated scopes
66
- def current_page
67
- @current_page ||= pagination_scope.page
68
- end
69
-
70
- def paginated?
71
- !!@pagination_scope
72
- end
47
+ # This is filled in when resolving a paginated scope.
48
+ attr_reader :current_page
73
49
 
74
50
  private
75
51
 
76
- def pagination_scope
77
- raise "Only available only for paginated scopes" unless paginated?
78
-
79
- @pagination_scope
80
- end
81
-
82
52
  def set_pagination_info(scope)
83
53
  raise ArgumentError, "per must be > 0" unless scope.per > 0
84
54
 
85
- @pagination_scope = scope
86
-
87
- # This branch is an optimization: the call to the database to count is
88
- # not necessary if we know that there are not even any results on the
89
- # first page.
90
55
  if scope.page <= 1 && empty?
91
- @current_page = 1
56
+ # This branch is an optimization: the call to the database to count is
57
+ # not necessary if we know that there are not even any results on the
58
+ # first page.
92
59
  @total_count = 0
93
- @total_pages = 1
94
- @total_fast_count = 0
95
- @total_fast_pages = 1
60
+ @current_page = 1
61
+ else
62
+ sql = "SELECT COUNT(*) FROM (#{scope.order_by(nil).to_sql(pagination: false)}) simple_sql_count"
63
+ @total_count = ::Simple::SQL.ask(sql, *scope.args)
64
+ @current_page = scope.page
96
65
  end
66
+
67
+ @total_pages = (@total_count * 1.0 / scope.per).ceil
97
68
  end
98
69
  end
@@ -3,8 +3,6 @@
3
3
  require_relative "scope/filters.rb"
4
4
  require_relative "scope/order.rb"
5
5
  require_relative "scope/pagination.rb"
6
- require_relative "scope/count.rb"
7
- require_relative "scope/count_by_groups.rb"
8
6
 
9
7
  # The Simple::SQL::Scope class helps building scopes; i.e. objects
10
8
  # that start as a quite basic SQL query, and allow one to add
@@ -26,11 +24,11 @@ class Simple::SQL::Scope
26
24
  #
27
25
  # Simple::SQL::Scope.new(table: "mytable", select: "*", where: { id: 1, foo: "bar" }, order_by: "id desc")
28
26
  #
29
- def initialize(sql, args = [])
27
+ def initialize(sql)
30
28
  expect! sql => [String, Hash]
31
29
 
32
30
  @sql = nil
33
- @args = args
31
+ @args = []
34
32
  @filters = []
35
33
 
36
34
  case sql
@@ -1,5 +1,5 @@
1
1
  module Simple
2
2
  module SQL
3
- VERSION = "0.4.41"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
data/spec/spec_helper.rb CHANGED
@@ -14,9 +14,7 @@ Dir.glob("./spec/support/**/*.rb").sort.each { |path| load path }
14
14
  require "simple/sql"
15
15
 
16
16
  unless ENV["USE_ACTIVE_RECORD"]
17
- database_url = Simple::SQL::Config.determine_url
18
-
19
- Simple::SQL.connect! database_url
17
+ Simple::SQL.connect!
20
18
  Simple::SQL.ask "DELETE FROM users"
21
19
  end
22
20
 
data/tasks/release.rake CHANGED
@@ -61,7 +61,7 @@ namespace :release do
61
61
 
62
62
  desc "Push code and tags"
63
63
  task :push do
64
- sh("git push origin #{$TARGET_BRANCH}")
64
+ sh('git push origin master')
65
65
  sh('git push --tags')
66
66
  end
67
67
 
@@ -71,37 +71,19 @@ namespace :release do
71
71
  end
72
72
 
73
73
  desc "Push Gem to gemfury"
74
- task :publish do
74
+ task :push_to_rubygems do
75
75
  Dir.chdir(GEM_ROOT) { sh("gem push #{Dir.glob('*.gem').first}") }
76
76
  end
77
77
 
78
- task :target_master do
79
- $TARGET_BRANCH = 'master'
80
- end
81
-
82
- task :target_stable do
83
- $TARGET_BRANCH = 'stable'
84
- end
85
-
86
- task :checkout do
87
- sh "git status --untracked-files=no --porcelain > /dev/null || (echo '*** working dir not clean' && false)"
88
- sh "git checkout #{$TARGET_BRANCH}"
89
- sh "git pull"
90
- end
91
-
92
78
  task default: [
93
- 'checkout',
94
79
  'version',
95
80
  'clean',
96
81
  'build',
97
82
  'commit',
98
83
  'push',
99
- 'publish'
84
+ 'push_to_rubygems'
100
85
  ]
101
-
102
- task master: %w(target_master default)
103
- task stable: %w(target_stable default)
104
86
  end
105
87
 
106
88
  desc "Clean, build, commit and push"
107
- task release: 'release:master'
89
+ task release: 'release:default'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.41
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-03-29 00:00:00.000000000 Z
12
+ date: 2019-03-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg_array_parser
@@ -218,8 +218,6 @@ files:
218
218
  - lib/simple/sql/result/association_loader.rb
219
219
  - lib/simple/sql/result/records.rb
220
220
  - lib/simple/sql/scope.rb
221
- - lib/simple/sql/scope/count.rb
222
- - lib/simple/sql/scope/count_by_groups.rb
223
221
  - lib/simple/sql/scope/filters.rb
224
222
  - lib/simple/sql/scope/order.rb
225
223
  - lib/simple/sql/scope/pagination.rb
@@ -234,15 +232,11 @@ files:
234
232
  - spec/simple/sql/associations_spec.rb
235
233
  - spec/simple/sql/config_spec.rb
236
234
  - spec/simple/sql/conversion_spec.rb
237
- - spec/simple/sql/count_by_groups_spec.rb
238
- - spec/simple/sql/count_spec.rb
239
235
  - spec/simple/sql/duplicate_spec.rb
240
236
  - spec/simple/sql/duplicate_unique_spec.rb
241
237
  - spec/simple/sql/each_spec.rb
242
238
  - spec/simple/sql/insert_spec.rb
243
- - spec/simple/sql/logging_spec.rb
244
239
  - spec/simple/sql/reflection_spec.rb
245
- - spec/simple/sql/result_count_spec.rb
246
240
  - spec/simple/sql/scope_spec.rb
247
241
  - spec/simple/sql/version_spec.rb
248
242
  - spec/simple/sql_locked_spec.rb
@@ -273,7 +267,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
273
267
  - !ruby/object:Gem::Version
274
268
  version: '0'
275
269
  requirements: []
276
- rubygems_version: 3.0.2
270
+ rubyforge_project:
271
+ rubygems_version: 2.7.6
277
272
  signing_key:
278
273
  specification_version: 4
279
274
  summary: SQL with a simple interface
@@ -284,15 +279,11 @@ test_files:
284
279
  - spec/simple/sql/associations_spec.rb
285
280
  - spec/simple/sql/config_spec.rb
286
281
  - spec/simple/sql/conversion_spec.rb
287
- - spec/simple/sql/count_by_groups_spec.rb
288
- - spec/simple/sql/count_spec.rb
289
282
  - spec/simple/sql/duplicate_spec.rb
290
283
  - spec/simple/sql/duplicate_unique_spec.rb
291
284
  - spec/simple/sql/each_spec.rb
292
285
  - spec/simple/sql/insert_spec.rb
293
- - spec/simple/sql/logging_spec.rb
294
286
  - spec/simple/sql/reflection_spec.rb
295
- - spec/simple/sql/result_count_spec.rb
296
287
  - spec/simple/sql/scope_spec.rb
297
288
  - spec/simple/sql/version_spec.rb
298
289
  - spec/simple/sql_locked_spec.rb
@@ -1,33 +0,0 @@
1
- class Simple::SQL::Scope
2
- EXACT_COUNT_THRESHOLD = 10_000
3
-
4
- # Returns the exact count of matching records
5
- def count
6
- sql = order_by(nil).to_sql(pagination: false)
7
- ::Simple::SQL.ask("SELECT COUNT(*) FROM (#{sql}) _total_count", *args)
8
- end
9
-
10
- # Returns the fast count of matching records
11
- #
12
- # For counts larger than EXACT_COUNT_THRESHOLD this returns an estimate
13
- def fast_count
14
- estimate = estimated_count
15
- return estimate if estimate > EXACT_COUNT_THRESHOLD
16
-
17
- sql = order_by(nil).to_sql(pagination: false)
18
- ::Simple::SQL.ask("SELECT COUNT(*) FROM (#{sql}) _total_count", *args)
19
- end
20
-
21
- private
22
-
23
- def estimated_count
24
- sql = order_by(nil).to_sql(pagination: false)
25
- ::Simple::SQL.each("EXPLAIN #{sql}", *args) do |line|
26
- next unless line =~ /\brows=(\d+)/
27
-
28
- return Integer($1)
29
- end
30
-
31
- -1
32
- end
33
- end
@@ -1,79 +0,0 @@
1
- # rubocop:disable Metrics/AbcSize
2
- # rubocop:disable Metrics/MethodLength
3
-
4
- class Simple::SQL::Scope
5
- # Potentially fast implementation of returning all different values for a specific group.
6
- #
7
- # For example:
8
- #
9
- # Scope.new("SELECT * FROM users").enumerate_groups("gender") -> [ "female", "male" ]
10
- #
11
- # It is possible to enumerate over multiple attributes, for example:
12
- #
13
- # scope.enumerate_groups fragment: "ARRAY[workflow, queue]"
14
- #
15
- # In any case it is important that an index exists that the database can use to group
16
- # by the +sql_fragment+, for example:
17
- #
18
- # CREATE INDEX ix3 ON table((ARRAY[workflow, queue]));
19
- #
20
- def enumerate_groups(sql_fragment)
21
- sql = order_by(nil).to_sql(pagination: false)
22
-
23
- _, max_cost = ::Simple::SQL.costs "SELECT MIN(#{sql_fragment}) FROM (#{sql}) sq", *args
24
- raise "enumerate_groups: takes too much time. Make sure to create a suitable index" if max_cost > 10_000
25
-
26
- groups = []
27
- var_name = "$#{@args.count + 1}"
28
- cur = ::Simple::SQL.ask "SELECT MIN(#{sql_fragment}) FROM (#{sql}) sq", *args
29
-
30
- while cur
31
- groups << cur
32
- cur = ::Simple::SQL.ask "SELECT MIN(#{sql_fragment}) FROM (#{sql}) sq"" WHERE #{sql_fragment} > #{var_name}", *args, cur
33
- end
34
-
35
- groups
36
- end
37
-
38
- def count_by(sql_fragment)
39
- sql = order_by(nil).to_sql(pagination: false)
40
-
41
- recs = ::Simple::SQL.all "SELECT #{sql_fragment} AS group, COUNT(*) AS count FROM (#{sql}) sq GROUP BY #{sql_fragment}", *args
42
- Hash[recs]
43
- end
44
-
45
- def fast_count_by(sql_fragment)
46
- sql = order_by(nil).to_sql(pagination: false)
47
-
48
- _, max_cost = ::Simple::SQL.costs "SELECT COUNT(*) FROM (#{sql}) sq GROUP BY #{sql_fragment}", *args
49
-
50
- return count_by(sql_fragment) if max_cost < 10_000
51
-
52
- # iterate over all groups, estimating the count for each. If the count is
53
- # less than EXACT_COUNT_THRESHOLD we ask for the exact count in that and
54
- # similarily sparse groups.
55
- var_name = "$#{@args.count + 1}"
56
-
57
- counts = {}
58
- sparse_groups = []
59
- enumerate_groups(sql_fragment).each do |group|
60
- scope = ::Simple::SQL::Scope.new("SELECT * FROM (#{sql}) sq WHERE #{sql_fragment}=#{var_name}", *args, group)
61
- counts[group] = scope.send(:estimated_count)
62
- sparse_groups << group if estimated_count < EXACT_COUNT_THRESHOLD
63
- end
64
-
65
- # fetch exact counts in all sparse_groups
66
- unless sparse_groups.empty?
67
- sparse_counts = ::Simple::SQL.all <<~SQL, *args, sparse_groups
68
- SELECT #{sql_fragment} AS group, COUNT(*) AS count
69
- FROM (#{sql}) sq
70
- WHERE #{sql_fragment} = ANY(#{var_name})
71
- GROUP BY #{sql_fragment}
72
- SQL
73
-
74
- counts.update Hash[sparse_counts]
75
- end
76
-
77
- counts
78
- end
79
- end
@@ -1,44 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe "Simple::SQL::Scope#count_by" do
4
- let!(:users) { 1.upto(10).map { |i| create(:user, role_id: i) } }
5
- let(:all_role_ids) { SQL.all("SELECT DISTINCT role_id FROM users") }
6
- let(:scope) { SQL::Scope.new("SELECT * FROM users") }
7
-
8
- describe "enumerate_groups" do
9
- it "returns all groups" do
10
- expect(scope.enumerate_groups("role_id")).to contain_exactly(*all_role_ids)
11
- expect(scope.where("role_id < 4").enumerate_groups("role_id")).to contain_exactly(*(1.upto(3).to_a))
12
- end
13
- end
14
-
15
- describe "count_by" do
16
- it "counts all groups" do
17
- create(:user, role_id: 1)
18
- create(:user, role_id: 1)
19
- create(:user, role_id: 1)
20
-
21
- expect(scope.count_by("role_id")).to include(1 => 4)
22
- expect(scope.count_by("role_id")).to include(2 => 1)
23
- expect(scope.count_by("role_id").keys).to contain_exactly(*all_role_ids)
24
- end
25
- end
26
-
27
- describe "fast_count_by" do
28
- before do
29
- # 10_000 is chosen "magically". It is large enough to switch to the fast algorithm,
30
- # but
31
- allow(::Simple::SQL).to receive(:costs).and_return([0, 10_000])
32
- end
33
-
34
- it "counts all groups" do
35
- create(:user, role_id: 1)
36
- create(:user, role_id: 1)
37
- create(:user, role_id: 1)
38
-
39
- expect(scope.fast_count_by("role_id")).to include(1 => 4)
40
- expect(scope.fast_count_by("role_id")).to include(2 => 1)
41
- expect(scope.fast_count_by("role_id").keys).to contain_exactly(*all_role_ids)
42
- end
43
- end
44
- end
@@ -1,29 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe "Simple::SQL::Scope#count" do
4
- let!(:users) { 1.upto(USER_COUNT).map { create(:user) } }
5
- let(:min_user_id) { SQL.ask "SELECT min(id) FROM users" }
6
- let(:scope) { SQL::Scope.new("SELECT * FROM users") }
7
-
8
- describe "exact count" do
9
- it "counts" do
10
- expect(scope.count).to eq(USER_COUNT)
11
- end
12
-
13
- it "evaluates conditions" do
14
- expect(scope.where("id < $1", min_user_id).count).to eq(0)
15
- expect(scope.where("id <= $1", min_user_id).count).to eq(1)
16
- end
17
- end
18
-
19
- describe "fast count" do
20
- it "counts" do
21
- expect(scope.fast_count).to eq(USER_COUNT)
22
- end
23
-
24
- it "evaluates conditions" do
25
- expect(scope.where("id < $1", min_user_id).fast_count).to eq(0)
26
- expect(scope.where("id <= $1", min_user_id).fast_count).to eq(1)
27
- end
28
- end
29
- end
@@ -1,16 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe "Simple::SQL logging" do
4
- context 'when running a slow query' do
5
- before do
6
- SQL::Logging.slow_query_treshold = 0.05
7
- end
8
- after do
9
- SQL::Logging.slow_query_treshold = nil
10
- end
11
-
12
- it "does not crash" do
13
- SQL.ask "SELECT pg_sleep(0.1)"
14
- end
15
- end
16
- end
@@ -1,57 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe "Simple::SQL::Result counts" do
4
- let!(:users) { 1.upto(USER_COUNT).map { create(:user) } }
5
- let(:min_user_id) { SQL.ask "SELECT min(id) FROM users" }
6
- let(:scope) { SQL::Scope.new("SELECT * FROM users") }
7
- let(:paginated_scope) { scope.paginate(per: 1, page: 1) }
8
-
9
- describe "exact counting" do
10
- it "counts" do
11
- result = SQL.all(paginated_scope)
12
- expect(result.total_count).to eq(USER_COUNT)
13
- expect(result.total_pages).to eq(USER_COUNT)
14
- expect(result.current_page).to eq(1)
15
- end
16
- end
17
-
18
- describe "fast counting" do
19
- it "counts fast" do
20
- result = SQL.all(paginated_scope)
21
-
22
- expect(result.total_fast_count).to eq(USER_COUNT)
23
- expect(result.total_fast_pages).to eq(USER_COUNT)
24
- expect(result.current_page).to eq(1)
25
- end
26
- end
27
-
28
- context 'when running with a non-paginated paginated_scope' do
29
- it "raises errors" do
30
- result = SQL.all(scope)
31
-
32
- expect { result.total_count }.to raise_error(RuntimeError)
33
- expect { result.total_pages }.to raise_error(RuntimeError)
34
- expect { result.current_page }.to raise_error(RuntimeError)
35
- expect { result.total_fast_count }.to raise_error(RuntimeError)
36
- expect { result.total_fast_pages }.to raise_error(RuntimeError)
37
- end
38
- end
39
-
40
-
41
- context 'when running with an empty, paginated paginated_scope' do
42
- let(:scope) { SQL::Scope.new("SELECT * FROM users WHERE FALSE") }
43
- let(:paginated_scope) { scope.paginate(per: 1, page: 1) }
44
-
45
- it "returns correct results" do
46
- result = SQL.all(paginated_scope)
47
-
48
- expect(result.total_count).to eq(0)
49
- expect(result.total_pages).to eq(1)
50
-
51
- expect(result.total_fast_count).to eq(0)
52
- expect(result.total_fast_pages).to eq(1)
53
-
54
- expect(result.current_page).to eq(1)
55
- end
56
- end
57
- end