sequel 5.14.0 → 5.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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