simple-sql 0.4.41 → 0.5.0

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 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