sequel 5.13.0 → 5.14.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: bc4068290931cd51d12ec42e40a1e383ffa9cc8448501970230ec0863fc1cc21
4
- data.tar.gz: 16c549d0adcd8700bfb498bc2a459437a37c57a85d9048099336105efff376ad
3
+ metadata.gz: a950f8be723b867d5ecc782005c2374fa84a05a6b54c4ba55652f5442de1c90b
4
+ data.tar.gz: c5b2c92ee3580933b9fcd0525fdb67a79f5915f8b8e337e4731b9046f771dbf0
5
5
  SHA512:
6
- metadata.gz: 3f6acb6af9670774bf87b19349e778cb7b967ac529bbd192ea3ce2b6790080c21afa4445faabe18de148cfcef894e5457924782fb5462289dc38ff9af4ab5fcb
7
- data.tar.gz: e03cf594244024d41de805ef57bcdc545bd9bb247e34fcd48e1d6fba8c558f8dacc49828b5f529e465b9a5a6089e66adc5e1a8d43cf7029225556429aa97441b
6
+ metadata.gz: 4671d49935656b0ec907f10d3d4a6b25e29815b328b7661e55d95e4bfabbf84818de2b3550d92d86f444fbd69a561f099c859a5b1156303e10d58a7402641ebc
7
+ data.tar.gz: 5488428f7290a548344849919a7895dfd1e8d7cbe7990c2b261b4118e2a46845d9bde30b00673928511e2e650b3e4098f81cb2087d8f42227653607d3ae90c6f
data/CHANGELOG CHANGED
@@ -1,3 +1,23 @@
1
+ === 5.14.0 (2018-11-01)
2
+
3
+ * Drop defaulting the :port option to 5432 in the postgres adapter, so that setting the :service option in :driver_options works (jeremyevans) (#1558)
4
+
5
+ * Do not cache values for columns without parseable defaults when using :cache option in defaults_setter plugin (jeremyevans)
6
+
7
+ * Emulate NULLS FIRST/LAST ordering on databases that do not natively support it (jeremyevans)
8
+
9
+ * Do not modify boolean expressions created from string or array if string or array is modified (jeremyevans)
10
+
11
+ * Make roots and roots_dataset dataset methods instead of class methods in the tree plugin (JelF) (#1554)
12
+
13
+ * Do not cache dataset SQL if dataset uses subquery that cannot cache SQL (jeremyevans)
14
+
15
+ * Make Model#=== work correctly for models with composite primary keys (jeremyevans)
16
+
17
+ * Add Model#pk_equal? as a more descriptive name for Model#=== (AlexWayfer) (#1550)
18
+
19
+ * Do not push down expression inversion in cases where it may result in incorrect behavior (e.g. ANY/SOME/ALL operators) (jeremyevans) (#1549)
20
+
1
21
  === 5.13.0 (2018-10-01)
2
22
 
3
23
  * Support :single_value type in prepared statements (rintaun) (#1547)
@@ -63,6 +63,14 @@ into the Database instance:
63
63
  DB.extension :pg_array
64
64
  Sequel.extension :pg_array_ops
65
65
 
66
+ With regard to common database types, please note that the generic String type
67
+ is +text+ on PostgreSQL and not <tt>varchar(255)</tt> as it is on some other
68
+ databases. +text+ is PostgreSQL's recommended type for storage of text data,
69
+ and is more similar to Ruby's String type as it allows for unlimited length.
70
+ If you want to set a maximum size for a text column, you must specify a
71
+ <tt>:size</tt> option. This will use a <tt>varchar($size)</tt> type and
72
+ impose a maximum size for the column.
73
+
66
74
  == PostgreSQL-specific DDL Support
67
75
 
68
76
  === Exclusion Constraints
@@ -301,6 +309,18 @@ can pass an +:update+ option with a hash of values to update. You must pass eit
301
309
  DB[:table].insert_conflict(target: :a, update: {b: Sequel[:excluded][:b]}).insert(a: 1, b: 2)
302
310
  # INSERT INTO TABLE (a, b) VALUES (1, 2)
303
311
  # ON CONFLICT (a) DO UPDATE SET b = excluded.b
312
+
313
+ If you want to update existing rows but using the current value of the column, you can build
314
+ the desired calculation using <tt>Sequel[]</tt>
315
+
316
+ DB[:table]
317
+ .insert_conflict(
318
+ target: :a,
319
+ update: {b: Sequel[:excluded][:b] + Sequel[:table][:a]}
320
+ )
321
+ .import([:a, :b], [ [1, 2] ])
322
+ # INSERT INTO TABLE (a, b) VALUES (1, 2)
323
+ # ON CONFLICT (a) DO UPDATE SET b = (excluded.b + table.a)
304
324
 
305
325
  Additionally, if you only want to do the update in certain cases, you can specify an
306
326
  +:update_where+ option, which will be used as a filter. If the row doesn't match the
@@ -0,0 +1,63 @@
1
+ = New Features
2
+
3
+ * The :nulls option when creating ordered expressions is now supported
4
+ on all databases that Sequel ships support for. For databases that
5
+ do not support NULLS FIRST/NULLS LAST, support is emulated.
6
+
7
+ ds.order(Sequel.asc(:name, :nulls=>:last))
8
+ # When emulated:
9
+ # ORDER BY (CASE WHEN (name IS NULL) THEN 2 ELSE 1 END), name ASC
10
+
11
+ * Model#pk_equal? has been added as a more descriptive name for
12
+ Model#===. Model#=== is now an alias of Model#pk_equal?.
13
+
14
+ * The roots and roots_dataset class methods in the tree plugin are now
15
+ also available as dataset methods.
16
+
17
+ = Other Improvements
18
+
19
+ * Inverting expressions using the ANY/SOME/ALL SQL operators now works
20
+ correctly:
21
+
22
+ # Sequel <5.14.0
23
+ Sequel.~(:a=>Sequel.function(:any, :x))
24
+ # "(a != any(x))"
25
+
26
+ # Sequel >=5.14.0
27
+ Sequel.~(:a=>Sequel.function(:any, :x))
28
+ # "NOT (a = any(x))"
29
+
30
+ Sequel has always tried to push inversion down to create SQL that is
31
+ easier to reason about. However, inversion cannot be pushed down if
32
+ an ANY/SOME/ALL SQL operator is used, because that is a different
33
+ type of operation that just happens to use the same syntax. Sequel
34
+ now avoids inversion push down for boolean operators where the
35
+ right hand side is an SQL::Function, LiteralString, or
36
+ SQL::PlaceholderLiteralString.
37
+
38
+ * When creating a boolean expression from a hash or array of pairs, if
39
+ the right hand side is an unfrozen array and string, use a frozen
40
+ copy in the expression, so that mutating the array or string
41
+ argument later does not affect the expression.
42
+
43
+ * When using the defaults_setter plugin with the :cache option, do not
44
+ cache values for columns without parseable defaults. If the default
45
+ value exists but is not parseable, caching such values could result
46
+ in incorrect behavior if the model instance is saved later.
47
+
48
+ * For models with composite primary keys, Model#=== now returns false
49
+ if any primary key value is nil, mirroring the behavior for the
50
+ scalar primary key case.
51
+
52
+ * Model datasets no longer cache SQL if they include a subquery that
53
+ cannot cache SQL.
54
+
55
+ * The SQL used for constraints in the constraint_validations
56
+ extension when the :allow_nil option is used is now clearer and
57
+ easier to understand.
58
+
59
+ * The postgres adapter no longer specifies a default port when using
60
+ the pg driver, in order to work with configurations where the
61
+ :service option is used in the :driver_options hash. The pg driver
62
+ defaults to port 5432 if no port is given, so this should not affect
63
+ backwards compatibility.
@@ -194,7 +194,7 @@ module Sequel
194
194
  if USES_PG
195
195
  connection_params = {
196
196
  :host => opts[:host],
197
- :port => opts[:port] || 5432,
197
+ :port => opts[:port],
198
198
  :dbname => opts[:database],
199
199
  :user => opts[:user],
200
200
  :password => opts[:password],
@@ -234,6 +234,11 @@ module Sequel
234
234
  end
235
235
  end
236
236
 
237
+ # Access does not natively support NULLS FIRST/LAST.
238
+ def requires_emulating_nulls_first?
239
+ true
240
+ end
241
+
237
242
  # Access doesn't support ESCAPE for LIKE.
238
243
  def requires_like_escape?
239
244
  false
@@ -418,6 +418,11 @@ module Sequel
418
418
  false
419
419
  end
420
420
 
421
+ # At least some versions of DB do not support NULLS FIRST/LAST.
422
+ def requires_emulating_nulls_first?
423
+ true
424
+ end
425
+
421
426
  # Modify the sql to limit the number of rows returned.
422
427
  # Uses :offset_strategy Database option to determine how to format the
423
428
  # limit and offset.
@@ -1046,6 +1046,11 @@ module Sequel
1046
1046
  end
1047
1047
  end
1048
1048
 
1049
+ # MSSQL does not natively support NULLS FIRST/LAST.
1050
+ def requires_emulating_nulls_first?
1051
+ true
1052
+ end
1053
+
1049
1054
  # MSSQL supports 100-nsec precision for time columns, but ruby by
1050
1055
  # default only supports usec precision.
1051
1056
  def sqltime_precision
@@ -1008,6 +1008,11 @@ module Sequel
1008
1008
  super || key == :insert_ignore || key == :update_ignore || key == :on_duplicate_key_update
1009
1009
  end
1010
1010
 
1011
+ # MySQL does not natively support NULLS FIRST/LAST.
1012
+ def requires_emulating_nulls_first?
1013
+ true
1014
+ end
1015
+
1011
1016
  def select_only_offset_sql(sql)
1012
1017
  sql << " LIMIT "
1013
1018
  literal_append(sql, @opts[:offset])
@@ -397,6 +397,11 @@ module Sequel
397
397
  :values
398
398
  end
399
399
 
400
+ # SQLAnywhere does not natively support NULLS FIRST/LAST.
401
+ def requires_emulating_nulls_first?
402
+ true
403
+ end
404
+
400
405
  def select_into_sql(sql)
401
406
  if i = @opts[:into]
402
407
  sql << " INTO "
@@ -825,6 +825,11 @@ module Sequel
825
825
  end
826
826
  end
827
827
 
828
+ # SQLite does not natively support NULLS FIRST/LAST.
829
+ def requires_emulating_nulls_first?
830
+ true
831
+ end
832
+
828
833
  # SQLite does not support FOR UPDATE, but silently ignore it
829
834
  # instead of raising an error for compatibility with other
830
835
  # databases.
@@ -220,6 +220,11 @@ module Sequel
220
220
  true
221
221
  end
222
222
 
223
+ # Whether ORDER BY col NULLS FIRST/LAST must be emulated.
224
+ def requires_emulating_nulls_first?
225
+ false
226
+ end
227
+
223
228
  # Whether common table expressions are supported in UNION/INTERSECT/EXCEPT clauses.
224
229
  def supports_cte_in_compounds?
225
230
  supports_cte_in_subqueries?
@@ -559,13 +559,30 @@ module Sequel
559
559
 
560
560
  # Append literalization of ordered expression to SQL string.
561
561
  def ordered_expression_sql_append(sql, oe)
562
+ if emulate = requires_emulating_nulls_first?
563
+ case oe.nulls
564
+ when :first
565
+ null_order = 0
566
+ when :last
567
+ null_order = 2
568
+ end
569
+
570
+ if null_order
571
+ literal_append(sql, Sequel.case({{oe.expression=>nil}=>null_order}, 1))
572
+ sql << ", "
573
+ end
574
+ end
575
+
562
576
  literal_append(sql, oe.expression)
563
577
  sql << (oe.descending ? ' DESC' : ' ASC')
564
- case oe.nulls
565
- when :first
566
- sql << " NULLS FIRST"
567
- when :last
568
- sql << " NULLS LAST"
578
+
579
+ unless emulate
580
+ case oe.nulls
581
+ when :first
582
+ sql << " NULLS FIRST"
583
+ when :last
584
+ sql << " NULLS LAST"
585
+ end
569
586
  end
570
587
  end
571
588
 
@@ -1553,7 +1570,13 @@ module Sequel
1553
1570
 
1554
1571
  # Append literalization of the subselect to SQL string.
1555
1572
  def subselect_sql_append(sql, ds)
1556
- subselect_sql_dataset(sql, ds).sql
1573
+ sds = subselect_sql_dataset(sql, ds)
1574
+ sds.sql
1575
+ unless sds.send(:cache_sql?)
1576
+ # If subquery dataset does not allow caching SQL,
1577
+ # then this dataset should not allow caching SQL.
1578
+ disable_sql_caching!
1579
+ end
1557
1580
  end
1558
1581
 
1559
1582
  def subselect_sql_dataset(sql, ds)
@@ -355,6 +355,18 @@ module Sequel
355
355
  super
356
356
  end
357
357
 
358
+ def constraint_validation_expression(cols, allow_nil)
359
+ exprs = cols.map do |c|
360
+ expr = yield c
361
+ if allow_nil
362
+ Sequel.|({c=>nil}, expr)
363
+ else
364
+ Sequel.&(Sequel.~(c=>nil), expr)
365
+ end
366
+ end
367
+ Sequel.&(*exprs)
368
+ end
369
+
358
370
  # For the given table, generator, and validations, add constraints
359
371
  # to the generator for each of the validations, as well as adding
360
372
  # validation metadata to the constraint validations table.
@@ -365,28 +377,44 @@ module Sequel
365
377
 
366
378
  case validation_type
367
379
  when :presence
368
- string_check = columns.select{|c| generator_string_column?(generator, table, c)}.map{|c| [Sequel.trim(c), blank_string_value]}
369
- generator_add_constraint_from_validation(generator, val, (Sequel.negate(string_check) unless string_check.empty?))
380
+ strings, non_strings = columns.partition{|c| generator_string_column?(generator, table, c)}
381
+ if !non_strings.empty? && !allow_nil
382
+ non_strings_expr = Sequel.&(*non_strings.map{|c| Sequel.~(c=>nil)})
383
+ end
384
+
385
+ unless strings.empty?
386
+ strings_expr = constraint_validation_expression(strings, allow_nil){|c| Sequel.~(Sequel.trim(c) => blank_string_value)}
387
+ end
388
+
389
+ expr = if non_strings_expr && strings_expr
390
+ Sequel.&(strings_expr, non_strings_expr)
391
+ else
392
+ strings_expr || non_strings_expr
393
+ end
394
+
395
+ if expr
396
+ generator.constraint(constraint, expr)
397
+ end
370
398
  when :exact_length
371
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {Sequel.char_length(c) => arg}}))
399
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| {Sequel.char_length(c) => arg}})
372
400
  when :min_length
373
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.char_length(c) >= arg}))
401
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.char_length(c) >= arg})
374
402
  when :max_length
375
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.char_length(c) <= arg}))
403
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.char_length(c) <= arg})
376
404
  when *REVERSE_OPERATOR_MAP.keys
377
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.identifier(c).public_send(REVERSE_OPERATOR_MAP[validation_type], arg)}))
405
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.identifier(c).public_send(REVERSE_OPERATOR_MAP[validation_type], arg)})
378
406
  when :length_range
379
407
  op = arg.exclude_end? ? :< : :<=
380
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| (Sequel.char_length(c) >= arg.begin) & Sequel.char_length(c).public_send(op, arg.end)}))
408
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| (Sequel.char_length(c) >= arg.begin) & Sequel.char_length(c).public_send(op, arg.end)})
381
409
  arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
382
410
  when :format
383
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {c => arg}}))
411
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| {c => arg}})
384
412
  if arg.casefold?
385
413
  validation_type = :iformat
386
414
  end
387
415
  arg = arg.source
388
416
  when :includes
389
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {c => arg}}))
417
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| {c => arg}})
390
418
  if arg.is_a?(Range)
391
419
  if arg.begin.is_a?(Integer) && arg.end.is_a?(Integer)
392
420
  validation_type = :includes_int_range
@@ -407,7 +435,7 @@ module Sequel
407
435
  raise Error, "validates includes only supports arrays and ranges currently, cannot handle: #{arg.inspect}"
408
436
  end
409
437
  when :like, :ilike
410
- generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.public_send(validation_type, c, arg)}))
438
+ generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.public_send(validation_type, c, arg)})
411
439
  when :unique
412
440
  generator.unique(columns, :name=>constraint)
413
441
  columns = [columns.join(',')]
@@ -438,23 +466,6 @@ module Sequel
438
466
  ds.multi_insert(rows.flatten)
439
467
  end
440
468
 
441
- # Add the constraint to the generator, including a NOT NULL constraint
442
- # for all columns unless the :allow_nil option is given.
443
- def generator_add_constraint_from_validation(generator, val, cons)
444
- if val[:allow_nil]
445
- nil_cons = Sequel[val[:columns].map{|c| [c, nil]}]
446
- cons = Sequel.|(nil_cons, cons) if cons
447
- else
448
- nil_cons = Sequel.negate(val[:columns].map{|c| [c, nil]})
449
- cons = cons ? Sequel.&(nil_cons, cons) : nil_cons
450
- end
451
-
452
- if cons
453
- generator.constraint(val[:name], cons)
454
- end
455
- end
456
-
457
-
458
469
  # Introspect the generator to determine if column
459
470
  # created is a string or not.
460
471
  def generator_string_column?(generator, table, c)
@@ -20,7 +20,7 @@
20
20
  #
21
21
  module Sequel
22
22
  module Integer64
23
- # Use timestamptz by default for generic timestamp value.
23
+ # Use same type as used for :Bignum by default for generic integer value.
24
24
  def type_literal_generic_integer(column)
25
25
  type_literal_generic_bignum_symbol(column)
26
26
  end
@@ -1103,16 +1103,33 @@ module Sequel
1103
1103
  eql?(obj)
1104
1104
  end
1105
1105
 
1106
- # If pk is not nil, true only if the objects have the same class and pk.
1107
- # If pk is nil, false.
1106
+ # Case equality. By default, checks equality of the primary key value, see
1107
+ # pk_equal?.
1108
1108
  #
1109
- # Artist[1] === Artist[1] # true
1110
- # Artist.new === Artist.new # false
1111
- # Artist[1].set(:name=>'Bob') == Artist[1] # => true
1109
+ # Artist[1] === Artist[1] # => true
1110
+ # Artist.new === Artist.new # => false
1111
+ # Artist[1].set(:name=>'Bob') === Artist[1] # => true
1112
1112
  def ===(obj)
1113
- pk.nil? ? false : (obj.class == model) && (obj.pk == pk)
1113
+ case pkv = pk
1114
+ when nil
1115
+ return false
1116
+ when Array
1117
+ return false if pk.any?(&:nil?)
1118
+ end
1119
+
1120
+ (obj.class == model) && (obj.pk == pkv)
1114
1121
  end
1115
-
1122
+
1123
+ # If the receiver has a primary key value, returns true if the objects have
1124
+ # the same class and primary key value.
1125
+ # If the receiver's primary key value is nil or is an array containing
1126
+ # nil, returns false.
1127
+ #
1128
+ # Artist[1].pk_equal?(Artist[1]) # => true
1129
+ # Artist.new.pk_equal?(Artist.new) # => false
1130
+ # Artist[1].set(:name=>'Bob').pk_equal?(Artist[1]) # => true
1131
+ alias pk_equal? ===
1132
+
1116
1133
  # class is defined in Object, but it is also a keyword,
1117
1134
  # and since a lot of instance methods call class methods,
1118
1135
  # this alias makes it so you can use model instead of
@@ -116,7 +116,7 @@ module Sequel
116
116
  # Use default value for a new record if values doesn't already contain an entry for it.
117
117
  def [](k)
118
118
  if new? && !values.has_key?(k)
119
- v = model.default_values[k]
119
+ v = model.default_values.fetch(k){return}
120
120
  v = v.call if v.respond_to?(:call)
121
121
  values[k] = v if model.cache_default_values?
122
122
  v
@@ -70,6 +70,7 @@ module Sequel
70
70
  attr_reader :children_association_name
71
71
 
72
72
  Plugins.inherited_instance_variables(self, :@parent_column=>nil, :@tree_order=>nil, :@parent_association_name=>nil, :@children_association_name=>nil)
73
+ Plugins.def_dataset_methods(self, [:roots, :roots_dataset])
73
74
 
74
75
  # Should freeze tree order if it is an array when freezing the model class.
75
76
  def freeze
@@ -77,22 +78,6 @@ module Sequel
77
78
 
78
79
  super
79
80
  end
80
-
81
- # Returns list of all root nodes (those with no parent nodes).
82
- #
83
- # TreeClass.roots # => [root1, root2]
84
- def roots
85
- roots_dataset.all
86
- end
87
-
88
- # Returns the dataset for retrieval of all root nodes
89
- #
90
- # TreeClass.roots_dataset # => Sequel::Dataset instance
91
- def roots_dataset
92
- ds = where(Sequel.or(Array(parent_column).zip([])))
93
- ds = ds.order(*tree_order) if tree_order
94
- ds
95
- end
96
81
  end
97
82
 
98
83
  module InstanceMethods
@@ -154,6 +139,24 @@ module Sequel
154
139
  end
155
140
  end
156
141
 
142
+ module DatasetMethods
143
+ # Returns list of all root nodes (those with no parent nodes).
144
+ #
145
+ # TreeClass.roots # => [root1, root2]
146
+ def roots
147
+ roots_dataset.all
148
+ end
149
+
150
+ # Returns the dataset for retrieval of all root nodes
151
+ #
152
+ # TreeClass.roots_dataset # => Sequel::Dataset instance
153
+ def roots_dataset
154
+ ds = where(Sequel.or(Array(model.parent_column).zip([])))
155
+ ds = ds.order(*model.tree_order) if model.tree_order
156
+ ds
157
+ end
158
+ end
159
+
157
160
  # Plugin included when :single_root option is passed.
158
161
  module SingleRoot
159
162
  module ClassMethods
@@ -265,12 +265,12 @@ module Sequel
265
265
  # methods overlap with the standard +BooleanMethods methods+, and they only
266
266
  # make sense for integers, they are only included in +NumericExpression+.
267
267
  #
268
- # :a.sql_number & :b # "a" & "b"
269
- # :a.sql_number | :b # "a" | "b"
270
- # :a.sql_number ^ :b # "a" ^ "b"
271
- # :a.sql_number << :b # "a" << "b"
272
- # :a.sql_number >> :b # "a" >> "b"
273
- # ~:a.sql_number # ~"a"
268
+ # Sequel[:a].sql_number & :b # "a" & "b"
269
+ # Sequel[:a].sql_number | :b # "a" | "b"
270
+ # Sequel[:a].sql_number ^ :b # "a" ^ "b"
271
+ # Sequel[:a].sql_number << :b # "a" << "b"
272
+ # Sequel[:a].sql_number >> :b # "a" >> "b"
273
+ # ~Sequel[:a].sql_number # ~"a"
274
274
  module BitwiseMethods
275
275
  ComplexExpression::BITWISE_OPERATORS.each do |o|
276
276
  module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
@@ -354,9 +354,15 @@ module Sequel
354
354
  end
355
355
 
356
356
  # Return an <tt>SQL::CaseExpression</tt> created with the given arguments.
357
+ # The first argument are the <tt>WHEN</tt>/<tt>THEN</tt> conditions,
358
+ # specified as an array or a hash. The second argument is the
359
+ # <tt>ELSE</tt> default value. The third optional argument is the
360
+ # <tt>CASE</tt> expression.
357
361
  #
358
- # Sequel.case([[{a: [2,3]}, 1]], 0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
362
+ # Sequel.case({a: 1}, 0) # SQL: CASE WHEN a THEN 1 ELSE 0 END
359
363
  # Sequel.case({a: 1}, 0, :b) # SQL: CASE b WHEN a THEN 1 ELSE 0 END
364
+ # Sequel.case({{a: [2,3]} => 1}, 0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
365
+ # Sequel.case([[{a: [2,3]}, 1]], 0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
360
366
  def case(*args)
361
367
  SQL::CaseExpression.new(*args)
362
368
  end
@@ -745,10 +751,10 @@ module Sequel
745
751
  # This module includes the inequality methods (>, <, >=, <=) that are defined on objects that can be
746
752
  # used in a numeric or string context in SQL.
747
753
  #
748
- # Sequel.lit('a') > :b # a > "b"
749
- # Sequel.lit('a') < :b # a > "b"
750
- # Sequel.lit('a') >= :b # a >= "b"
751
- # Sequel.lit('a') <= :b # a <= "b"
754
+ # Sequel[:a] > :b # a > "b"
755
+ # Sequel[:a] < :b # a > "b"
756
+ # Sequel[:a] >= :b # a >= "b"
757
+ # Sequel[:a] <= :b # a <= "b"
752
758
  module InequalityMethods
753
759
  ComplexExpression::INEQUALITY_OPERATORS.each do |o|
754
760
  module_eval("def #{o}(o) BooleanExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
@@ -759,10 +765,10 @@ module Sequel
759
765
  # that are defined on objects that can be used in a numeric context in SQL
760
766
  # (+Symbol+, +LiteralString+, and +SQL::GenericExpression+).
761
767
  #
762
- # :a + :b # "a" + "b"
763
- # :a - :b # "a" - "b"
764
- # :a * :b # "a" * "b"
765
- # :a / :b # "a" / "b"
768
+ # Sequel[:a] + :b # "a" + "b"
769
+ # Sequel[:a] - :b # "a" - "b"
770
+ # Sequel[:a] * :b # "a" * "b"
771
+ # Sequel[:a] / :b # "a" / "b"
766
772
  #
767
773
  # One exception to this is if + is called with a +String+ or +StringExpression+,
768
774
  # in which case the || operator is used instead of the + operator:
@@ -1079,7 +1085,13 @@ module Sequel
1079
1085
  expr = new(:AND, expr, new(r.exclude_end? ? :< : :<=, l, r.end))
1080
1086
  end
1081
1087
  expr
1082
- when ::Array, ::Sequel::Dataset
1088
+ when ::Array
1089
+ r = r.dup.freeze unless r.frozen?
1090
+ new(:IN, l, r)
1091
+ when ::String
1092
+ r = r.dup.freeze unless r.frozen?
1093
+ new(:'=', l, r)
1094
+ when ::Sequel::Dataset
1083
1095
  new(:IN, l, r)
1084
1096
  when NegativeBooleanConstant
1085
1097
  new(:"IS NOT", l, r.constant)
@@ -1111,8 +1123,21 @@ module Sequel
1111
1123
  case op = ce.op
1112
1124
  when :AND, :OR
1113
1125
  BooleanExpression.new(OPERTATOR_INVERSIONS[op], *ce.args.map{|a| BooleanExpression.invert(a)})
1114
- else
1126
+ when :IN, :"NOT IN"
1115
1127
  BooleanExpression.new(OPERTATOR_INVERSIONS[op], *ce.args.dup)
1128
+ else
1129
+ if ce.args.length == 2
1130
+ case ce.args[1]
1131
+ when Function, LiteralString, PlaceholderLiteralString
1132
+ # Special behavior to not push down inversion in this case because doing so
1133
+ # can result in incorrect behavior for ANY/SOME/ALL operators.
1134
+ BooleanExpression.new(:NOT, ce)
1135
+ else
1136
+ BooleanExpression.new(OPERTATOR_INVERSIONS[op], *ce.args.dup)
1137
+ end
1138
+ else
1139
+ BooleanExpression.new(OPERTATOR_INVERSIONS[op], *ce.args.dup)
1140
+ end
1116
1141
  end
1117
1142
  when StringExpression, NumericExpression
1118
1143
  raise(Sequel::Error, "cannot invert #{ce.inspect}")
@@ -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 = 13
9
+ MINOR = 14
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -1411,6 +1411,10 @@ describe "Dataset#order" do
1411
1411
  @dataset.order(Sequel.asc(:name, :nulls=>:last), Sequel.desc(:price, :nulls=>:first)).sql.must_equal 'SELECT * FROM test ORDER BY name ASC NULLS LAST, price DESC NULLS FIRST'
1412
1412
  end
1413
1413
 
1414
+ it "should emulate :nulls options for asc and desc if not natively supported" do
1415
+ @dataset.with_extend{def requires_emulating_nulls_first?; true end}.order(Sequel.asc(:name, :nulls=>:last), Sequel.desc(:price, :nulls=>:first)).sql.must_equal 'SELECT * FROM test ORDER BY (CASE WHEN (name IS NULL) THEN 2 ELSE 1 END), name ASC, (CASE WHEN (price IS NULL) THEN 0 ELSE 1 END), price DESC'
1416
+ end
1417
+
1414
1418
  it "should override a previous ordering" do
1415
1419
  @dataset.order(:name).order(:stamp).sql.must_equal 'SELECT * FROM test ORDER BY stamp'
1416
1420
  end
@@ -2344,6 +2348,14 @@ describe "Dataset#from_self" do
2344
2348
  @ds.server(:blah).from_self(:alias=>:some_name).opts[:server].must_equal :blah
2345
2349
  end
2346
2350
 
2351
+ it "should work correctly when a delayed evaluation is used " do
2352
+ a = true
2353
+ ds = @ds.where(Sequel.delay{a}).from_self
2354
+ ds.sql.must_equal "SELECT * FROM (SELECT name FROM test WHERE 't' LIMIT 1) AS t1"
2355
+ a = false
2356
+ ds.sql.must_equal "SELECT * FROM (SELECT name FROM test WHERE 'f' LIMIT 1) AS t1"
2357
+ end
2358
+
2347
2359
  it "should hoist WITH clauses in current dataset if dataset doesn't support WITH in subselect" do
2348
2360
  ds = Sequel.mock.dataset
2349
2361
  ds = ds.with_extend do
@@ -54,6 +54,22 @@ describe "Blockless Ruby Filters" do
54
54
  @d.l(~~Sequel.|(:x, :y)).must_equal '(x OR y)'
55
55
  end
56
56
 
57
+ it "should not modifying boolean expression created from array if array is modified" do
58
+ a = [1]
59
+ expr = Sequel.expr(:b=>a)
60
+ @d.l(expr).must_equal '(b IN (1))'
61
+ a << 2
62
+ @d.l(expr).must_equal '(b IN (1))'
63
+ end
64
+
65
+ it "should not modifying boolean expression created from string if string is modified" do
66
+ a = '1'.dup
67
+ expr = Sequel.expr(:b=>a)
68
+ @d.l(expr).must_equal "(b = '1')"
69
+ a << '2'
70
+ @d.l(expr).must_equal "(b = '1')"
71
+ end
72
+
57
73
  it "should support = via Hash" do
58
74
  @d.l(:x => 100).must_equal '(x = 100)'
59
75
  @d.l(:x => 'a').must_equal '(x = \'a\')'
@@ -78,6 +94,12 @@ describe "Blockless Ruby Filters" do
78
94
  @d.l(~Sequel.expr(:x => false)).must_equal '(x IS NOT FALSE)'
79
95
  @d.l(~Sequel.expr(:x => nil)).must_equal '(x IS NOT NULL)'
80
96
  end
97
+
98
+ it "should use NOT for inverting boolean expressions where right hand side is function or literal strings" do
99
+ @d.l(~Sequel.expr(:x => Sequel.function(:any))).must_equal 'NOT (x = any())'
100
+ @d.l(~Sequel.expr(:x => Sequel.lit('any()'))).must_equal 'NOT (x = any())'
101
+ @d.l(~Sequel.expr(:x => Sequel.lit('any(?)', 1))).must_equal 'NOT (x = any(1))'
102
+ end
81
103
 
82
104
  it "should support = and similar operations via =~ method" do
83
105
  @d.l{x =~ 100}.must_equal '(x = 100)'
@@ -107,12 +107,38 @@ describe "constraint_validations extension" do
107
107
  sqls.must_equal ["BEGIN", "COMMIT", "CREATE TABLE foo (name varchar(255), CONSTRAINT cons CHECK ((name IS NOT NULL) AND (trim(name) != '')))"]
108
108
  end
109
109
 
110
- it "should handle multiple columns when adding validations" do
110
+ it "should handle multiple string columns when adding presence validations" do
111
111
  @db.create_table(:foo){String :name; String :bar; validate{presence [:name, :bar]}}
112
112
  sqls = @db.sqls
113
113
  parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"name", :table=>"foo")
114
114
  parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"bar", :table=>"foo")
115
- sqls.must_equal ["BEGIN", "COMMIT", "CREATE TABLE foo (name varchar(255), bar varchar(255), CHECK ((name IS NOT NULL) AND (bar IS NOT NULL) AND (trim(name) != '') AND (trim(bar) != '')))"]
115
+ sqls.must_equal ["BEGIN", "COMMIT", "CREATE TABLE foo (name varchar(255), bar varchar(255), CHECK ((name IS NOT NULL) AND (trim(name) != '') AND (bar IS NOT NULL) AND (trim(bar) != '')))"]
116
+ end
117
+
118
+ it "should handle multiple string columns when adding presence validations with :allow_nil" do
119
+ @db.create_table(:foo){String :name; String :bar; validate{presence [:name, :bar], :allow_nil=>true}}
120
+ sqls = @db.sqls
121
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"name", :table=>"foo", :allow_nil=>'t')
122
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"bar", :table=>"foo", :allow_nil=>'t')
123
+ sqls.must_equal ["BEGIN", "COMMIT", "CREATE TABLE foo (name varchar(255), bar varchar(255), CHECK (((name IS NULL) OR (trim(name) != '')) AND ((bar IS NULL) OR (trim(bar) != ''))))"]
124
+ end
125
+
126
+ it "should handle multiple string columns when adding presence validations" do
127
+ @db.create_table(:foo){String :name; Integer :x; String :bar; validate{presence [:name, :x, :bar]}}
128
+ sqls = @db.sqls
129
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"name", :table=>"foo")
130
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"x", :table=>"foo")
131
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"bar", :table=>"foo")
132
+ sqls.must_equal ["BEGIN", "COMMIT", "CREATE TABLE foo (name varchar(255), x integer, bar varchar(255), CHECK ((name IS NOT NULL) AND (trim(name) != '') AND (bar IS NOT NULL) AND (trim(bar) != '') AND (x IS NOT NULL)))"]
133
+ end
134
+
135
+ it "should handle multiple string columns when adding presence validations with :allow_nil" do
136
+ @db.create_table(:foo){String :name; Integer :x; String :bar; validate{presence [:name, :x, :bar], :allow_nil=>true}}
137
+ sqls = @db.sqls
138
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"name", :table=>"foo", :allow_nil=>'t')
139
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"x", :table=>"foo", :allow_nil=>'t')
140
+ parse_insert(sqls.slice!(1)).must_equal(:validation_type=>"presence", :column=>"bar", :table=>"foo", :allow_nil=>'t')
141
+ sqls.must_equal ["BEGIN", "COMMIT", "CREATE TABLE foo (name varchar(255), x integer, bar varchar(255), CHECK (((name IS NULL) OR (trim(name) != '')) AND ((bar IS NULL) OR (trim(bar) != ''))))"]
116
142
  end
117
143
 
118
144
  it "should handle presence validation on non-String columns" do
@@ -78,6 +78,15 @@ describe "Sequel::Plugins::DefaultsSetter" do
78
78
  o.a.must_be_same_as(o.a)
79
79
  end
80
80
 
81
+ it "should not cache default values if :cache plugin option is used and there is no default values" do
82
+ @c.plugin :defaults_setter, :cache => true
83
+ o = @c.new
84
+ o.a.must_be_nil
85
+ o.values.must_be_empty
86
+ o.a.must_be_nil
87
+ o.a.must_be_same_as(o.a)
88
+ end
89
+
81
90
  it "should not override a given value" do
82
91
  @pr.call(2)
83
92
  @c.new('a'=>3).a.must_equal 3
@@ -63,10 +63,13 @@ describe Sequel::Model, "tree plugin" do
63
63
  @c.dataset = @c.dataset.with_fetch([{:id=>1, :parent_id=>nil, :name=>'r'}])
64
64
  @c.roots.must_equal [@c.load(:id=>1, :parent_id=>nil, :name=>'r')]
65
65
  @db.sqls.must_equal ["SELECT * FROM nodes WHERE (parent_id IS NULL)"]
66
+ @c.exclude(id: 2).roots.must_equal [@c.load(:id=>1, :parent_id=>nil, :name=>'r')]
67
+ @db.sqls.must_equal ["SELECT * FROM nodes WHERE ((id != 2) AND (parent_id IS NULL))"]
66
68
  end
67
69
 
68
70
  it "should have roots_dataset be a dataset representing the tree's roots" do
69
71
  @c.roots_dataset.sql.must_equal "SELECT * FROM nodes WHERE (parent_id IS NULL)"
72
+ @c.exclude(id: 2).roots_dataset.sql.must_equal "SELECT * FROM nodes WHERE ((id != 2) AND (parent_id IS NULL))"
70
73
  end
71
74
 
72
75
  it "should have ancestors return the ancestors of the current node" do
@@ -39,6 +39,15 @@ describe "Simple Dataset operations" do
39
39
  @ds.order(:id).all.must_equal [{:id=>1, :number=>10}, {:id=>100, :number=>20}]
40
40
  end
41
41
 
42
+ it "should support ordering considering NULLS" do
43
+ @ds.insert(:number=>20)
44
+ @ds.insert(:number=>nil)
45
+ @ds.order(Sequel[:number].asc(:nulls=>:first)).select_map(:number).must_equal [nil, 10, 20]
46
+ @ds.order(Sequel[:number].asc(:nulls=>:last)).select_map(:number).must_equal [10, 20, nil]
47
+ @ds.order(Sequel[:number].desc(:nulls=>:first)).select_map(:number).must_equal [nil, 20, 10]
48
+ @ds.order(Sequel[:number].desc(:nulls=>:last)).select_map(:number).must_equal [20, 10, nil]
49
+ end
50
+
42
51
  it "should have insert return primary key value" do
43
52
  @ds.insert(:number=>20).must_equal 2
44
53
  @ds.filter(:id=>2).first[:number].must_equal 20
@@ -1709,7 +1709,9 @@ describe "Sequel::Plugins::Tree" do
1709
1709
 
1710
1710
  it "iterate top-level nodes in order" do
1711
1711
  @Node.roots_dataset.count.must_equal 5
1712
- @Node.roots.map{|p| p.name}.must_equal %w'one two three four five'
1712
+ @Node.roots.map(&:name).must_equal %w'one two three four five'
1713
+ @Node.where(:name=>%w'one two.one').roots_dataset.count.must_equal 1
1714
+ @Node.where(:name=>%w'one two.one').roots.map(&:name).must_equal %w'one'
1713
1715
  end
1714
1716
 
1715
1717
  it "should have children" do
@@ -2026,8 +2028,9 @@ describe "Sequel::Plugins::ConstraintValidations" do
2026
2028
  max_length 6, :minlen, opts.merge(:name=>:maxl2)
2027
2029
  operator :<, 'm', :exactlen, opts.merge(:name=>:lt)
2028
2030
  operator :>=, 5, :num, opts.merge(:name=>:gte)
2031
+ presence [:m1, :m2, :m3], opts.merge(:name=>:pm)
2029
2032
  end
2030
- @valid_row = {:pre=>'a', :exactlen=>'12345', :minlen=>'12345', :maxlen=>'12345', :lenrange=>'1234', :lik=>'fooabc', :ilik=>'FooABC', :inc=>'abc', :uniq=>'u', :num=>5}
2033
+ @valid_row = {:pre=>'a', :exactlen=>'12345', :minlen=>'12345', :maxlen=>'12345', :lenrange=>'1234', :lik=>'fooabc', :ilik=>'FooABC', :inc=>'abc', :uniq=>'u', :num=>5, :m1=>'a', :m2=>1, :m3=>'a'}
2031
2034
  @violations = [
2032
2035
  [:pre, [nil, '', ' ']],
2033
2036
  [:exactlen, [nil, '', '1234', '123456', 'n1234']],
@@ -2068,6 +2071,41 @@ describe "Sequel::Plugins::ConstraintValidations" do
2068
2071
  end
2069
2072
  end
2070
2073
 
2074
+ try = @valid_row.dup
2075
+ if @validation_opts[:allow_nil]
2076
+ [:m1, :m2, :m3].each do |c|
2077
+ @ds.insert(try.merge(c=>nil))
2078
+ @ds.delete
2079
+ end
2080
+ @ds.insert(try.merge(:m1=>nil, :m2=>nil))
2081
+ @ds.delete
2082
+ @ds.insert(try.merge(:m1=>nil, :m3=>nil))
2083
+ @ds.delete
2084
+ @ds.insert(try.merge(:m2=>nil, :m3=>nil))
2085
+ @ds.delete
2086
+ @ds.insert(try.merge(:m1=>nil, :m2=>nil, :m3=>nil))
2087
+ @ds.delete
2088
+ else
2089
+ [:m1, :m2, :m3].each do |c|
2090
+ proc{@ds.insert(try.merge(c=>nil))}.must_raise(Sequel::DatabaseError)
2091
+ end
2092
+ proc{@ds.insert(try.merge(:m1=>nil, :m2=>nil))}.must_raise(Sequel::DatabaseError)
2093
+ proc{@ds.insert(try.merge(:m1=>nil, :m3=>nil))}.must_raise(Sequel::DatabaseError)
2094
+ proc{@ds.insert(try.merge(:m2=>nil, :m3=>nil))}.must_raise(Sequel::DatabaseError)
2095
+ proc{@ds.insert(try.merge(:m1=>nil, :m2=>nil, :m3=>nil))}.must_raise(Sequel::DatabaseError)
2096
+ end
2097
+
2098
+ unless @db.database_type == :oracle
2099
+ [:m1, :m3].each do |c|
2100
+ proc{@ds.insert(try.merge(c=>''))}.must_raise(Sequel::DatabaseError)
2101
+ end
2102
+ proc{@ds.insert(try.merge(:m1=>'', :m3=>''))}.must_raise(Sequel::DatabaseError)
2103
+ proc{@ds.insert(try.merge(:m1=>'', :m2=>nil))}.must_raise(Sequel::DatabaseError)
2104
+ proc{@ds.insert(try.merge(:m1=>nil, :m3=>''))}.must_raise(Sequel::DatabaseError)
2105
+ proc{@ds.insert(try.merge(:m2=>nil, :m3=>''))}.must_raise(Sequel::DatabaseError)
2106
+ proc{@ds.insert(try.merge(:m1=>'', :m2=>nil, :m3=>''))}.must_raise(Sequel::DatabaseError)
2107
+ end
2108
+
2071
2109
  # Test for dropping of constraint
2072
2110
  @db.alter_table(:cv_test){validate{drop :maxl2}}
2073
2111
  @ds.insert(@valid_row.merge(:minlen=>'1234567'))
@@ -2092,6 +2130,34 @@ describe "Sequel::Plugins::ConstraintValidations" do
2092
2130
  c.new(try).wont_be :valid?
2093
2131
  end
2094
2132
  end
2133
+
2134
+ try = @valid_row.dup
2135
+ if @validation_opts[:allow_nil]
2136
+ [:m1, :m2, :m3].each do |col|
2137
+ c.new(try.merge(col=>nil)).must_be :valid?
2138
+ end
2139
+ c.new(try.merge(:m1=>nil, :m2=>nil)).must_be :valid?
2140
+ c.new(try.merge(:m1=>nil, :m3=>nil)).must_be :valid?
2141
+ c.new(try.merge(:m2=>nil, :m3=>nil)).must_be :valid?
2142
+ c.new(try.merge(:m1=>nil, :m2=>nil, :m3=>nil)).must_be :valid?
2143
+ else
2144
+ [:m1, :m2, :m3].each do |col|
2145
+ c.new(try.merge(col=>nil)).wont_be :valid?
2146
+ end
2147
+ c.new(try.merge(:m1=>nil, :m2=>nil)).wont_be :valid?
2148
+ c.new(try.merge(:m1=>nil, :m3=>nil)).wont_be :valid?
2149
+ c.new(try.merge(:m2=>nil, :m3=>nil)).wont_be :valid?
2150
+ c.new(try.merge(:m1=>nil, :m2=>nil, :m3=>nil)).wont_be :valid?
2151
+ end
2152
+ c.new(try.merge(:m1=>'', :m2=>nil)).wont_be :valid?
2153
+ c.new(try.merge(:m1=>nil, :m3=>'')).wont_be :valid?
2154
+ c.new(try.merge(:m2=>nil, :m3=>'')).wont_be :valid?
2155
+ c.new(try.merge(:m1=>'', :m2=>nil, :m3=>'')).wont_be :valid?
2156
+ [:m1, :m3].each do |col|
2157
+ c.new(try.merge(col=>'')).wont_be :valid?
2158
+ end
2159
+ c.new(try.merge(:m1=>'', :m3=>'')).wont_be :valid?
2160
+
2095
2161
  c.db.constraint_validations = nil
2096
2162
  end
2097
2163
  end
@@ -2116,6 +2182,9 @@ describe "Sequel::Plugins::ConstraintValidations" do
2116
2182
  String :inc
2117
2183
  String :uniq, :null=>false
2118
2184
  Integer :num
2185
+ String :m1
2186
+ Integer :m2
2187
+ String :m3
2119
2188
  validate(&validate_block)
2120
2189
  end
2121
2190
  end
@@ -2162,6 +2231,9 @@ describe "Sequel::Plugins::ConstraintValidations" do
2162
2231
  add_column :form, String
2163
2232
  end
2164
2233
  add_column :num, Integer
2234
+ add_column :m1, String
2235
+ add_column :m2, Integer
2236
+ add_column :m3, String
2165
2237
  validate(&validate_block)
2166
2238
  end
2167
2239
  end
@@ -1445,35 +1445,46 @@ describe Sequel::Model, "#==" do
1445
1445
  end
1446
1446
  end
1447
1447
 
1448
- describe Sequel::Model, "#===" do
1449
- it "should compare instances by class and pk if pk is not nil" do
1450
- z = Class.new(Sequel::Model)
1451
- z.columns :id, :x
1452
- y = Class.new(Sequel::Model)
1453
- y.columns :id, :x
1454
- a = z.load(:id => 1, :x => 3)
1455
- b = z.load(:id => 1, :x => 4)
1456
- c = z.load(:id => 2, :x => 3)
1457
- d = y.load(:id => 1, :x => 3)
1458
-
1459
- a.must_be :===, b
1460
- a.wont_be :===, c
1461
- a.wont_be :===, d
1462
- end
1448
+ [:===, :pk_equal?].each do |method_name|
1449
+ describe Sequel::Model, "##{method_name}" do
1450
+ it "should compare instances by class and pk if pk is not nil" do
1451
+ z = Class.new(Sequel::Model)
1452
+ z.columns :id, :x
1453
+ y = Class.new(Sequel::Model)
1454
+ y.columns :id, :x
1455
+ a = z.load(:id => 1, :x => 3)
1456
+ b = z.load(:id => 1, :x => 4)
1457
+ c = z.load(:id => 2, :x => 3)
1458
+ d = y.load(:id => 1, :x => 3)
1459
+
1460
+ a.must_be method_name, b
1461
+ a.wont_be method_name, c
1462
+ a.wont_be method_name, d
1463
+ end
1463
1464
 
1464
- it "should always be false if the primary key is nil" do
1465
- z = Class.new(Sequel::Model)
1466
- z.columns :id, :x
1467
- y = Class.new(Sequel::Model)
1468
- y.columns :id, :x
1469
- a = z.new(:x => 3)
1470
- b = z.new(:x => 4)
1471
- c = z.new(:x => 3)
1472
- d = y.new(:x => 3)
1473
-
1474
- a.wont_be :===, b
1475
- a.wont_be :===, c
1476
- a.wont_be :===, d
1465
+ it "should always be false if the primary key is nil" do
1466
+ z = Class.new(Sequel::Model)
1467
+ z.columns :id, :x
1468
+ y = Class.new(Sequel::Model)
1469
+ y.columns :id, :x
1470
+ a = z.new(:x => 3)
1471
+ b = z.new(:x => 4)
1472
+ c = z.new(:x => 3)
1473
+ d = y.new(:x => 3)
1474
+
1475
+ a.wont_be method_name, b
1476
+ a.wont_be method_name, c
1477
+ a.wont_be method_name, d
1478
+ end
1479
+
1480
+ it "should always be false if the primary key is an array containing nil" do
1481
+ z = Class.new(Sequel::Model)
1482
+ z.columns :id, :x
1483
+ z.set_primary_key [:id, :x]
1484
+ z.load(:id => nil, :x => nil).wont_be method_name, z.load(:id => nil, :x => nil)
1485
+ z.load(:id => 1, :x => nil).wont_be method_name, z.load(:id => 1, :x => nil)
1486
+ z.load(:id => nil, :x => 2).wont_be method_name, z.load(:id => nil, :x => 2)
1487
+ end
1477
1488
  end
1478
1489
  end
1479
1490
 
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.13.0
4
+ version: 5.14.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-10-01 00:00:00.000000000 Z
11
+ date: 2018-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -197,6 +197,7 @@ extra_rdoc_files:
197
197
  - doc/release_notes/5.11.0.txt
198
198
  - doc/release_notes/5.12.0.txt
199
199
  - doc/release_notes/5.13.0.txt
200
+ - doc/release_notes/5.14.0.txt
200
201
  files:
201
202
  - CHANGELOG
202
203
  - MIT-LICENSE
@@ -280,6 +281,7 @@ files:
280
281
  - doc/release_notes/5.11.0.txt
281
282
  - doc/release_notes/5.12.0.txt
282
283
  - doc/release_notes/5.13.0.txt
284
+ - doc/release_notes/5.14.0.txt
283
285
  - doc/release_notes/5.2.0.txt
284
286
  - doc/release_notes/5.3.0.txt
285
287
  - doc/release_notes/5.4.0.txt
@@ -805,7 +807,12 @@ files:
805
807
  homepage: http://sequel.jeremyevans.net
806
808
  licenses:
807
809
  - MIT
808
- metadata: {}
810
+ metadata:
811
+ bug_tracker_uri: https://github.com/jeremyevans/sequel/issues
812
+ changelog_uri: http://sequel.jeremyevans.net/rdoc/files/CHANGELOG.html
813
+ documentation_uri: http://sequel.jeremyevans.net/documentation.html
814
+ mailing_list_uri: https://groups.google.com/forum/#!forum/sequel-talk
815
+ source_code_uri: https://github.com/jeremyevans/sequel
809
816
  post_install_message:
810
817
  rdoc_options:
811
818
  - "--quiet"