sequel 5.25.0 → 5.26.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: 7a91737db8ac64d56de1b6ac6ffcd48c657b3e52cd42c310872ed7935cf1ef24
4
- data.tar.gz: c650010eaa68c3f0b086ad9d283e363c30d2c4667ffe8d660801f3d65e06f243
3
+ metadata.gz: 8c7e9baac1a3a326ffdc2506dde798441b376fecbe71594cd91efb54bc56a9a7
4
+ data.tar.gz: d051640fc5fb89053beaa43915eca9920222d7978be91bc704cde94f1b425532
5
5
  SHA512:
6
- metadata.gz: 6779429490fea9527a7b920616db24bbd10f5daab731516b396053cd9626a3b9d44413b337576c1bffa0f04e511fa090ef536b270bddc3931b88519224514596
7
- data.tar.gz: c0376415c7a8eafe79ceadc66940944d64e9261209738905790cdd8821c377eea31270026730a2cb4a17adce921c60f5519591c31fb4d62250328bb085b5cfaa
6
+ metadata.gz: 0e424392185dec97acca2092fb5b3127a6619033dd62eef683f29501ece0ecacd17ada2a2b1edf27682155c6d59633fa88a475dd1ebf4bdbf8f1641f73a566a8
7
+ data.tar.gz: 8c1ac728b94de7b5f0cf9a3df230f35732906accea64a0b61b6eba90203e49396eacc95786ad7a78bc56b3b812cda8bd94d4e8f1d0be188b9d9ae4f49792a9de
data/CHANGELOG CHANGED
@@ -1,3 +1,17 @@
1
+ === 5.26.0 (2019-11-01)
2
+
3
+ * Recognize two additional foreign key constraint violation codes on MySQL 8.0.13+ (rianmcguire) (#1657)
4
+
5
+ * Support table aliases for single-table INSERT statements on PostgreSQL 9.5+ (jeremyevans) (#1656)
6
+
7
+ * Implement Sequel::Postgres::PGRange#hash so instances work correctly in hashes (jeremyevans) (#1648)
8
+
9
+ * Make dirty plugin work correctly with typecast_on_load plugin (jeremyevans) (#1647)
10
+
11
+ * Add support for :require_modification option when setting up nested_attributes (jeremyevans)
12
+
13
+ * Add support for SQL/JSON path expressions to the pg_json_ops extension, supported by PostgreSQL 12+ (jeremyevans)
14
+
1
15
  === 5.25.0 (2019-10-01)
2
16
 
3
17
  * Fix Sequel::SQL::NumericMethods#coerce to not raise NoMethodError if super method is not defined (jeremyevans) (#1645)
@@ -578,7 +578,7 @@ A single model instance can also be fetched by specifying a condition:
578
578
  post = Post.first(title: 'hello world')
579
579
  post = Post.first{num_comments < 10}
580
580
 
581
- The dataset for a model class returns rows a model instances instead of plain hashes:
581
+ The dataset for a model class returns rows of model instances instead of plain hashes:
582
582
 
583
583
  DB[:posts].first.class # => Hash
584
584
  Post.first.class # => Post
@@ -341,12 +341,12 @@ Dataset#overriding_system_value and Dataset#overriding_user_value to use this ne
341
341
  syntax:
342
342
 
343
343
  DB.create_table(:table){primary_key :id}
344
- # Ignore the given value for id, using the identity's sequence value
344
+ # Ignore the given value for id, using the identity's sequence value.
345
345
  DB[:table].overriding_user_value.insert(:id=>1)
346
346
 
347
347
  DB.create_table(:table){primary_key :id, :identity=>:always}
348
348
  # Force the use of the given value for id, because otherwise the insert will
349
- # raise an error, since GENERATED ALWAYS was using when creating the column.
349
+ # raise an error, since GENERATED ALWAYS was used when creating the column.
350
350
  DB[:table].overriding_system_value.insert(:id=>1)
351
351
 
352
352
  === Distinct On Specific Columns
@@ -0,0 +1,35 @@
1
+ = New Features
2
+
3
+ * Support for SQL/JSON path expressions has been added to the
4
+ pg_json_ops extension. These are supported in PostgreSQL 12+.
5
+ Examples:
6
+
7
+ j = Sequel.pg_json_op(:json_column)
8
+ j.path_exists('$.foo') # (jsonb_column @? '$.foo')
9
+ j.path_match('$.foo') # (jsonb_column @@ '$.foo')
10
+ j.path_exists!('$.foo') # jsonb_path_exists(jsonb_column, '$.foo')
11
+ j.path_match!('$.foo') # jsonb_path_match(jsonb_column, '$.foo')
12
+ j.path_query('$.foo') # jsonb_path_query(jsonb_column, '$.foo')
13
+ j.path_query_array('$.foo') # jsonb_path_query_array(jsonb_column, '$.foo')
14
+ j.path_query_first('$.foo') # jsonb_path_query_first(jsonb_column, '$.foo')
15
+
16
+ * The nested_attributes method in the nested_attributes plugin now
17
+ supports a :require_modification option, which can override the
18
+ default require_modification setting for the nested objects. This
19
+ can be useful to avoid errors if multiple requests are submitted
20
+ simultaneously to delete the same nested row.
21
+
22
+ = Other Improvements
23
+
24
+ * The dirty plugin now works correctly with the typecast_on_load
25
+ plugin.
26
+
27
+ * Sequel::Postgres::PGRange#hash has been added to the pg_range
28
+ extension, allowing PGRange instances to be usable as hash keys.
29
+
30
+ * Table aliases are now supported for single table INSERT
31
+ statements on PostgreSQL 9.5+, which can make some insert_conflict
32
+ usage easier.
33
+
34
+ * Two more foreign key constraint violation types are now recognized
35
+ on MySQL 8.0.13+.
@@ -1806,6 +1806,16 @@ module Sequel
1806
1806
  end
1807
1807
  end
1808
1808
 
1809
+ # Include aliases when inserting into a single table on PostgreSQL 9.5+.
1810
+ def insert_into_sql(sql)
1811
+ sql << " INTO "
1812
+ if (f = @opts[:from]) && f.length == 1
1813
+ identifier_append(sql, server_version >= 90500 ? f.first : unaliased_identifier(f.first))
1814
+ else
1815
+ source_list_append(sql, f)
1816
+ end
1817
+ end
1818
+
1809
1819
  # Return the primary key to use for RETURNING in an INSERT statement
1810
1820
  def insert_pk
1811
1821
  if (f = opts[:from]) && !f.empty?
@@ -55,7 +55,7 @@ module Sequel
55
55
  NotNullConstraintViolation
56
56
  when 1062
57
57
  UniqueConstraintViolation
58
- when 1451, 1452
58
+ when 1451, 1452, 1216, 1217
59
59
  ForeignKeyConstraintViolation
60
60
  when 4025
61
61
  CheckConstraintViolation
@@ -12,7 +12,7 @@
12
12
  #
13
13
  # Note that wrapping JSON primitives changes the behavior for
14
14
  # JSON false and null values. Because only +false+ and +nil+
15
- # in Ruby are considered falesy, wrapping these objects results
15
+ # in Ruby are considered falsey, wrapping these objects results
16
16
  # in unexpected behavior if you use the values directly in
17
17
  # conditionals:
18
18
  #
@@ -73,6 +73,23 @@
73
73
  # j.pretty # jsonb_pretty(jsonb_column)
74
74
  # j.set(%w'0 a', :h) # jsonb_set(jsonb_column, ARRAY['0','a'], h, true)
75
75
  #
76
+ # On PostgreSQL 12+ SQL/JSON functions and operators are supported:
77
+ #
78
+ # j.path_exists('$.foo') # (jsonb_column @? '$.foo')
79
+ # j.path_match('$.foo') # (jsonb_column @@ '$.foo')
80
+ #
81
+ # j.path_exists!('$.foo') # jsonb_path_exists(jsonb_column, '$.foo')
82
+ # j.path_match!('$.foo') # jsonb_path_match(jsonb_column, '$.foo')
83
+ # j.path_query('$.foo') # jsonb_path_query(jsonb_column, '$.foo')
84
+ # j.path_query_array('$.foo') # jsonb_path_query_array(jsonb_column, '$.foo')
85
+ # j.path_query_first('$.foo') # jsonb_path_query_first(jsonb_column, '$.foo')
86
+ #
87
+ # For the PostgreSQL 12+ SQL/JSON functions, one argument is required (+path+) and
88
+ # two more arguments are optional (+vars+ and +silent+). +path+ specifies the JSON path.
89
+ # +vars+ specifies a hash or a string in JSON format of named variables to be
90
+ # substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
91
+ # errors are not suppressed.
92
+ #
76
93
  # If you are also using the pg_json extension, you should load it before
77
94
  # loading this extension. Doing so will allow you to use the #op method on
78
95
  # JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
@@ -292,6 +309,8 @@ module Sequel
292
309
  CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
293
310
  DELETE_PATH = ["(".freeze, " #- ".freeze, ")".freeze].freeze
294
311
  HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
312
+ PATH_EXISTS = ["(".freeze, " @? ".freeze, ")".freeze].freeze
313
+ PATH_MATCH = ["(".freeze, " @@ ".freeze, ")".freeze].freeze
295
314
 
296
315
  # jsonb expression for deletion of the given argument from the
297
316
  # current jsonb.
@@ -362,6 +381,95 @@ module Sequel
362
381
  self.class.new(function(:insert, wrap_input_array(path), wrap_input_jsonb(other), insert_after))
363
382
  end
364
383
 
384
+ # Returns whether the JSON path returns any item for the json object.
385
+ #
386
+ # json_op.path_exists("$.foo") # (json @? '$.foo')
387
+ def path_exists(path)
388
+ bool_op(PATH_EXISTS, path)
389
+ end
390
+
391
+ # Returns whether the JSON path returns any item for the json object.
392
+ #
393
+ # json_op.path_exists!("$.foo")
394
+ # # jsonb_path_exists(json, '$.foo')
395
+ #
396
+ # json_op.path_exists!("$.foo ? ($ > $x)", x: 2)
397
+ # # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}')
398
+ #
399
+ # json_op.path_exists!("$.foo ? ($ > $x)", {x: 2}, true)
400
+ # # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}', true)
401
+ def path_exists!(path, vars=nil, silent=nil)
402
+ Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_exists, path, vars, silent))
403
+ end
404
+
405
+ # Returns the first item of the result of JSON path predicate check for the json object.
406
+ # Returns nil if the first item is not true or false.
407
+ #
408
+ # json_op.path_match("$.foo") # (json @@ '$.foo')
409
+ def path_match(path)
410
+ bool_op(PATH_MATCH, path)
411
+ end
412
+
413
+ # Returns the first item of the result of JSON path predicate check for the json object.
414
+ # Returns nil if the first item is not true or false and silent is true.
415
+ #
416
+ # json_op.path_match!("$.foo")
417
+ # # jsonb_path_match(json, '$.foo')
418
+ #
419
+ # json_op.path_match!("$.foo ? ($ > $x)", x: 2)
420
+ # # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}')
421
+ #
422
+ # json_op.path_match!("$.foo ? ($ > $x)", {x: 2}, true)
423
+ # # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}', true)
424
+ def path_match!(path, vars=nil, silent=nil)
425
+ Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_match, path, vars, silent))
426
+ end
427
+
428
+ # Returns a set of all jsonb values specified by the JSON path
429
+ # for the json object.
430
+ #
431
+ # json_op.path_query("$.foo")
432
+ # # jsonb_path_query(json, '$.foo')
433
+ #
434
+ # json_op.path_query("$.foo ? ($ > $x)", x: 2)
435
+ # # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}')
436
+ #
437
+ # json_op.path_query("$.foo ? ($ > $x)", {x: 2}, true)
438
+ # # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}', true)
439
+ def path_query(path, vars=nil, silent=nil)
440
+ _path_function(:jsonb_path_query, path, vars, silent)
441
+ end
442
+
443
+ # Returns a jsonb array of all values specified by the JSON path
444
+ # for the json object.
445
+ #
446
+ # json_op.path_query_array("$.foo")
447
+ # # jsonb_path_query_array(json, '$.foo')
448
+ #
449
+ # json_op.path_query_array("$.foo ? ($ > $x)", x: 2)
450
+ # # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}')
451
+ #
452
+ # json_op.path_query_array("$.foo ? ($ > $x)", {x: 2}, true)
453
+ # # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}', true)
454
+ def path_query_array(path, vars=nil, silent=nil)
455
+ JSONBOp.new(_path_function(:jsonb_path_query_array, path, vars, silent))
456
+ end
457
+
458
+ # Returns the first item of the result specified by the JSON path
459
+ # for the json object.
460
+ #
461
+ # json_op.path_query_first("$.foo")
462
+ # # jsonb_path_query_first(json, '$.foo')
463
+ #
464
+ # json_op.path_query_first("$.foo ? ($ > $x)", x: 2)
465
+ # # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}')
466
+ #
467
+ # json_op.path_query_first("$.foo ? ($ > $x)", {x: 2}, true)
468
+ # # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}', true)
469
+ def path_query_first(path, vars=nil, silent=nil)
470
+ JSONBOp.new(_path_function(:jsonb_path_query_first, path, vars, silent))
471
+ end
472
+
365
473
  # Return the receiver, since it is already a JSONBOp.
366
474
  def pg_jsonb
367
475
  self
@@ -386,6 +494,22 @@ module Sequel
386
494
 
387
495
  private
388
496
 
497
+ # Internals of the jsonb SQL/JSON path functions.
498
+ def _path_function(func, path, vars, silent)
499
+ args = []
500
+ if vars
501
+ if vars.is_a?(Hash)
502
+ vars = vars.to_json
503
+ end
504
+ args << vars
505
+
506
+ unless silent.nil?
507
+ args << silent
508
+ end
509
+ end
510
+ SQL::Function.new(func, self, path, *args)
511
+ end
512
+
389
513
  # Return a placeholder literal with the given str and args, wrapped
390
514
  # in a boolean expression, used by operators that return booleans.
391
515
  def bool_op(str, other)
@@ -402,6 +402,15 @@ module Sequel
402
402
  end
403
403
  alias == eql?
404
404
 
405
+ # Make sure equal ranges have the same hash.
406
+ def hash
407
+ if @empty
408
+ @db_type.hash
409
+ else
410
+ [@begin, @end, @exclude_begin, @exclude_end, @db_type].hash
411
+ end
412
+ end
413
+
405
414
  # Allow PGRange values in case statements, where they return true if they
406
415
  # are equal to each other using eql?, or if this PGRange can be converted
407
416
  # to a Range, delegating to that range.
@@ -9,8 +9,8 @@
9
9
  # #
10
10
  #
11
11
  # As you can see, this uses single line SQL comments (--) suffixed
12
- # by a newline. This # plugin transforms all consecutive
13
- # whitespace in the comment to # a single string:
12
+ # by a newline. This plugin transforms all consecutive whitespace
13
+ # in the comment to a single string:
14
14
  #
15
15
  # ds = DB[:table].comment("Some\r\nComment Here").all
16
16
  # # SELECT * FROM table -- Some Comment Here
@@ -1069,7 +1069,7 @@ module Sequel
1069
1069
  @new = true
1070
1070
  @modified = true
1071
1071
  initialize_set(values)
1072
- _changed_columns.clear
1072
+ _clear_changed_columns(:initialize)
1073
1073
  yield self if block_given?
1074
1074
  end
1075
1075
 
@@ -1626,6 +1626,13 @@ module Sequel
1626
1626
  def _changed_columns
1627
1627
  @changed_columns ||= []
1628
1628
  end
1629
+
1630
+ # Clear the changed columns. Reason is the reason for clearing
1631
+ # the columns, and should be one of: :initialize, :refresh, :create
1632
+ # or :update.
1633
+ def _clear_changed_columns(_reason)
1634
+ _changed_columns.clear
1635
+ end
1629
1636
 
1630
1637
  # Do the deletion of the object's dataset, and check that the row
1631
1638
  # was actually deleted.
@@ -1716,7 +1723,7 @@ module Sequel
1716
1723
  # is used for reading newly inserted values from the database
1717
1724
  def _refresh(dataset)
1718
1725
  _refresh_set_values(_refresh_get(dataset) || raise(NoExistingObject, "Record not found"))
1719
- _changed_columns.clear
1726
+ _clear_changed_columns(:refresh)
1720
1727
  end
1721
1728
 
1722
1729
  # Get the row of column data from the database.
@@ -1754,7 +1761,7 @@ module Sequel
1754
1761
  @this = nil
1755
1762
  @new = false
1756
1763
  @modified = false
1757
- pk ? _save_refresh : _changed_columns.clear
1764
+ pk ? _save_refresh : _clear_changed_columns(:create)
1758
1765
  after_create
1759
1766
  true
1760
1767
  end
@@ -1771,7 +1778,7 @@ module Sequel
1771
1778
  cc.clear
1772
1779
  else
1773
1780
  columns_updated = _save_update_all_columns_hash
1774
- _changed_columns.clear
1781
+ _clear_changed_columns(:update)
1775
1782
  end
1776
1783
  else # update only the specified columns
1777
1784
  columns = Array(columns)
@@ -1798,7 +1805,7 @@ module Sequel
1798
1805
  # can be overridden to avoid the refresh.
1799
1806
  def _save_refresh
1800
1807
  _save_set_values(_refresh_get(this.server?(:default)) || raise(NoExistingObject, "Record not found"))
1801
- _changed_columns.clear
1808
+ _clear_changed_columns(:create)
1802
1809
  end
1803
1810
 
1804
1811
  # Set values to the provided hash. Called after a create,
@@ -26,6 +26,9 @@ module Sequel
26
26
  # * Model.with_pk!
27
27
  # * Model.[] # when argument is not hash or nil
28
28
  # * many_to_one association method # without dynamic callback, when primary key matches
29
+ #
30
+ # You should not use this plugin if you are using sharding and there are different
31
+ # rows for the same primary key on different shards.
29
32
  #
30
33
  # Usage:
31
34
  #
@@ -82,9 +82,11 @@ module Sequel
82
82
  end
83
83
  END
84
84
  else
85
+ # :nodoc:
85
86
  def self.csv_call(*args, opts, &block)
86
87
  CSV.send(*args, opts, &block)
87
88
  end
89
+ # :nodoc:
88
90
  end
89
91
 
90
92
  module ClassMethods
@@ -159,9 +159,9 @@ module Sequel
159
159
 
160
160
  private
161
161
 
162
- # Reset the initial values when setting values.
163
- def _refresh_set_values(hash)
164
- reset_initial_values
162
+ # Reset initial values when clearing changed columns
163
+ def _clear_changed_columns(reason)
164
+ reset_initial_values if reason == :initialize || reason == :refresh
165
165
  super
166
166
  end
167
167
 
@@ -214,12 +214,6 @@ module Sequel
214
214
  self
215
215
  end
216
216
 
217
- # Reset the initial values when initializing.
218
- def initialize_set(h)
219
- super
220
- reset_initial_values
221
- end
222
-
223
217
  # Array holding column symbols that were not present initially. This is necessary
224
218
  # to differentiate between values that were not present and values that were
225
219
  # present but equal to nil.
@@ -113,6 +113,10 @@ module Sequel
113
113
  # value, the attribute hash is ignored.
114
114
  # :remove :: Allow disassociation of nested records (can remove the associated
115
115
  # object from the parent object, but not destroy the associated object).
116
+ # :require_modification :: Whether to require modification of nested objects when
117
+ # updating or deleting them (checking that a single row was
118
+ # updated). By default, uses the default require_modification
119
+ # setting for the nested object.
116
120
  # :transform :: A proc to transform attribute hashes before they are
117
121
  # passed to associated object. Takes two arguments, the parent object and
118
122
  # the attribute hash. Uses the return value as the new attribute hash.
@@ -282,6 +286,9 @@ module Sequel
282
286
  obj = Array(public_send(reflection[:name])).find{|x| Array(x.pk).map(&:to_s) == pk}
283
287
  end
284
288
  if obj
289
+ unless (require_modification = meta[:require_modification]).nil?
290
+ obj.require_modification = require_modification
291
+ end
285
292
  attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
286
293
  if meta[:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
287
294
  nested_attributes_remove(meta, obj, :destroy=>true)
@@ -41,7 +41,9 @@ module Sequel
41
41
  # Typecast values using #load_typecast when the values are retrieved
42
42
  # from the database.
43
43
  def call(values)
44
- super.load_typecast
44
+ o = super.load_typecast
45
+ o.send(:_clear_changed_columns, :initialize)
46
+ o
45
47
  end
46
48
 
47
49
  # Freeze typecast on load columns when freezing model class.
@@ -63,7 +65,6 @@ module Sequel
63
65
  set_column_value("#{c}=", v)
64
66
  end
65
67
  end
66
- _changed_columns.clear
67
68
  self
68
69
  end
69
70
 
@@ -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 = 25
9
+ MINOR = 26
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -368,6 +368,14 @@ describe "PostgreSQL", 'INSERT ON CONFLICT' do
368
368
  @ds.insert_conflict(:constraint=>:ic_test_a_uidx, :update=>{:b=>6}, :update_where=>{Sequel[:ic_test][:b]=>4}).insert(1, 3, 4).must_be_nil
369
369
  @ds.all.must_equal [{:a=>1, :b=>5, :c=>5, :c_is_unique=>false}]
370
370
  end
371
+
372
+ it "Dataset#insert_conflict should support table aliases" do
373
+ @ds = @db[Sequel[:ic_test].as(:foo)]
374
+ @ds.insert(1, 2, 5)
375
+ proc{@ds.insert(1, 3, 4)}.must_raise Sequel::UniqueConstraintViolation
376
+ @ds.insert_conflict(:target=>:a, :update=>{:b=>Sequel[:foo][:c] + Sequel[:excluded][:c]}).insert(1, 7, 10)
377
+ @ds.all.must_equal [{:a=>1, :b=>15, :c=>5, :c_is_unique=>false}]
378
+ end
371
379
  end if DB.server_version >= 90500
372
380
 
373
381
  describe "A PostgreSQL database" do
@@ -3341,6 +3349,65 @@ describe 'PostgreSQL json type' do
3341
3349
  @db.from(jo.each_text).select_order_map(:key).must_equal %w'a b'
3342
3350
  @db.from(jo.each_text).order(:key).where(:key=>'b').get(:value).gsub(' ', '').must_match(/\{"d":\{"e":3\},"c":2\}|\{"c":2,"d":\{"e":3\}\}/)
3343
3351
 
3352
+ if DB.server_version >= 120000 && json_type == :jsonb
3353
+ @db.get(jo.path_exists('$.b.d.e')).must_equal true
3354
+ @db.get(jo.path_exists('$.b.d.f')).must_equal false
3355
+
3356
+ @db.get(jo.path_exists!('$.b.d.e')).must_equal true
3357
+ @db.get(jo.path_exists!('$.b.d.f')).must_equal false
3358
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal true
3359
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal false
3360
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 2)).must_equal true
3361
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 4)).must_equal false
3362
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal true
3363
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal false
3364
+
3365
+ @db.get(jo.path_match('$.b.d.e')).must_be_nil
3366
+ @db.get(jo.path_match('$.b.d.f')).must_be_nil
3367
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match('$.b.d.e')).must_equal true
3368
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match('$.b.d.e')).must_equal false
3369
+
3370
+ proc{@db.get(jo.path_match!('$.b.d.e'))}.must_raise(Sequel::DatabaseError)
3371
+ proc{@db.get(jo.path_match!('$.b.d.f'))}.must_raise(Sequel::DatabaseError)
3372
+ @db.get(jo.path_match!('$.b.d.e', {}, true)).must_be_nil
3373
+ @db.get(jo.path_match!('$.b.d.f', {}, true)).must_be_nil
3374
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match!('$.b.d.e')).must_equal true
3375
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match!('$.b.d.e')).must_equal false
3376
+ @db.get(jo.path_match!('$.b.d.e > $x', '{"x":2}')).must_equal true
3377
+ @db.get(jo.path_match!('$.b.d.e > $x', '{"x":4}')).must_equal false
3378
+ @db.get(jo.path_match!('$.b.d.e > $x', x: 2)).must_equal true
3379
+ @db.get(jo.path_match!('$.b.d.e > $x', x: 4)).must_equal false
3380
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 2}, false)).must_equal true
3381
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 4}, true)).must_equal false
3382
+
3383
+ @db.get(jo.path_query_first('$.b.d.e')).must_equal 3
3384
+ @db.get(jo.path_query_first('$.b.d.f')).must_be_nil
3385
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal 3
3386
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":4}')).must_be_nil
3387
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 2)).must_equal 3
3388
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 4)).must_be_nil
3389
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal 3
3390
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_be_nil
3391
+
3392
+ @db.get(jo.path_query_array('$.b.d.e')).must_equal [3]
3393
+ @db.get(jo.path_query_array('$.b.d.f')).must_equal []
3394
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal [3]
3395
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal []
3396
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 2)).must_equal [3]
3397
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 4)).must_equal []
3398
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal [3]
3399
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal []
3400
+
3401
+ @db.from(jo.path_query('$.b.d.e').as(:a, [:b])).get(:b).must_equal 3
3402
+ @db.from(jo.path_query('$.b.d.f').as(:a, [:b])).get(:b).must_be_nil
3403
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":2}').as(:a, [:b])).get(:b).must_equal 3
3404
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":4}').as(:a, [:b])).get(:b).must_be_nil
3405
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 2).as(:a, [:b])).get(:b).must_equal 3
3406
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 4).as(:a, [:b])).get(:b).must_be_nil
3407
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 2}, true).as(:a, [:b])).get(:b).must_equal 3
3408
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 4}, false).as(:a, [:b])).get(:b).must_be_nil
3409
+ end
3410
+
3344
3411
  Sequel.extension :pg_row_ops
3345
3412
  @db.create_table!(:items) do
3346
3413
  Integer :a
@@ -162,6 +162,39 @@ describe "Sequel::Plugins::Dirty" do
162
162
  @o.save
163
163
  @o.column_changes.must_equal({})
164
164
  end
165
+
166
+ it "should work with the typecast_on_load plugin" do
167
+ @c.instance_variable_set(:@db_schema, :initial=>{:type=>:integer})
168
+ @c.plugin :typecast_on_load, :initial
169
+
170
+ @o = @c.call(:initial=>'1')
171
+ @o.column_changes.must_equal({})
172
+ @o.save
173
+ @o.previous_changes.must_equal({})
174
+ end
175
+
176
+ it "should have column_changes work with the typecast_on_load in after hooks" do
177
+ @c.instance_variable_set(:@db_schema, :initial=>{:type=>:integer})
178
+ @c.plugin :typecast_on_load, :initial
179
+
180
+ @o = @c.new
181
+ @o.initial = 1
182
+ @o.column_changes.must_equal({:initial=>[nil, 1]})
183
+ column_changes_in_after_save = nil
184
+ @o.define_singleton_method(:after_save) do
185
+ column_changes_in_after_save = column_changes
186
+ super()
187
+ end
188
+ @db.fetch = {:initial=>1}
189
+ @o.save
190
+ column_changes_in_after_save.must_equal({:initial=>[nil, 1]})
191
+
192
+ @o.initial = 2
193
+ @o.column_changes.must_equal({:initial=>[1, 2]})
194
+ @o.save
195
+ column_changes_in_after_save.must_equal({:initial=>[1, 2]})
196
+ @o.previous_changes.must_equal({:initial=>[1, 2]})
197
+ end
165
198
  end
166
199
 
167
200
  describe "with existing instance" do
@@ -492,6 +492,54 @@ 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 raise a NoExistingObject error if object to be updated no longer exists, if the :require_modification=>true option is used" do
496
+ @Artist.nested_attributes :albums, :require_modification=>true, :destroy=>true
497
+ al = @Album.load(:id=>10, :name=>'Al')
498
+ ar = @Artist.load(:id=>20, :name=>'Ar')
499
+ ar.associations[:albums] = [al]
500
+ ar.set(:albums_attributes=>[{:id=>10, :name=>'L'}])
501
+ @db.sqls.must_equal []
502
+ @db.numrows = [1, 0]
503
+ proc{ar.save}.must_raise Sequel::NoExistingObject
504
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "UPDATE albums SET name = 'L' WHERE (id = 10)"]
505
+ end
506
+
507
+ it "should not raise an Error if object to be updated no longer exists, if the :require_modification=>false option is used" do
508
+ @Artist.nested_attributes :albums, :require_modification=>false, :destroy=>true
509
+ al = @Album.load(:id=>10, :name=>'Al')
510
+ ar = @Artist.load(:id=>20, :name=>'Ar')
511
+ ar.associations[:albums] = [al]
512
+ ar.set(:albums_attributes=>[{:id=>10, :name=>'L'}])
513
+ @db.sqls.must_equal []
514
+ @db.numrows = [1, 0]
515
+ ar.save
516
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "UPDATE albums SET name = 'L' WHERE (id = 10)"]
517
+ end
518
+
519
+ it "should raise a NoExistingObject error if object to be deleted no longer exists, if the :require_modification=>true option is used" do
520
+ @Artist.nested_attributes :albums, :require_modification=>true, :destroy=>true
521
+ al = @Album.load(:id=>10, :name=>'Al')
522
+ ar = @Artist.load(:id=>20, :name=>'Ar')
523
+ ar.associations[:albums] = [al]
524
+ ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
525
+ @db.sqls.must_equal []
526
+ @db.numrows = [1, 0]
527
+ proc{ar.save}.must_raise Sequel::NoExistingObject
528
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "DELETE FROM albums WHERE (id = 10)"]
529
+ end
530
+
531
+ it "should not raise an Error if object to be deleted no longer exists, if the :require_modification=>false option is used" do
532
+ @Artist.nested_attributes :albums, :require_modification=>false, :destroy=>true
533
+ al = @Album.load(:id=>10, :name=>'Al')
534
+ ar = @Artist.load(:id=>20, :name=>'Ar')
535
+ ar.associations[:albums] = [al]
536
+ ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
537
+ @db.sqls.must_equal []
538
+ @db.numrows = [1, 0]
539
+ ar.save
540
+ @db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "DELETE FROM albums WHERE (id = 10)"]
541
+ end
542
+
495
543
  it "should not attempt to validate nested attributes twice for one_to_many associations when creating them" do
496
544
  @Artist.nested_attributes :albums
497
545
  validated = []
@@ -286,4 +286,71 @@ describe "Sequel::Postgres::JSONOp" do
286
286
  it "should allow transforming JSONBHash instances into ArrayOp instances" do
287
287
  @db.literal(Sequel.pg_jsonb('a'=>1).op['a']).must_equal "('{\"a\":1}'::jsonb -> 'a')"
288
288
  end
289
+
290
+ it "#path_exists should use the @? operator" do
291
+ @l[@jb.path_exists('$')].must_equal "(j @? '$')"
292
+ end
293
+
294
+ it "#path_exists result should be a boolean expression" do
295
+ @jb.path_exists('$').must_be_kind_of Sequel::SQL::BooleanExpression
296
+ end
297
+
298
+ it "#path_match should use the @@ operator" do
299
+ @l[@jb.path_match('$')].must_equal "(j @@ '$')"
300
+ end
301
+
302
+ it "#path_match result should be a boolean expression" do
303
+ @jb.path_match('$').must_be_kind_of Sequel::SQL::BooleanExpression
304
+ end
305
+
306
+ it "#path_exists! should use the jsonb_path_exists function" do
307
+ @l[@jb.path_exists!('$')].must_equal "jsonb_path_exists(j, '$')"
308
+ @l[@jb.path_exists!('$', '{"x":2}')].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}')"
309
+ @l[@jb.path_exists!('$', x: 2)].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}')"
310
+ @l[@jb.path_exists!('$', {x: 2}, true)].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}', true)"
311
+ end
312
+
313
+ it "#path_exists! result should be a boolean expression" do
314
+ @jb.path_exists!('$').must_be_kind_of Sequel::SQL::BooleanExpression
315
+ end
316
+
317
+ it "#path_match! should use the jsonb_path_match function" do
318
+ @l[@jb.path_match!('$')].must_equal "jsonb_path_match(j, '$')"
319
+ @l[@jb.path_match!('$', '{"x":2}')].must_equal "jsonb_path_match(j, '$', '{\"x\":2}')"
320
+ @l[@jb.path_match!('$', x: 2)].must_equal "jsonb_path_match(j, '$', '{\"x\":2}')"
321
+ @l[@jb.path_match!('$', {x: 2}, true)].must_equal "jsonb_path_match(j, '$', '{\"x\":2}', true)"
322
+ end
323
+
324
+ it "#path_match! result should be a boolean expression" do
325
+ @jb.path_match!('$').must_be_kind_of Sequel::SQL::BooleanExpression
326
+ end
327
+
328
+ it "#path_query should use the jsonb_path_query function" do
329
+ @l[@jb.path_query('$')].must_equal "jsonb_path_query(j, '$')"
330
+ @l[@jb.path_query('$', '{"x":2}')].must_equal "jsonb_path_query(j, '$', '{\"x\":2}')"
331
+ @l[@jb.path_query('$', x: 2)].must_equal "jsonb_path_query(j, '$', '{\"x\":2}')"
332
+ @l[@jb.path_query('$', {x: 2}, true)].must_equal "jsonb_path_query(j, '$', '{\"x\":2}', true)"
333
+ end
334
+
335
+ it "#path_query_array should use the jsonb_path_query_array function" do
336
+ @l[@jb.path_query_array('$')].must_equal "jsonb_path_query_array(j, '$')"
337
+ @l[@jb.path_query_array('$', '{"x":2}')].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}')"
338
+ @l[@jb.path_query_array('$', x: 2)].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}')"
339
+ @l[@jb.path_query_array('$', {x: 2}, true)].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}', true)"
340
+ end
341
+
342
+ it "#path_query_array result should be a JSONBOp" do
343
+ @l[@jb.path_query_array('$').path_query_array('$')].must_equal "jsonb_path_query_array(jsonb_path_query_array(j, '$'), '$')"
344
+ end
345
+
346
+ it "#path_query_first should use the jsonb_path_query_first function" do
347
+ @l[@jb.path_query_first('$')].must_equal "jsonb_path_query_first(j, '$')"
348
+ @l[@jb.path_query_first('$', '{"x":2}')].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}')"
349
+ @l[@jb.path_query_first('$', x: 2)].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}')"
350
+ @l[@jb.path_query_first('$', {x: 2}, true)].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}', true)"
351
+ end
352
+
353
+ it "#path_query_first result should be a JSONBOp" do
354
+ @l[@jb.path_query_first('$').path_query_first('$')].must_equal "jsonb_path_query_first(jsonb_path_query_first(j, '$'), '$')"
355
+ end
289
356
  end
@@ -136,6 +136,20 @@ describe "pg_range extension" do
136
136
  s[1][1][:ruby_default].must_equal Sequel::Postgres::PGRange.new(1, 5, :exclude_end=>true, :db_type=>'int4range')
137
137
  end
138
138
 
139
+ it "should work correctly in hashes" do
140
+ h = Hash.new(1)
141
+ h[@R.new(1, 2)] = 2
142
+ h[@R.new(nil, nil, :empty => true)] = 3
143
+ h[@R.new(1, 2)].must_equal 2
144
+ h[@R.new(1, 3)].must_equal 1
145
+ h[@R.new(2, 2)].must_equal 1
146
+ h[@R.new(1, 2, :exclude_begin => true)].must_equal 1
147
+ h[@R.new(1, 2, :exclude_end => true)].must_equal 1
148
+ h[@R.new(1, 2, :db_type => :int)].must_equal 1
149
+ h[@R.new(nil, nil, :empty => true)].must_equal 3
150
+ h[@R.new(nil, nil, :empty => true, :db_type => :int)].must_equal 1
151
+ end
152
+
139
153
  describe "database typecasting" do
140
154
  before do
141
155
  @o = @R.new(1, 2, :db_type=>'int4range')
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.25.0
4
+ version: 5.26.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: 2019-10-01 00:00:00.000000000 Z
11
+ date: 2019-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -223,6 +223,7 @@ extra_rdoc_files:
223
223
  - doc/release_notes/5.23.0.txt
224
224
  - doc/release_notes/5.24.0.txt
225
225
  - doc/release_notes/5.25.0.txt
226
+ - doc/release_notes/5.26.0.txt
226
227
  files:
227
228
  - CHANGELOG
228
229
  - MIT-LICENSE
@@ -319,6 +320,7 @@ files:
319
320
  - doc/release_notes/5.23.0.txt
320
321
  - doc/release_notes/5.24.0.txt
321
322
  - doc/release_notes/5.25.0.txt
323
+ - doc/release_notes/5.26.0.txt
322
324
  - doc/release_notes/5.3.0.txt
323
325
  - doc/release_notes/5.4.0.txt
324
326
  - doc/release_notes/5.5.0.txt