sequel 5.4.0 → 5.5.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: 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