sequel 4.35.0 → 4.36.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/doc/association_basics.rdoc +27 -4
  4. data/doc/migration.rdoc +24 -0
  5. data/doc/release_notes/4.36.0.txt +116 -0
  6. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  7. data/lib/sequel/adapters/mysql2.rb +11 -1
  8. data/lib/sequel/adapters/oracle.rb +3 -5
  9. data/lib/sequel/adapters/postgres.rb +2 -2
  10. data/lib/sequel/adapters/shared/access.rb +1 -1
  11. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  12. data/lib/sequel/adapters/shared/postgres.rb +1 -1
  13. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  14. data/lib/sequel/connection_pool.rb +5 -0
  15. data/lib/sequel/connection_pool/sharded_single.rb +1 -1
  16. data/lib/sequel/connection_pool/sharded_threaded.rb +29 -14
  17. data/lib/sequel/connection_pool/single.rb +1 -1
  18. data/lib/sequel/connection_pool/threaded.rb +5 -3
  19. data/lib/sequel/database/schema_methods.rb +7 -1
  20. data/lib/sequel/dataset/sql.rb +4 -0
  21. data/lib/sequel/extensions/arbitrary_servers.rb +1 -1
  22. data/lib/sequel/extensions/connection_expiration.rb +89 -0
  23. data/lib/sequel/extensions/connection_validator.rb +11 -3
  24. data/lib/sequel/extensions/constraint_validations.rb +28 -0
  25. data/lib/sequel/extensions/string_agg.rb +178 -0
  26. data/lib/sequel/model.rb +13 -56
  27. data/lib/sequel/model/associations.rb +3 -1
  28. data/lib/sequel/model/base.rb +104 -7
  29. data/lib/sequel/plugins/constraint_validations.rb +17 -3
  30. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  31. data/lib/sequel/sql.rb +8 -0
  32. data/lib/sequel/version.rb +1 -1
  33. data/spec/adapters/postgres_spec.rb +4 -0
  34. data/spec/core/dataset_spec.rb +4 -0
  35. data/spec/core/expression_filters_spec.rb +4 -0
  36. data/spec/extensions/connection_expiration_spec.rb +121 -0
  37. data/spec/extensions/connection_validator_spec.rb +7 -0
  38. data/spec/extensions/constraint_validations_plugin_spec.rb +14 -0
  39. data/spec/extensions/constraint_validations_spec.rb +64 -0
  40. data/spec/extensions/string_agg_spec.rb +85 -0
  41. data/spec/extensions/validation_helpers_spec.rb +2 -0
  42. data/spec/integration/plugin_test.rb +37 -2
  43. data/spec/model/association_reflection_spec.rb +10 -0
  44. data/spec/model/model_spec.rb +49 -0
  45. metadata +8 -2
@@ -3,61 +3,14 @@
3
3
  require 'sequel/core'
4
4
 
5
5
  module Sequel
6
- # Lets you create a Model subclass with its dataset already set.
7
- # +source+ should be an instance of one of the following classes:
8
- #
9
- # Database :: Sets the database for this model to +source+.
10
- # Generally only useful when subclassing directly
11
- # from the returned class, where the name of the
12
- # subclass sets the table name (which is combined
13
- # with the +Database+ in +source+ to create the
14
- # dataset to use)
15
- # Dataset :: Sets the dataset for this model to +source+.
16
- # other :: Sets the table name for this model to +source+. The
17
- # class will use the default database for model
18
- # classes in order to create the dataset.
19
- #
20
- # The purpose of this method is to set the dataset/database automatically
21
- # for a model class, if the table name doesn't match the implicit
22
- # name. This is neater than using set_dataset inside the class,
23
- # doesn't require a bogus query for the schema.
24
- #
25
- # # Using a symbol
26
- # class Comment < Sequel::Model(:something)
27
- # table_name # => :something
28
- # end
29
- #
30
- # # Using a dataset
31
- # class Comment < Sequel::Model(DB1[:something])
32
- # dataset # => DB1[:something]
33
- # end
34
- #
35
- # # Using a database
36
- # class Comment < Sequel::Model(DB1)
37
- # dataset # => DB1[:comments]
38
- # end
39
- def self.Model(source)
40
- if cache_anonymous_models && (klass = Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source]})
41
- return klass
42
- end
43
- klass = if source.is_a?(Database)
44
- c = Class.new(Model)
45
- c.db = source
46
- c
47
- else
48
- Class.new(Model).set_dataset(source)
49
- end
50
- Model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source] = klass} if cache_anonymous_models
51
- klass
6
+ # Delegate to Sequel::Model, only for backwards compatibility.
7
+ def self.cache_anonymous_models
8
+ Model.cache_anonymous_models
52
9
  end
53
10
 
54
- @cache_anonymous_models = true
55
-
56
- class << self
57
- # Whether to cache the anonymous models created by Sequel::Model(). This is
58
- # required for reloading them correctly (avoiding the superclass mismatch). True
59
- # by default for backwards compatibility.
60
- attr_accessor :cache_anonymous_models
11
+ # Delegate to Sequel::Model, only for backwards compatibility.
12
+ def self.cache_anonymous_models=(v)
13
+ Model.cache_anonymous_models = v
61
14
  end
62
15
 
63
16
  # <tt>Sequel::Model</tt> is an object relational mapper built on top of Sequel core. Each
@@ -78,10 +31,10 @@ module Sequel
78
31
 
79
32
  # Map that stores model classes created with <tt>Sequel::Model()</tt>, to allow the reopening
80
33
  # of classes when dealing with code reloading.
81
- ANONYMOUS_MODEL_CLASSES = {}
34
+ ANONYMOUS_MODEL_CLASSES = @Model_cache = {}
82
35
 
83
36
  # Mutex protecting access to ANONYMOUS_MODEL_CLASSES
84
- ANONYMOUS_MODEL_CLASSES_MUTEX = Mutex.new
37
+ ANONYMOUS_MODEL_CLASSES_MUTEX = @Model_mutex = Mutex.new
85
38
 
86
39
  # Class methods added to model that call the method of the same name on the dataset
87
40
  DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
@@ -125,7 +78,8 @@ module Sequel
125
78
  :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
126
79
  :@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil,
127
80
  :@fast_instance_delete_sql=>nil, :@finders=>:dup, :@finder_loaders=>:dup,
128
- :@db=>nil, :@default_set_fields_options=>:dup, :@require_valid_table=>nil}
81
+ :@db=>nil, :@default_set_fields_options=>:dup, :@require_valid_table=>nil,
82
+ :@cache_anonymous_models=>nil, :@Model_mutex=>nil}
129
83
 
130
84
  # Regular expression that determines if a method name is normal in the sense that
131
85
  # it could be used literally in ruby code without using send. Used to
@@ -137,6 +91,7 @@ module Sequel
137
91
  SETTER_METHOD_REGEXP = /=\z/
138
92
 
139
93
  @allowed_columns = nil
94
+ @cache_anonymous_models = true
140
95
  @db = nil
141
96
  @db_schema = nil
142
97
  @dataset = nil
@@ -174,5 +129,7 @@ module Sequel
174
129
  # The setter methods (methods ending with =) that are never allowed
175
130
  # to be called automatically via +set+/+update+/+new+/etc..
176
131
  RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).grep(SETTER_METHOD_REGEXP)
132
+
133
+ def_Model(::Sequel)
177
134
  end
178
135
  end
@@ -1486,7 +1486,9 @@ module Sequel
1486
1486
  # :class :: The associated class or its name as a string or symbol. If not
1487
1487
  # given, uses the association's name, which is camelized (and
1488
1488
  # singularized unless the type is :many_to_one, :one_to_one, or one_through_one). If this is specified
1489
- # as a string or symbol, you must specify the full class name (e.g. "SomeModule::MyModel").
1489
+ # as a string or symbol, you must specify the full class name (e.g. "::SomeModule::MyModel").
1490
+ # :class_namespace :: If :class is given as a string or symbol, sets the default namespace in which to look for
1491
+ # the class. <tt>:class=>'Foo', :class_namespace=>'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1490
1492
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1491
1493
  # to remove all objects associated to the current object (*_to_many assocations).
1492
1494
  # :clone :: Merge the current options and block into the options and block used in defining
@@ -14,6 +14,11 @@ module Sequel
14
14
  # (default: not set, so all columns not otherwise restricted are allowed).
15
15
  attr_reader :allowed_columns
16
16
 
17
+ # Whether to cache the anonymous models created by Sequel::Model(). This is
18
+ # required for reloading them correctly (avoiding the superclass mismatch). True
19
+ # by default for backwards compatibility.
20
+ attr_accessor :cache_anonymous_models
21
+
17
22
  # Array of modules that extend this model's dataset. Stored
18
23
  # so that if the model's dataset is changed, it will be extended
19
24
  # with all of these modules.
@@ -48,11 +53,8 @@ module Sequel
48
53
  attr_accessor :raise_on_save_failure
49
54
 
50
55
  # Whether to raise an error when unable to typecast data for a column
51
- # (default: true). This should be set to false if you want to use
52
- # validations to display nice error messages to the user (e.g. most
53
- # web applications). You can use the validates_schema_types validation
54
- # (from the validation_helpers plugin) in connection with this setting to
55
- # check for typecast failures during validation.
56
+ # (default: false). This should be set to true if you want to have model
57
+ # setter methods raise errors if the argument cannot be typecast properly.
56
58
  attr_accessor :raise_on_typecast_failure
57
59
 
58
60
  # Whether to raise an error if an UPDATE or DELETE query related to
@@ -116,6 +118,94 @@ module Sequel
116
118
  # If you are sending database queries in before_* or after_* hooks, you shouldn't change
117
119
  # the default setting without a good reason.
118
120
  attr_accessor :use_transactions
121
+
122
+ # Define a Model method on the given module that calls the Model
123
+ # method on the receiver. This is how the Sequel::Model() method is
124
+ # defined, and allows you to define Model() methods on other modules,
125
+ # making it easier to have custom model settings for all models under
126
+ # a namespace. Example:
127
+ #
128
+ # module Foo
129
+ # Model = Class.new(Sequel::Model)
130
+ # Model.def_Model(self)
131
+ # DB = Model.db = Sequel.connect(ENV['FOO_DATABASE_URL'])
132
+ # Model.plugin :prepared_statements
133
+ #
134
+ # class Bar < Model
135
+ # # Uses Foo::DB[:bars]
136
+ # end
137
+ #
138
+ # class Baz < Model(:my_baz)
139
+ # # Uses Foo::DB[:my_baz]
140
+ # end
141
+ # end
142
+ def def_Model(mod)
143
+ model = self
144
+ (class << mod; self; end).send(:define_method, :Model) do |source|
145
+ model.Model(source)
146
+ end
147
+ end
148
+
149
+ # Lets you create a Model subclass with its dataset already set.
150
+ # +source+ should be an instance of one of the following classes:
151
+ #
152
+ # Database :: Sets the database for this model to +source+.
153
+ # Generally only useful when subclassing directly
154
+ # from the returned class, where the name of the
155
+ # subclass sets the table name (which is combined
156
+ # with the +Database+ in +source+ to create the
157
+ # dataset to use)
158
+ # Dataset :: Sets the dataset for this model to +source+.
159
+ # other :: Sets the table name for this model to +source+. The
160
+ # class will use the default database for model
161
+ # classes in order to create the dataset.
162
+ #
163
+ # The purpose of this method is to set the dataset/database automatically
164
+ # for a model class, if the table name doesn't match the implicit
165
+ # name. This is neater than using set_dataset inside the class,
166
+ # doesn't require a bogus query for the schema.
167
+ #
168
+ # When creating subclasses of Sequel::Model itself, this method is usually
169
+ # called on Sequel itself, using <tt>Sequel::Model(:something)</tt>.
170
+ #
171
+ # # Using a symbol
172
+ # class Comment < Sequel::Model(:something)
173
+ # table_name # => :something
174
+ # end
175
+ #
176
+ # # Using a dataset
177
+ # class Comment < Sequel::Model(DB1[:something])
178
+ # dataset # => DB1[:something]
179
+ # end
180
+ #
181
+ # # Using a database
182
+ # class Comment < Sequel::Model(DB1)
183
+ # dataset # => DB1[:comments]
184
+ # end
185
+ def Model(source)
186
+ if cache_anonymous_models
187
+ mutex = @Model_mutex
188
+ cache = mutex.synchronize{@Model_cache ||= {}}
189
+
190
+ if klass = mutex.synchronize{cache[source]}
191
+ return klass
192
+ end
193
+ end
194
+
195
+ klass = Class.new(self)
196
+
197
+ if source.is_a?(::Sequel::Database)
198
+ klass.db = source
199
+ else
200
+ klass.set_dataset(source)
201
+ end
202
+
203
+ if cache_anonymous_models
204
+ mutex.synchronize{cache[source] = klass}
205
+ end
206
+
207
+ klass
208
+ end
119
209
 
120
210
  # Returns the first record from the database matching the conditions.
121
211
  # If a hash is given, it is used as the conditions. If another
@@ -992,11 +1082,18 @@ module Sequel
992
1082
  case opts[:class]
993
1083
  when String, Symbol
994
1084
  # Delete :class to allow late binding
995
- opts[:class_name] ||= opts.delete(:class).to_s
1085
+ class_name = opts.delete(:class).to_s
1086
+
1087
+ if (namespace = opts[:class_namespace]) && !class_name.start_with?('::')
1088
+ class_name = "::#{namespace}::#{class_name}"
1089
+ end
1090
+
1091
+ opts[:class_name] ||= class_name
996
1092
  when Class
997
1093
  opts[:class_name] ||= opts[:class].name
998
1094
  end
999
- opts[:class_name] ||= ((name || '').split("::")[0..-2] + [camelize(default)]).join('::')
1095
+
1096
+ opts[:class_name] ||= '::' + ((name || '').split("::")[0..-2] + [camelize(default)]).join('::')
1000
1097
  end
1001
1098
 
1002
1099
  # Module that the class includes that holds methods the class adds for column accessors and
@@ -33,6 +33,10 @@ module Sequel
33
33
  # The default constraint validation metadata table name.
34
34
  DEFAULT_CONSTRAINT_VALIDATIONS_TABLE = :sequel_constraint_validations
35
35
 
36
+ # Mapping of operator names in table to ruby operators
37
+ OPERATOR_MAP = {:str_lt => :<, :str_lte => :<=, :str_gt => :>, :str_gte => :>=,
38
+ :int_lt => :<, :int_lte => :<=, :int_gt => :>, :int_gte => :>=}.freeze
39
+
36
40
  # Automatically load the validation_helpers plugin to run the actual validations.
37
41
  def self.apply(model, opts=OPTS)
38
42
  model.instance_eval do
@@ -154,6 +158,10 @@ module Sequel
154
158
  when :includes_int_range
155
159
  arg = constraint_validation_int_range(arg)
156
160
  type = :includes
161
+ when *OPERATOR_MAP.keys
162
+ arg = arg.to_i if type.to_s =~ /\Aint_/
163
+ operator = OPERATOR_MAP[type]
164
+ type = :operator
157
165
  end
158
166
 
159
167
  column = if type == :unique
@@ -163,16 +171,22 @@ module Sequel
163
171
  end
164
172
 
165
173
  if type_opts = @constraint_validation_options[type]
166
- opts = opts.merge(type_opts)
174
+ opts.merge!(type_opts)
167
175
  end
168
176
 
169
- reflection_opts = opts
177
+ reflection_opts = opts.dup
170
178
  a = [:"validates_#{type}"]
171
179
 
180
+ if operator
181
+ a << operator
182
+ reflection_opts[:operator] = operator
183
+ end
184
+
172
185
  if arg
173
186
  a << arg
174
- reflection_opts = reflection_opts.merge(:argument=>arg)
187
+ reflection_opts[:argument] = arg
175
188
  end
189
+
176
190
  a << column
177
191
  unless opts.empty?
178
192
  a << opts
@@ -159,7 +159,7 @@ module Sequel
159
159
  # Check attribute value(s) against a specified value and operation, e.g.
160
160
  # validates_operator(:>, 3, :value) validates that value > 3.
161
161
  def validates_operator(operator, rhs, atts, opts=OPTS)
162
- validatable_attributes_for_type(:operator, atts, opts){|a,v,m| validation_error_message(m, operator, rhs) unless v.send(operator, rhs)}
162
+ validatable_attributes_for_type(:operator, atts, opts){|a,v,m| validation_error_message(m, operator, rhs) if v.nil? || !v.send(operator, rhs)}
163
163
  end
164
164
 
165
165
  # Validates for all of the model columns (or just the given columns)
@@ -1333,6 +1333,14 @@ module Sequel
1333
1333
  with_opts(:lateral=>true)
1334
1334
  end
1335
1335
 
1336
+ # Return a new function where the function will be ordered. Only useful for aggregate
1337
+ # functions that are order dependent.
1338
+ #
1339
+ # Sequel.function(:foo, :a).order(:a, Sequel.desc(:b)) # foo(a ORDER BY a, b DESC)
1340
+ def order(*args)
1341
+ with_opts(:order=>args)
1342
+ end
1343
+
1336
1344
  # Return a new function with an OVER clause (making it a window function).
1337
1345
  #
1338
1346
  # Sequel.function(:row_number).over(:partition=>:col) # row_number() OVER (PARTITION BY col)
@@ -5,7 +5,7 @@ module Sequel
5
5
  MAJOR = 4
6
6
  # The minor version of Sequel. Bumped for every non-patch level
7
7
  # release, generally around once a month.
8
- MINOR = 35
8
+ MINOR = 36
9
9
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
10
10
  # releases that fix regressions from previous versions.
11
11
  TINY = 0
@@ -243,6 +243,10 @@ describe "A PostgreSQL database" do
243
243
  @db.values([[1, 2], [3, 4]]).map([:column1, :column2]).must_equal [[1, 2], [3, 4]]
244
244
  end
245
245
 
246
+ it "should support ordering in aggregate functions" do
247
+ @db.from(@db.values([['1'], ['2']]).as(:t, [:a])).get{string_agg(:a, '-').order(Sequel.desc(:a)).as(:c)}.must_equal '2-1'
248
+ end if DB.server_version >= 90000
249
+
246
250
  it "should support ordering and limiting with #values" do
247
251
  @db.values([[1, 2], [3, 4]]).reverse(:column2, :column1).limit(1).map([:column1, :column2]).must_equal [[3, 4]]
248
252
  @db.values([[1, 2], [3, 4]]).reverse(:column2, :column1).offset(1).map([:column1, :column2]).must_equal [[1, 2]]
@@ -3884,6 +3884,10 @@ describe "Sequel::Dataset#qualify" do
3884
3884
  @ds.select{sum(:a).over(:partition=>:b, :order=>:c)}.qualify.sql.must_equal 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
3885
3885
  end
3886
3886
 
3887
+ it "should handle SQL::Functions with orders" do
3888
+ @ds.select{sum(:a).order(:a)}.qualify.sql.must_equal 'SELECT sum(t.a ORDER BY t.a) FROM t'
3889
+ end
3890
+
3887
3891
  it "should handle SQL::DelayedEvaluation" do
3888
3892
  t = :a
3889
3893
  ds = @ds.filter(Sequel.delay{t}).qualify
@@ -557,6 +557,10 @@ describe Sequel::SQL::VirtualRow do
557
557
  @d.l{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}.must_equal 'count(*) OVER ("win" PARTITION BY "a" ORDER BY "b" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
558
558
  end
559
559
 
560
+ it "should support order method on functions to specify orders for aggregate functions" do
561
+ @d.l{rank(:c).order(:a, :b)}.must_equal 'rank("c" ORDER BY "a", "b")'
562
+ end
563
+
560
564
  it "should support over method on functions to create window functions" do
561
565
  @d.l{rank{}.over}.must_equal 'rank() OVER ()'
562
566
  @d.l{sum(c).over(:partition=>a, :order=>b, :window=>:win, :frame=>:rows)}.must_equal 'sum("c") OVER ("win" PARTITION BY "a" ORDER BY "b" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
@@ -0,0 +1,121 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ connection_expiration_specs = shared_description do
4
+ describe "connection expiration" do
5
+ before do
6
+ @db.extend(Module.new do
7
+ def disconnect_connection(conn)
8
+ @sqls << 'disconnect'
9
+ end
10
+ end)
11
+ @db.extension(:connection_expiration)
12
+ @db.pool.connection_expiration_timeout = 2
13
+ end
14
+
15
+ it "should still allow new connections" do
16
+ @db.synchronize{|c| c}.must_be_kind_of(Sequel::Mock::Connection)
17
+ end
18
+
19
+ it "should not override connection_expiration_timeout when loading extension" do
20
+ @db.extension(:connection_expiration)
21
+ @db.pool.connection_expiration_timeout.must_equal 2
22
+ end
23
+
24
+ it "should only expire if older than timeout" do
25
+ c1 = @db.synchronize{|c| c}
26
+ @db.sqls.must_equal []
27
+ @db.synchronize{|c| c}.must_be_same_as(c1)
28
+ @db.sqls.must_equal []
29
+ end
30
+
31
+ it "should disconnect connection if expired" do
32
+ c1 = @db.synchronize{|c| c}
33
+ @db.sqls.must_equal []
34
+ simulate_sleep(c1)
35
+ c2 = @db.synchronize{|c| c}
36
+ @db.sqls.must_equal ['disconnect']
37
+ c2.wont_be_same_as(c1)
38
+ end
39
+
40
+ it "should disconnect only expired connections among multiple" do
41
+ c1, c2 = multiple_connections
42
+
43
+ # Expire c1 only.
44
+ simulate_sleep(c1)
45
+ simulate_sleep(c2, 1)
46
+ c1, c2 = multiple_connections
47
+
48
+ c3 = @db.synchronize{|c| c}
49
+ @db.sqls.must_equal ['disconnect']
50
+ c3.wont_be_same_as(c1)
51
+ c3.must_be_same_as(c2)
52
+ end
53
+
54
+ it "should disconnect connections repeatedly if they are expired" do
55
+ c1, c2 = multiple_connections
56
+
57
+ simulate_sleep(c1)
58
+ simulate_sleep(c2)
59
+
60
+ c3 = @db.synchronize{|c| c}
61
+ @db.sqls.must_equal ['disconnect', 'disconnect']
62
+ c3.wont_be_same_as(c1)
63
+ c3.wont_be_same_as(c2)
64
+ end
65
+
66
+ it "should not leak connection references to expiring connections" do
67
+ c1 = @db.synchronize{|c| c}
68
+ simulate_sleep(c1)
69
+ c2 = @db.synchronize{|c| c}
70
+ c2.wont_be_same_as(c1)
71
+ @db.pool.instance_variable_get(:@connection_expiration_timestamps).must_include(c2)
72
+ @db.pool.instance_variable_get(:@connection_expiration_timestamps).wont_include(c1)
73
+ end
74
+
75
+ it "should not leak connection references during disconnect" do
76
+ c1, c2 = multiple_connections
77
+ @db.pool.instance_variable_get(:@connection_expiration_timestamps).size.must_equal 2
78
+ @db.disconnect
79
+ @db.pool.instance_variable_get(:@connection_expiration_timestamps).size.must_equal 0
80
+ end
81
+
82
+ def multiple_connections
83
+ q, q1 = Queue.new, Queue.new
84
+ c1 = nil
85
+ c2 = nil
86
+ @db.synchronize do |c|
87
+ Thread.new do
88
+ @db.synchronize do |cc|
89
+ c2 = cc
90
+ end
91
+ q1.pop
92
+ q.push nil
93
+ end
94
+ q1.push nil
95
+ q.pop
96
+ c1 = c
97
+ end
98
+ [c1, c2]
99
+ end
100
+
101
+ # Set the timestamp back in time to simulate sleep / passage of time.
102
+ def simulate_sleep(conn, sleep_time = 3)
103
+ timestamps = @db.pool.instance_variable_get(:@connection_expiration_timestamps)
104
+ timestamps[conn] -= sleep_time
105
+ @db.pool.instance_variable_set(:@connection_expiration_timestamps, timestamps)
106
+ end
107
+ end
108
+ end
109
+
110
+ describe "Sequel::ConnectionExpiration with threaded pool" do
111
+ before do
112
+ @db = Sequel.mock
113
+ end
114
+ include connection_expiration_specs
115
+ end
116
+ describe "Sequel::ConnectionExpiration with sharded threaded pool" do
117
+ before do
118
+ @db = Sequel.mock(:servers=>{})
119
+ end
120
+ include connection_expiration_specs
121
+ end