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