sequel 5.4.0 → 5.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31c759888ccdeebe39b3d3bd9e953c74d8584bade28bdc1bc2d6e38545c8896d
4
- data.tar.gz: dbf22a0e145192f4a50ea421a4e93a8b182472c5430d06061d710f218bd61d24
3
+ metadata.gz: 34e1f1ee3e3e1b863d4efbc3710cae912ae394a655b6e9fed41325f02d54f23e
4
+ data.tar.gz: af6f231e63231de19bb7956ea16cff29ec2faebe379a0ba86e12df933af0b91b
5
5
  SHA512:
6
- metadata.gz: 764c3d3d8856d8be93d5ee7492e331b1a56caeef4a2ad2d8a53a6c781e34650007f44a1ea0d3cb97b03ebf633842da3c67846edcfe0a0a9c7f788e15123403a9
7
- data.tar.gz: f0f32cc6e140e46a7cf91f78ace601a0c5bf7c77b87f2fcbfd236fca04090e2bc97e89f579206bb10b9bce84a340215aafe029c9923aec816f33f60aa54da80d
6
+ metadata.gz: d2f482f59fe3bb9eb19d2fa88a6fccfdf5e259cb23a52e2d21c3bb692f4f3a04339c2011922a03ceb4593b4a2708fbafdd45dbde2a882f7f77d20a3e2048e000
7
+ data.tar.gz: 19e9f635bcdb299673ab5d042159f7e76dbdee1807fd02b8e897c434e6ad11214113ac252064106507620ae3e9a19d3facb49d181ab28cc08e47c5a5b2ebfa65
data/CHANGELOG CHANGED
@@ -1,3 +1,29 @@
1
+ === 5.5.0 (2018-01-31)
2
+
3
+ * Make Database#copy_table in the postgres adapter handle errors that occur while processing rows (jeremyevans) (#1470)
4
+
5
+ * Cache results of changed_columns method in local variables in many places for better performance (jeremyevans)
6
+
7
+ * Make modification_detection plugin not break column change detection for new objects (jeremyevans) (#1468)
8
+
9
+ * Make pg_range extension set :ruby_default schema value for recognized range defaults (jeremyevans)
10
+
11
+ * Make pg_interval extension set :ruby_default schema value for recognized interval defaults (jeremyevans)
12
+
13
+ * Make pg_json extension set :callable_default schema value for empty json/jsonb array/hash defaults (jeremyevans)
14
+
15
+ * Make pg_inet extension set :ruby_default schema value for recognized inet/cidr defaults (jeremyevans)
16
+
17
+ * Make pg_hstore extension set :callable_default schema value for empty hstore defaults (jeremyevans)
18
+
19
+ * Make pg_array extension set :callable_default schema value for recognized empty array defaults (jeremyevans) (#1466)
20
+
21
+ * Make defaults_setter plugin prefer :callable_default db_schema values over :ruby_default db_schema values (jeremyevans)
22
+
23
+ * Add defaults_setter plugin :cache option for caching default values returned (jeremyevans)
24
+
25
+ * Freeze string values in hashes returned by Database#schema (jeremyevans)
26
+
1
27
  === 5.4.0 (2018-01-04)
2
28
 
3
29
  * Enable fractional seconds in timestamps on DB2 (jeremyevans) (#1463)
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2007-2008 Sharon Rosner
2
- Copyright (c) 2008-2017 Jeremy Evans
2
+ Copyright (c) 2008-2018 Jeremy Evans
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to
@@ -124,7 +124,7 @@ You can also specify optional parameters, such as the connection pool size, or l
124
124
  It is also possible to use a hash instead of a connection URL, but make sure to include the :adapter option in this case:
125
125
 
126
126
  DB = Sequel.connect(adapter: :postgres, user: 'user', password: 'password', host: 'host', port: port,
127
- database: 'database_name", max_connections: 10, logger: Logger.new('log/db.log'))
127
+ database: 'database_name', max_connections: 10, logger: Logger.new('log/db.log'))
128
128
 
129
129
  You can specify a block to connect, which will disconnect from the database after it completes:
130
130
 
@@ -0,0 +1,61 @@
1
+ = New Features
2
+
3
+ * The defaults_setter plugin now supports a :cache option, which
4
+ will cache default values in the model object's values hash:
5
+
6
+ Model.plugin :defaults_setter
7
+ o = Model.new
8
+ o.column # => 1 # default value
9
+ o.values # => {}
10
+
11
+ Model.plugin :defaults_setter, cache: true
12
+ o = Model.new
13
+ o.column # => 1 # default value
14
+ o.values # => {:column => 1}
15
+
16
+ * The pg_array extension now sets a :callable_default schema entry
17
+ for recognized empty array defaults.
18
+
19
+ * The pg_hstore extension now sets a :callable_default schema entry
20
+ for recognized empty hstore defaults.
21
+
22
+ * The pg_json extension now sets a :callable_default schema entry for
23
+ recognized empty json/jsonb array/hash defaults.
24
+
25
+ * The pg_inet extension now sets a :ruby_default schema entry for
26
+ recognized inet/cidr defaults.
27
+
28
+ * The pg_range extension now sets a :ruby_default schema entry for
29
+ recognized range defaults.
30
+
31
+ * The defaults_setter plugin will now give preference to a
32
+ :callable_default schema entry over a :ruby_default schema entry.
33
+ Combined with the other changes listed above, this makes default
34
+ values recognized by the pg_array, pg_hstore, and pg_json extensions
35
+ work well if the defaults_setter :cache option is also used.
36
+
37
+ = Other Improvements
38
+
39
+ * The modification_detection plugin no longer breaks column change
40
+ detection for new objects.
41
+
42
+ * Database#copy_table in the postgres adapter now handles errors that
43
+ occur when processing rows. Previously, an exception could be
44
+ raised on the next query in that case.
45
+
46
+ * The results of the changed_columns method are now cached in many
47
+ places internally where they are called in a loop. This results
48
+ in better performance, especially if the modification_detection or
49
+ serialization_modification_detection plugins are used.
50
+
51
+ = Backwards Compatibility
52
+
53
+ * The pg_interval extension now sets a :ruby_default schema entry for
54
+ recognized interval defaults to the same value Sequel would return
55
+ if the default value was returned. Previously, Sequel would use a
56
+ string in the :ruby_schema schema value.
57
+
58
+ * String values in hashes returned by Database#schema are now frozen
59
+ to prevent possible thread-safety issues and issues with
60
+ unintentional modification of a shared string. The hashes
61
+ themselves are not frozen and can still be modified.
@@ -309,12 +309,18 @@ module Sequel
309
309
  while buf = conn.get_copy_data
310
310
  yield buf
311
311
  end
312
- nil
312
+ b = nil
313
313
  else
314
314
  b = String.new
315
315
  b << buf while buf = conn.get_copy_data
316
- b
317
316
  end
317
+
318
+ res = conn.get_last_result
319
+ if !res || res.result_status != 1
320
+ raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved"
321
+ end
322
+
323
+ b
318
324
  rescue => e
319
325
  raise_error(e, :disconnect=>true)
320
326
  ensure
@@ -175,6 +175,10 @@ module Sequel
175
175
  if !c[:max_length] && c[:type] == :string && (max_length = column_schema_max_length(c[:db_type]))
176
176
  c[:max_length] = max_length
177
177
  end
178
+
179
+ c.each_value do |v|
180
+ v.freeze if v.is_a?(String)
181
+ end
178
182
  end
179
183
  Sequel.synchronize{@schemas[quoted_name] = cols} if cache_schema
180
184
  cols
@@ -253,6 +253,17 @@ module Sequel
253
253
  end
254
254
  end
255
255
 
256
+ # Set the :callable_default value if the default value is recognized as an empty array.
257
+ def schema_parse_table(*)
258
+ super.each do |a|
259
+ h = a[1]
260
+ if h[:default] =~ /\A(?:'\{\}'|ARRAY\[\])::([\w ]+)\[\]\z/
261
+ type = $1.freeze
262
+ h[:callable_default] = lambda{Sequel.pg_array([], type)}
263
+ end
264
+ end
265
+ end
266
+
256
267
  # Convert ruby arrays to PostgreSQL arrays when used as default values.
257
268
  def column_definition_default_sql(sql, column)
258
269
  if (d = column[:default]) && d.is_a?(Array) && !Sequel.condition_specifier?(d)
@@ -154,6 +154,16 @@ module Sequel
154
154
  db_type == 'hstore' ? :hstore : super
155
155
  end
156
156
 
157
+ # Set the :callable_default value if the default value is recognized as an empty hstore.
158
+ def schema_parse_table(*)
159
+ super.each do |a|
160
+ h = a[1]
161
+ if h[:type] == :hstore && h[:default] =~ /\A''::hstore\z/
162
+ h[:callable_default] = lambda{HStore.new({})}
163
+ end
164
+ end
165
+ end
166
+
157
167
  # Typecast value correctly to HStore. If already an
158
168
  # HStore instance, return as is. If a hash, return
159
169
  # an HStore version of it. If a string, assume it is
@@ -85,6 +85,16 @@ module Sequel
85
85
  end
86
86
  end
87
87
 
88
+ # Set the :ruby_default value if the default value is recognized as an ip address.
89
+ def schema_parse_table(*)
90
+ super.each do |a|
91
+ h = a[1]
92
+ if h[:type] == :ipaddr && h[:default] =~ /\A'([:a-fA-F0-9\.\/]+)'::(?:inet|cidr)\z/
93
+ h[:ruby_default] = IPAddr.new($1)
94
+ end
95
+ end
96
+ end
97
+
88
98
  # Typecast the given value to an IPAddr object.
89
99
  def typecast_value_ipaddr(value)
90
100
  case value
@@ -146,6 +146,16 @@ module Sequel
146
146
  end
147
147
  end
148
148
 
149
+ # Set the :ruby_default value if the default value is recognized as an interval.
150
+ def schema_parse_table(*)
151
+ super.each do |a|
152
+ h = a[1]
153
+ if h[:type] == :interval && h[:default] =~ /\A'([\w ]+)'::interval\z/
154
+ h[:ruby_default] = PARSER.call($1)
155
+ end
156
+ end
157
+ end
158
+
149
159
  # Typecast value correctly to an ActiveSupport::Duration instance.
150
160
  # If already an ActiveSupport::Duration, return it.
151
161
  # If a numeric argument is given, assume it represents a number
@@ -214,6 +214,30 @@ module Sequel
214
214
  end
215
215
  end
216
216
 
217
+ # Set the :callable_default value if the default value is recognized as an empty json/jsonb array/hash.
218
+ def schema_parse_table(*)
219
+ super.each do |a|
220
+ h = a[1]
221
+ if (h[:type] == :json || h[:type] == :jsonb) && h[:default] =~ /\A'(\{\}|\[\])'::jsonb?\z/
222
+ is_array = $1 == '[]'
223
+
224
+ klass = if h[:type] == :json
225
+ if is_array
226
+ JSONArray
227
+ else
228
+ JSONHash
229
+ end
230
+ elsif is_array
231
+ JSONBArray
232
+ else
233
+ JSONBHash
234
+ end
235
+
236
+ h[:callable_default] = lambda{klass.new(is_array ? [] : {})}
237
+ end
238
+ end
239
+ end
240
+
217
241
  # Convert the value given to a JSONArray or JSONHash
218
242
  def typecast_value_json(value)
219
243
  case value
@@ -262,6 +262,20 @@ module Sequel
262
262
  end
263
263
  end
264
264
 
265
+ # Set the :ruby_default value if the default value is recognized as a range.
266
+ def schema_parse_table(*)
267
+ super.each do |a|
268
+ h = a[1]
269
+ db_type = h[:db_type]
270
+ if @pg_range_schema_types[db_type] && h[:default] =~ /\A'([^']+)'::#{db_type}\z/
271
+ default = $1
272
+ if convertor = conversion_procs[h[:oid]]
273
+ h[:ruby_default] = convertor.call($1)
274
+ end
275
+ end
276
+ end
277
+ end
278
+
265
279
  # Typecast value correctly to a PGRange. If already an
266
280
  # PGRange instance with the same db_type, return as is.
267
281
  # If a PGRange with a different subtype, return a new
@@ -1060,7 +1060,7 @@ module Sequel
1060
1060
  @new = true
1061
1061
  @modified = true
1062
1062
  initialize_set(values)
1063
- changed_columns.clear
1063
+ _changed_columns.clear
1064
1064
  yield self if block_given?
1065
1065
  end
1066
1066
 
@@ -1137,9 +1137,9 @@ module Sequel
1137
1137
  # a.name = 'Bob'
1138
1138
  # a.changed_columns # => [:name]
1139
1139
  def changed_columns
1140
- @changed_columns ||= []
1140
+ _changed_columns
1141
1141
  end
1142
-
1142
+
1143
1143
  # Deletes and returns +self+. Does not run destroy hooks.
1144
1144
  # Look into using +destroy+ instead.
1145
1145
  #
@@ -1212,7 +1212,7 @@ module Sequel
1212
1212
  # errors, or dataset.
1213
1213
  def freeze
1214
1214
  values.freeze
1215
- changed_columns.freeze
1215
+ _changed_columns.freeze
1216
1216
  unless errors.frozen?
1217
1217
  validate
1218
1218
  errors.freeze
@@ -1316,9 +1316,7 @@ module Sequel
1316
1316
  # a.modified!(:name)
1317
1317
  # a.name.gsub!(/[aeou]/, 'i')
1318
1318
  def modified!(column=nil)
1319
- if column && !changed_columns.include?(column)
1320
- changed_columns << column
1321
- end
1319
+ _add_changed_column(column) if column
1322
1320
  @modified = true
1323
1321
  end
1324
1322
 
@@ -1583,6 +1581,17 @@ module Sequel
1583
1581
 
1584
1582
  private
1585
1583
 
1584
+ # Add a column as a changed column.
1585
+ def _add_changed_column(column)
1586
+ cc = _changed_columns
1587
+ cc << column unless cc.include?(column)
1588
+ end
1589
+
1590
+ # Internal changed_columns method that just returns stored array.
1591
+ def _changed_columns
1592
+ @changed_columns ||= []
1593
+ end
1594
+
1586
1595
  # Do the deletion of the object's dataset, and check that the row
1587
1596
  # was actually deleted.
1588
1597
  def _delete
@@ -1672,7 +1681,7 @@ module Sequel
1672
1681
  # is used for reading newly inserted values from the database
1673
1682
  def _refresh(dataset)
1674
1683
  _refresh_set_values(_refresh_get(dataset) || raise(NoExistingObject, "Record not found"))
1675
- changed_columns.clear
1684
+ _changed_columns.clear
1676
1685
  end
1677
1686
 
1678
1687
  # Get the row of column data from the database.
@@ -1710,7 +1719,7 @@ module Sequel
1710
1719
  @this = nil
1711
1720
  @new = false
1712
1721
  @modified = false
1713
- pk ? _save_refresh : changed_columns.clear
1722
+ pk ? _save_refresh : _changed_columns.clear
1714
1723
  after_create
1715
1724
  true
1716
1725
  end
@@ -1721,16 +1730,18 @@ module Sequel
1721
1730
  before_update
1722
1731
  columns = opts[:columns]
1723
1732
  if columns.nil?
1724
- columns_updated = if opts[:changed]
1725
- @values.reject{|k,v| !changed_columns.include?(k)}
1733
+ if opts[:changed]
1734
+ cc = changed_columns
1735
+ columns_updated = @values.reject{|k,v| !cc.include?(k)}
1736
+ cc.clear
1726
1737
  else
1727
- _save_update_all_columns_hash
1738
+ columns_updated = _save_update_all_columns_hash
1739
+ _changed_columns.clear
1728
1740
  end
1729
- changed_columns.clear
1730
1741
  else # update only the specified columns
1731
1742
  columns = Array(columns)
1732
1743
  columns_updated = @values.reject{|k, v| !columns.include?(k)}
1733
- changed_columns.reject!{|c| columns.include?(c)}
1744
+ _changed_columns.reject!{|c| columns.include?(c)}
1734
1745
  end
1735
1746
  _update_columns(columns_updated)
1736
1747
  @this = nil
@@ -1752,7 +1763,7 @@ module Sequel
1752
1763
  # can be overridden to avoid the refresh.
1753
1764
  def _save_refresh
1754
1765
  _save_set_values(_refresh_get(this.server?(:default)) || raise(NoExistingObject, "Record not found"))
1755
- changed_columns.clear
1766
+ _changed_columns.clear
1756
1767
  end
1757
1768
 
1758
1769
  # Set values to the provided hash. Called after a create,
@@ -1769,7 +1780,8 @@ module Sequel
1769
1780
  # to their existing values.
1770
1781
  def _save_update_all_columns_hash
1771
1782
  v = Hash[@values]
1772
- Array(primary_key).each{|x| v.delete(x) unless changed_columns.include?(x)}
1783
+ cc = changed_columns
1784
+ Array(primary_key).each{|x| v.delete(x) unless cc.include?(x)}
1773
1785
  v
1774
1786
  end
1775
1787
 
@@ -1847,8 +1859,7 @@ module Sequel
1847
1859
 
1848
1860
  # Change the value of the column to given value, recording the change.
1849
1861
  def change_column_value(column, value)
1850
- cc = changed_columns
1851
- cc << column unless cc.include?(column)
1862
+ _add_changed_column(column)
1852
1863
  @values[column] = value
1853
1864
  end
1854
1865
 
@@ -23,6 +23,24 @@ module Sequel
23
23
  #
24
24
  # Album.default_values[:a] = lambda{Date.today}
25
25
  # Album.new.a # => Date.today
26
+ #
27
+ # By default, default values returned are not cached:
28
+ #
29
+ # Album.new.a.equal?(Album.new.a) # => false
30
+ #
31
+ # However, you can turn on caching of default values:
32
+ #
33
+ # Album.plugin :defaults_setter, cache: true
34
+ # Album.new.a.equal?(Album.new.a) # => false
35
+ #
36
+ # Note that if the cache is turned on, the cached values are stored in
37
+ # the values hash:
38
+ #
39
+ # Album.plugin :defaults_setter, cache: true
40
+ # album = Album.new
41
+ # album.values # => {}
42
+ # album.a
43
+ # album.values # => {:a => Date.today}
26
44
  #
27
45
  # Usage:
28
46
  #
@@ -32,22 +50,31 @@ module Sequel
32
50
  # # Make the Album class set defaults
33
51
  # Album.plugin :defaults_setter
34
52
  module DefaultsSetter
35
- # Set the default values based on the model schema
36
- def self.configure(model)
37
- model.send(:set_default_values)
53
+ # Set the default values based on the model schema. Options:
54
+ # :cache :: Cache default values returned in the model's values hash.
55
+ def self.configure(model, opts=OPTS)
56
+ model.instance_exec do
57
+ set_default_values
58
+ @cache_default_values = opts[:cache] if opts.has_key?(:cache)
59
+ end
38
60
  end
39
61
 
40
62
  module ClassMethods
41
- # The default values to set in initialize for this model. A hash with column symbol
63
+ # The default values to use for this model. A hash with column symbol
42
64
  # keys and default values. If the default values respond to +call+, it will be called
43
65
  # to get the value, otherwise the value will be used directly. You can manually modify
44
66
  # this hash to set specific default values, by default the ones will be parsed from the database.
45
67
  attr_reader :default_values
46
-
68
+
47
69
  Plugins.after_set_dataset(self, :set_default_values)
48
70
 
49
- Plugins.inherited_instance_variables(self, :@default_values=>:dup)
71
+ Plugins.inherited_instance_variables(self, :@default_values=>:dup, :@cache_default_values=>nil)
50
72
 
73
+ # Whether default values should be cached in the values hash after being retrieved.
74
+ def cache_default_values?
75
+ @cache_default_values
76
+ end
77
+
51
78
  # Freeze default values when freezing model class
52
79
  def freeze
53
80
  @default_values.freeze
@@ -59,7 +86,15 @@ module Sequel
59
86
  # Parse the cached database schema for this model and set the default values appropriately.
60
87
  def set_default_values
61
88
  h = {}
62
- @db_schema.each{|k, v| h[k] = convert_default_value(v[:ruby_default]) unless v[:ruby_default].nil?} if @db_schema
89
+ if @db_schema
90
+ @db_schema.each do |k, v|
91
+ if v[:callable_default]
92
+ h[k] = v[:callable_default]
93
+ elsif !v[:ruby_default].nil?
94
+ h[k] = convert_default_value(v[:ruby_default])
95
+ end
96
+ end
97
+ end
63
98
  @default_values = h.merge!(@default_values || {})
64
99
  end
65
100
 
@@ -82,7 +117,9 @@ module Sequel
82
117
  def [](k)
83
118
  if new? && !values.has_key?(k)
84
119
  v = model.default_values[k]
85
- v.respond_to?(:call) ? v.call : v
120
+ v = v.call if v.respond_to?(:call)
121
+ values[k] = v if model.cache_default_values?
122
+ v
86
123
  else
87
124
  super
88
125
  end
@@ -137,7 +137,7 @@ module Sequel
137
137
  # name.gsub(/i/i, 'o')
138
138
  # column_change(:name) # => ['Initial', 'onotoal']
139
139
  def will_change_column(column)
140
- changed_columns << column unless changed_columns.include?(column)
140
+ _add_changed_column(column)
141
141
  check_missing_initial_value(column)
142
142
 
143
143
  value = if initial_values.has_key?(column)
@@ -186,7 +186,7 @@ module Sequel
186
186
  initial = iv[column]
187
187
  super
188
188
  if value == initial
189
- changed_columns.delete(column) unless missing_initial_values.include?(column)
189
+ _changed_columns.delete(column) unless missing_initial_values.include?(column)
190
190
  iv.delete(column)
191
191
  end
192
192
  else
@@ -58,13 +58,20 @@ module Sequel
58
58
  # Detect which columns have been modified by comparing the cached hash
59
59
  # value to the hash of the current value.
60
60
  def changed_columns
61
- cc = super
62
- changed = []
63
- v = @values
61
+ changed = super
64
62
  if vh = @values_hashes
65
- (vh.keys - cc).each{|c| changed << c unless v.has_key?(c) && vh[c] == v[c].hash}
63
+ values = @values
64
+ changed = changed.dup if frozen?
65
+ vh.each do |c, v|
66
+ match = values.has_key?(c) && v == values[c].hash
67
+ if changed.include?(c)
68
+ changed.delete(c) if match
69
+ else
70
+ changed << c unless match
71
+ end
72
+ end
66
73
  end
67
- cc + changed
74
+ changed
68
75
  end
69
76
 
70
77
  private
@@ -75,7 +75,8 @@ module Sequel
75
75
  # it to be assigned.
76
76
  def _save_update_all_columns_hash
77
77
  v = @values.dup
78
- Array(primary_key).each{|x| v.delete(x) unless changed_columns.include?(x)}
78
+ cc = changed_columns
79
+ Array(primary_key).each{|x| v.delete(x) unless cc.include?(x)}
79
80
  v.delete(model.lock_column)
80
81
  v
81
82
  end
@@ -155,8 +155,9 @@ module Sequel
155
155
  end
156
156
  end
157
157
  define_method("#{column}=") do |v|
158
- if !changed_columns.include?(column) && (new? || get_column_value(column) != v)
159
- changed_columns << column
158
+ cc = changed_columns
159
+ if !cc.include?(column) && (new? || get_column_value(column) != v)
160
+ cc << column
160
161
 
161
162
  will_change_column(column) if respond_to?(:will_change_column)
162
163
  end
@@ -63,7 +63,7 @@ module Sequel
63
63
  set_column_value("#{c}=", v)
64
64
  end
65
65
  end
66
- changed_columns.clear
66
+ _changed_columns.clear
67
67
  self
68
68
  end
69
69
 
@@ -252,7 +252,8 @@ module Sequel
252
252
  atts.each do |a|
253
253
  arr = Array(a)
254
254
  next if arr.any?{|x| errors.on(x)}
255
- next if opts.fetch(:only_if_modified, true) && !new? && !arr.any?{|x| changed_columns.include?(x)}
255
+ cc = changed_columns
256
+ next if opts.fetch(:only_if_modified, true) && !new? && !arr.any?{|x| cc.include?(x)}
256
257
  ds = opts[:dataset] || model.dataset
257
258
  ds = if where
258
259
  where.call(ds, self, arr)
@@ -5,7 +5,7 @@ module Sequel
5
5
  MAJOR = 5
6
6
  # The minor version of Sequel. Bumped for every non-patch level
7
7
  # release, generally around once a month.
8
- MINOR = 4
8
+ MINOR = 5
9
9
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
10
10
  # releases that fix regressions from previous versions.
11
11
  TINY = 0
@@ -523,11 +523,6 @@ describe "A PostgreSQL dataset" do
523
523
  @db[:atest].get(Sequel.extract(:year, :t)).must_equal 2010
524
524
  end
525
525
 
526
- it "should be able to parse the default value for an interval type" do
527
- @db.create_table!(:atest){interval :t, :default=>'1 week'}
528
- @db.schema(:atest).first.last[:ruby_default].must_equal '7 days'
529
- end
530
-
531
526
  it "should have #transaction support various types of synchronous options" do
532
527
  @db.transaction(:synchronous=>:on){}
533
528
  @db.transaction(:synchronous=>true){}
@@ -1877,6 +1872,11 @@ if uses_pg_or_jdbc && DB.server_version >= 90000
1877
1872
  e.wrapped_exception.must_be_kind_of ArgumentError
1878
1873
  e.message.must_include "foo"
1879
1874
  end
1875
+
1876
+ it "should handle errors raised during row processing" do
1877
+ proc{@db.copy_table(@db[:test_copy].select(Sequel[1]/(Sequel[:x] - 3)))}.must_raise Sequel::DatabaseError
1878
+ @db.get(1).must_equal 1
1879
+ end
1880
1880
  end
1881
1881
  end
1882
1882
 
@@ -2362,6 +2362,19 @@ describe 'PostgreSQL array handling' do
2362
2362
  c.where(:i=>o2.i, :f=>o2.f, :d=>o2.d, :t=>o2.t).all.must_equal [o]
2363
2363
  end
2364
2364
 
2365
+ it 'with empty array default values and defaults_setter plugin' do
2366
+ @db.create_table!(:items) do
2367
+ column :n, 'integer[]', :default=>[]
2368
+ end
2369
+ c = Class.new(Sequel::Model(@db[:items]))
2370
+ c.plugin :defaults_setter, :cache=>true
2371
+ o = c.new
2372
+ o.n.class.must_equal(Sequel::Postgres::PGArray)
2373
+ o.n.to_a.must_be_same_as(o.n.to_a)
2374
+ o.n << 1
2375
+ o.save.n.must_equal [1]
2376
+ end
2377
+
2365
2378
  it 'operations/functions with pg_array_ops' do
2366
2379
  Sequel.extension :pg_array_ops
2367
2380
  @db.create_table!(:items){column :i, 'integer[]'; column :i2, 'integer[]'; column :i3, 'integer[]'; column :i4, 'integer[]'; column :i5, 'integer[]'}
@@ -2599,6 +2612,19 @@ describe 'PostgreSQL hstore handling' do
2599
2612
  c.filter(:mtm_items=>c.filter(:id=>o.id)).all.must_equal [o]
2600
2613
  end
2601
2614
 
2615
+ it 'with empty hstore default values and defaults_setter plugin' do
2616
+ @db.create_table!(:items) do
2617
+ hstore :h, :default=>Sequel.hstore({})
2618
+ end
2619
+ c = Class.new(Sequel::Model(@db[:items]))
2620
+ c.plugin :defaults_setter, :cache=>true
2621
+ o = c.new
2622
+ o.h.class.must_equal(Sequel::Postgres::HStore)
2623
+ o.h.to_hash.must_be_same_as(o.h.to_hash)
2624
+ o.h['a'] = 'b'
2625
+ o.save.h.must_equal('a'=>'b')
2626
+ end
2627
+
2602
2628
  it 'operations/functions with pg_hstore_ops' do
2603
2629
  Sequel.extension :pg_hstore_ops, :pg_array_ops
2604
2630
  @db.create_table!(:items){hstore :h1; hstore :h2; hstore :h3; String :t}
@@ -2695,11 +2721,10 @@ describe 'PostgreSQL json type' do
2695
2721
  json_types.each do |json_type|
2696
2722
  json_array_type = "#{json_type}[]"
2697
2723
  pg_json = lambda{|v| Sequel.send(:"pg_#{json_type}", v)}
2724
+ hash_class = json_type == :jsonb ? Sequel::Postgres::JSONBHash : Sequel::Postgres::JSONHash
2725
+ array_class = json_type == :jsonb ? Sequel::Postgres::JSONBArray : Sequel::Postgres::JSONArray
2698
2726
 
2699
2727
  it 'insert and retrieve json values' do
2700
- hash_class = json_type == :jsonb ? Sequel::Postgres::JSONBHash : Sequel::Postgres::JSONHash
2701
- array_class = json_type == :jsonb ? Sequel::Postgres::JSONBArray : Sequel::Postgres::JSONArray
2702
-
2703
2728
  @db.create_table!(:items){column :j, json_type}
2704
2729
  @ds.insert(pg_json.call(@h))
2705
2730
  @ds.count.must_equal 1
@@ -2753,6 +2778,25 @@ describe 'PostgreSQL json type' do
2753
2778
  c.create(:h=>pg_json.call(@a)).h.must_equal @a
2754
2779
  end
2755
2780
 
2781
+ it 'with empty json default values and defaults_setter plugin' do
2782
+ @db.create_table!(:items) do
2783
+ column :h, json_type, :default=>hash_class.new({})
2784
+ column :a, json_type, :default=>array_class.new([])
2785
+ end
2786
+ c = Class.new(Sequel::Model(@db[:items]))
2787
+ c.plugin :defaults_setter, :cache=>true
2788
+ o = c.new
2789
+ o.h.class.must_equal(hash_class)
2790
+ o.a.class.must_equal(array_class)
2791
+ o.h.to_hash.must_be_same_as(o.h.to_hash)
2792
+ o.a.to_a.must_be_same_as(o.a.to_a)
2793
+ o.h['a'] = 'b'
2794
+ o.a << 1
2795
+ o.save
2796
+ o.h.must_equal('a'=>'b')
2797
+ o.a.must_equal([1])
2798
+ end
2799
+
2756
2800
  it 'use json in bound variables' do
2757
2801
  @db.create_table!(:items){column :i, json_type}
2758
2802
  @ds.call(:insert, {:i=>pg_json.call(@h)}, {:i=>:$i})
@@ -2956,6 +3000,15 @@ describe 'PostgreSQL inet/cidr types' do
2956
3000
  @ds.filter(:i=>:$i, :c=>:$c, :m=>:$m).call(:delete, :i=>[@ipv4], :c=>[@ipv4nm], :m=>['12:34:56:78:90:ab']).must_equal 1
2957
3001
  end if uses_pg_or_jdbc
2958
3002
 
3003
+ it 'parse default values for schema' do
3004
+ @db.create_table!(:items) do
3005
+ inet :i, :default=>IPAddr.new('127.0.0.1')
3006
+ cidr :c, :default=>IPAddr.new('127.0.0.1')
3007
+ end
3008
+ @db.schema(:items)[0][1][:ruby_default].must_equal IPAddr.new('127.0.0.1')
3009
+ @db.schema(:items)[1][1][:ruby_default].must_equal IPAddr.new('127.0.0.1')
3010
+ end
3011
+
2959
3012
  it 'with models' do
2960
3013
  @db.create_table!(:items) do
2961
3014
  primary_key :id
@@ -3113,6 +3166,15 @@ describe 'PostgreSQL range types' do
3113
3166
  @ds.filter(h).call(:delete, @ra).must_equal 1
3114
3167
  end if uses_pg_or_jdbc
3115
3168
 
3169
+ it 'parse default values for schema' do
3170
+ @db.create_table!(:items) do
3171
+ Integer :j
3172
+ int4range :i, :default=>1..4
3173
+ end
3174
+ @db.schema(:items)[0][1][:ruby_default].must_be_nil
3175
+ @db.schema(:items)[1][1][:ruby_default].must_equal Sequel::Postgres::PGRange.new(1, 5, :exclude_end=>true, :db_type=>'int4range')
3176
+ end
3177
+
3116
3178
  it 'with models' do
3117
3179
  @db.create_table!(:items){primary_key :id; int4range :i4; int8range :i8; numrange :n; daterange :d; tsrange :t; tstzrange :tz}
3118
3180
  c = Class.new(Sequel::Model(@db[:items]))
@@ -3282,6 +3344,15 @@ describe 'PostgreSQL interval types' do
3282
3344
  @ds.filter(:i=>:$i).call(:delete, :i=>[d]).must_equal 1
3283
3345
  end if uses_pg_or_jdbc
3284
3346
 
3347
+ it 'parse default values for schema' do
3348
+ @db.create_table!(:items) do
3349
+ Integer :j
3350
+ interval :i, :default=>ActiveSupport::Duration.new(3*86400, :days=>3)
3351
+ end
3352
+ @db.schema(:items)[0][1][:ruby_default].must_be_nil
3353
+ @db.schema(:items)[1][1][:ruby_default].must_equal ActiveSupport::Duration.new(3*86400, :days=>3)
3354
+ end
3355
+
3285
3356
  it 'with models' do
3286
3357
  @db.create_table!(:items) do
3287
3358
  primary_key :id
@@ -1619,6 +1619,16 @@ describe "Schema Parser" do
1619
1619
  @db.schema(:x).wont_be_same_as(@db.schema(:x))
1620
1620
  end
1621
1621
 
1622
+ it "should freeze string values in resulting hash" do
1623
+ @db.define_singleton_method(:schema_parse_table) do |t, opts|
1624
+ [[:a, {:oid=>1, :db_type=>'integer'.dup, :default=>"'a'".dup, :ruby_default=>'a'.dup}]]
1625
+ end
1626
+ c = @db.schema(:x)[0][1]
1627
+ c[:db_type].frozen?.must_equal true
1628
+ c[:default].frozen?.must_equal true
1629
+ c[:ruby_default].frozen?.must_equal true
1630
+ end
1631
+
1622
1632
  it "should provide options if given a table name" do
1623
1633
  c = nil
1624
1634
  @db.define_singleton_method(:schema_parse_table) do |t, opts|
@@ -41,6 +41,15 @@ describe "Sequel::Plugins::DefaultsSetter" do
41
41
  (t - Time.now).must_be :<, 1
42
42
  end
43
43
 
44
+ it "should handle :callable_default values in schema in preference to :ruby_default" do
45
+ @db.define_singleton_method(:schema) do |*|
46
+ [[:id, {:primary_key=>true}],
47
+ [:a, {:ruby_default => Time.now, :callable_default=>lambda{Date.today}, :primary_key=>false}]]
48
+ end
49
+ @c.dataset = @c.dataset
50
+ @c.new.a.must_equal Date.today
51
+ end
52
+
44
53
  it "should handle Sequel::CURRENT_TIMESTAMP default by using the current DateTime if Sequel.datetime_class is DateTime" do
45
54
  Sequel.datetime_class = DateTime
46
55
  t = @pr.call(Sequel::CURRENT_TIMESTAMP).new.a
@@ -60,6 +69,15 @@ describe "Sequel::Plugins::DefaultsSetter" do
60
69
  @db.sqls.must_equal ["INSERT INTO foo (a) VALUES (CURRENT_TIMESTAMP)", "SELECT * FROM foo WHERE id = 1"]
61
70
  end
62
71
 
72
+ it "should cache default values if :cache plugin option is used" do
73
+ @c.plugin :defaults_setter, :cache => true
74
+ @c.default_values[:a] = 'a'
75
+ o = @c.new
76
+ o.a.must_equal 'a'
77
+ o.values[:a].must_equal 'a'
78
+ o.a.must_be_same_as(o.a)
79
+ end
80
+
63
81
  it "should not override a given value" do
64
82
  @pr.call(2)
65
83
  @c.new('a'=>3).a.must_equal 3
@@ -11,6 +11,13 @@ describe "serialization_modification_detection plugin" do
11
11
  @ds.db.sqls
12
12
  end
13
13
 
14
+ it "should detect setting new column values on new objects" do
15
+ @o = @c.new
16
+ @o.changed_columns.must_equal []
17
+ @o.a = 'c'
18
+ @o.changed_columns.must_equal [:a]
19
+ end
20
+
14
21
  it "should only detect columns that have been changed" do
15
22
  @o.changed_columns.must_equal []
16
23
  @o.a << 'b'
@@ -32,6 +39,12 @@ describe "serialization_modification_detection plugin" do
32
39
  @o.changed_columns.must_equal []
33
40
  end
34
41
 
42
+ it "should detect columns that have been changed on frozen objects" do
43
+ @o.freeze
44
+ @o.a << 'b'
45
+ @o.changed_columns.must_equal [:a]
46
+ end
47
+
35
48
  it "should not list a column twice" do
36
49
  @o.a = 'b'.dup
37
50
  @o.a << 'a'
@@ -209,6 +209,17 @@ describe "pg_array extension" do
209
209
  @db.schema(:items).map{|e| e[1][:type]}.must_equal [:integer, :integer_array, :real_array, :decimal_array, :string_array]
210
210
  end
211
211
 
212
+ it "should set :callable_default schema entries if default value is recognized" do
213
+ @db.fetch = [{:name=>'id', :db_type=>'integer', :default=>'1'}, {:name=>'t', :db_type=>'text[]', :default=>"'{}'::text[]"}]
214
+ s = @db.schema(:items)
215
+ s[0][1][:callable_default].must_be_nil
216
+ v = s[1][1][:callable_default].call
217
+ Sequel::Postgres::PGArray.===(v).must_equal true
218
+ @db.literal(v).must_equal "'{}'::text[]"
219
+ v << 'a'
220
+ @db.literal(v).must_equal "ARRAY['a']::text[]"
221
+ end
222
+
212
223
  it "should support typecasting of the various array types" do
213
224
  {
214
225
  :integer=>{:class=>Integer, :convert=>['1', 1, '1']},
@@ -186,6 +186,17 @@ describe "pg_hstore extension" do
186
186
  @db.schema(:items).map{|e| e[1][:type]}.must_equal [:integer, :hstore]
187
187
  end
188
188
 
189
+ it "should set :callable_default schema entries if default value is recognized" do
190
+ @db.fetch = [{:name=>'id', :db_type=>'integer', :default=>'1'}, {:name=>'t', :db_type=>'hstore', :default=>"''::hstore"}]
191
+ s = @db.schema(:items)
192
+ s[0][1][:callable_default].must_be_nil
193
+ v = s[1][1][:callable_default].call
194
+ Sequel::Postgres::HStore.===(v).must_equal true
195
+ @db.literal(v).must_equal "''::hstore"
196
+ v['a'] = 'b'
197
+ @db.literal(v).must_equal "'\"a\"=>\"b\"'::hstore"
198
+ end
199
+
189
200
  it "should support typecasting for the hstore type" do
190
201
  h = Sequel.hstore(1=>2)
191
202
  @db.typecast_value(:hstore, h).object_id.must_equal(h.object_id)
@@ -51,6 +51,12 @@ describe "pg_inet extension" do
51
51
  @db.schema(:items).map{|e| e[1][:type]}.must_equal [:integer, :ipaddr, :ipaddr]
52
52
  end
53
53
 
54
+ it "should set :ruby_default schema entries if default value is recognized" do
55
+ @db.fetch = [{:name=>'id', :db_type=>'integer', :default=>'1'}, {:name=>'t', :db_type=>'inet', :default=>"'127.0.0.1'::inet"}]
56
+ s = @db.schema(:items)
57
+ s[1][1][:ruby_default].must_equal IPAddr.new('127.0.0.1')
58
+ end
59
+
54
60
  it "should support typecasting for the ipaddr type" do
55
61
  ip = IPAddr.new('127.0.0.1')
56
62
  @db.typecast_value(:ipaddr, ip).must_be_same_as(ip)
@@ -66,6 +66,12 @@ describe "pg_interval extension" do
66
66
  @db.schema(:items).map{|e| e[1][:type]}.must_equal [:integer, :interval]
67
67
  end
68
68
 
69
+ it "should set :ruby_default schema entries if default value is recognized" do
70
+ @db.fetch = [{:name=>'id', :db_type=>'integer', :default=>'1'}, {:name=>'t', :db_type=>'interval', :default=>"'3 days'::interval"}]
71
+ s = @db.schema(:items)
72
+ s[1][1][:ruby_default].must_equal ActiveSupport::Duration.new(3*86400, :days=>3)
73
+ end
74
+
69
75
  it "should support typecasting for the interval type" do
70
76
  d = ActiveSupport::Duration.new(31557600 + 2*86400*30 + 3*86400*7 + 4*86400 + 5*3600 + 6*60 + 7, [[:years, 1], [:months, 2], [:days, 25], [:seconds, 18367]])
71
77
  @db.typecast_value(:interval, d).object_id.must_equal d.object_id
@@ -183,6 +183,35 @@ describe "pg_json extension" do
183
183
  @db.schema(:items).map{|e| e[1][:type]}.must_equal [:integer, :jsonb]
184
184
  end
185
185
 
186
+ it "should set :callable_default schema entries if default value is recognized" do
187
+ @db.fetch = [{:name=>'id', :db_type=>'integer', :default=>'1'}, {:name=>'jh', :db_type=>'json', :default=>"'{}'::json"}, {:name=>'ja', :db_type=>'json', :default=>"'[]'::json"}, {:name=>'jbh', :db_type=>'jsonb', :default=>"'{}'::jsonb"}, {:name=>'jba', :db_type=>'jsonb', :default=>"'[]'::jsonb"}]
188
+ s = @db.schema(:items)
189
+ s[0][1][:callable_default].must_be_nil
190
+ v = s[1][1][:callable_default].call
191
+ Sequel::Postgres::JSONHash.===(v).must_equal true
192
+ @db.literal(v).must_equal "'{}'::json"
193
+ v['a'] = 'b'
194
+ @db.literal(v).must_equal "'{\"a\":\"b\"}'::json"
195
+
196
+ v = s[2][1][:callable_default].call
197
+ Sequel::Postgres::JSONArray.===(v).must_equal true
198
+ @db.literal(v).must_equal "'[]'::json"
199
+ v << 1
200
+ @db.literal(v).must_equal "'[1]'::json"
201
+
202
+ v = s[3][1][:callable_default].call
203
+ Sequel::Postgres::JSONBHash.===(v).must_equal true
204
+ @db.literal(v).must_equal "'{}'::jsonb"
205
+ v['a'] = 'b'
206
+ @db.literal(v).must_equal "'{\"a\":\"b\"}'::jsonb"
207
+
208
+ v = s[4][1][:callable_default].call
209
+ Sequel::Postgres::JSONBArray.===(v).must_equal true
210
+ @db.literal(v).must_equal "'[]'::jsonb"
211
+ v << 1
212
+ @db.literal(v).must_equal "'[1]'::jsonb"
213
+ end
214
+
186
215
  it "should support typecasting for the json type" do
187
216
  h = Sequel.pg_json(1=>2)
188
217
  a = Sequel.pg_json([1])
@@ -93,6 +93,12 @@ describe "pg_range extension" do
93
93
  @db.schema(:items).map{|e| e[1][:type]}.must_equal [:integer, :int4range_array, :int8range_array, :numrange_array, :daterange_array, :tsrange_array, :tstzrange_array]
94
94
  end
95
95
 
96
+ it "should set :ruby_default schema entries if default value is recognized" do
97
+ @db.fetch = [{:name=>'id', :db_type=>'integer', :default=>'1'}, {:oid=>3904, :name=>'t', :db_type=>'int4range', :default=>"'[1,5)'::int4range"}]
98
+ s = @db.schema(:items)
99
+ s[1][1][:ruby_default].must_equal Sequel::Postgres::PGRange.new(1, 5, :exclude_end=>true, :db_type=>'int4range')
100
+ end
101
+
96
102
  describe "database typecasting" do
97
103
  before do
98
104
  @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.4.0
4
+ version: 5.5.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-01-04 00:00:00.000000000 Z
11
+ date: 2018-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -255,6 +255,7 @@ extra_rdoc_files:
255
255
  - doc/release_notes/5.2.0.txt
256
256
  - doc/release_notes/5.3.0.txt
257
257
  - doc/release_notes/5.4.0.txt
258
+ - doc/release_notes/5.5.0.txt
258
259
  files:
259
260
  - CHANGELOG
260
261
  - MIT-LICENSE
@@ -404,6 +405,7 @@ files:
404
405
  - doc/release_notes/5.2.0.txt
405
406
  - doc/release_notes/5.3.0.txt
406
407
  - doc/release_notes/5.4.0.txt
408
+ - doc/release_notes/5.5.0.txt
407
409
  - doc/schema_modification.rdoc
408
410
  - doc/security.rdoc
409
411
  - doc/sharding.rdoc