sequel 5.17.0 → 5.18.0

Sign up to get free protection for your applications and to get access to all the features.
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