simple-sql 0.5.36 → 0.9.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.
@@ -19,55 +19,113 @@ class Simple::SQL::Connection::Scope
19
19
  #
20
20
  def enumerate_groups(sql_fragment)
21
21
  sql = order_by(nil).to_sql(pagination: false)
22
+ @connection.all "SELECT DISTINCT #{sql_fragment} FROM (#{sql}) sq", *args
23
+ end
22
24
 
23
- cost = @connection.estimate_cost "SELECT MIN(#{sql_fragment}) FROM (#{sql}) sq", *args
24
-
25
- # cost estimates are good, but are hard to check against a hard coded value.
26
- # see https://issues.mediafellows.com/issues/75232
27
- #
28
- # if cost > 10_000
29
- # raise "enumerate_groups(#{sql_fragment.inspect}) takes too much time. Make sure to create a suitable index"
30
- # end
25
+ def count_by(sql_fragment)
26
+ expect! sql_fragment => String
31
27
 
32
- groups = []
33
- var_name = "$#{@args.count + 1}"
34
- cur = @connection.ask "SELECT MIN(#{sql_fragment}) FROM (#{sql}) sq", *args
28
+ sql = order_by(nil).to_sql(pagination: false)
35
29
 
36
- while cur
37
- groups << cur
38
- cur = @connection.ask "SELECT MIN(#{sql_fragment}) FROM (#{sql}) sq"" WHERE #{sql_fragment} > #{var_name}", *args, cur
30
+ recs = @connection.all "SELECT COUNT(*) AS count, #{sql_fragment} AS group FROM (#{sql}) sq GROUP BY #{sql_fragment}", *args
31
+
32
+ # if we count by a single value (e.g. `count_by("role_id")`) each entry in recs consists of an array [group_value, count].
33
+ # The resulting Hash will have entries of group_value => count.
34
+ if recs.first&.length == 2
35
+ recs.each_with_object({}) do |count_and_group, hsh|
36
+ count, group = *count_and_group
37
+ hsh[group] = count
38
+ end
39
+ else
40
+ recs.each_with_object({}) do |count_and_group, hsh|
41
+ count, *group = *count_and_group
42
+ hsh[group] = count
43
+ end
39
44
  end
40
-
41
- groups
42
45
  end
43
46
 
44
- def count_by(sql_fragment)
45
- sql = order_by(nil).to_sql(pagination: false)
47
+ private
46
48
 
47
- recs = @connection.all "SELECT #{sql_fragment} AS group, COUNT(*) AS count FROM (#{sql}) sq GROUP BY #{sql_fragment}", *args
48
- Hash[recs]
49
+ # cost estimate threshold for count_by method. Can be set to false, true, or
50
+ # a number.
51
+ #
52
+ # Note that cost estimates are problematic, since they are not reported in
53
+ # any "real" unit, meaning any comparison really is a bit pointless.
54
+ COUNT_BY_ESTIMATE_COST_THRESHOLD = 10_000
55
+
56
+ # estimates the cost to run a sql query. If COUNT_BY_ESTIMATE_COST_THRESHOLD
57
+ # is set and the cost estimate is less than COUNT_BY_ESTIMATE_COST_THRESHOLD
58
+ # \a count_by_estimate is using the estimating code path.
59
+ def use_count_by_estimate?(sql_group_by_fragment)
60
+ case COUNT_BY_ESTIMATE_COST_THRESHOLD
61
+ when true then true
62
+ when false then false
63
+ else
64
+ # estimate the effort to exact counting over all groups.
65
+ base_sql = order_by(nil).to_sql(pagination: false)
66
+ count_sql = "SELECT COUNT(*) FROM (#{base_sql}) sq GROUP BY #{sql_group_by_fragment}"
67
+ cost = @connection.estimate_cost count_sql, *args
68
+
69
+ cost >= COUNT_BY_ESTIMATE_COST_THRESHOLD
70
+ end
49
71
  end
50
72
 
73
+ public
74
+
51
75
  def count_by_estimate(sql_fragment)
52
- return count_by(sql_fragment)
76
+ expect! sql_fragment => String
53
77
 
54
- # The code below runs an estimate on the effort to count by a group. This is currently
55
- # disabled (see https://issues.mediafellows.com/issues/75237).
78
+ return count_by(sql_fragment) unless use_count_by_estimate?(sql_fragment)
56
79
 
57
- sql = order_by(nil).to_sql(pagination: false)
58
- cost = @connection.estimate_cost "SELECT COUNT(*) FROM (#{sql}) sq GROUP BY #{sql_fragment}", *args
80
+ # iterate over all groups, estimating the count for each.
81
+ #
82
+ # For larger groups we'll use that estimate - preventing a full table scan.
83
+ # Groups smaller than EXACT_COUNT_THRESHOLD are counted exactly - in the
84
+ # hope that this query can be answered from an index.
85
+
86
+ #
87
+ # Usually Simple::SQL.all normalizes each result row into its first value,
88
+ # if the row only consists of a single value. Here, however, we don't
89
+ # know the width of a group; so to understand this we just add a dummy
90
+ # value to the sql_fragment and then remove it again.
91
+ #
92
+ groups = enumerate_groups("1 AS __dummy__, #{sql_fragment}")
93
+ groups = groups.each(&:shift)
59
94
 
60
- return count_by(sql_fragment) if cost < 10_000
95
+ # no groups? well, then...
96
+ return {} if groups.empty?
61
97
 
62
- # iterate over all groups, estimating the count for each. If the count is
63
- # less than EXACT_COUNT_THRESHOLD we ask for the exact count in that and
64
- # similarily sparse groups.
65
- var_name = "$#{@args.count + 1}"
98
+ #
99
+ # The estimating code only works for groups of size 1. This is a limitation
100
+ # of simple-sql - for larger groups we would have to be able to encode arrays
101
+ # of arrays on their way to the postgres server. We are not able to do that
102
+ # currently.
103
+ #
104
+ group_size = groups.first&.length
105
+ if group_size > 1
106
+ return count_by(sql_fragment)
107
+ end
108
+
109
+ # The code below only works for groups of size 1
110
+ groups = groups.map(&:first)
111
+
112
+ #
113
+ # Now we estimate the count of entries in each group. For large groups we
114
+ # just use the estimate - because it is usually pretty close to being correct.
115
+ # Small groups are collected in the `sparse_groups` array, to be counted
116
+ # exactly later on.
117
+ #
66
118
 
67
119
  counts = {}
120
+
68
121
  sparse_groups = []
69
- enumerate_groups(sql_fragment).each do |group|
70
- scope = @connection.scope("SELECT * FROM (#{sql}) sq WHERE #{sql_fragment}=#{var_name}", *args, group)
122
+ base_sql = order_by(nil).to_sql(pagination: false)
123
+
124
+ var_name = "$#{@args.count + 1}"
125
+
126
+ groups.each do |group|
127
+ scope = @connection.scope("SELECT * FROM (#{base_sql}) sq WHERE #{sql_fragment}=#{var_name}", args + [group])
128
+
71
129
  estimated_count = scope.send(:estimated_count)
72
130
  counts[group] = estimated_count
73
131
  sparse_groups << group if estimated_count < EXACT_COUNT_THRESHOLD
@@ -77,7 +135,7 @@ class Simple::SQL::Connection::Scope
77
135
  unless sparse_groups.empty?
78
136
  sparse_counts = @connection.all <<~SQL, *args, sparse_groups
79
137
  SELECT #{sql_fragment} AS group, COUNT(*) AS count
80
- FROM (#{sql}) sq
138
+ FROM (#{base_sql}) sq
81
139
  WHERE #{sql_fragment} = ANY(#{var_name})
82
140
  GROUP BY #{sql_fragment}
83
141
  SQL
@@ -57,7 +57,7 @@ module Simple::SQL::Connection::Scope::Search
57
57
  return scope if filters.empty?
58
58
 
59
59
  filters.inject(scope) do |scp, (k, v)|
60
- scp.where k => resolve_static_matches(v, column_type: column_types.fetch(k))
60
+ scp.where({ k => resolve_static_matches(v, column_type: column_types.fetch(k)) })
61
61
  end
62
62
  end
63
63
 
@@ -78,6 +78,7 @@ module Simple::SQL::Connection::Scope::Search
78
78
  def empty_filter?(_key, value)
79
79
  return true if value.nil?
80
80
  return true if value.is_a?(Enumerable) && value.empty? # i.e. Hash, Array
81
+
81
82
  false
82
83
  end
83
84
 
@@ -23,11 +23,9 @@ class Simple::SQL::Connection::Scope
23
23
  # scope = scope.where(metadata: { uid: 1 }, jsonb: false)
24
24
  #
25
25
  def where(sql_fragment, arg = :__dummy__no__arg, placeholder: "?", jsonb: true)
26
- duplicate.send(:where!, sql_fragment, arg, placeholder: placeholder, jsonb: jsonb)
26
+ duplicate.where!(sql_fragment, arg, placeholder: placeholder, jsonb: jsonb)
27
27
  end
28
28
 
29
- private
30
-
31
29
  def where!(first_arg, arg = :__dummy__no__arg, placeholder: "?", jsonb: true)
32
30
  if arg != :__dummy__no__arg
33
31
  where_sql_with_argument!(first_arg, arg, placeholder: placeholder)
@@ -40,6 +38,8 @@ class Simple::SQL::Connection::Scope
40
38
  self
41
39
  end
42
40
 
41
+ private
42
+
43
43
  def where_sql!(sql_fragment)
44
44
  @where << sql_fragment
45
45
  end
@@ -57,6 +57,7 @@ class Simple::SQL::Connection
57
57
 
58
58
  def disconnect!
59
59
  return unless @connection_class && @connection_class != ::ActiveRecord::Base
60
+
60
61
  @connection_class.remove_connection
61
62
  end
62
63
 
@@ -7,6 +7,7 @@ module Simple::SQL::Helpers::Decoder
7
7
  # rubocop:disable Metrics/AbcSize
8
8
  # rubocop:disable Metrics/CyclomaticComplexity
9
9
  # rubocop:disable Naming/UncommunicativeMethodParamName
10
+ # rubocop:disable Style/MultipleComparison
10
11
  def decode_value(type, s)
11
12
  case type
12
13
  when :unknown then s
@@ -18,8 +19,8 @@ module Simple::SQL::Helpers::Decoder
18
19
  when :'integer[]' then s.scan(/-?\d+/).map { |part| Integer(part) }
19
20
  when :"character varying[]" then parse_pg_array(s)
20
21
  when :"text[]" then parse_pg_array(s)
21
- when :"timestamp without time zone" then ::Time.parse(s)
22
- when :"timestamp with time zone" then ::Time.parse(s)
22
+ when :"timestamp without time zone" then decode_time(s)
23
+ when :"timestamp with time zone" then decode_time(s)
23
24
  when :hstore then HStore.parse(s)
24
25
  when :json then ::JSON.parse(s)
25
26
  when :jsonb then ::JSON.parse(s)
@@ -36,6 +37,12 @@ module Simple::SQL::Helpers::Decoder
36
37
  require "pg_array_parser"
37
38
  extend PgArrayParser
38
39
 
40
+ def decode_time(s)
41
+ return s if s.is_a?(Time)
42
+
43
+ ::Time.parse(s)
44
+ end
45
+
39
46
  # HStore parsing
40
47
  module HStore
41
48
  extend self
@@ -68,6 +75,7 @@ end
68
75
 
69
76
  module Simple::SQL::Helpers::Decoder
70
77
  def self.new(result, into:, column_info:)
78
+ # rubocop:disable Lint/ElseLayout
71
79
  if into == Hash then HashRecord.new(column_info)
72
80
  elsif result.nfields == 1 then SingleColumn.new(column_info)
73
81
  else MultiColumns.new(column_info)
@@ -27,7 +27,7 @@ module Simple::SQL::Helpers::RowConverter
27
27
  ary.first
28
28
  end
29
29
 
30
- class TypeConverter #:nodoc:
30
+ class TypeConverter # :nodoc:
31
31
  def initialize(type:, associations:)
32
32
  @type = type
33
33
  @associations = associations
@@ -57,7 +57,7 @@ module Simple::SQL::Helpers::RowConverter
57
57
  end
58
58
  end
59
59
 
60
- class ImmutableConverter < TypeConverter #:nodoc:
60
+ class ImmutableConverter < TypeConverter # :nodoc:
61
61
  Immutable = ::Simple::Immutable
62
62
 
63
63
  def build_row_in_target_type(hsh)
@@ -65,7 +65,7 @@ module Simple::SQL::Helpers::RowConverter
65
65
  end
66
66
  end
67
67
 
68
- class TypeConverter2 < TypeConverter #:nodoc:
68
+ class TypeConverter2 < TypeConverter # :nodoc:
69
69
  def initialize(type:, associations:, fq_table_name:)
70
70
  super(type: type, associations: associations)
71
71
  @fq_table_name = fq_table_name
@@ -1,5 +1,7 @@
1
1
  # This file contains some monkey patches
2
2
 
3
+ # rubocop:disable Lint/DuplicateMethods
4
+
3
5
  module Simple::SQL::MonkeyPatches
4
6
  def self.warn(msg)
5
7
  return if ENV["SIMPLE_SQL_SILENCE"] == "1"
@@ -29,6 +31,7 @@ when /^5.2/
29
31
  class ActiveRecord::ConnectionAdapters::ConnectionPool::Reaper
30
32
  def run
31
33
  return unless frequency && frequency > 0
34
+
32
35
  Simple::SQL::MonkeyPatches.warn "simple-sql disables reapers for all connection pools, see https://github.com/rails/rails/issues/33600"
33
36
  end
34
37
  end
@@ -45,6 +48,7 @@ when /^6/
45
48
  class ActiveRecord::ConnectionAdapters::ConnectionPool::Reaper
46
49
  def run
47
50
  return unless frequency && frequency > 0
51
+
48
52
  Simple::SQL::MonkeyPatches.warn "simple-sql disables reapers for all connection pools, see https://github.com/rails/rails/issues/33600"
49
53
  end
50
54
  end
@@ -92,8 +92,8 @@ module ::Simple::SQL::Result::AssociationLoader # :nodoc:
92
92
 
93
93
  foreign_ids = H.pluck(records, belonging_column).uniq.compact
94
94
 
95
- scope = connection.scope(table: relation.having_table)
96
- scope = scope.where(having_column => foreign_ids)
95
+ scope = connection.scope({ table: relation.having_table })
96
+ scope = scope.where({ having_column => foreign_ids })
97
97
 
98
98
  recs = connection.all(scope, into: Hash)
99
99
  recs_by_id = H.by_key(recs, having_column)
@@ -122,7 +122,7 @@ module ::Simple::SQL::Result::AssociationLoader # :nodoc:
122
122
  host_ids = H.pluck(records, having_column).uniq.compact
123
123
 
124
124
  scope = connection.scope(table: relation.belonging_table)
125
- scope = scope.where(belonging_column => host_ids)
125
+ scope = scope.where({ belonging_column => host_ids })
126
126
  scope = scope.order_by(order_by) if order_by
127
127
 
128
128
  recs = connection.all(scope, into: Hash)
@@ -31,6 +31,7 @@ class ::Simple::SQL::Result < Array
31
31
  attr_reader :connection
32
32
 
33
33
  def initialize(connection, records) # :nodoc:
34
+ super()
34
35
  @connection = connection
35
36
  replace(records)
36
37
  end
@@ -6,6 +6,7 @@ module Simple
6
6
  def version(name)
7
7
  spec = Gem.loaded_specs[name]
8
8
  return "unreleased" unless spec
9
+
9
10
  version = spec.version.to_s
10
11
  version += "+unreleased" if unreleased?(spec)
11
12
  version
@@ -17,6 +18,7 @@ module Simple
17
18
  return false unless defined?(Bundler::Source::Gemspec)
18
19
  return true if spec.source.is_a?(::Bundler::Source::Gemspec)
19
20
  return true if spec.source.is_a?(::Bundler::Source::Path)
21
+
20
22
  false
21
23
  end
22
24
  end
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+
3
+ set -eu -o pipefail
4
+
5
+ echo "Starting integration tests. We log into log/integration_tests.log"
6
+
7
+ rm log/integration_tests.log
8
+ touch log/integration_tests.log
9
+
10
+ export SIMPLE_SQL_SILENCE=1
11
+
12
+ run_test() {
13
+ local activerecord_spec=$1
14
+ local pg_spec=$2
15
+
16
+ export SIMPLE_SQL_ACTIVERECORD_SPECS="$activerecord_spec"
17
+ export SIMPLE_SQL_PG_SPECS="$pg_spec"
18
+
19
+ printf "=== Running test w/SIMPLE_SQL_ACTIVERECORD_SPECS='%s' SIMPLE_SQL_PG_SPECS='%s'\n" "$SIMPLE_SQL_ACTIVERECORD_SPECS" "$SIMPLE_SQL_PG_SPECS" | tee -a log/integration_tests.log
20
+
21
+ if ! bundle update >> log/integration_tests.log ; then
22
+ echo "Bundling failed"
23
+ set -xv
24
+ bundle update
25
+ exit 1
26
+ fi
27
+
28
+ if ! bundle exec rspec >> log/integration_tests.log ; then
29
+ echo "Tests failed"
30
+ set -xv
31
+ bundle exec rspec
32
+ exit 1
33
+ fi
34
+ }
35
+
36
+ run_test "> 5,< 6" "~> 0.20"
37
+ run_test "> 5,< 6" "~> 1.0.0"
38
+ run_test "> 5,< 6" "~> 1.1.0"
39
+ run_test "> 5,< 6" "~> 1.2.0"
40
+ run_test "> 5,< 6" "~> 1.3.0"
41
+
42
+ run_test "> 6,< 7" "~> 1.1.0"
43
+ run_test "> 6,< 7" "~> 1.2.0"
44
+ run_test "> 6,< 7" "~> 1.3.0"
45
+
46
+ run_test "> 7,< 8" "~> 1.1.0"
47
+ run_test "> 7,< 8" "~> 1.2.0"
48
+ run_test "> 7,< 8" "~> 1.3.0"
49
+
data/simple-sql.gemspec CHANGED
@@ -21,29 +21,19 @@ Gem::Specification.new do |gem|
21
21
  # executables are used for development purposes only
22
22
  gem.executables = []
23
23
 
24
- gem.required_ruby_version = '~> 2.3'
24
+ gem.required_ruby_version = '~> 3.3'
25
25
 
26
26
  gem.add_dependency 'pg_array_parser', '~> 0', '>= 0.0.9'
27
- gem.add_dependency 'pg', '~> 0.20'
28
27
  gem.add_dependency 'expectation', '~> 1'
29
28
 
30
29
  gem.add_dependency 'digest-crc', '~> 0'
31
30
  gem.add_dependency 'simple-immutable', '~> 1.0'
32
31
 
32
+ pg_specs = ENV["SIMPLE_SQL_PG_SPECS"] || '~> 1.0'
33
+ gem.add_dependency 'pg', *(pg_specs.split(","))
34
+
33
35
  # during tests we check the SIMPLE_SQL_ACTIVERECORD_SPECS environment setting.
34
36
  # Run make tests to run all tests
35
- if ENV["SIMPLE_SQL_ACTIVERECORD_SPECS"]
36
- gem.add_dependency 'activerecord', '>= 5.2.4.5', *(ENV["SIMPLE_SQL_ACTIVERECORD_SPECS"].split(","))
37
- else
38
- gem.add_dependency 'activerecord', '>= 5.2.4.5', '< 6.1'
39
- end
40
-
41
- # optional gems (required by some of the parts)
42
-
43
- # development gems
44
- gem.add_development_dependency 'pg', '0.20'
45
- gem.add_development_dependency 'rake', '>= 12.3.3'
46
- gem.add_development_dependency 'rspec', '~> 3.7'
47
- gem.add_development_dependency 'rubocop', '~> 0.61.1'
48
- gem.add_development_dependency 'simplecov', '~> 0'
37
+ activerecord_specs = ENV["SIMPLE_SQL_ACTIVERECORD_SPECS"] || '< 6.1'
38
+ gem.add_dependency 'activerecord', '>= 5.2.4.5', *(activerecord_specs.split(","))
49
39
  end
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe "Simple::SQL::Config" do
4
4
  describe ".determine_url" do
5
- it "reads config/database.yml" do
5
+ xit "reads config/database.yml" do
6
6
  expect(SQL::Config.determine_url).to eq "postgres://127.0.0.1/simple-sql-test"
7
7
  end
8
8
  end
@@ -1,44 +1,63 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Simple::SQL::Connection::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("SELECT * FROM users") }
4
+ let!(:users) { 1.upto(10).map { |i| create(:user, role_id: i) } }
5
+ let(:scope) { SQL.scope("SELECT * FROM users") }
6
+
7
+ let(:all_role_ids) { 1.upto(10).to_a }
8
+ let(:all_role_ids_w_squares) { all_role_ids.map { |role_id| [role_id, role_id*role_id] } }
9
+
10
+ before do
11
+ # initially we have 10 users, one per role_id in the range 1 .. 10
12
+ # This adds another 3 users with role_id of 1.
13
+ create(:user, role_id: 1)
14
+ create(:user, role_id: 1)
15
+ create(:user, role_id: 1)
16
+ end
7
17
 
8
18
  describe "enumerate_groups" do
9
- it "returns all groups" do
19
+ it "returns all groups by a single column" do
10
20
  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))
21
+ end
22
+
23
+ it "obeys where conditions" do
24
+ expect(scope.where("role_id < $1", 4).enumerate_groups("role_id")).to contain_exactly(1,2,3)
25
+ end
26
+
27
+ it "counts all groups by multiple columns" do
28
+ expect(scope.where("role_id < $1", 4).enumerate_groups("role_id, role_id * role_id")).to contain_exactly([1, 1], [2, 4], [3, 9])
12
29
  end
13
30
  end
14
31
 
15
32
  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
-
33
+ it "counts all groups by a single column" do
21
34
  expect(scope.count_by("role_id")).to include(1 => 4)
22
35
  expect(scope.count_by("role_id")).to include(2 => 1)
23
36
  expect(scope.count_by("role_id").keys).to contain_exactly(*all_role_ids)
24
37
  end
38
+
39
+ it "counts all groups by multiple columns" do
40
+ expect(scope.where("role_id < $1", 4).count_by("role_id, role_id * role_id")).to include([1,1] => 4)
41
+ expect(scope.where("role_id < $1", 4).count_by("role_id, role_id * role_id")).to include([2, 4] => 1)
42
+ expect(scope.where("role_id < $1", 4).count_by("role_id, role_id * role_id").keys).to contain_exactly([1, 1], [2, 4], [3, 9])
43
+ end
25
44
  end
26
45
 
27
46
  describe "count_by_estimate" do
28
47
  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])
48
+ expect_any_instance_of(Simple::SQL::Connection).to receive(:estimate_cost).at_least(:once).and_return(10_000)
32
49
  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
50
 
51
+ it "counts all groups by a single column" do
39
52
  expect(scope.count_by_estimate("role_id")).to include(1 => 4)
40
53
  expect(scope.count_by_estimate("role_id")).to include(2 => 1)
41
54
  expect(scope.count_by_estimate("role_id").keys).to contain_exactly(*all_role_ids)
42
55
  end
56
+
57
+ it "counts all groups by multiple columns and conditions" do
58
+ expect(scope.where("role_id < $1", 4).count_by_estimate("role_id, role_id * role_id")).to include([1,1] => 4)
59
+ expect(scope.where("role_id < $1", 4).count_by_estimate("role_id, role_id * role_id")).to include([2, 4] => 1)
60
+ expect(scope.where("role_id < $1", 4).count_by_estimate("role_id, role_id * role_id").keys).to contain_exactly([1, 1], [2, 4], [3, 9])
61
+ end
43
62
  end
44
63
  end
@@ -7,7 +7,7 @@ describe "Simple::SQL.insert" do
7
7
  let!(:initial_ids) { SQL.all("SELECT id FROM users") }
8
8
 
9
9
  it "inserts a single user" do
10
- id = SQL.insert :users, first_name: "foo", last_name: "bar"
10
+ id = SQL.insert :users, { first_name: "foo", last_name: "bar" }
11
11
  expect(id).to be_a(Integer)
12
12
  expect(initial_ids).not_to include(id)
13
13
  expect(SQL.ask("SELECT count(*) FROM users")).to eq(USER_COUNT+1)
@@ -19,7 +19,7 @@ describe "Simple::SQL.insert" do
19
19
  end
20
20
 
21
21
  it "returns the id" do
22
- id = SQL.insert :users, first_name: "foo", last_name: "bar"
22
+ id = SQL.insert :users, { first_name: "foo", last_name: "bar" }
23
23
  expect(id).to be_a(Integer)
24
24
  expect(initial_ids).not_to include(id)
25
25
  end
@@ -33,36 +33,36 @@ describe "Simple::SQL::Connection::Scope" do
33
33
 
34
34
  context "that do not match" do
35
35
  it "does not match with string keys" do
36
- expect(SQL.ask(scope.where(id: -1))).to be_nil
36
+ expect(SQL.ask(scope.where({id: -1}))).to be_nil
37
37
  end
38
38
 
39
39
  it "does not match with symbol keys" do
40
- expect(SQL.ask(scope.where("id" => -1))).to be_nil
40
+ expect(SQL.ask(scope.where({"id" => -1}))).to be_nil
41
41
  end
42
42
  end
43
43
 
44
44
  context "that match" do
45
45
  it "matches with string keys" do
46
- expect(SQL.ask(scope.where("id" => user_id))).to eq(1)
46
+ expect(SQL.ask(scope.where({"id" => user_id}))).to eq(1)
47
47
  end
48
48
 
49
49
  it "matches with symbol keys" do
50
- expect(SQL.ask(scope.where(id: user_id))).to eq(1)
50
+ expect(SQL.ask(scope.where({id: user_id}))).to eq(1)
51
51
  end
52
52
  end
53
53
 
54
54
  context "with array arguments" do
55
55
  it "matches against array arguments" do
56
- expect(SQL.ask(scope.where("id" => [-333, user_id]))).to eq(1)
57
- expect(SQL.ask(scope.where("id" => [-333, -1]))).to be_nil
58
- expect(SQL.ask(scope.where("id" => []))).to be_nil
56
+ expect(SQL.ask(scope.where({"id" => [-333, user_id]}))).to eq(1)
57
+ expect(SQL.ask(scope.where({"id" => [-333, -1]}))).to be_nil
58
+ expect(SQL.ask(scope.where({"id" => []}))).to be_nil
59
59
  end
60
60
  end
61
61
 
62
62
  context "with invalid arguments" do
63
63
  it "raises an ArgumentError" do
64
64
  expect {
65
- scope.where(1 => 3)
65
+ scope.where({1 => 3})
66
66
  }.to raise_error(ArgumentError)
67
67
  end
68
68
  end
@@ -4,7 +4,7 @@ describe SQL do
4
4
  describe "VERSION" do
5
5
  it "defines a version string" do
6
6
  # Note: this allows for 0.12.34beta
7
- expect(SQL::VERSION).to match(/^\d+\.\d+\.\d+/)
7
+ expect(SQL::VERSION).to match(/^(\d+\.\d+\.\d+|unreleased)/)
8
8
  end
9
9
  end
10
10
  end
@@ -1,3 +1,5 @@
1
+ require "logger"
2
+
1
3
  # connect to the database and setup the schema
2
4
  require "active_record"
3
5
 
@@ -5,14 +7,21 @@ require "active_record"
5
7
  require "simple-sql"
6
8
 
7
9
  require "yaml"
8
- abc = YAML.load_file("config/database.yml")
10
+
11
+ path = "config/database.yml"
12
+ abc = if Psych::VERSION > '4.0'
13
+ YAML.safe_load(File.read(path), aliases: true)
14
+ else
15
+ YAML.safe_load(File.read(path), [], [], true)
16
+ end
17
+
9
18
  ActiveRecord::Base.establish_connection(abc["test"])
10
19
 
11
20
  if ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks=)
12
21
  ActiveRecord::Base.raise_in_transactional_callbacks = true
13
22
  end
14
23
 
15
- ActiveRecord::Base.logger = Logger.new("log/test.log")
24
+ ActiveRecord::Base.logger = ::Logger.new("log/test.log")
16
25
 
17
26
  ActiveRecord::Schema.define do
18
27
  self.verbose = false