File tree Expand file tree Collapse file tree 6 files changed +87
-11
lines changed
lib/active_record/connection_adapters Expand file tree Collapse file tree 6 files changed +87
-11
lines changed Original file line number Diff line number Diff line change @@ -46,13 +46,20 @@ def type
46
46
def serialize ( value )
47
47
case value
48
48
when ::Numeric , ::Symbol , ActiveSupport ::Duration then value . to_s
49
+ when ::String then serialize_cast_value ( value )
49
50
when true then @true
50
51
when false then @false
51
52
else super
52
53
end
53
54
end
54
55
55
56
def serialize_cast_value ( value ) # :nodoc:
57
+ if value &.encoding == Encoding ::BINARY
58
+ # If we can treat the bytes as UTF-8 without changing them, then use UTF-8 as encoding
59
+ new_value = value . dup . force_encoding ( Encoding ::UTF_8 )
60
+ return new_value if new_value . valid_encoding?
61
+ end
62
+
56
63
value
57
64
end
58
65
Original file line number Diff line number Diff line change @@ -17,6 +17,26 @@ class ImmutableStringTest < ActiveModel::TestCase
17
17
assert_same s , type . cast ( s )
18
18
assert_same s , type . deserialize ( s )
19
19
end
20
+
21
+ test "leaves validly encoded strings untouched" do
22
+ s = "string with àccénts" . encode ( Encoding ::ISO_8859_1 )
23
+ type = Type ::ImmutableString . new
24
+ assert_same s , type . serialize ( s )
25
+ end
26
+
27
+ test "serializes valid, binary-encoded strings to UTF-8" do
28
+ s = "string with àccénts" . b
29
+ type = Type ::ImmutableString . new
30
+ serialized = type . serialize ( s )
31
+ assert_equal Encoding ::UTF_8 , serialized . encoding
32
+ assert_equal s . bytes , serialized . bytes
33
+ end
34
+
35
+ test "leaves true binary data untouched" do
36
+ binary_data = "\xEE \x49 \xC7 " . b
37
+ type = Type ::ImmutableString . new
38
+ assert_same binary_data , type . serialize ( binary_data )
39
+ end
20
40
end
21
41
end
22
42
end
Original file line number Diff line number Diff line change @@ -16,7 +16,10 @@ def quote(value)
16
16
when false then quoted_false
17
17
when nil then "NULL"
18
18
# BigDecimals need to be put in a non-normalized form and quoted.
19
- when BigDecimal then value . to_s ( "F" )
19
+ # Additionally, for Ruby 2.7, the string returned by `to_s` is ASCII-8BIT.
20
+ # We want to avoid that, as that will cause the string to be quoted as
21
+ # binary. It is safe to force the encoding to US-ASCII.
22
+ when BigDecimal then value . to_s ( "F" ) . force_encoding ( Encoding ::US_ASCII )
20
23
when Numeric then value . to_s
21
24
when Type ::Binary ::Data then quoted_binary ( value )
22
25
when Type ::Time ::Value then "'#{ quoted_time ( value ) } '"
Original file line number Diff line number Diff line change @@ -6,14 +6,30 @@ module ActiveRecord
6
6
module ConnectionAdapters
7
7
module MySQL
8
8
module Quoting # :nodoc:
9
+ def quote ( value )
10
+ case value
11
+ when String
12
+ if value . encoding == Encoding ::BINARY
13
+ quoted_binary ( value )
14
+ else
15
+ "'#{ quote_string ( value . to_s ) } '"
16
+ end
17
+ else
18
+ super
19
+ end
20
+ end
21
+
9
22
def cast_bound_value ( value )
10
23
case value
11
24
when Rational
12
25
value . to_f . to_s
26
+ when BigDecimal
27
+ # For Ruby 2.7, the string returned by `to_s` is ASCII-8BIT.
28
+ # We want to avoid that, as that will cause the string to be quoted as
29
+ # binary. It is safe to force the encoding to US-ASCII.
30
+ value . to_s ( "F" ) . force_encoding ( Encoding ::US_ASCII )
13
31
when Numeric
14
32
value . to_s
15
- when BigDecimal
16
- value . to_s ( "F" )
17
33
when true
18
34
"1"
19
35
when false
@@ -51,7 +67,11 @@ def quoted_date(value)
51
67
end
52
68
53
69
def quoted_binary ( value )
54
- "x'#{ value . hex } '"
70
+ if value . is_a? String
71
+ "x'#{ value . unpack1 ( "H*" ) } '"
72
+ else
73
+ "x'#{ value . hex } '"
74
+ end
55
75
end
56
76
57
77
def unquote_identifier ( identifier )
Original file line number Diff line number Diff line change @@ -4,6 +4,19 @@ module ActiveRecord
4
4
module ConnectionAdapters
5
5
module SQLite3
6
6
module Quoting # :nodoc:
7
+ def quote ( value )
8
+ case value
9
+ when String
10
+ if value . encoding == Encoding ::BINARY
11
+ quoted_binary ( value )
12
+ else
13
+ "'#{ quote_string ( value . to_s ) } '"
14
+ end
15
+ else
16
+ super
17
+ end
18
+ end
19
+
7
20
def quote_string ( s )
8
21
::SQLite3 ::Database . quote ( s )
9
22
end
@@ -26,7 +39,11 @@ def quoted_time(value)
26
39
end
27
40
28
41
def quoted_binary ( value )
29
- "x'#{ value . hex } '"
42
+ if value . is_a? String
43
+ "x'#{ value . unpack1 ( "H*" ) } '"
44
+ else
45
+ "x'#{ value . hex } '"
46
+ end
30
47
end
31
48
32
49
def quoted_true
@@ -62,12 +79,6 @@ def type_cast(value) # :nodoc:
62
79
case value
63
80
when BigDecimal
64
81
value . to_f
65
- when String
66
- if value . encoding == Encoding ::ASCII_8BIT
67
- super ( value . encode ( Encoding ::UTF_8 ) )
68
- else
69
- super
70
- end
71
82
else
72
83
super
73
84
end
Original file line number Diff line number Diff line change @@ -37,4 +37,19 @@ def test_load_save
37
37
assert_equal data , bin . reload . data , "Reloaded data differs from original"
38
38
end
39
39
end
40
+
41
+ unless current_adapter? ( :PostgreSQLAdapter )
42
+ def test_does_not_cause_database_warnings
43
+ original_db_warnings_action = ActiveRecord . db_warnings_action
44
+ ActiveRecord . db_warnings_action = :raise
45
+
46
+ Binary . delete_all
47
+ binary_data = "\xEE \x49 \xC7 " . b
48
+ Binary . connection . insert ( Arel . sql ( "INSERT INTO binaries(data) VALUES(?)" , binary_data ) )
49
+ binary = Binary . first
50
+ assert_equal binary_data , binary . data
51
+ ensure
52
+ ActiveRecord . db_warnings_action = original_db_warnings_action || :ignore
53
+ end
54
+ end
40
55
end
You can’t perform that action at this time.
0 commit comments