sequel 4.35.0 → 4.36.0

Sign up to get free protection for your applications and to get access to all the features.
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