sequel 5.14.0 → 5.15.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: a950f8be723b867d5ecc782005c2374fa84a05a6b54c4ba55652f5442de1c90b
4
- data.tar.gz: c5b2c92ee3580933b9fcd0525fdb67a79f5915f8b8e337e4731b9046f771dbf0
3
+ metadata.gz: 965fa50e6e91a5630a4e484651180b38b9d2eb2c785be8b91dd5b099e3ba5be6
4
+ data.tar.gz: 3c3c5bd93d51326fff0081272e41a0a23b0dbb47d393603f073cbc0f0486e6c7
5
5
  SHA512:
6
- metadata.gz: 4671d49935656b0ec907f10d3d4a6b25e29815b328b7661e55d95e4bfabbf84818de2b3550d92d86f444fbd69a561f099c859a5b1156303e10d58a7402641ebc
7
- data.tar.gz: 5488428f7290a548344849919a7895dfd1e8d7cbe7990c2b261b4118e2a46845d9bde30b00673928511e2e650b3e4098f81cb2087d8f42227653607d3ae90c6f
6
+ metadata.gz: a633436d4a066e195e847cffc7dd68d0cd2902915ddca33d5df724dc020aee22dbd95a261f8b15ea1581a73517d5736423a8693f40a3ef59fb27c88b8c24ca01
7
+ data.tar.gz: bee56c79793138296ced8395deabf2652a68fe2de3bc4f144470d885b253883017e15ca0281e36e04c6064b590b505daed285b23699140e77cae119bddee3914
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 5.15.0 (2018-12-01)
2
+
3
+ * Add :conn_str option in the postgres adapter for PostgreSQL connection strings, if the pg driver is used (graywolf) (#1572)
4
+
5
+ * Add :qualify_tables option to class_table_inheritance plugin to automatically qualify subclass tables with superclass qualifier (benalavi) (#1571)
6
+
7
+ * Access already allocated connections in a thread safe manner when checking out connections in the sharded threaded connection pool (jeremyevans)
8
+
9
+ * Automatically support datasets using qualified tables in the class_table_inheritance plugin without having to use the :alias option (benalavi) (#1565)
10
+
11
+ * Support rename_column without emulation on SQLite 3.25+ (jeremyevans)
12
+
13
+ * Do not remove currently cached many_to_one associated objects when changing the related foreign key value from nil to non-nil (jeremyevans)
14
+
15
+ * Do not validate new *_to_many associated objects twice when saving in the nested_attributes plugin (jeremyevans)
16
+
17
+ * Add Model#skip_validation_on_next_save! for skipping validation on next save call (jeremyevans)
18
+
1
19
  === 5.14.0 (2018-11-01)
2
20
 
3
21
  * Drop defaulting the :port option to 5432 in the postgres adapter, so that setting the :service option in :driver_options works (jeremyevans) (#1558)
@@ -309,6 +309,10 @@ The following additional options are supported:
309
309
  conversion is done, so an error is raised if you attempt to retrieve an infinite
310
310
  timestamp/date. You can set this to :nil to convert to nil, :string to leave
311
311
  as a string, or :float to convert to an infinite float.
312
+ :conn_str :: Use connection string (in form of `host=x port=y ...`). Ignores all other options, only supported with pg
313
+ library. See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING and
314
+ https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS for format and list of supported
315
+ options.
312
316
  :connect_timeout :: Set the number of seconds to wait for a connection (default 20, only respected
313
317
  if using the pg library).
314
318
  :driver_options :: A hash of options to pass to the underlying driver (only respected if using the pg library)
@@ -0,0 +1,39 @@
1
+ = New Features
2
+
3
+ * A :qualify_tables option has been added to the
4
+ class_table_inheritance plugin, which will automatically qualify
5
+ subclass tables with the same qualifier as the superclass table
6
+ if the superclass table is qualified.
7
+
8
+ * Model#save_validation_on_next_save! has been added, which skips all
9
+ validation on the next save to the object, including the running
10
+ of validation related hooks. This method is designed for use only
11
+ when Model#valid? is called on the object before saving, to avoid
12
+ running validations on the object twice. This method takes
13
+ precedence even over an explicit validate: true option passed to
14
+ Model#save, and as such should be used with care.
15
+
16
+ * The postgres adapter now supports a :conn_str Database option to
17
+ use a PostgreSQL connection string (e.g. "host=foo port=2442") when
18
+ connecting. This option has preference over other connection
19
+ related options if it is present.
20
+
21
+ = Other Improvements
22
+
23
+ * If a foreign key for a model object is changed from a nil value to
24
+ a non-nil value, any cached associated objects related to the
25
+ foreign key are no longer removed. Such associated objects could
26
+ only be set manually, and if they have been set manually, it is
27
+ probably not a good idea to remove them automatically.
28
+
29
+ * When using the nested_attributes plugin, new *_to_many associated
30
+ objects are not validated twice when saving.
31
+
32
+ * The default table alias when using the class_table_inheritance
33
+ plugin now correctly handles qualified tables.
34
+
35
+ * A theoretical thread safety issue when assigning connections
36
+ in the threaded connection pools has been fixed.
37
+
38
+ * Renaming columns is now supported without emulation when using
39
+ SQLite 3.25+.
@@ -535,7 +535,7 @@ Note that the column names used in the errors are used verbatim in the error mes
535
535
  errors.full_messages
536
536
  # => ["Album name is not valid"]
537
537
 
538
- Alternatively, feel free to override Sequel::Model::Errors#full_messages. As long as it returns an array of strings, overridding it is completely safe.
538
+ Alternatively, feel free to override Sequel::Model::Errors#full_messages. As long as it returns an array of strings, overriding it is completely safe.
539
539
 
540
540
  === +count+
541
541
 
@@ -203,7 +203,7 @@ module Sequel
203
203
  :sslrootcert => opts[:sslrootcert]
204
204
  }.delete_if { |key, value| blank_object?(value) }
205
205
  connection_params.merge!(opts[:driver_options]) if opts[:driver_options]
206
- conn = Adapter.connect(connection_params)
206
+ conn = Adapter.connect(opts[:conn_str] || connection_params)
207
207
 
208
208
  conn.instance_variable_set(:@prepared_statements, {})
209
209
 
@@ -40,6 +40,12 @@ module Sequel
40
40
  # :numrows :: The number of rows affected by the stored procedure
41
41
  # output params :: Values for any output paramters, using the name given for the output parameter
42
42
  #
43
+ # Because Sequel datasets only support a single result set per query, and retrieving
44
+ # the result code and number of rows requires a query, this does not support
45
+ # stored procedures which also return result sets. To handle such stored procedures,
46
+ # you should drop down to the connection/driver level by using Sequel::Database#synchronize
47
+ # to get access to the underlying connection object.
48
+ #
43
49
  # Examples:
44
50
  #
45
51
  # DB.call_mssql_sproc(:SequelTest, {args: ['input arg', :output]})
@@ -217,8 +217,12 @@ module Sequel
217
217
  ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
218
218
  duplicate_table(table, :old_columns_proc=>ocp){|columns| columns.delete_if{|s| s[:name].to_s == op[:name].to_s}}
219
219
  when :rename_column
220
- ncp = lambda{|nc| nc.map!{|c| c.to_s == op[:name].to_s ? op[:new_name] : c}}
221
- duplicate_table(table, :new_columns_proc=>ncp){|columns| columns.each{|s| s[:name] = op[:new_name] if s[:name].to_s == op[:name].to_s}}
220
+ if sqlite_version >= 32500
221
+ super
222
+ else
223
+ ncp = lambda{|nc| nc.map!{|c| c.to_s == op[:name].to_s ? op[:new_name] : c}}
224
+ duplicate_table(table, :new_columns_proc=>ncp){|columns| columns.each{|s| s[:name] = op[:new_name] if s[:name].to_s == op[:name].to_s}}
225
+ end
222
226
  when :set_column_default
223
227
  duplicate_table(table){|columns| columns.each{|s| s[:default] = op[:default] if s[:name].to_s == op[:name].to_s}}
224
228
  when :set_column_null
@@ -46,6 +46,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
46
46
  # A hash of connections currently being used for the given server, key is the
47
47
  # Thread, value is the connection. Nonexistent servers will return nil. Treat
48
48
  # this as read only, do not modify the resulting object.
49
+ # The calling code should already have the mutex before calling this.
49
50
  def allocated(server=:default)
50
51
  @allocated[server]
51
52
  end
@@ -70,13 +71,14 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
70
71
  # An array of connections opened but not currently used, for the given
71
72
  # server. Nonexistent servers will return nil. Treat this as read only, do
72
73
  # not modify the resulting object.
74
+ # The calling code should already have the mutex before calling this.
73
75
  def available_connections(server=:default)
74
76
  @available_connections[server]
75
77
  end
76
78
 
77
79
  # The total number of connections opened for the given server.
78
80
  # Nonexistent servers will return the created count of the default server.
79
- # The calling code should not have the mutex before calling this.
81
+ # The calling code should NOT have the mutex before calling this.
80
82
  def size(server=:default)
81
83
  @mutex.synchronize{_size(server)}
82
84
  end
@@ -221,10 +223,11 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
221
223
  # Assign a connection to the thread, or return nil if one cannot be assigned.
222
224
  # The caller should NOT have the mutex before calling this.
223
225
  def assign_connection(thread, server)
224
- alloc = allocated(server)
226
+ alloc = nil
225
227
 
226
228
  do_make_new = false
227
229
  sync do
230
+ alloc = allocated(server)
228
231
  if conn = next_available(server)
229
232
  alloc[thread] = conn
230
233
  return conn
@@ -286,7 +289,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
286
289
  end
287
290
 
288
291
  # Disconnect all available connections immediately, and schedule currently allocated connections for disconnection
289
- # as soon as they are returned to the pool. The calling code should not
292
+ # as soon as they are returned to the pool. The calling code should NOT
290
293
  # have the mutex before calling this.
291
294
  def disconnect_connections(conns)
292
295
  conns.each{|conn| disconnect_connection(conn)}
@@ -11,10 +11,11 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
11
11
  attr_reader :max_size
12
12
 
13
13
  # An array of connections that are available for use by the pool.
14
+ # The calling code should already have the mutex before calling this.
14
15
  attr_reader :available_connections
15
16
 
16
- # A hash with thread keys and connection values for currently allocated
17
- # connections.
17
+ # A hash with thread keys and connection values for currently allocated connections.
18
+ # The calling code should already have the mutex before calling this.
18
19
  attr_reader :allocated
19
20
 
20
21
  # The following additional options are respected:
@@ -171,6 +172,8 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
171
172
  # Assign a connection to the thread, or return nil if one cannot be assigned.
172
173
  # The caller should NOT have the mutex before calling this.
173
174
  def assign_connection(thread)
175
+ # Thread safe as instance variable is only assigned to local variable
176
+ # and not operated on outside mutex.
174
177
  allocated = @allocated
175
178
  do_make_new = false
176
179
  to_disconnect = nil
@@ -183,7 +186,7 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
183
186
  if (n = _size) >= (max = @max_size)
184
187
  allocated.keys.each do |t|
185
188
  unless t.alive?
186
- (to_disconnect ||= []) << @allocated.delete(t)
189
+ (to_disconnect ||= []) << allocated.delete(t)
187
190
  end
188
191
  end
189
192
  n = nil
@@ -2406,15 +2406,32 @@ module Sequel
2406
2406
  # cached associations.
2407
2407
  def change_column_value(column, value)
2408
2408
  if assocs = model.autoreloading_associations[column]
2409
+ vals = @values
2409
2410
  if new?
2410
2411
  # Do deeper checking for new objects, so that associations are
2411
2412
  # not deleted when values do not change. This code is run at
2412
2413
  # a higher level for existing objects.
2413
- vals = @values
2414
- return super unless !vals.include?(column) || value != (c = vals[column]) || value.class != c.class
2414
+ if value == (c = vals[column]) && value.class == c.class
2415
+ # If the value is the same, there is no reason to delete
2416
+ # the related associations, so exit early in that case.
2417
+ return super
2418
+ end
2419
+
2420
+ only_delete_nil = c.nil?
2421
+ elsif vals[column].nil?
2422
+ only_delete_nil = true
2415
2423
  end
2416
2424
 
2417
- assocs.each{|a| associations.delete(a)}
2425
+ if only_delete_nil
2426
+ # If the current foreign key value is nil, but the association
2427
+ # is already present in the cache, it was probably added to the
2428
+ # cache for a reason, and we do not want to delete it in that case.
2429
+ # However, we still want to delete associations with nil values
2430
+ # to remove the cached false negative.
2431
+ assocs.each{|a| associations.delete(a) if associations[a].nil?}
2432
+ else
2433
+ assocs.each{|a| associations.delete(a)}
2434
+ end
2418
2435
  end
2419
2436
  super
2420
2437
  end
@@ -1456,7 +1456,7 @@ module Sequel
1456
1456
  def save(opts=OPTS)
1457
1457
  raise Sequel::Error, "can't save frozen object" if frozen?
1458
1458
  set_server(opts[:server]) if opts[:server]
1459
- unless checked_save_failure(opts){_valid?(opts)}
1459
+ unless _save_valid?(opts)
1460
1460
  raise(ValidationFailed.new(self)) if raise_on_failure?(opts)
1461
1461
  return
1462
1462
  end
@@ -1551,6 +1551,15 @@ module Sequel
1551
1551
  super
1552
1552
  end
1553
1553
 
1554
+ # Skip all validation of the object on the next call to #save,
1555
+ # including the running of validation hooks. This is designed for
1556
+ # and should only be used in cases where #valid? is called before
1557
+ # saving and the <tt>validate: false</tt> option cannot be passed to
1558
+ # #save.
1559
+ def skip_validation_on_next_save!
1560
+ @skip_validation_on_next_save = true
1561
+ end
1562
+
1554
1563
  # Returns (naked) dataset that should return only this instance.
1555
1564
  #
1556
1565
  # Artist[1].this
@@ -1809,6 +1818,19 @@ module Sequel
1809
1818
  v
1810
1819
  end
1811
1820
 
1821
+ # Validate the object if validating on save. Skips validation
1822
+ # completely (including validation hooks) if
1823
+ # skip_validation_on_save! has been called on the object,
1824
+ # resetting the flag so that future saves will validate.
1825
+ def _save_valid?(opts)
1826
+ if @skip_validation_on_next_save
1827
+ @skip_validation_on_next_save = false
1828
+ return true
1829
+ end
1830
+
1831
+ checked_save_failure(opts){_valid?(opts)}
1832
+ end
1833
+
1812
1834
  # Call _update with the given columns, if any are present.
1813
1835
  # Plugins can override this method in order to update with
1814
1836
  # additional columns, even when the column hash is initially empty.
@@ -198,6 +198,9 @@ module Sequel
198
198
  # Overrides implicit table names.
199
199
  # :ignore_subclass_columns :: Array with column names as symbols that are ignored
200
200
  # on all sub-classes.
201
+ # :qualify_tables :: Boolean true to qualify automatically determined
202
+ # subclass tables with the same qualifier as their
203
+ # superclass.
201
204
  def self.configure(model, opts = OPTS)
202
205
  SingleTableInheritance.configure model, opts[:key], opts
203
206
 
@@ -207,8 +210,14 @@ module Sequel
207
210
  @cti_instance_dataset = @instance_dataset
208
211
  @cti_table_columns = columns
209
212
  @cti_table_map = opts[:table_map] || {}
210
- @cti_alias = opts[:alias] || @dataset.first_source
213
+ @cti_alias = opts[:alias] || case source = @dataset.first_source
214
+ when SQL::QualifiedIdentifier
215
+ @dataset.unqualified_column_for(source)
216
+ else
217
+ source
218
+ end
211
219
  @cti_ignore_subclass_columns = opts[:ignore_subclass_columns] || []
220
+ @cti_qualify_tables = !!opts[:qualify_tables]
212
221
  end
213
222
  end
214
223
 
@@ -239,6 +248,13 @@ module Sequel
239
248
  # primary key column is always allowed to be duplicated
240
249
  attr_reader :cti_ignore_subclass_columns
241
250
 
251
+ # A boolean indicating whether or not to automatically qualify tables
252
+ # backing subclasses with the same qualifier as their superclass, if
253
+ # the superclass is qualified. Specified with the :qualify_tables
254
+ # option to the plugin and only applied to automatically determined
255
+ # table names (not to the :table_map option).
256
+ attr_reader :cti_qualify_tables
257
+
242
258
  # Freeze CTI information when freezing model class.
243
259
  def freeze
244
260
  @cti_models.freeze
@@ -250,7 +266,7 @@ module Sequel
250
266
  super
251
267
  end
252
268
 
253
- Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil, :@cti_ignore_subclass_columns=>nil)
269
+ Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil, :@cti_ignore_subclass_columns=>nil, :@cti_qualify_tables=>nil)
254
270
 
255
271
  def inherited(subclass)
256
272
  ds = sti_dataset
@@ -267,7 +283,11 @@ module Sequel
267
283
  if table = cti_table_map[n.to_sym]
268
284
  columns = db.from(table).columns
269
285
  else
270
- table = subclass.implicit_table_name
286
+ table = if cti_qualify_tables && (schema = dataset.schema_and_table(table_name).first)
287
+ SQL::QualifiedIdentifier.new(schema, subclass.implicit_table_name)
288
+ else
289
+ subclass.implicit_table_name
290
+ end
271
291
  columns = check_non_connection_error(false){db.from(table).columns}
272
292
  table = nil if !columns || columns.empty?
273
293
  end
@@ -176,6 +176,7 @@ module Sequel
176
176
  delay_validate_associated_object(reflection, obj)
177
177
  if reflection.returns_array?
178
178
  public_send(reflection[:name]) << obj
179
+ obj.skip_validation_on_next_save!
179
180
  after_save_hook{public_send(reflection[:add_method], obj)}
180
181
  else
181
182
  associations[reflection[:name]] = obj
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 14
9
+ MINOR = 15
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -566,3 +566,119 @@ describe "class_table_inheritance plugin with duplicate columns" do
566
566
  end
567
567
  end
568
568
 
569
+ describe "class_table_inheritance plugin with dataset defined with QualifiedIdentifier" do
570
+ before do
571
+ @db = Sequel.mock(:numrows=>1, :autoid=>proc{|sql| 1})
572
+ def @db.supports_schema_parsing?() true end
573
+ def @db.schema(table, opts={})
574
+ {Sequel[:hr][:employees]=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
575
+ Sequel[:hr][:managers]=>[[:id, {:type=>:integer}]],
576
+ Sequel[:hr][:staff]=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
577
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
578
+ end
579
+ @db.extend_datasets do
580
+ def columns
581
+ {[Sequel[:hr][:employees]]=>[:id, :name, :kind],
582
+ [Sequel[:hr][:managers]]=>[:id],
583
+ [Sequel[:hr][:staff]]=>[:id, :manager_id],
584
+ [Sequel[:hr][:employees], Sequel[:hr][:managers]]=>[:id, :name, :kind],
585
+ [Sequel[:hr][:employees], Sequel[:hr][:staff]]=>[:id, :name, :kind, :manager_id],
586
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
587
+ end
588
+ end
589
+ end
590
+ after do
591
+ [:Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s) if Object.const_defined?(s)}
592
+ end
593
+
594
+ describe "with table_map used to qualify subclasses" do
595
+ before do
596
+ ::Employee = Class.new(Sequel::Model)
597
+ ::Employee.db = @db
598
+ ::Employee.set_dataset(Sequel[:hr][:employees])
599
+ class ::Employee
600
+ def _save_refresh; @values[:id] = 1 end
601
+ def self.columns
602
+ dataset.columns || dataset.opts[:from].first.expression.columns
603
+ end
604
+ plugin :class_table_inheritance, :table_map=>{:Manager=>Sequel[:hr][:managers],:Staff=>Sequel[:hr][:staff]}
605
+ end
606
+ class ::Manager < Employee
607
+ one_to_many :staff_members, :class=>:Staff
608
+ end
609
+ class ::Staff < Employee
610
+ many_to_one :manager
611
+ end
612
+ end
613
+
614
+ it "should handle many_to_one relationships correctly" do
615
+ Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E')
616
+ Staff.load(:manager_id=>3).manager.must_equal Manager.load(:id=>3, :name=>'E')
617
+ @db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees WHERE (id = 3) LIMIT 1']
618
+ end
619
+
620
+ it "should handle one_to_many relationships correctly" do
621
+ Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :manager_id=>3)
622
+ Manager.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :manager_id=>3)]
623
+ @db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees WHERE (employees.manager_id = 3)']
624
+ end
625
+ end
626
+
627
+ describe "without table_map or qualify_tables set" do
628
+ it "should use a non-qualified subquery in subclasses" do
629
+ def @db.schema(table, opts={})
630
+ {Sequel[:hr][:employees]=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
631
+ :managers=>[[:id, {:type=>:integer}]],
632
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
633
+ end
634
+ @db.extend_datasets do
635
+ def columns
636
+ {[Sequel[:hr][:employees]]=>[:id, :name, :kind],
637
+ [:managers]=>[:id],
638
+ [Sequel[:hr][:employees], :managers]=>[:id, :name, :kind]
639
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
640
+ end
641
+ end
642
+ ::Employee = Class.new(Sequel::Model)
643
+ ::Employee.db = @db
644
+ ::Employee.set_dataset(Sequel[:hr][:employees])
645
+ class ::Employee
646
+ def _save_refresh; @values[:id] = 1 end
647
+ def self.columns
648
+ dataset.columns || dataset.opts[:from].first.expression.columns
649
+ end
650
+ plugin :class_table_inheritance
651
+ end
652
+ class ::Manager < ::Employee
653
+ end
654
+
655
+ Employee.dataset.sql.must_equal 'SELECT * FROM hr.employees'
656
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN managers ON (managers.id = hr.employees.id)) AS employees'
657
+ end
658
+ end
659
+
660
+ describe "with qualify_tables option set" do
661
+ it "should use a subquery with the same qualifier in subclasses" do
662
+ ::Employee = Class.new(Sequel::Model)
663
+ ::Employee.db = @db
664
+ ::Employee.set_dataset(Sequel[:hr][:employees])
665
+ class ::Employee
666
+ def _save_refresh; @values[:id] = 1 end
667
+ def self.columns
668
+ dataset.columns || dataset.opts[:from].first.expression.columns
669
+ end
670
+ plugin :class_table_inheritance, :table_map=>{:Staff=>Sequel[:hr][:staff]}, qualify_tables: true
671
+ end
672
+ class ::Manager < ::Employee
673
+ one_to_many :staff_members, :class=>:Staff
674
+ end
675
+ class ::Staff < ::Employee
676
+ many_to_one :manager
677
+ end
678
+
679
+ Employee.dataset.sql.must_equal 'SELECT * FROM hr.employees'
680
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees'
681
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees'
682
+ end
683
+ end
684
+ end
@@ -11,8 +11,14 @@ describe "constant_sql_override extension" do
11
11
  @db.sqls.must_equal ["SELECT * FROM tbl WHERE (foo = CURRENT TIMESTAMP AT TIME ZONE 'UTC') LIMIT 1"]
12
12
  end
13
13
 
14
- it 'freezes the constant_sqls hash' do
14
+ it 'does not change behavior for unconfigured constants' do
15
+ @db[:tbl].where(foo: Sequel::CURRENT_TIMESTAMP).first
16
+ @db.sqls.must_equal ["SELECT * FROM tbl WHERE (foo = CURRENT_TIMESTAMP) LIMIT 1"]
17
+ end
18
+
19
+ it 'freezes the constant_sqls hash when frozen' do
15
20
  @db.freeze
16
21
  @db.constant_sqls.frozen?.must_equal true
22
+ proc{@db.set_constant_sql(Sequel::CURRENT_TIMESTAMP, "CURRENT TIMESTAMP AT TIME ZONE 'UTC'")}.must_raise RuntimeError
17
23
  end
18
24
  end
@@ -492,6 +492,24 @@ describe "NestedAttributes plugin" do
492
492
  @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 10)"]
493
493
  end
494
494
 
495
+ it "should not attempt to validate nested attributes twice for one_to_many associations when creating them" do
496
+ @Artist.nested_attributes :albums
497
+ validated = []
498
+ @Album.class_eval do
499
+ define_method(:validate) do
500
+ super()
501
+ validated << self
502
+ end
503
+ end
504
+ a = @Artist.new(:name=>'Ar', :albums_attributes=>[{:name=>'Al'}])
505
+ @db.sqls.must_equal []
506
+ validated.length.must_equal 0
507
+ a.save
508
+ validated.length.must_equal 1
509
+ check_sql_array("INSERT INTO artists (name) VALUES ('Ar')",
510
+ ["INSERT INTO albums (artist_id, name) VALUES (1, 'Al')", "INSERT INTO albums (name, artist_id) VALUES ('Al', 1)"])
511
+ end
512
+
495
513
  it "should not save if nested attribute is not valid and should include nested attribute validation errors in the main object's validation errors" do
496
514
  @Artist.class_eval do
497
515
  def validate
@@ -189,6 +189,68 @@ describe Sequel::Model, "associate" do
189
189
  o.associations.must_equal(:c=>1)
190
190
  end
191
191
 
192
+ it "should not autoreload associations when the current foreign key value is nil" do
193
+ c = Class.new(Sequel::Model(Sequel::Model.db[:c]))
194
+ c.many_to_one :c
195
+ o = c.new
196
+ o.associations[:c] = 1
197
+ o[:c_id] = 2
198
+ o.associations[:c].must_equal 1
199
+
200
+ o = c.load({})
201
+ o.associations[:c] = 1
202
+ o[:c_id] = 2
203
+ o.associations[:c].must_equal 1
204
+ end
205
+
206
+ it "should autoreload associations when the current foreign key is nil and the current associated value is nil" do
207
+ c = Class.new(Sequel::Model(Sequel::Model.db[:c]))
208
+ c.many_to_one :c
209
+ o = c.new
210
+ o.associations[:c] = nil
211
+ o[:c_id] = 2
212
+ o.associations.must_be_empty
213
+
214
+ o = c.load({})
215
+ o.associations[:c] = nil
216
+ o[:c_id] = 2
217
+ o.associations.must_be_empty
218
+ end
219
+
220
+ it "should handle autoreloading for multiple associations when the current foreign key is nil" do
221
+ c = Class.new(Sequel::Model(Sequel::Model.db[:c]))
222
+ c.many_to_one :c
223
+ c.many_to_one :d, :key=>:c_id
224
+ o = c.new
225
+ o.associations[:c] = nil
226
+ o.associations[:d] = 1
227
+ o[:c_id] = nil
228
+ o.associations.must_equal(:c=>nil, :d=>1)
229
+
230
+ o[:c_id] = 2
231
+ o.associations.must_equal(:d=>1)
232
+
233
+ o[:c_id] = 2
234
+ o.associations.must_equal(:d=>1)
235
+
236
+ o[:c_id] = nil
237
+ o.associations.must_be_empty
238
+
239
+ o = c.load({:c_id=>nil})
240
+ o.associations[:c] = nil
241
+ o.associations[:d] = 1
242
+ o[:c_id] = nil
243
+ o.associations.must_equal(:c=>nil, :d=>1)
244
+
245
+ o[:c_id] = 2
246
+ o.associations.must_equal(:d=>1)
247
+
248
+ o[:c_id] = 2
249
+ o.associations.must_equal(:d=>1)
250
+
251
+ o[:c_id] = nil
252
+ o.associations.must_be_empty
253
+ end
192
254
  end
193
255
 
194
256
  describe Sequel::Model, "many_to_one" do
@@ -170,6 +170,33 @@ describe "Model#save" do
170
170
  DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
171
171
  end
172
172
 
173
+ it "should skip validations if the skip_validation_on_save! method is used" do
174
+ @m.raise_on_save_failure = false
175
+ @m.wont_be :valid?
176
+ @m.skip_validation_on_next_save!
177
+ @m.save
178
+ DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
179
+ end
180
+
181
+ it "should not skip future validations if the skip_validation_on_save! method is used" do
182
+ @m.wont_be :valid?
183
+ @m.skip_validation_on_next_save!
184
+ @m.save
185
+ DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
186
+ proc{@m.save}.must_raise Sequel::ValidationFailed
187
+
188
+ @m.skip_validation_on_next_save!
189
+ @m.save
190
+ DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
191
+ end
192
+
193
+ it "should skip validations if the skip_validation_on_save! method is used and :validate=>true option is used" do
194
+ @m.wont_be :valid?
195
+ @m.skip_validation_on_next_save!
196
+ @m.save(:validate=>true)
197
+ DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
198
+ end
199
+
173
200
  it "should raise error if validations fail and raise_on_save_failure is true" do
174
201
  begin
175
202
  @m.save
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.14.0
4
+ version: 5.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-01 00:00:00.000000000 Z
11
+ date: 2018-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -131,8 +131,8 @@ extra_rdoc_files:
131
131
  - doc/schema_modification.rdoc
132
132
  - doc/sharding.rdoc
133
133
  - doc/testing.rdoc
134
- - doc/transactions.rdoc
135
134
  - doc/validations.rdoc
135
+ - doc/transactions.rdoc
136
136
  - doc/release_notes/4.0.0.txt
137
137
  - doc/release_notes/4.1.0.txt
138
138
  - doc/release_notes/4.10.0.txt
@@ -198,6 +198,7 @@ extra_rdoc_files:
198
198
  - doc/release_notes/5.12.0.txt
199
199
  - doc/release_notes/5.13.0.txt
200
200
  - doc/release_notes/5.14.0.txt
201
+ - doc/release_notes/5.15.0.txt
201
202
  files:
202
203
  - CHANGELOG
203
204
  - MIT-LICENSE
@@ -282,6 +283,7 @@ files:
282
283
  - doc/release_notes/5.12.0.txt
283
284
  - doc/release_notes/5.13.0.txt
284
285
  - doc/release_notes/5.14.0.txt
286
+ - doc/release_notes/5.15.0.txt
285
287
  - doc/release_notes/5.2.0.txt
286
288
  - doc/release_notes/5.3.0.txt
287
289
  - doc/release_notes/5.4.0.txt