sequel 5.17.0 → 5.18.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +18 -0
  3. data/MIT-LICENSE +1 -1
  4. data/doc/release_notes/5.18.0.txt +69 -0
  5. data/doc/testing.rdoc +1 -0
  6. data/lib/sequel/adapters/jdbc.rb +25 -16
  7. data/lib/sequel/adapters/jdbc/oracle.rb +6 -5
  8. data/lib/sequel/adapters/jdbc/postgresql.rb +20 -20
  9. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +5 -6
  10. data/lib/sequel/adapters/jdbc/sqlite.rb +4 -2
  11. data/lib/sequel/adapters/jdbc/sqlserver.rb +3 -2
  12. data/lib/sequel/adapters/mysql.rb +12 -9
  13. data/lib/sequel/adapters/mysql2.rb +3 -0
  14. data/lib/sequel/adapters/postgres.rb +4 -1
  15. data/lib/sequel/adapters/shared/postgres.rb +36 -32
  16. data/lib/sequel/adapters/sqlite.rb +58 -50
  17. data/lib/sequel/extensions/connection_expiration.rb +4 -4
  18. data/lib/sequel/extensions/connection_validator.rb +5 -4
  19. data/lib/sequel/extensions/named_timezones.rb +34 -17
  20. data/lib/sequel/model/base.rb +13 -3
  21. data/lib/sequel/plugins/after_initialize.rb +1 -1
  22. data/lib/sequel/plugins/nested_attributes.rb +12 -1
  23. data/lib/sequel/plugins/throw_failures.rb +110 -0
  24. data/lib/sequel/sql.rb +5 -0
  25. data/lib/sequel/version.rb +1 -1
  26. data/spec/adapters/spec_helper.rb +1 -0
  27. data/spec/core/database_spec.rb +2 -1
  28. data/spec/core/expression_filters_spec.rb +6 -2
  29. data/spec/extensions/after_initialize_spec.rb +4 -0
  30. data/spec/extensions/named_timezones_spec.rb +3 -1
  31. data/spec/extensions/spec_helper.rb +1 -0
  32. data/spec/extensions/throw_failures_spec.rb +74 -0
  33. data/spec/integration/dataset_test.rb +4 -0
  34. data/spec/integration/plugin_test.rb +1 -1
  35. data/spec/integration/spec_helper.rb +1 -0
  36. data/spec/model/model_spec.rb +6 -3
  37. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ba219a366f7927beb723a4440c7d497d62b9a2d3b066c44ea5368253a668698
4
- data.tar.gz: 570fde96f4cce21da26853210b29eef64e1c6adbf911d085faf5ebf907b0f013
3
+ metadata.gz: 8f181e0ec05e457e72b2517202bbd159a6a066c9dd946201de44576b64bca302
4
+ data.tar.gz: f554f6268798f72c667851c6d952f960564e70ebb1e2d3d1208db74bbcd98834
5
5
  SHA512:
6
- metadata.gz: dd6070cf9080a359fd506a5ea8a9d1aaedb2a293c0ed4a123795a55a76e3e9b6ae9720da2f03ec5d1f904258a6fb12c462a2bd661a8ff79c5d0fbd27488664cd
7
- data.tar.gz: 402bade89fccfe94c5f29a9a7767fe7cca1bd0549e37fe8591268507b8025365b2ff7af7ef36dfa6a281607d44f4d6fb683dbff801d55003c546981bca1b932d
6
+ metadata.gz: 474c441a62bb901abaf12968ec62cdd239f9679ce4cb1743cd58e7ec6032cb1e26d128fd49bbe7c3edfce9a0fb63b33975aa6426ebb8ee79f1309a09967ffef5
7
+ data.tar.gz: 8717400fcb03235ad51ab208d35e9accca05018c6531da025bbcefb8f4b557345e1afbbfd65862f2c213ca4b05826db72c5ecc6b8abdda5920af83dcd8fe35d9
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 5.18.0 (2019-03-01)
2
+
3
+ * Use singleton .call methods on plain objects instead of procs/methods for faster type conversion (jeremyevans)
4
+
5
+ * Add Sequel::SQL::Blob.call to avoid indirection when converting values from the database (jeremyevans)
6
+
7
+ * Use while instead of each for inner loops in sqlite and jdbc adapters for better performance (jeremyevans)
8
+
9
+ * Make after_initialize plugin not make the argument to Model.call optional (jeremyevans)
10
+
11
+ * Allow Dataset#paged_each to be called without a block in the postgres and mysql2 adapters (jeremyevans)
12
+
13
+ * Remove flow-control exceptions in connection_expiration and connection_validator extensions (jeremyevans)
14
+
15
+ * Add throw_failures plugin for throwing ValidationFailed and HookFailed exceptions instead of raising them, up to 10x performance increase on JRuby (jeremyevans)
16
+
17
+ * Support tzinfo 2 in addition to tzinfo 1 in the named_timezones extension (jeremyevans) (#1596)
18
+
1
19
  === 5.17.0 (2019-02-01)
2
20
 
3
21
  * Support skip_auto_validations instance method in auto_validations plugin (oldgreen, jeremyevans) (#1592)
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2007-2008 Sharon Rosner
2
- Copyright (c) 2008-2018 Jeremy Evans
2
+ Copyright (c) 2008-2019 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
@@ -0,0 +1,69 @@
1
+ = New Features
2
+
3
+ * A throw_failures plugin has been added for throwing ValidationFailed
4
+ and HookFailed exceptions instead of raising them. This can improve
5
+ performance by up to 10x on JRuby and 10-15% on CRuby. However,
6
+ you would need to modify your exception handling from:
7
+
8
+ begin
9
+ # model.save
10
+ rescue Sequel::ValidationFailed => e
11
+ # handle failure
12
+ end
13
+
14
+ to:
15
+
16
+ e = catch(Sequel::ValidationFailed) do
17
+ # model.save
18
+ end
19
+ if e.is_a?(Sequel::ValidationFailed)
20
+ # handle failure
21
+ end
22
+
23
+ The throw_failures plugin will still work if you are not catching
24
+ the exception, falling back to the default behavior of raising
25
+ the exception.
26
+
27
+ * SQL::Blob.call has been added, so that SQL::Blob can be used
28
+ directly as a callable to create a new instance, resulting in
29
+ better performance in cases where a callable is needed.
30
+
31
+ = Other Improvements
32
+
33
+ * Type conversion is many adapters is now faster by switching from
34
+ Proc/Method instances to using singleton call methods on plain
35
+ objects. This can improve performance of row fetching by up to
36
+ 10% in some cases.
37
+
38
+ * Row fetching is slightly faster in the jdbc and sqlite adapters,
39
+ by switching from each to while.
40
+
41
+ * tzinfo 2 is now supported when using the named_timezones extension.
42
+ tzinfo 1 remains supported.
43
+
44
+ * The optimized Dataset#paged_each methods in the postgres and mysql2
45
+ adapters now support being called without a block, returning an
46
+ Enumerator in that case, to mirror the behavior of the default
47
+ Dataset#paged_each method.
48
+
49
+ * Sequel no longer uses flow-control exceptions in the
50
+ connection_expiration and connection_validator extensions,
51
+ significantly improving performance on JRuby.
52
+
53
+ * The after_initialize plugin no longer makes the argument to
54
+ Model.call optional.
55
+
56
+ = Backwards Compatibility
57
+
58
+ * Some internal by not private constants and methods previously used
59
+ for type conversion in adapters have been removed:
60
+
61
+ * JDBC::Oracle.OracleDecimal
62
+ * JDBC::Oracle.OracleClob
63
+ * JDBC::Postgres.RubyPGArray
64
+ * JDBC::Postgres.RubyPGHstore
65
+ * JDBC::SqlAnywhere.SqlAnywhereBoolean
66
+ * JDBC::SQLServer.MSSQLRubyTime
67
+ * MySQL::TYPE_TRANSLATOR
68
+ * Postgres::TYPE_TRANSLATOR
69
+
@@ -161,6 +161,7 @@ SEQUEL_FREEZE_DATABASE :: Freeze the database before running the integration spe
161
161
  SEQUEL_IDENTIFIER_MANGLING :: Use the identifier_mangling extension when running the specs
162
162
  SEQUEL_INTEGER64 :: Use the integer64 extension when running the adapter or integration specs
163
163
  SEQUEL_MODEL_PREPARED_STATEMENTS :: Use the prepared_statements plugin when running the specs
164
+ SEQUEL_MODEL_THROW_FAILURES :: Use the throw_failures plugin when running the specs
164
165
  SEQUEL_NO_CACHE_ASSOCIATIONS :: Don't cache association metadata when running the specs
165
166
  SEQUEL_NO_CHECK_SQLS :: Don't check for specific SQL syntax when running the specs
166
167
  SEQUEL_CHECK_PENDING :: Try running all specs (note, can cause lockups for some adapters), and raise errors for skipped specs that don't fail
@@ -57,45 +57,53 @@ module Sequel
57
57
  end
58
58
 
59
59
  class TypeConvertor
60
+ CONVERTORS = convertors = {}
60
61
  %w'Boolean Float Double Int Long Short'.each do |meth|
61
- class_eval("def #{meth}(r, i) v = r.get#{meth}(i); v unless r.wasNull end", __FILE__, __LINE__)
62
+ x = convertors[meth.to_sym] = Object.new
63
+ class_eval("def x.call(r, i) v = r.get#{meth}(i); v unless r.wasNull end", __FILE__, __LINE__)
62
64
  end
63
65
  %w'Object Array String Time Date Timestamp BigDecimal Blob Bytes Clob'.each do |meth|
64
- class_eval("def #{meth}(r, i) r.get#{meth}(i) end", __FILE__, __LINE__)
66
+ x = convertors[meth.to_sym] = Object.new
67
+ class_eval("def x.call(r, i) r.get#{meth}(i) end", __FILE__, __LINE__)
65
68
  end
66
- def RubyTime(r, i)
69
+ x = convertors[:RubyTime] = Object.new
70
+ def x.call(r, i)
67
71
  if v = r.getTime(i)
68
72
  Sequel.string_to_time("#{v.to_string}.#{sprintf('%03i', v.getTime.divmod(1000).last)}")
69
73
  end
70
74
  end
71
- def RubyDate(r, i)
75
+ x = convertors[:RubyDate] = Object.new
76
+ def x.call(r, i)
72
77
  if v = r.getDate(i)
73
78
  Date.civil(v.getYear + 1900, v.getMonth + 1, v.getDate)
74
79
  end
75
80
  end
76
- def RubyTimestamp(r, i)
81
+ x = convertors[:RubyTimestamp] = Object.new
82
+ def x.call(r, i)
77
83
  if v = r.getTimestamp(i)
78
84
  Sequel.database_to_application_timestamp([v.getYear + 1900, v.getMonth + 1, v.getDate, v.getHours, v.getMinutes, v.getSeconds, v.getNanos])
79
85
  end
80
86
  end
81
- def RubyBigDecimal(r, i)
87
+ x = convertors[:RubyBigDecimal] = Object.new
88
+ def x.call(r, i)
82
89
  if v = r.getBigDecimal(i)
83
90
  ::Kernel::BigDecimal(v.to_string)
84
91
  end
85
92
  end
86
- def RubyBlob(r, i)
93
+ x = convertors[:RubyBlob] = Object.new
94
+ def x.call(r, i)
87
95
  if v = r.getBytes(i)
88
96
  Sequel::SQL::Blob.new(String.from_java_bytes(v))
89
97
  end
90
98
  end
91
- def RubyClob(r, i)
99
+ x = convertors[:RubyClob] = Object.new
100
+ def x.call(r, i)
92
101
  if v = r.getClob(i)
93
102
  v.getSubString(1, v.length)
94
103
  end
95
104
  end
96
105
 
97
- o = new
98
- MAP = Hash.new(o.method(:Object))
106
+ MAP = Hash.new(convertors[:Object])
99
107
  types = Java::JavaSQL::Types
100
108
 
101
109
  {
@@ -113,7 +121,7 @@ module Sequel
113
121
  :TINYINT => :Short,
114
122
  :VARCHAR => :String,
115
123
  }.each do |type, meth|
116
- MAP[types.const_get(type)] = o.method(meth)
124
+ MAP[types.const_get(type)] = convertors[meth]
117
125
  end
118
126
  BASIC_MAP = MAP.dup
119
127
 
@@ -130,13 +138,11 @@ module Sequel
130
138
  :TIMESTAMP => :Timestamp,
131
139
  :VARBINARY => :Blob,
132
140
  }.each do |type, meth|
133
- BASIC_MAP[types.const_get(type)] = o.method(meth)
134
- MAP[types.const_get(type)] = o.method(:"Ruby#{meth}")
141
+ BASIC_MAP[types.const_get(type)] = convertors[meth]
142
+ MAP[types.const_get(type)] = convertors[:"Ruby#{meth}"]
135
143
  end
136
-
137
144
  MAP.freeze
138
145
  BASIC_MAP.freeze
139
- freeze
140
146
  end
141
147
 
142
148
  class Database < Sequel::Database
@@ -785,11 +791,14 @@ module Sequel
785
791
  i += 1
786
792
  cols << [output_identifier(meta.getColumnLabel(i)), i, convert ? type_convertor(map, meta, meta.getColumnType(i), i) : basic_type_convertor(map, meta, meta.getColumnType(i), i)]
787
793
  end
794
+ max = i
788
795
  self.columns = cols.map{|c| c[0]}
789
796
 
790
797
  while result.next
791
798
  row = {}
792
- cols.each do |n, j, pr|
799
+ i = -1
800
+ while (i += 1) < max
801
+ n, j, pr = cols[i]
793
802
  row[n] = pr.call(result, j)
794
803
  end
795
804
  yield row
@@ -16,8 +16,8 @@ module Sequel
16
16
 
17
17
  module Oracle
18
18
  JAVA_BIG_DECIMAL_CONSTRUCTOR = java.math.BigDecimal.java_class.constructor(Java::long).method(:new_instance)
19
-
20
- def self.OracleDecimal(r, i)
19
+ ORACLE_DECIMAL = Object.new
20
+ def ORACLE_DECIMAL.call(r, i)
21
21
  if v = r.getBigDecimal(i)
22
22
  i = v.long_value
23
23
  if v == JAVA_BIG_DECIMAL_CONSTRUCTOR.call(i)
@@ -28,7 +28,8 @@ module Sequel
28
28
  end
29
29
  end
30
30
 
31
- def self.OracleClob(r, i)
31
+ ORACLE_CLOB = Object.new
32
+ def ORACLE_CLOB.call(r, i)
32
33
  return unless clob = r.getClob(i)
33
34
  str = clob.getSubString(1, clob.length)
34
35
  clob.freeTemporary if clob.isTemporary
@@ -110,8 +111,8 @@ module Sequel
110
111
 
111
112
  def setup_type_convertor_map
112
113
  super
113
- @type_convertor_map[:OracleDecimal] = Oracle.method(:OracleDecimal)
114
- @type_convertor_map[:OracleClob] = Oracle.method(:OracleClob)
114
+ @type_convertor_map[:OracleDecimal] = ORACLE_DECIMAL
115
+ @type_convertor_map[:OracleClob] = ORACLE_CLOB
115
116
  end
116
117
  end
117
118
 
@@ -14,24 +14,6 @@ module Sequel
14
14
  end
15
15
 
16
16
  module Postgres
17
- # Return PostgreSQL array types as ruby Arrays instead of
18
- # JDBC PostgreSQL driver-specific array type. Only used if the
19
- # database does not have a conversion proc for the type.
20
- def self.RubyPGArray(r, i)
21
- if v = r.getArray(i)
22
- v.array.to_ary
23
- end
24
- end
25
-
26
- # Return PostgreSQL hstore types as ruby Hashes instead of
27
- # Java HashMaps. Only used if the database does not have a
28
- # conversion proc for the type.
29
- def self.RubyPGHstore(r, i)
30
- if v = r.getObject(i)
31
- v.to_hash
32
- end
33
- end
34
-
35
17
  module DatabaseMethods
36
18
  include Sequel::Postgres::DatabaseMethods
37
19
 
@@ -213,9 +195,27 @@ module Sequel
213
195
 
214
196
  STRING_TYPE = Java::JavaSQL::Types::VARCHAR
215
197
  ARRAY_TYPE = Java::JavaSQL::Types::ARRAY
216
- ARRAY_METHOD = Postgres.method(:RubyPGArray)
217
198
  PG_SPECIFIC_TYPES = [ARRAY_TYPE, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT, Java::JavaSQL::Types::TIME_WITH_TIMEZONE, Java::JavaSQL::Types::TIME].freeze
218
- HSTORE_METHOD = Postgres.method(:RubyPGHstore)
199
+
200
+ # Return PostgreSQL array types as ruby Arrays instead of
201
+ # JDBC PostgreSQL driver-specific array type. Only used if the
202
+ # database does not have a conversion proc for the type.
203
+ ARRAY_METHOD = Object.new
204
+ def ARRAY_METHOD.call(r, i)
205
+ if v = r.getArray(i)
206
+ v.array.to_ary
207
+ end
208
+ end
209
+
210
+ # Return PostgreSQL hstore types as ruby Hashes instead of
211
+ # Java HashMaps. Only used if the database does not have a
212
+ # conversion proc for the type.
213
+ HSTORE_METHOD = Object.new
214
+ def HSTORE_METHOD.call(r, i)
215
+ if v = r.getObject(i)
216
+ v.to_hash
217
+ end
218
+ end
219
219
 
220
220
  def type_convertor(map, meta, type, i)
221
221
  case type
@@ -30,11 +30,6 @@ module Sequel
30
30
  end
31
31
 
32
32
  module SqlAnywhere
33
- def self.SqlAnywhereBoolean(r, i)
34
- v = r.getShort(i)
35
- v != 0 unless r.wasNull
36
- end
37
-
38
33
  module DatabaseMethods
39
34
  include Sequel::SqlAnywhere::DatabaseMethods
40
35
  include Sequel::JDBC::Transactions
@@ -58,7 +53,11 @@ module Sequel
58
53
  private
59
54
 
60
55
  SMALLINT_TYPE = Java::JavaSQL::Types::SMALLINT
61
- BOOLEAN_METHOD = SqlAnywhere.method(:SqlAnywhereBoolean)
56
+ BOOLEAN_METHOD = Object.new
57
+ def BOOLEAN_METHOD.call(r, i)
58
+ v = r.getShort(i)
59
+ v != 0 unless r.wasNull
60
+ end
62
61
 
63
62
  def type_convertor(map, meta, type, i)
64
63
  if convert_smallint_to_bool && type == SMALLINT_TYPE
@@ -78,12 +78,14 @@ module Sequel
78
78
  super
79
79
  @type_convertor_map[Java::JavaSQL::Types::INTEGER] = @type_convertor_map[Java::JavaSQL::Types::BIGINT]
80
80
  @basic_type_convertor_map[Java::JavaSQL::Types::INTEGER] = @basic_type_convertor_map[Java::JavaSQL::Types::BIGINT]
81
- @type_convertor_map[Java::JavaSQL::Types::DATE] = lambda do |r, i|
81
+ x = @type_convertor_map[Java::JavaSQL::Types::DATE] = Object.new
82
+ def x.call(r, i)
82
83
  if v = r.getString(i)
83
84
  Sequel.string_to_date(v)
84
85
  end
85
86
  end
86
- @type_convertor_map[Java::JavaSQL::Types::BLOB] = lambda do |r, i|
87
+ x = @type_convertor_map[Java::JavaSQL::Types::BLOB] = Object.new
88
+ def x.call(r, i)
87
89
  if v = r.getBytes(i)
88
90
  Sequel::SQL::Blob.new(String.from_java_bytes(v))
89
91
  elsif !r.wasNull
@@ -15,7 +15,8 @@ module Sequel
15
15
  end
16
16
 
17
17
  module SQLServer
18
- def self.MSSQLRubyTime(r, i)
18
+ MSSQL_RUBY_TIME = Object.new
19
+ def MSSQL_RUBY_TIME.call(r, i)
19
20
  # MSSQL-Server TIME should be fetched as string to keep the precision intact, see:
20
21
  # https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql#a-namebackwardcompatibilityfordownlevelclientsa-backward-compatibility-for-down-level-clients
21
22
  if v = r.getString(i)
@@ -29,7 +30,7 @@ module Sequel
29
30
  def setup_type_convertor_map
30
31
  super
31
32
  map = @type_convertor_map
32
- map[Java::JavaSQL::Types::TIME] = SQLServer.method(:MSSQLRubyTime)
33
+ map[Java::JavaSQL::Types::TIME] = MSSQL_RUBY_TIME
33
34
 
34
35
  # Work around constant lazy loading in some drivers
35
36
  begin
@@ -8,19 +8,22 @@ require_relative 'utils/mysql_prepared_statements'
8
8
 
9
9
  module Sequel
10
10
  module MySQL
11
- TYPE_TRANSLATOR = tt = Class.new do
12
- def boolean(s) s.to_i != 0 end
13
- def integer(s) s.to_i end
14
- def float(s) s.to_f end
15
- end.new.freeze
11
+ boolean = Object.new
12
+ def boolean.call(s) s.to_i != 0 end
13
+ TYPE_TRANSLATOR_BOOLEAN = boolean.freeze
14
+ integer = Object.new
15
+ def integer.call(s) s.to_i end
16
+ TYPE_TRANSLATOR_INTEGER = integer.freeze
17
+ float = Object.new
18
+ def float.call(s) s.to_f end
16
19
 
17
20
  # Hash with integer keys and callable values for converting MySQL types.
18
21
  MYSQL_TYPES = {}
19
22
  {
20
23
  [0, 246] => ::Kernel.method(:BigDecimal),
21
- [2, 3, 8, 9, 13, 247, 248] => tt.method(:integer),
22
- [4, 5] => tt.method(:float),
23
- [249, 250, 251, 252] => ::Sequel::SQL::Blob.method(:new)
24
+ [2, 3, 8, 9, 13, 247, 248] => integer,
25
+ [4, 5] => float,
26
+ [249, 250, 251, 252] => ::Sequel::SQL::Blob
24
27
  }.each do |k,v|
25
28
  k.each{|n| MYSQL_TYPES[n] = v}
26
29
  end
@@ -131,7 +134,7 @@ module Sequel
131
134
  # Modify the type translator used for the tinyint type based
132
135
  # on the value given.
133
136
  def convert_tinyint_to_bool=(v)
134
- @conversion_procs[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
137
+ @conversion_procs[1] = v ? TYPE_TRANSLATOR_BOOLEAN : TYPE_TRANSLATOR_INTEGER
135
138
  @convert_tinyint_to_bool = v
136
139
  end
137
140
 
@@ -245,6 +245,9 @@ module Sequel
245
245
  # it hasn't been disabled.
246
246
  def paged_each(opts=OPTS, &block)
247
247
  if STREAMING_SUPPORTED && opts[:stream] != false
248
+ unless block_given?
249
+ return enum_for(:paged_each, opts)
250
+ end
248
251
  stream.each(&block)
249
252
  else
250
253
  super
@@ -508,7 +508,7 @@ module Sequel
508
508
  @use_iso_date_format = typecast_value_boolean(@opts.fetch(:use_iso_date_format, true))
509
509
  initialize_postgres_adapter
510
510
  add_conversion_proc(17, method(:unescape_bytea)) if USES_PG
511
- add_conversion_proc(1082, TYPE_TRANSLATOR.method(:date)) if @use_iso_date_format
511
+ add_conversion_proc(1082, TYPE_TRANSLATOR_DATE) if @use_iso_date_format
512
512
  self.convert_infinite_timestamps = @opts[:convert_infinite_timestamps]
513
513
  end
514
514
 
@@ -612,6 +612,9 @@ module Sequel
612
612
 
613
613
  # Use a cursor for paging.
614
614
  def paged_each(opts=OPTS, &block)
615
+ unless block_given?
616
+ return enum_for(:paged_each, opts)
617
+ end
615
618
  use_cursor(opts).each(&block)
616
619
  end
617
620
 
@@ -23,43 +23,47 @@ module Sequel
23
23
  PLUS_INFINITY = 1.0/0.0
24
24
  MINUS_INFINITY = -1.0/0.0
25
25
 
26
- TYPE_TRANSLATOR = tt = Class.new do
27
- def boolean(s) s == 't' end
28
- def integer(s) s.to_i end
29
- def float(s)
30
- case s
31
- when 'NaN'
32
- NAN
33
- when 'Infinity'
34
- PLUS_INFINITY
35
- when '-Infinity'
36
- MINUS_INFINITY
37
- else
38
- s.to_f
39
- end
26
+ boolean = Object.new
27
+ def boolean.call(s) s == 't' end
28
+ integer = Object.new
29
+ def integer.call(s) s.to_i end
30
+ float = Object.new
31
+ def float.call(s)
32
+ case s
33
+ when 'NaN'
34
+ NAN
35
+ when 'Infinity'
36
+ PLUS_INFINITY
37
+ when '-Infinity'
38
+ MINUS_INFINITY
39
+ else
40
+ s.to_f
40
41
  end
41
- def date(s) ::Date.new(*s.split('-').map(&:to_i)) end
42
- def bytea(str)
43
- str = if str =~ /\A\\x/
44
- # PostgreSQL 9.0+ bytea hex format
45
- str[2..-1].gsub(/(..)/){|s| s.to_i(16).chr}
46
- else
47
- # Historical PostgreSQL bytea escape format
48
- str.gsub(/\\(\\|'|[0-3][0-7][0-7])/) {|s|
49
- if s.size == 2 then s[1,1] else s[1,3].oct.chr end
50
- }
51
- end
52
- ::Sequel::SQL::Blob.new(str)
53
- end
54
- end.new.freeze
42
+ end
43
+ date = Object.new
44
+ def date.call(s) ::Date.new(*s.split('-').map(&:to_i)) end
45
+ TYPE_TRANSLATOR_DATE = date.freeze
46
+ bytea = Object.new
47
+ def bytea.call(str)
48
+ str = if str =~ /\A\\x/
49
+ # PostgreSQL 9.0+ bytea hex format
50
+ str[2..-1].gsub(/(..)/){|s| s.to_i(16).chr}
51
+ else
52
+ # Historical PostgreSQL bytea escape format
53
+ str.gsub(/\\(\\|'|[0-3][0-7][0-7])/) {|s|
54
+ if s.size == 2 then s[1,1] else s[1,3].oct.chr end
55
+ }
56
+ end
57
+ ::Sequel::SQL::Blob.new(str)
58
+ end
55
59
 
56
60
  CONVERSION_PROCS = {}
57
61
 
58
62
  {
59
- [16] => tt.method(:boolean),
60
- [17] => tt.method(:bytea),
61
- [20, 21, 23, 26] => tt.method(:integer),
62
- [700, 701] => tt.method(:float),
63
+ [16] => boolean,
64
+ [17] => bytea,
65
+ [20, 21, 23, 26] => integer,
66
+ [700, 701] => float,
63
67
  [1700] => ::Kernel.method(:BigDecimal),
64
68
  [1083, 1266] => ::Sequel.method(:string_to_time),
65
69
  [1082] => ::Sequel.method(:string_to_date),
@@ -7,67 +7,72 @@ module Sequel
7
7
  module SQLite
8
8
  FALSE_VALUES = (%w'0 false f no n' + [0]).freeze
9
9
 
10
- tt = Class.new do
11
- def blob(s)
12
- Sequel::SQL::Blob.new(s.to_s)
13
- end
10
+ blob = Object.new
11
+ def blob.call(s)
12
+ Sequel::SQL::Blob.new(s.to_s)
13
+ end
14
14
 
15
- def boolean(s)
16
- s = s.downcase if s.is_a?(String)
17
- !FALSE_VALUES.include?(s)
18
- end
15
+ boolean = Object.new
16
+ def boolean.call(s)
17
+ s = s.downcase if s.is_a?(String)
18
+ !FALSE_VALUES.include?(s)
19
+ end
19
20
 
20
- def date(s)
21
- case s
22
- when String
23
- Sequel.string_to_date(s)
24
- when Integer
25
- Date.jd(s)
26
- when Float
27
- Date.jd(s.to_i)
28
- else
29
- raise Sequel::Error, "unhandled type when converting to date: #{s.inspect} (#{s.class.inspect})"
30
- end
21
+ date = Object.new
22
+ def date.call(s)
23
+ case s
24
+ when String
25
+ Sequel.string_to_date(s)
26
+ when Integer
27
+ Date.jd(s)
28
+ when Float
29
+ Date.jd(s.to_i)
30
+ else
31
+ raise Sequel::Error, "unhandled type when converting to date: #{s.inspect} (#{s.class.inspect})"
31
32
  end
33
+ end
32
34
 
33
- def integer(s)
34
- s.to_i
35
- end
35
+ integer = Object.new
36
+ def integer.call(s)
37
+ s.to_i
38
+ end
36
39
 
37
- def float(s)
38
- s.to_f
39
- end
40
+ float = Object.new
41
+ def float.call(s)
42
+ s.to_f
43
+ end
40
44
 
41
- def numeric(s)
42
- s = s.to_s unless s.is_a?(String)
43
- BigDecimal(s) rescue s
44
- end
45
+ numeric = Object.new
46
+ def numeric.call(s)
47
+ s = s.to_s unless s.is_a?(String)
48
+ BigDecimal(s) rescue s
49
+ end
45
50
 
46
- def time(s)
47
- case s
48
- when String
49
- Sequel.string_to_time(s)
50
- when Integer
51
- Sequel::SQLTime.create(s/3600, (s % 3600)/60, s % 60)
52
- when Float
53
- s, f = s.divmod(1)
54
- Sequel::SQLTime.create(s/3600, (s % 3600)/60, s % 60, (f*1000000).round)
55
- else
56
- raise Sequel::Error, "unhandled type when converting to date: #{s.inspect} (#{s.class.inspect})"
57
- end
51
+ time = Object.new
52
+ def time.call(s)
53
+ case s
54
+ when String
55
+ Sequel.string_to_time(s)
56
+ when Integer
57
+ Sequel::SQLTime.create(s/3600, (s % 3600)/60, s % 60)
58
+ when Float
59
+ s, f = s.divmod(1)
60
+ Sequel::SQLTime.create(s/3600, (s % 3600)/60, s % 60, (f*1000000).round)
61
+ else
62
+ raise Sequel::Error, "unhandled type when converting to date: #{s.inspect} (#{s.class.inspect})"
58
63
  end
59
- end.new
64
+ end
60
65
 
61
66
  # Hash with string keys and callable values for converting SQLite types.
62
67
  SQLITE_TYPES = {}
63
68
  {
64
- %w'date' => tt.method(:date),
65
- %w'time' => tt.method(:time),
66
- %w'bit bool boolean' => tt.method(:boolean),
67
- %w'integer smallint mediumint int bigint' => tt.method(:integer),
68
- %w'numeric decimal money' => tt.method(:numeric),
69
- %w'float double real dec fixed' + ['double precision'] => tt.method(:float),
70
- %w'blob' => tt.method(:blob)
69
+ %w'date' => date,
70
+ %w'time' => time,
71
+ %w'bit bool boolean' => boolean,
72
+ %w'integer smallint mediumint int bigint' => integer,
73
+ %w'numeric decimal money' => numeric,
74
+ %w'float double real dec fixed' + ['double precision'] => float,
75
+ %w'blob' => blob
71
76
  }.each do |k,v|
72
77
  k.each{|n| SQLITE_TYPES[n] = v}
73
78
  end
@@ -317,10 +322,13 @@ module Sequel
317
322
  cps = db.conversion_procs
318
323
  type_procs = result.types.map{|t| cps[base_type_name(t)]}
319
324
  cols = result.columns.map{|c| i+=1; [output_identifier(c), i, type_procs[i]]}
325
+ max = i+1
320
326
  self.columns = cols.map(&:first)
321
327
  result.each do |values|
322
328
  row = {}
323
- cols.each do |name,id,type_proc|
329
+ i = -1
330
+ while (i += 1) < max
331
+ name, id, type_proc = cols[i]
324
332
  v = values[id]
325
333
  if type_proc && v
326
334
  v = type_proc.call(v)
@@ -32,6 +32,7 @@
32
32
  module Sequel
33
33
  module ConnectionExpiration
34
34
  class Retry < Error; end
35
+ Sequel::Deprecation.deprecate_constant(self, :Retry)
35
36
 
36
37
  # The number of seconds that need to pass since
37
38
  # connection creation before expiring a connection.
@@ -72,7 +73,8 @@ module Sequel
72
73
  # If it is expired, disconnect the connection, and retry with a new
73
74
  # connection.
74
75
  def acquire(*a)
75
- begin
76
+ conn = nil
77
+ 1.times do
76
78
  if (conn = super) &&
77
79
  (cet = sync{@connection_expiration_timestamps[conn]}) &&
78
80
  Sequel.elapsed_seconds_since(cet[0]) > cet[1]
@@ -84,10 +86,8 @@ module Sequel
84
86
  end
85
87
 
86
88
  disconnect_connection(conn)
87
- raise Retry
89
+ redo
88
90
  end
89
- rescue Retry
90
- retry
91
91
  end
92
92
 
93
93
  conn
@@ -51,6 +51,7 @@
51
51
  module Sequel
52
52
  module ConnectionValidator
53
53
  class Retry < Error; end
54
+ Sequel::Deprecation.deprecate_constant(self, :Retry)
54
55
 
55
56
  # The number of seconds that need to pass since
56
57
  # connection checkin before attempting to validate
@@ -94,7 +95,9 @@ module Sequel
94
95
  # test the connection for validity. If it is not valid,
95
96
  # disconnect the connection, and retry with a new connection.
96
97
  def acquire(*a)
97
- begin
98
+ conn = nil
99
+
100
+ 1.times do
98
101
  if (conn = super) &&
99
102
  (timer = sync{@connection_timestamps.delete(conn)}) &&
100
103
  Sequel.elapsed_seconds_since(timer) > @connection_validation_timeout &&
@@ -107,10 +110,8 @@ module Sequel
107
110
  end
108
111
 
109
112
  disconnect_connection(conn)
110
- raise Retry
113
+ redo
111
114
  end
112
- rescue Retry
113
- retry
114
115
  end
115
116
 
116
117
  conn
@@ -63,24 +63,41 @@ module Sequel
63
63
 
64
64
  private
65
65
 
66
- # Assume the given DateTime has a correct time but a wrong timezone. It is
67
- # currently in UTC timezone, but it should be converted to the input_timezone.
68
- # Keep the time the same but convert the timezone to the input_timezone.
69
- # Expects the input_timezone to be a TZInfo::Timezone instance.
70
- def convert_input_datetime_other(v, input_timezone)
71
- local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset_rational
72
- (v - local_offset).new_offset(local_offset)
73
- end
66
+ # Handle both TZInfo 1 and TZInfo 2
67
+ if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
68
+ # :nodoc:
69
+ def convert_input_datetime_other(v, input_timezone)
70
+ local_offset = Rational(input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset, 86400)
71
+ (v - local_offset).new_offset(local_offset)
72
+ end
74
73
 
75
- # Convert the given DateTime to use the given output_timezone.
76
- # Expects the output_timezone to be a TZInfo::Timezone instance.
77
- def convert_output_datetime_other(v, output_timezone)
78
- # TZInfo converts times, but expects the given DateTime to have an offset
79
- # of 0 and always leaves the timezone offset as 0
80
- v = output_timezone.utc_to_local(v.new_offset(0))
81
- local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset_rational
82
- # Convert timezone offset from UTC to the offset for the output_timezone
83
- (v - local_offset).new_offset(local_offset)
74
+ def convert_output_datetime_other(v, output_timezone)
75
+ v = output_timezone.utc_to_local(v.new_offset(0))
76
+
77
+ # Force DateTime output instead of TZInfo::DateTimeWithOffset
78
+ DateTime.jd(v.jd, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
79
+ end
80
+ # :nodoc:
81
+ else
82
+ # Assume the given DateTime has a correct time but a wrong timezone. It is
83
+ # currently in UTC timezone, but it should be converted to the input_timezone.
84
+ # Keep the time the same but convert the timezone to the input_timezone.
85
+ # Expects the input_timezone to be a TZInfo::Timezone instance.
86
+ def convert_input_datetime_other(v, input_timezone)
87
+ local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset_rational
88
+ (v - local_offset).new_offset(local_offset)
89
+ end
90
+
91
+ # Convert the given DateTime to use the given output_timezone.
92
+ # Expects the output_timezone to be a TZInfo::Timezone instance.
93
+ def convert_output_datetime_other(v, output_timezone)
94
+ # TZInfo 1 converts times, but expects the given DateTime to have an offset
95
+ # of 0 and always leaves the timezone offset as 0
96
+ v = output_timezone.utc_to_local(v.new_offset(0))
97
+ local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset_rational
98
+ # Convert timezone offset from UTC to the offset for the output_timezone
99
+ (v - local_offset).new_offset(local_offset)
100
+ end
84
101
  end
85
102
 
86
103
  # Returns TZInfo::Timezone instance if given a String.
@@ -1459,7 +1459,7 @@ module Sequel
1459
1459
  raise Sequel::Error, "can't save frozen object" if frozen?
1460
1460
  set_server(opts[:server]) if opts[:server]
1461
1461
  unless _save_valid?(opts)
1462
- raise(ValidationFailed.new(self)) if raise_on_failure?(opts)
1462
+ raise(validation_failed_error) if raise_on_failure?(opts)
1463
1463
  return
1464
1464
  end
1465
1465
  checked_save_failure(opts){checked_transaction(opts){_save(opts)}}
@@ -1916,6 +1916,11 @@ module Sequel
1916
1916
  Errors
1917
1917
  end
1918
1918
 
1919
+ # A HookFailed exception for the given message tied to the current instance.
1920
+ def hook_failed_error(msg)
1921
+ HookFailed.new(msg, self)
1922
+ end
1923
+
1919
1924
  # Clone constructor -- freeze internal data structures if the original's
1920
1925
  # are frozen.
1921
1926
  def initialize_clone(other)
@@ -1965,9 +1970,9 @@ module Sequel
1965
1970
  "a hook failed"
1966
1971
  end
1967
1972
 
1968
- raise HookFailed.new(msg, self)
1973
+ raise hook_failed_error(msg)
1969
1974
  end
1970
-
1975
+
1971
1976
  # Get the ruby class or classes related to the given column's type.
1972
1977
  def schema_type_class(column)
1973
1978
  if (sch = db_schema[column]) && (type = sch[:type])
@@ -2060,6 +2065,11 @@ module Sequel
2060
2065
  def use_transaction?(opts = OPTS)
2061
2066
  opts.fetch(:transaction, use_transactions)
2062
2067
  end
2068
+
2069
+ # An ValidationFailed exception instance to raise for this instance.
2070
+ def validation_failed_error
2071
+ ValidationFailed.new(self)
2072
+ end
2063
2073
  end
2064
2074
 
2065
2075
  # DatasetMethods contains methods that all model datasets have.
@@ -15,7 +15,7 @@ module Sequel
15
15
  module AfterInitialize
16
16
  module ClassMethods
17
17
  # Call after_initialize for model objects loaded from the database.
18
- def call(h={})
18
+ def call(_)
19
19
  v = super
20
20
  v.after_initialize
21
21
  v
@@ -70,10 +70,21 @@ module Sequel
70
70
  #
71
71
  # Then you can do:
72
72
  #
73
- # artist.update_fields(params['artist'], %w'name albums_artists')
73
+ # artist.update_fields(params['artist'], %w'name albums_attributes')
74
+ #
75
+ # Note that Rails 5+ does not use a Hash for submitted parameters, and therefore
76
+ # the above will not work. With Rails 5+, you have to use:
77
+ #
78
+ # artist.update_fields(params.to_unsafe_h['artist'], %w'name albums_attributes')
74
79
  #
75
80
  # To save changes to the artist, create the first album and associate it to the artist,
76
81
  # and update the other existing associated album.
82
+ #
83
+ # You can pass options for individual nested attributes, which will override the default
84
+ # nested attributes options for that association. This is useful for per-call filtering
85
+ # of the allowed fields:
86
+ #
87
+ # a.set_nested_attributes(:albums, params['artist'], :fields=>%w'name')
77
88
  module NestedAttributes
78
89
  # Depend on the validate_associated plugin.
79
90
  def self.apply(model)
@@ -0,0 +1,110 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The throw_failures plugin throws HookFailed and ValidationFailed exceptions instead
6
+ # of raising them. If there is no matching catch block, the UncaughtThrowError will be rescued
7
+ # and the HookFailed or ValidationFailed exception will be raised normally.
8
+ #
9
+ # If you are setting up the catch blocks to handle these failures, in the failure case this
10
+ # plugin is about 10-15% faster on CRuby and 10x faster on JRuby. If you are not
11
+ # setting up the catch blocks, in the failure case this plugin is about 30% slower on CRuby
12
+ # and 2x slower on JRuby. So this plugin should only be used if you are setting up catch
13
+ # blocks manually.
14
+ #
15
+ # This plugin will setup catch blocks automatically for internally rescued HookFailed
16
+ # exceptions when the model is configured to not raise exceptions on failure (by default,
17
+ # the exceptions are internally rescued in that case.
18
+ #
19
+ # To set up the catch blocks, use the class of the exception:
20
+ #
21
+ # ret = catch(Sequel::ValidationFailed) do
22
+ # model_instance.save
23
+ # end
24
+ # if ret.is_a?(Sequel::ValidationFailed)
25
+ # # handle failure
26
+ # else
27
+ # # handle success
28
+ # end
29
+ #
30
+ # Usage:
31
+ #
32
+ # # Make all model subclass instances throw HookFailed and ValidationFailed exceptions
33
+ # # (called before loading subclasses)
34
+ # Sequel::Model.plugin :throw_failures
35
+ #
36
+ # # Make the Album class throw HookFailed and ValidationFailed exceptions
37
+ # Album.plugin :throw_failures
38
+ module ThrowFailures
39
+ module InstanceMethods
40
+ # Catch any thrown HookFailed exceptions.
41
+ def valid?(opts = OPTS)
42
+ catch_hook_failures{super} || false
43
+ end
44
+
45
+ private
46
+
47
+ # Catch any HookFailed exceptions thrown inside the block, and return
48
+ # nil if there were any.
49
+ def catch_hook_failures
50
+ called = ret = nil
51
+ caught = catch(HookFailed) do
52
+ ret = yield
53
+ called = true
54
+ end
55
+ ret if called
56
+ end
57
+
58
+ # Catch any thrown HookFailed exceptions if not raising on failure.
59
+ def checked_save_failure(opts)
60
+ if raise_on_failure?(opts)
61
+ super
62
+ else
63
+ catch_hook_failures{super}
64
+ end
65
+ end
66
+
67
+ if RUBY_VERSION >= '2.2' && (!defined?(JRUBY_VERSION) || JRUBY_VERSION > '9.1')
68
+ # Throw HookFailed with the generated error. If the throw is not
69
+ # caught, just return the originally generated error.
70
+ def hook_failed_error(msg)
71
+ e = super
72
+ throw HookFailed, e
73
+ rescue UncaughtThrowError
74
+ e
75
+ end
76
+
77
+ # Throw ValidationFailed with the generated error. If the throw is not
78
+ # caught, just return the originally generated error.
79
+ def validation_failed_error
80
+ e = super
81
+ throw ValidationFailed, e
82
+ rescue UncaughtThrowError
83
+ e
84
+ end
85
+ else
86
+ # UncaughtThrowError was added in Ruby 2.2. Older Ruby versions
87
+ # used ArgumentError with "uncaught throw" at the start of the message
88
+
89
+ # :nocov:
90
+ def hook_failed_error(msg)
91
+ e = super
92
+ throw HookFailed, e
93
+ rescue ArgumentError => e2
94
+ raise e2 unless e2.message.start_with?('uncaught throw')
95
+ e
96
+ end
97
+
98
+ def validation_failed_error
99
+ e = super
100
+ throw ValidationFailed, e
101
+ rescue ArgumentError => e2
102
+ raise e2 unless e2.message.start_with?('uncaught throw')
103
+ e
104
+ end
105
+ # :nocov:
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1020,6 +1020,11 @@ module Sequel
1020
1020
  include SQL::AliasMethods
1021
1021
  include SQL::CastMethods
1022
1022
 
1023
+ class << self
1024
+ # Alias new to call for usage in conversion procs
1025
+ alias call new
1026
+ end
1027
+
1023
1028
  # Return a LiteralString with the same content if no args are given, otherwise
1024
1029
  # return a SQL::PlaceholderLiteralString with the current string and the given args.
1025
1030
  def lit(*args)
@@ -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 = 17
9
+ MINOR = 18
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -20,6 +20,7 @@ Sequel::Database.extension :duplicate_column_handler if ENV['SEQUEL_DUPLICATE_CO
20
20
  Sequel::Database.extension :columns_introspection if ENV['SEQUEL_COLUMNS_INTROSPECTION']
21
21
  Sequel::Model.cache_associations = false if ENV['SEQUEL_NO_CACHE_ASSOCIATIONS']
22
22
  Sequel::Model.plugin :prepared_statements if ENV['SEQUEL_MODEL_PREPARED_STATEMENTS']
23
+ Sequel::Model.plugin :throw_failures if ENV['SEQUEL_MODEL_THROW_FAILURES']
23
24
  Sequel::Model.cache_anonymous_models = false
24
25
 
25
26
  require_relative '../guards_helper'
@@ -2681,10 +2681,11 @@ describe "Database extensions" do
2681
2681
  x = []
2682
2682
  Sequel::Database.register_extension(:a, Module.new{define_singleton_method(:extended){|_| x << :a}})
2683
2683
  Sequel::Database.register_extension(:b, Module.new{define_singleton_method(:extended){|_| x << :b}})
2684
+ m = Mutex.new
2684
2685
  c = Class.new(Sequel::Database) do
2685
2686
  def dataset_class_default; Sequel::Dataset end
2686
2687
  define_method(:connect) do |_|
2687
- x << :c
2688
+ m.synchronize{x << :c}
2688
2689
  :connect
2689
2690
  end
2690
2691
  end
@@ -1280,8 +1280,12 @@ describe "Sequel::SQL::Wrapper" do
1280
1280
  end
1281
1281
  end
1282
1282
 
1283
- describe "Sequel::SQL::Blob#to_sequel_blob" do
1284
- it "should return self" do
1283
+ describe "Sequel::SQL::Blob" do
1284
+ it ".call should be an alias for .new" do
1285
+ Sequel::SQL::Blob.call('a').must_equal Sequel::SQL::Blob.new('a')
1286
+ end
1287
+
1288
+ it "#to_sequel_blob should return self" do
1285
1289
  c = Sequel::SQL::Blob.new('a')
1286
1290
  c.to_sequel_blob.must_be_same_as(c)
1287
1291
  end
@@ -21,4 +21,8 @@ describe "Sequel::Plugins::AfterInitialize" do
21
21
  it "should have after_initialize hook be called for objects loaded from the database" do
22
22
  @c.call(:id=>1, :name=>'foo').values.must_equal(:id=>3, :name=>'foofoo')
23
23
  end
24
+
25
+ it "should not allow .call to be called without arguments" do
26
+ proc{@c.call}.must_raise ArgumentError
27
+ end
24
28
  end
@@ -73,11 +73,13 @@ describe "Sequel named_timezones extension" do
73
73
 
74
74
  it "should assume datetimes coming out of the database that don't have an offset as coming from database_timezone" do
75
75
  dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30')
76
+ dt.must_be_instance_of DateTime
76
77
  dt.must_equal @dt
77
78
  dt.offset.must_equal(-7/24.0)
78
79
 
79
80
  dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30')
80
- dt.must_equal @dt + 1/6.0
81
+ dt.must_be_instance_of DateTime
82
+ dt.must_equal(@dt + 1/6.0)
81
83
  dt.offset.must_equal(-7/24.0)
82
84
  end
83
85
 
@@ -60,3 +60,4 @@ end
60
60
  if ENV['SEQUEL_NO_CACHE_ASSOCIATIONS']
61
61
  Sequel::Model.cache_associations = false
62
62
  end
63
+ Sequel::Model.plugin :throw_failures if ENV['SEQUEL_MODEL_THROW_FAILURES']
@@ -0,0 +1,74 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "throw_failures plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:items)) do
6
+ plugin :throw_failures
7
+ columns :x
8
+ set_primary_key :x
9
+ unrestrict_primary_key
10
+ def before_create
11
+ super
12
+ cancel_action 'bc' if x == 2
13
+ end
14
+ def before_destroy
15
+ super
16
+ cancel_action 'bd' if x == 2
17
+ end
18
+ def validate
19
+ super
20
+ errors.add(:x, "3") if x == 3
21
+ end
22
+ end
23
+ DB.reset
24
+ end
25
+
26
+ it "should work normally if no exceptions are thrown/raised" do
27
+ o = @c.create(:x=>1)
28
+ o.must_be_kind_of @c
29
+ o.valid?.must_equal true
30
+ o.destroy.must_equal o
31
+ end
32
+
33
+ it "should work normally when not rescuing exceptions internally when calling save" do
34
+ @c.new.set(:x => 2).save(:raise_on_failure=>false).must_be_nil
35
+ @c.raise_on_save_failure = false
36
+ @c.create(:x => 2).must_be_nil
37
+ @c.load(:x => 2).destroy(:raise_on_failure=>false).must_be_nil
38
+ end
39
+
40
+ it "should work normally when not rescuing exceptions internally when calling valid?" do
41
+ @c.send(:define_method, :before_validation){cancel_action "bv"}
42
+ @c.new(:x => 2).valid?.must_equal false
43
+ end
44
+
45
+ it "should raise exceptions if no catch blocks have been setup and set to raise on failure" do
46
+ begin
47
+ @c.create(:x => 2)
48
+ rescue Sequel::HookFailed => e
49
+ e.backtrace.wont_be_empty
50
+ 1
51
+ end.must_equal 1
52
+
53
+ begin
54
+ @c.create(:x => 3)
55
+ rescue Sequel::ValidationFailed => e
56
+ e.backtrace.wont_be_empty
57
+ 1
58
+ end.must_equal 1
59
+ end
60
+
61
+ it "should allow catching exceptions instead of rescuing them" do
62
+ e = catch(Sequel::HookFailed){@c.create(:x => 2)}
63
+ e.must_be_kind_of Sequel::HookFailed
64
+ e.backtrace.must_be_nil
65
+
66
+ e = catch(Sequel::ValidationFailed){@c.create(:x => 3)}
67
+ e.must_be_kind_of Sequel::ValidationFailed
68
+ e.backtrace.must_be_nil
69
+
70
+ e = catch(Sequel::HookFailed){@c.load(:x => 2).destroy}
71
+ e.must_be_kind_of Sequel::HookFailed
72
+ e.backtrace.must_be_nil
73
+ end
74
+ end
@@ -149,6 +149,10 @@ describe "Simple Dataset operations" do
149
149
  rows.must_equal((1..100).map{|i| {:id=>i, :number=>i*10}}.reverse)
150
150
  end
151
151
 
152
+ rows = []
153
+ @ds.order(:number).limit(50, 25).paged_each(:rows_per_fetch=>3).each{|row| rows << row}
154
+ rows.must_equal((26..75).map{|i| {:id=>i, :number=>i*10}})
155
+
152
156
  rows = []
153
157
  @ds.order(:number).limit(50, 25).paged_each(:rows_per_fetch=>3){|row| rows << row}
154
158
  rows.must_equal((26..75).map{|i| {:id=>i, :number=>i*10}})
@@ -140,7 +140,7 @@ describe "Class Table Inheritance Plugin" do
140
140
  end
141
141
 
142
142
  it "should update rows in all tables" do
143
- Executive.first.update(:name=>'Ex2', :num_managers=>8, :num_staff=>9)
143
+ Executive[:id=>@i4].update(:name=>'Ex2', :num_managers=>8, :num_staff=>9)
144
144
  @db[:employees][:id=>@i4].must_equal(:id=>@i4, :name=>'Ex2', :kind=>'Executive')
145
145
  @db[:managers][:id=>@i4].must_equal(:id=>@i4, :num_staff=>9)
146
146
  @db[:executives][:id=>@i4].must_equal(:id=>@i4, :num_managers=>8)
@@ -20,6 +20,7 @@ Sequel.split_symbols = true if ENV['SEQUEL_SPLIT_SYMBOLS']
20
20
  Sequel::Database.extension :columns_introspection if ENV['SEQUEL_COLUMNS_INTROSPECTION']
21
21
  Sequel::Model.cache_associations = false if ENV['SEQUEL_NO_CACHE_ASSOCIATIONS']
22
22
  Sequel::Model.plugin :prepared_statements if ENV['SEQUEL_MODEL_PREPARED_STATEMENTS']
23
+ Sequel::Model.plugin :throw_failures if ENV['SEQUEL_MODEL_THROW_FAILURES']
23
24
  Sequel::Model.use_transactions = false
24
25
  Sequel::Model.cache_anonymous_models = false
25
26
 
@@ -588,9 +588,12 @@ describe Sequel::Model, ".fetch" do
588
588
  end
589
589
 
590
590
  it "should return true for .empty? and not raise an error on empty selection" do
591
- rows = @c.fetch("SELECT * FROM items WHERE FALSE")
592
- @c.send(:define_method, :fetch_rows){|sql| yield({:count => 0})}
593
- rows.empty?
591
+ @c.dataset = @c.dataset.with_extend do
592
+ def fetch_rows(sql)
593
+ yield({:count => 0})
594
+ end
595
+ end
596
+ @c.fetch("SELECT * FROM items WHERE FALSE").empty?
594
597
  end
595
598
  end
596
599
 
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.17.0
4
+ version: 5.18.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-02-01 00:00:00.000000000 Z
11
+ date: 2019-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -201,6 +201,7 @@ extra_rdoc_files:
201
201
  - doc/release_notes/5.15.0.txt
202
202
  - doc/release_notes/5.16.0.txt
203
203
  - doc/release_notes/5.17.0.txt
204
+ - doc/release_notes/5.18.0.txt
204
205
  files:
205
206
  - CHANGELOG
206
207
  - MIT-LICENSE
@@ -288,6 +289,7 @@ files:
288
289
  - doc/release_notes/5.15.0.txt
289
290
  - doc/release_notes/5.16.0.txt
290
291
  - doc/release_notes/5.17.0.txt
292
+ - doc/release_notes/5.18.0.txt
291
293
  - doc/release_notes/5.2.0.txt
292
294
  - doc/release_notes/5.3.0.txt
293
295
  - doc/release_notes/5.4.0.txt
@@ -534,6 +536,7 @@ files:
534
536
  - lib/sequel/plugins/subset_conditions.rb
535
537
  - lib/sequel/plugins/table_select.rb
536
538
  - lib/sequel/plugins/tactical_eager_loading.rb
539
+ - lib/sequel/plugins/throw_failures.rb
537
540
  - lib/sequel/plugins/timestamps.rb
538
541
  - lib/sequel/plugins/touch.rb
539
542
  - lib/sequel/plugins/tree.rb
@@ -709,6 +712,7 @@ files:
709
712
  - spec/extensions/table_select_spec.rb
710
713
  - spec/extensions/tactical_eager_loading_spec.rb
711
714
  - spec/extensions/thread_local_timezones_spec.rb
715
+ - spec/extensions/throw_failures_spec.rb
712
716
  - spec/extensions/timestamps_spec.rb
713
717
  - spec/extensions/to_dot_spec.rb
714
718
  - spec/extensions/touch_spec.rb