sequel 5.48.0 → 5.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +80 -0
- data/README.rdoc +12 -5
- data/doc/migration.rdoc +1 -1
- data/doc/opening_databases.rdoc +1 -1
- data/doc/postgresql.rdoc +8 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/testing.rdoc +3 -1
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +3 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +9 -11
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +42 -44
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +3 -3
- data/lib/sequel/adapters/postgres.rb +27 -29
- data/lib/sequel/adapters/shared/access.rb +2 -0
- data/lib/sequel/adapters/shared/db2.rb +2 -0
- data/lib/sequel/adapters/shared/mysql.rb +4 -2
- data/lib/sequel/adapters/shared/postgres.rb +59 -6
- data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
- data/lib/sequel/adapters/shared/sqlite.rb +1 -1
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +16 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/core.rb +17 -18
- data/lib/sequel/database/connecting.rb +2 -2
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/dataset/actions.rb +2 -2
- data/lib/sequel/dataset/query.rb +45 -3
- data/lib/sequel/dataset/sql.rb +18 -9
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/inflector.rb +1 -1
- data/lib/sequel/extensions/migration.rb +4 -1
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array_ops.rb +1 -1
- data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +1 -0
- data/lib/sequel/extensions/pg_json.rb +3 -5
- data/lib/sequel/extensions/pg_json_ops.rb +71 -1
- data/lib/sequel/extensions/pg_multirange.rb +372 -0
- data/lib/sequel/extensions/pg_range.rb +4 -12
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +108 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/model/associations.rb +3 -1
- data/lib/sequel/model/base.rb +9 -13
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/plugins/auto_validations.rb +25 -5
- data/lib/sequel/plugins/column_encryption.rb +1 -1
- data/lib/sequel/plugins/composition.rb +1 -0
- data/lib/sequel/plugins/json_serializer.rb +2 -2
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/serialization.rb +1 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
- data/lib/sequel/plugins/unused_associations.rb +2 -2
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +7 -1
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- 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
|
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
|
54
|
-
# loading this extension. Doing so will allow you to use
|
55
|
-
#
|
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
|
-
|
191
|
+
send INCLUDE_METH, Sequel::Postgres::RangeOpMethods
|
164
192
|
end
|
165
193
|
end
|
166
194
|
end
|
data/lib/sequel/extensions/s.rb
CHANGED
@@ -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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
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 =
|
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
|
-
|
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
|
@@ -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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
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
|
data/lib/sequel/model/base.rb
CHANGED
@@ -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
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
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
|
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
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
false
|
1637
|
-
end
|
1631
|
+
_valid?(opts)
|
1632
|
+
rescue HookFailed
|
1633
|
+
false
|
1638
1634
|
end
|
1639
1635
|
|
1640
1636
|
private
|
@@ -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,
|
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
|
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
|