sequel 5.48.0 → 5.52.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +80 -0
  3. data/README.rdoc +12 -5
  4. data/doc/migration.rdoc +1 -1
  5. data/doc/opening_databases.rdoc +1 -1
  6. data/doc/postgresql.rdoc +8 -0
  7. data/doc/release_notes/5.49.0.txt +59 -0
  8. data/doc/release_notes/5.50.0.txt +78 -0
  9. data/doc/release_notes/5.51.0.txt +47 -0
  10. data/doc/release_notes/5.52.0.txt +87 -0
  11. data/doc/testing.rdoc +3 -1
  12. data/lib/sequel/adapters/ado/access.rb +1 -1
  13. data/lib/sequel/adapters/ado.rb +1 -1
  14. data/lib/sequel/adapters/amalgalite.rb +3 -5
  15. data/lib/sequel/adapters/ibmdb.rb +2 -2
  16. data/lib/sequel/adapters/jdbc/derby.rb +3 -0
  17. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  18. data/lib/sequel/adapters/jdbc.rb +9 -11
  19. data/lib/sequel/adapters/mysql.rb +80 -67
  20. data/lib/sequel/adapters/mysql2.rb +42 -44
  21. data/lib/sequel/adapters/odbc.rb +1 -1
  22. data/lib/sequel/adapters/oracle.rb +3 -3
  23. data/lib/sequel/adapters/postgres.rb +27 -29
  24. data/lib/sequel/adapters/shared/access.rb +2 -0
  25. data/lib/sequel/adapters/shared/db2.rb +2 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +4 -2
  27. data/lib/sequel/adapters/shared/postgres.rb +59 -6
  28. data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
  29. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  30. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  31. data/lib/sequel/adapters/sqlite.rb +16 -18
  32. data/lib/sequel/adapters/tinytds.rb +1 -1
  33. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  34. data/lib/sequel/ast_transformer.rb +6 -0
  35. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  36. data/lib/sequel/connection_pool/single.rb +6 -8
  37. data/lib/sequel/core.rb +17 -18
  38. data/lib/sequel/database/connecting.rb +2 -2
  39. data/lib/sequel/database/misc.rb +6 -0
  40. data/lib/sequel/database/query.rb +1 -1
  41. data/lib/sequel/dataset/actions.rb +2 -2
  42. data/lib/sequel/dataset/query.rb +45 -3
  43. data/lib/sequel/dataset/sql.rb +18 -9
  44. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  45. data/lib/sequel/extensions/core_refinements.rb +36 -11
  46. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  47. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  48. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  49. data/lib/sequel/extensions/inflector.rb +1 -1
  50. data/lib/sequel/extensions/migration.rb +4 -1
  51. data/lib/sequel/extensions/pagination.rb +1 -1
  52. data/lib/sequel/extensions/pg_array_ops.rb +1 -1
  53. data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
  54. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  55. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  56. data/lib/sequel/extensions/pg_interval.rb +1 -0
  57. data/lib/sequel/extensions/pg_json.rb +3 -5
  58. data/lib/sequel/extensions/pg_json_ops.rb +71 -1
  59. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  60. data/lib/sequel/extensions/pg_range.rb +4 -12
  61. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  62. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  63. data/lib/sequel/extensions/s.rb +2 -1
  64. data/lib/sequel/extensions/server_block.rb +8 -12
  65. data/lib/sequel/extensions/sql_comments.rb +108 -3
  66. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  67. data/lib/sequel/extensions/string_agg.rb +1 -1
  68. data/lib/sequel/extensions/string_date_time.rb +19 -23
  69. data/lib/sequel/model/associations.rb +3 -1
  70. data/lib/sequel/model/base.rb +9 -13
  71. data/lib/sequel/model/inflections.rb +1 -1
  72. data/lib/sequel/plugins/auto_validations.rb +25 -5
  73. data/lib/sequel/plugins/column_encryption.rb +1 -1
  74. data/lib/sequel/plugins/composition.rb +1 -0
  75. data/lib/sequel/plugins/json_serializer.rb +2 -2
  76. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  77. data/lib/sequel/plugins/serialization.rb +1 -0
  78. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
  79. data/lib/sequel/plugins/sql_comments.rb +189 -0
  80. data/lib/sequel/plugins/static_cache.rb +1 -1
  81. data/lib/sequel/plugins/subclasses.rb +28 -11
  82. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
  83. data/lib/sequel/plugins/unused_associations.rb +2 -2
  84. data/lib/sequel/plugins/update_or_create.rb +1 -1
  85. data/lib/sequel/plugins/validation_helpers.rb +7 -1
  86. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  87. data/lib/sequel/sql.rb +1 -1
  88. data/lib/sequel/timezones.rb +12 -14
  89. data/lib/sequel/version.rb +1 -1
  90. metadata +17 -4
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
  #
3
3
  # The pg_range_ops extension adds support to Sequel's DSL to make
4
- # it easier to call PostgreSQL range functions and operators.
4
+ # it easier to call PostgreSQL range and multirange functions and operators.
5
5
  #
6
6
  # To load the extension:
7
7
  #
@@ -11,10 +11,11 @@
11
11
  #
12
12
  # r = Sequel.pg_range_op(:range)
13
13
  #
14
- # If you have also loaded the pg_range extension, you can use
15
- # Sequel.pg_range as well:
14
+ # If you have also loaded the pg_range or pg_multirange extensions, you can use
15
+ # Sequel.pg_range or Sequel.pg_multirange as well:
16
16
  #
17
17
  # r = Sequel.pg_range(:range)
18
+ # r = Sequel.pg_multirange(:range)
18
19
  #
19
20
  # Also, on most Sequel expression objects, you can call the pg_range
20
21
  # method:
@@ -46,13 +47,25 @@
46
47
  # r.upper_inc # upper_inc(range)
47
48
  # r.lower_inf # lower_inf(range)
48
49
  # r.upper_inf # upper_inf(range)
50
+ #
51
+ # All of the above methods work for both ranges and multiranges, as long
52
+ # as PostgreSQL supports the operation. The following methods are also
53
+ # supported:
54
+ #
55
+ # r.range_merge # range_merge(range)
56
+ # r.unnest # unnest(range)
57
+ # r.multirange # multirange(range)
58
+ #
59
+ # +range_merge+ and +unnest+ expect the receiver to represent a multirange
60
+ # value, while +multi_range+ expects the receiver to represent a range value.
49
61
  #
50
- # See the PostgreSQL range function and operator documentation for more
62
+ # See the PostgreSQL range and multirange function and operator documentation for more
51
63
  # details on what these functions and operators do.
52
64
  #
53
- # If you are also using the pg_range extension, you should load it before
54
- # loading this extension. Doing so will allow you to use PGArray#op to get
55
- # an RangeOp, allowing you to perform range operations on range literals.
65
+ # If you are also using the pg_range or pg_multirange extension, you should
66
+ # load them before loading this extension. Doing so will allow you to use
67
+ # PGRange#op and PGMultiRange#op to get a RangeOp, allowing you to perform
68
+ # range operations on range literals.
56
69
  #
57
70
  # Related module: Sequel::Postgres::RangeOp
58
71
 
@@ -77,9 +90,12 @@ module Sequel
77
90
  :overlaps => ["(".freeze, " && ".freeze, ")".freeze].freeze,
78
91
  }.freeze
79
92
 
80
- %w'lower upper isempty lower_inc upper_inc lower_inf upper_inf'.each do |f|
93
+ %w'lower upper isempty lower_inc upper_inc lower_inf upper_inf unnest'.each do |f|
81
94
  class_eval("def #{f}; function(:#{f}) end", __FILE__, __LINE__)
82
95
  end
96
+ %w'range_merge multirange'.each do |f|
97
+ class_eval("def #{f}; RangeOp.new(function(:#{f})) end", __FILE__, __LINE__)
98
+ end
83
99
  OPERATORS.each_key do |f|
84
100
  class_eval("def #{f}(v); operator(:#{f}, v) end", __FILE__, __LINE__)
85
101
  end
@@ -127,6 +143,18 @@ module Sequel
127
143
  end
128
144
  end
129
145
  end
146
+
147
+ # :nocov:
148
+ if defined?(PGMultiRange)
149
+ # :nocov:
150
+ class PGMultiRange
151
+ # Wrap the PGRange instance in an RangeOp, allowing you to easily use
152
+ # the PostgreSQL range functions and operators with literal ranges.
153
+ def op
154
+ RangeOp.new(self)
155
+ end
156
+ end
157
+ end
130
158
  end
131
159
 
132
160
  module SQL::Builders
@@ -160,7 +188,7 @@ end
160
188
  if defined?(Sequel::CoreRefinements)
161
189
  module Sequel::CoreRefinements
162
190
  refine Symbol do
163
- include Sequel::Postgres::RangeOpMethods
191
+ send INCLUDE_METH, Sequel::Postgres::RangeOpMethods
164
192
  end
165
193
  end
166
194
  end
@@ -210,7 +210,7 @@ end
210
210
  if defined?(Sequel::CoreRefinements)
211
211
  module Sequel::CoreRefinements
212
212
  refine Symbol do
213
- include Sequel::Postgres::PGRowOp::ExpressionMethods
213
+ send INCLUDE_METH, Sequel::Postgres::PGRowOp::ExpressionMethods
214
214
  end
215
215
  end
216
216
  end
@@ -51,9 +51,10 @@ module Sequel::S
51
51
 
52
52
  # :nocov:
53
53
  if RUBY_VERSION >= '2.0.0'
54
+ include_meth = RUBY_VERSION >= '3.1' ? :import_methods : :include
54
55
  # :nocov:
55
56
  refine Object do
56
- include Sequel::S
57
+ send include_meth, Sequel::S
57
58
  end
58
59
  end
59
60
  end
@@ -88,12 +88,10 @@ module Sequel
88
88
  module UnthreadedServerBlock
89
89
  # Set a default server/shard to use inside the block.
90
90
  def with_server(default_server, read_only_server=default_server)
91
- begin
92
- set_default_server(default_server, read_only_server)
93
- yield
94
- ensure
95
- clear_default_server
96
- end
91
+ set_default_server(default_server, read_only_server)
92
+ yield
93
+ ensure
94
+ clear_default_server
97
95
  end
98
96
 
99
97
  private
@@ -131,12 +129,10 @@ module Sequel
131
129
  # Set a default server/shard to use inside the block for the current
132
130
  # thread.
133
131
  def with_server(default_server, read_only_server=default_server)
134
- begin
135
- set_default_server(default_server, read_only_server)
136
- yield
137
- ensure
138
- clear_default_server
139
- end
132
+ set_default_server(default_server, read_only_server)
133
+ yield
134
+ ensure
135
+ clear_default_server
140
136
  end
141
137
 
142
138
  private
@@ -44,11 +44,51 @@
44
44
  #
45
45
  # DB.extension(:sql_comments)
46
46
  #
47
+ # Loading the sql_comments extension into the database also adds
48
+ # support for block-level comment support via Database#with_comments.
49
+ # You call #with_comments with a hash. Queries inside the hash will
50
+ # include a comment based on the hash (assuming they are inside the
51
+ # same thread):
52
+ #
53
+ # DB.with_comments(model: Album, action: :all) do
54
+ # DB[:albums].all
55
+ # # SELECT * FROM albums -- model:Album,action:all
56
+ # end
57
+ #
58
+ # You can nest calls to #with_comments, which will combine the
59
+ # entries from both calls:
60
+ #
61
+ # DB.with_comments(application: App, path: :scrubbed_path) do
62
+ # DB.with_comments(model: Album, action: :all) do
63
+ # ds = DB[:albums].all
64
+ # # SELECT * FROM albums
65
+ # # -- application:App,path:scrubbed_path,model:Album,action:all
66
+ # end
67
+ # end
68
+ #
69
+ # You can override comment entries specified in earlier blocks, or
70
+ # remove entries specified earlier using a nil value:
71
+ #
72
+ # DB.with_comments(application: App, path: :scrubbed_path) do
73
+ # DB.with_comments(application: Foo, path: nil) do
74
+ # ds = DB[:albums].all
75
+ # # SELECT * FROM albums # -- application:Foo
76
+ # end
77
+ # end
78
+ #
79
+ # You can combine block-level comments with dataset-specific
80
+ # comments:
81
+ #
82
+ # DB.with_comments(model: Album, action: :all) do
83
+ # DB[:table].comment("Some Comment").all
84
+ # # SELECT * FROM albums -- model:Album,action:all -- Some Comment
85
+ # end
86
+ #
47
87
  # Note that Microsoft Access does not support inline comments,
48
88
  # and attempting to use comments on it will result in SQL syntax
49
89
  # errors.
50
90
  #
51
- # Related module: Sequel::SQLComments
91
+ # Related modules: Sequel::SQLComments, Sequel::Database::SQLComments
52
92
 
53
93
  #
54
94
  module Sequel
@@ -62,7 +102,7 @@ module Sequel
62
102
  %w'select insert update delete'.each do |type|
63
103
  define_method(:"#{type}_sql") do |*a|
64
104
  sql = super(*a)
65
- if comment = @opts[:comment]
105
+ if comment = _sql_comment
66
106
  # This assumes that the comment stored in the dataset has
67
107
  # already been formatted. If not, this could result in SQL
68
108
  # injection.
@@ -74,8 +114,10 @@ module Sequel
74
114
  if sql.frozen?
75
115
  sql += comment
76
116
  sql.freeze
77
- else
117
+ elsif @opts[:append_sql] || @opts[:placeholder_literalizer]
78
118
  sql << comment
119
+ else
120
+ sql += comment
79
121
  end
80
122
  end
81
123
  sql
@@ -84,6 +126,11 @@ module Sequel
84
126
 
85
127
  private
86
128
 
129
+ # The comment to include in the SQL query, if any.
130
+ def _sql_comment
131
+ @opts[:comment]
132
+ end
133
+
87
134
  # Format the comment. For maximum compatibility, this uses a
88
135
  # single line SQL comment, and converts all consecutive whitespace
89
136
  # in the comment to a single space.
@@ -92,5 +139,63 @@ module Sequel
92
139
  end
93
140
  end
94
141
 
142
+ module Database::SQLComments
143
+ def self.extended(db)
144
+ db.instance_variable_set(:@comment_hashes, {})
145
+ db.extend_datasets DatasetSQLComments
146
+ end
147
+
148
+ # A map of threads to comment hashes, used for correctly setting
149
+ # comments for all queries inside #with_comments blocks.
150
+ attr_reader :comment_hashes
151
+
152
+ # Store the comment hash and use it to create comments inside the block
153
+ def with_comments(comment_hash)
154
+ hashes = @comment_hashes
155
+ t = Sequel.current
156
+ new_hash = if hash = Sequel.synchronize{hashes[t]}
157
+ hash.merge(comment_hash)
158
+ else
159
+ comment_hash.dup
160
+ end
161
+ yield Sequel.synchronize{hashes[t] = new_hash}
162
+ ensure
163
+ if hash
164
+ Sequel.synchronize{hashes[t] = hash}
165
+ else
166
+ t && Sequel.synchronize{hashes.delete(t)}
167
+ end
168
+ end
169
+
170
+ module DatasetSQLComments
171
+ include Sequel::SQLComments
172
+
173
+ # Include comments added via Database#with_comments in the output SQL.
174
+ def _sql_comment
175
+ specific_comment = super
176
+ return specific_comment if @opts[:append_sql]
177
+
178
+ t = Sequel.current
179
+ hashes = db.comment_hashes
180
+ block_comment = if comment_hash = Sequel.synchronize{hashes[t]}
181
+ comment_array = comment_hash.map{|k,v| "#{k}:#{v}" unless v.nil?}
182
+ comment_array.compact!
183
+ comment_array.join(",")
184
+ end
185
+
186
+ if block_comment
187
+ if specific_comment
188
+ format_sql_comment(block_comment + specific_comment)
189
+ else
190
+ format_sql_comment(block_comment)
191
+ end
192
+ else
193
+ specific_comment
194
+ end
195
+ end
196
+ end
197
+ end
198
+
95
199
  Dataset.register_extension(:sql_comments, SQLComments)
200
+ Database.register_extension(:sql_comments, Database::SQLComments)
96
201
  end
@@ -0,0 +1,108 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The sql_log_normalizer extension normalizes the SQL that is logged,
4
+ # removing the literal strings and numbers in the SQL, and removing the
5
+ # logging of any bound variables:
6
+ #
7
+ # ds = DB[:table].first(a: 1, b: 'something')
8
+ # # Without sql_log_normalizer extension
9
+ # # SELECT * FROM "table" WHERE (("a" = 1) AND ("b" = 'something')) LIMIT 1
10
+ #
11
+ # # With sql_log_normalizer_extension
12
+ # # SELECT * FROM "table" WHERE (("a" = ?) AND ("b" = ?)) LIMIT ?
13
+ #
14
+ # The normalization is done by scanning the SQL string being executed
15
+ # for literal strings and numbers, and replacing them with question
16
+ # marks. While this should work for all or almost all production queries,
17
+ # there are pathlogical queries that will not be handled correctly, such as
18
+ # the use of apostrophes in identifiers:
19
+ #
20
+ # DB[:"asf'bar"].where(a: 1, b: 'something').first
21
+ # # Logged as:
22
+ # # SELECT * FROM "asf?something')) LIMIT ?
23
+ #
24
+ # The expected use case for this extension is when you want to normalize
25
+ # logs to group similar queries, or when you want to protect sensitive
26
+ # data from being stored in the logs.
27
+ #
28
+ # Related module: Sequel::SQLLogNormalizer
29
+
30
+ #
31
+ module Sequel
32
+ module SQLLogNormalizer
33
+ def self.extended(db)
34
+ type = case db.literal("'")
35
+ when "''''"
36
+ :standard
37
+ when "'\\''"
38
+ :backslash
39
+ when "N''''"
40
+ :n_standard
41
+ else
42
+ raise Error, "SQL log normalization is not supported on this database (' literalized as #{db.literal("'").inspect})"
43
+ end
44
+ db.instance_variable_set(:@sql_string_escape_type, type)
45
+ end
46
+
47
+ # Normalize the SQL before calling super.
48
+ def log_connection_yield(sql, conn, args=nil)
49
+ unless skip_logging?
50
+ sql = normalize_logged_sql(sql)
51
+ args = nil
52
+ end
53
+ super
54
+ end
55
+
56
+ # Replace literal strings and numbers in SQL with question mark placeholders.
57
+ def normalize_logged_sql(sql)
58
+ sql = sql.dup
59
+ sql.force_encoding('BINARY')
60
+ start_index = 0
61
+ check_n = @sql_string_escape_type == :n_standard
62
+ outside_string = true
63
+
64
+ if @sql_string_escape_type == :backslash
65
+ search_char = /[\\']/
66
+ escape_char_offset = 0
67
+ escape_char_value = 92 # backslash
68
+ else
69
+ search_char = "'"
70
+ escape_char_offset = 1
71
+ escape_char_value = 39 # apostrophe
72
+ end
73
+
74
+ # The approach used here goes against Sequel's philosophy of never attempting
75
+ # to parse SQL. However, parsing the SQL is basically the only way to implement
76
+ # this support with Sequel's design, and it's better to be pragmatic and accept
77
+ # this than not be able to support this.
78
+
79
+ # Replace literal strings
80
+ while outside_string && (index = start_index = sql.index("'", start_index))
81
+ if check_n && index != 0 && sql.getbyte(index-1) == 78 # N' start
82
+ start_index -= 1
83
+ end
84
+ index += 1
85
+ outside_string = false
86
+
87
+ while (index = sql.index(search_char, index)) && (sql.getbyte(index + escape_char_offset) == escape_char_value)
88
+ # skip escaped characters inside string literal
89
+ index += 2
90
+ end
91
+
92
+ if index
93
+ # Found end of string
94
+ sql[start_index..index] = '?'
95
+ start_index += 1
96
+ outside_string = true
97
+ end
98
+ end
99
+
100
+ # Replace integer and decimal floating point numbers
101
+ sql.gsub!(/\b-?\d+(?:\.\d+)?\b/, '?')
102
+
103
+ sql
104
+ end
105
+ end
106
+
107
+ Database.register_extension(:sql_log_normalizer, SQLLogNormalizer)
108
+ end
@@ -147,7 +147,7 @@ module Sequel
147
147
  def initialize(expr, separator=nil)
148
148
  @expr = expr
149
149
  @separator = separator
150
- yield self if block_given?
150
+ yield self if defined?(yield)
151
151
  freeze
152
152
  end
153
153
 
@@ -4,6 +4,10 @@
4
4
  # for converting the strings to a date (e.g. String#to_date), allowing
5
5
  # for backwards compatibility with legacy Sequel code.
6
6
  #
7
+ # These methods calls +parse+ on the related class, and as such, can
8
+ # result in denial of service in older versions of Ruby for large
9
+ # untrusted input, and raise exceptions in newer versions of Ruby.
10
+ #
7
11
  # To load the extension:
8
12
  #
9
13
  # Sequel.extension :string_date_time
@@ -11,42 +15,34 @@
11
15
  class String
12
16
  # Converts a string into a Date object.
13
17
  def to_date
14
- begin
15
- Date.parse(self, Sequel.convert_two_digit_years)
16
- rescue => e
17
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
18
- end
18
+ Date.parse(self, Sequel.convert_two_digit_years)
19
+ rescue => e
20
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
19
21
  end
20
22
 
21
23
  # Converts a string into a DateTime object.
22
24
  def to_datetime
23
- begin
24
- DateTime.parse(self, Sequel.convert_two_digit_years)
25
- rescue => e
26
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
27
- end
25
+ DateTime.parse(self, Sequel.convert_two_digit_years)
26
+ rescue => e
27
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
28
28
  end
29
29
 
30
30
  # Converts a string into a Time or DateTime object, depending on the
31
31
  # value of Sequel.datetime_class
32
32
  def to_sequel_time
33
- begin
34
- if Sequel.datetime_class == DateTime
35
- DateTime.parse(self, Sequel.convert_two_digit_years)
36
- else
37
- Sequel.datetime_class.parse(self)
38
- end
39
- rescue => e
40
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
33
+ if Sequel.datetime_class == DateTime
34
+ DateTime.parse(self, Sequel.convert_two_digit_years)
35
+ else
36
+ Sequel.datetime_class.parse(self)
41
37
  end
38
+ rescue => e
39
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
42
40
  end
43
41
 
44
42
  # Converts a string into a Time object.
45
43
  def to_time
46
- begin
47
- Time.parse(self)
48
- rescue => e
49
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
50
- end
44
+ Time.parse(self)
45
+ rescue => e
46
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
51
47
  end
52
48
  end
@@ -1836,7 +1836,9 @@ module Sequel
1836
1836
 
1837
1837
  if opts[:clone]
1838
1838
  cloned_assoc = association_reflection(opts[:clone])
1839
+ remove_class_name = orig_opts[:class] && !orig_opts[:class_name]
1839
1840
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1841
+ orig_opts.delete(:class_name) if remove_class_name
1840
1842
  end
1841
1843
 
1842
1844
  opts = Hash[default_association_options]
@@ -3128,7 +3130,7 @@ module Sequel
3128
3130
 
3129
3131
  # The secondary eager loading method. Loads all associations in a single query. This
3130
3132
  # method should only be used if you need to filter or order based on columns in associated tables,
3131
- # or if you have done comparative benchmarking it and determined it is faster.
3133
+ # or if you have done comparative benchmarking and determined it is faster.
3132
3134
  #
3133
3135
  # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
3134
3136
  # tables. Then it uses the graph's metadata to build the associations from the single hash, and
@@ -682,13 +682,11 @@ module Sequel
682
682
 
683
683
  # Yield to the passed block and if do_raise is false, swallow all errors other than DatabaseConnectionErrors.
684
684
  def check_non_connection_error(do_raise=require_valid_table)
685
- begin
686
- db.transaction(:savepoint=>:only){yield}
687
- rescue Sequel::DatabaseConnectionError
688
- raise
689
- rescue Sequel::Error
690
- raise if do_raise
691
- end
685
+ db.transaction(:savepoint=>:only){yield}
686
+ rescue Sequel::DatabaseConnectionError
687
+ raise
688
+ rescue Sequel::Error
689
+ raise if do_raise
692
690
  end
693
691
 
694
692
  # Convert the given object to a Dataset that should be used as
@@ -1093,7 +1091,7 @@ module Sequel
1093
1091
  @modified = true
1094
1092
  initialize_set(values)
1095
1093
  _clear_changed_columns(:initialize)
1096
- yield self if block_given?
1094
+ yield self if defined?(yield)
1097
1095
  end
1098
1096
 
1099
1097
  # Returns value of the column's attribute.
@@ -1630,11 +1628,9 @@ module Sequel
1630
1628
  # artist.set(name: 'Invalid').valid? # => false
1631
1629
  # artist.errors.full_messages # => ['name cannot be Invalid']
1632
1630
  def valid?(opts = OPTS)
1633
- begin
1634
- _valid?(opts)
1635
- rescue HookFailed
1636
- false
1637
- end
1631
+ _valid?(opts)
1632
+ rescue HookFailed
1633
+ false
1638
1634
  end
1639
1635
 
1640
1636
  private
@@ -4,7 +4,7 @@ module Sequel
4
4
  # Yield the Inflections module if a block is given, and return
5
5
  # the Inflections module.
6
6
  def self.inflections
7
- yield Inflections if block_given?
7
+ yield Inflections if defined?(yield)
8
8
  Inflections
9
9
  end
10
10
 
@@ -71,12 +71,14 @@ module Sequel
71
71
  MAX_LENGTH_OPTIONS = {:from=>:values, :allow_nil=>true}.freeze
72
72
  SCHEMA_TYPES_OPTIONS = NOT_NULL_OPTIONS
73
73
  UNIQUE_OPTIONS = NOT_NULL_OPTIONS
74
+ NO_NULL_BYTE_OPTIONS = MAX_LENGTH_OPTIONS
74
75
  EMPTY_ARRAY = [].freeze
75
76
 
76
77
  def self.apply(model, opts=OPTS)
77
78
  model.instance_exec do
78
79
  plugin :validation_helpers
79
80
  @auto_validate_presence = false
81
+ @auto_validate_no_null_byte_columns = []
80
82
  @auto_validate_not_null_columns = []
81
83
  @auto_validate_explicit_not_null_columns = []
82
84
  @auto_validate_max_length_columns = []
@@ -84,6 +86,7 @@ module Sequel
84
86
  @auto_validate_types = true
85
87
 
86
88
  @auto_validate_options = {
89
+ :no_null_byte=>NO_NULL_BYTE_OPTIONS,
87
90
  :not_null=>NOT_NULL_OPTIONS,
88
91
  :explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
89
92
  :max_length=>MAX_LENGTH_OPTIONS,
@@ -102,14 +105,14 @@ module Sequel
102
105
  end
103
106
 
104
107
  h = @auto_validate_options.dup
105
- [:not_null, :explicit_not_null, :max_length, :schema_types, :unique].each do |type|
108
+ [:not_null, :explicit_not_null, :max_length, :no_null_byte, :schema_types, :unique].each do |type|
106
109
  if type_opts = opts[:"#{type}_opts"]
107
110
  h[type] = h[type].merge(type_opts).freeze
108
111
  end
109
112
  end
110
113
 
111
114
  if opts[:skip_invalid]
112
- [:not_null, :explicit_not_null, :max_length, :schema_types].each do |type|
115
+ [:not_null, :explicit_not_null, :no_null_byte, :max_length, :schema_types].each do |type|
113
116
  h[type] = h[type].merge(:skip_invalid=>true).freeze
114
117
  end
115
118
  end
@@ -119,6 +122,9 @@ module Sequel
119
122
  end
120
123
 
121
124
  module ClassMethods
125
+ # The columns with automatic no_null_byte validations
126
+ attr_reader :auto_validate_no_null_byte_columns
127
+
122
128
  # The columns with automatic not_null validations
123
129
  attr_reader :auto_validate_not_null_columns
124
130
 
@@ -135,7 +141,15 @@ module Sequel
135
141
  # Inherited options
136
142
  attr_reader :auto_validate_options
137
143
 
138
- Plugins.inherited_instance_variables(self, :@auto_validate_presence=>nil, :@auto_validate_types=>nil, :@auto_validate_not_null_columns=>:dup, :@auto_validate_explicit_not_null_columns=>:dup, :@auto_validate_max_length_columns=>:dup, :@auto_validate_unique_columns=>:dup, :@auto_validate_options => :dup)
144
+ Plugins.inherited_instance_variables(self,
145
+ :@auto_validate_presence=>nil,
146
+ :@auto_validate_types=>nil,
147
+ :@auto_validate_no_null_byte_columns=>:dup,
148
+ :@auto_validate_not_null_columns=>:dup,
149
+ :@auto_validate_explicit_not_null_columns=>:dup,
150
+ :@auto_validate_max_length_columns=>:dup,
151
+ :@auto_validate_unique_columns=>:dup,
152
+ :@auto_validate_options => :dup)
139
153
  Plugins.after_set_dataset(self, :setup_auto_validations)
140
154
 
141
155
  # Whether to use a presence validation for not null columns
@@ -150,6 +164,7 @@ module Sequel
150
164
 
151
165
  # Freeze auto_validation settings when freezing model class.
152
166
  def freeze
167
+ @auto_validate_no_null_byte_columns.freeze
153
168
  @auto_validate_not_null_columns.freeze
154
169
  @auto_validate_explicit_not_null_columns.freeze
155
170
  @auto_validate_max_length_columns.freeze
@@ -158,12 +173,13 @@ module Sequel
158
173
  super
159
174
  end
160
175
 
161
- # Skip automatic validations for the given validation type (:not_null, :types, :unique).
176
+ # Skip automatic validations for the given validation type
177
+ # (:not_null, :types, :unique, :max_length, :no_null_byte).
162
178
  # If :all is given as the type, skip all auto validations.
163
179
  def skip_auto_validations(type)
164
180
  case type
165
181
  when :all
166
- [:not_null, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
182
+ [:not_null, :no_null_byte, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
167
183
  when :not_null
168
184
  auto_validate_not_null_columns.clear
169
185
  auto_validate_explicit_not_null_columns.clear
@@ -183,6 +199,7 @@ module Sequel
183
199
  explicit_not_null_cols += Array(primary_key)
184
200
  @auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
185
201
  @auto_validate_max_length_columns = db_schema.select{|col, sch| sch[:type] == :string && sch[:max_length].is_a?(Integer)}.map{|col, sch| [col, sch[:max_length]]}
202
+ @auto_validate_no_null_byte_columns = db_schema.select{|_, sch| sch[:type] == :string}.map{|col, _| col}
186
203
  table = dataset.first_source_table
187
204
  @auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
188
205
  db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns].length == 1 ? idx[:columns].first : idx[:columns]}
@@ -209,6 +226,9 @@ module Sequel
209
226
  return if skip.include?(:all)
210
227
  opts = model.auto_validate_options
211
228
 
229
+ unless skip.include?(:no_null_byte) || (no_null_byte_columns = model.auto_validate_no_null_byte_columns).empty?
230
+ validates_no_null_byte(no_null_byte_columns, opts[:no_null_byte])
231
+ end
212
232
 
213
233
  unless skip.include?(:not_null)
214
234
  not_null_method = model.auto_validate_presence? ? :validates_presence : :validates_not_null
@@ -608,7 +608,7 @@ module Sequel
608
608
 
609
609
  # Setup encryption for the given column.
610
610
  def _encrypt_column(column, opts)
611
- cryptor ||= if block_given?
611
+ cryptor ||= if defined?(yield)
612
612
  dsl = ColumnDSL.new
613
613
  yield dsl
614
614
  Cryptor.new(dsl.keys)
@@ -174,6 +174,7 @@ module Sequel
174
174
  compositions
175
175
  super
176
176
  compositions.freeze
177
+ self
177
178
  end
178
179
 
179
180
  # For each composition, set the columns in the model class based