activerecord-cipherstash-pg-adapter 0.8.1 → 0.8.2
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.md +4 -0
- data/activerecord-cipherstash-pg-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/column.rb +70 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/database_statements.rb +199 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/point.rb +64 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/range.rb +124 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/type_map_initializer.rb +125 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/uuid.rb +35 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/oid.rb +38 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/quoting.rb +237 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/referential_integrity.rb +71 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/schema_creation.rb +170 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/schema_definitions.rb +372 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/schema_dumper.rb +116 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/schema_statements.rb +1110 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/7.1/cipherstash_pg/utils.rb +79 -0
- data/lib/active_record/connection_adapters/7.1/postgres_cipherstash_adapter.rb +1266 -0
- data/lib/active_record/connection_adapters/postgres_cipherstash_adapter.rb +5 -1
- data/lib/version.rb +1 -1
- metadata +44 -5
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "strscan"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module CipherStashPG
|
8
|
+
module OID # :nodoc:
|
9
|
+
class Hstore < Type::Value # :nodoc:
|
10
|
+
ERROR = "Invalid Hstore document: %s"
|
11
|
+
|
12
|
+
include ActiveModel::Type::Helpers::Mutable
|
13
|
+
|
14
|
+
def type
|
15
|
+
:hstore
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(value)
|
19
|
+
return value unless value.is_a?(::String)
|
20
|
+
|
21
|
+
scanner = StringScanner.new(value)
|
22
|
+
hash = {}
|
23
|
+
|
24
|
+
until scanner.eos?
|
25
|
+
unless scanner.skip(/"/)
|
26
|
+
raise(ArgumentError, ERROR % scanner.string.inspect)
|
27
|
+
end
|
28
|
+
|
29
|
+
unless key = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
|
30
|
+
raise(ArgumentError, ERROR % scanner.string.inspect)
|
31
|
+
end
|
32
|
+
|
33
|
+
unless scanner.skip(/"=>?/)
|
34
|
+
raise(ArgumentError, ERROR % scanner.string.inspect)
|
35
|
+
end
|
36
|
+
|
37
|
+
if scanner.scan(/NULL/)
|
38
|
+
value = nil
|
39
|
+
else
|
40
|
+
unless scanner.skip(/"/)
|
41
|
+
raise(ArgumentError, ERROR % scanner.string.inspect)
|
42
|
+
end
|
43
|
+
|
44
|
+
unless value = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
|
45
|
+
raise(ArgumentError, ERROR % scanner.string.inspect)
|
46
|
+
end
|
47
|
+
|
48
|
+
unless scanner.skip(/"/)
|
49
|
+
raise(ArgumentError, ERROR % scanner.string.inspect)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
key.gsub!('\"', '"')
|
54
|
+
key.gsub!("\\\\", "\\")
|
55
|
+
|
56
|
+
if value
|
57
|
+
value.gsub!('\"', '"')
|
58
|
+
value.gsub!("\\\\", "\\")
|
59
|
+
end
|
60
|
+
|
61
|
+
hash[key] = value
|
62
|
+
|
63
|
+
unless scanner.skip(/, /) || scanner.eos?
|
64
|
+
raise(ArgumentError, ERROR % scanner.string.inspect)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
hash
|
69
|
+
end
|
70
|
+
|
71
|
+
def serialize(value)
|
72
|
+
if value.is_a?(::Hash)
|
73
|
+
value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ")
|
74
|
+
elsif value.respond_to?(:to_unsafe_h)
|
75
|
+
serialize(value.to_unsafe_h)
|
76
|
+
else
|
77
|
+
value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def accessor
|
82
|
+
ActiveRecord::Store::StringKeyedHashAccessor
|
83
|
+
end
|
84
|
+
|
85
|
+
# Will compare the Hash equivalents of +raw_old_value+ and +new_value+.
|
86
|
+
# By comparing hashes, this avoids an edge case where the order of
|
87
|
+
# the keys change between the two hashes, and they would not be marked
|
88
|
+
# as equal.
|
89
|
+
def changed_in_place?(raw_old_value, new_value)
|
90
|
+
deserialize(raw_old_value) != new_value
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
def escape_hstore(value)
|
95
|
+
if value.nil?
|
96
|
+
"NULL"
|
97
|
+
else
|
98
|
+
if value == ""
|
99
|
+
'""'
|
100
|
+
else
|
101
|
+
'"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/duration"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module CipherStashPG
|
8
|
+
module OID # :nodoc:
|
9
|
+
class Interval < Type::Value # :nodoc:
|
10
|
+
def type
|
11
|
+
:interval
|
12
|
+
end
|
13
|
+
|
14
|
+
def cast_value(value)
|
15
|
+
case value
|
16
|
+
when ::ActiveSupport::Duration
|
17
|
+
value
|
18
|
+
when ::String
|
19
|
+
begin
|
20
|
+
::ActiveSupport::Duration.parse(value)
|
21
|
+
rescue ::ActiveSupport::Duration::ISO8601Parser::ParsingError
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize(value)
|
30
|
+
case value
|
31
|
+
when ::ActiveSupport::Duration
|
32
|
+
value.iso8601(precision: self.precision)
|
33
|
+
when ::Numeric
|
34
|
+
# Sometimes operations on Times returns just float number of seconds so we need to handle that.
|
35
|
+
# Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
|
36
|
+
value.seconds.iso8601(precision: self.precision)
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def type_cast_for_schema(value)
|
43
|
+
serialize(value).inspect
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class LegacyPoint < Type::Value # :nodoc:
|
8
|
+
include ActiveModel::Type::Helpers::Mutable
|
9
|
+
|
10
|
+
def type
|
11
|
+
:point
|
12
|
+
end
|
13
|
+
|
14
|
+
def cast(value)
|
15
|
+
case value
|
16
|
+
when ::String
|
17
|
+
if value.start_with?("(") && value.end_with?(")")
|
18
|
+
value = value[1...-1]
|
19
|
+
end
|
20
|
+
cast(value.split(","))
|
21
|
+
when ::Array
|
22
|
+
value.map { |v| Float(v) }
|
23
|
+
else
|
24
|
+
value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def serialize(value)
|
29
|
+
if value.is_a?(::Array)
|
30
|
+
"(#{number_for_point(value[0])},#{number_for_point(value[1])})"
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def number_for_point(number)
|
38
|
+
number.to_s.delete_suffix(".0")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Macaddr < Type::String # :nodoc:
|
8
|
+
def type
|
9
|
+
:macaddr
|
10
|
+
end
|
11
|
+
|
12
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
13
|
+
old_value.class != new_value.class ||
|
14
|
+
new_value && old_value.casecmp(new_value) != 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def changed_in_place?(raw_old_value, new_value)
|
18
|
+
raw_old_value.class != new_value.class ||
|
19
|
+
new_value && raw_old_value.casecmp(new_value) != 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Money < Type::Decimal # :nodoc:
|
8
|
+
def type
|
9
|
+
:money
|
10
|
+
end
|
11
|
+
|
12
|
+
def scale
|
13
|
+
2
|
14
|
+
end
|
15
|
+
|
16
|
+
def cast_value(value)
|
17
|
+
return value unless ::String === value
|
18
|
+
|
19
|
+
# Because money output is formatted according to the locale, there are two
|
20
|
+
# cases to consider (note the decimal separators):
|
21
|
+
# (1) $12,345,678.12
|
22
|
+
# (2) $12.345.678,12
|
23
|
+
# Negative values are represented as follows:
|
24
|
+
# (3) -$2.55
|
25
|
+
# (4) ($2.55)
|
26
|
+
|
27
|
+
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
|
28
|
+
case value
|
29
|
+
when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
|
30
|
+
value.gsub!(/[^-\d.]/, "")
|
31
|
+
when /^-?\D*+[\d.]+,\d{2}$/ # (2)
|
32
|
+
value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
|
33
|
+
end
|
34
|
+
|
35
|
+
super(value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
Point = Struct.new(:x, :y)
|
5
|
+
|
6
|
+
module ConnectionAdapters
|
7
|
+
module CipherStashPG
|
8
|
+
module OID # :nodoc:
|
9
|
+
class Point < Type::Value # :nodoc:
|
10
|
+
include ActiveModel::Type::Helpers::Mutable
|
11
|
+
|
12
|
+
def type
|
13
|
+
:point
|
14
|
+
end
|
15
|
+
|
16
|
+
def cast(value)
|
17
|
+
case value
|
18
|
+
when ::String
|
19
|
+
return if value.blank?
|
20
|
+
|
21
|
+
if value.start_with?("(") && value.end_with?(")")
|
22
|
+
value = value[1...-1]
|
23
|
+
end
|
24
|
+
x, y = value.split(",")
|
25
|
+
build_point(x, y)
|
26
|
+
when ::Array
|
27
|
+
build_point(*value)
|
28
|
+
else
|
29
|
+
value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def serialize(value)
|
34
|
+
case value
|
35
|
+
when ActiveRecord::Point
|
36
|
+
"(#{number_for_point(value.x)},#{number_for_point(value.y)})"
|
37
|
+
when ::Array
|
38
|
+
serialize(build_point(*value))
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def type_cast_for_schema(value)
|
45
|
+
if ActiveRecord::Point === value
|
46
|
+
[value.x, value.y]
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def number_for_point(number)
|
54
|
+
number.to_s.delete_suffix(".0")
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_point(x, y)
|
58
|
+
ActiveRecord::Point.new(Float(x), Float(y))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Range < Type::Value # :nodoc:
|
8
|
+
attr_reader :subtype, :type
|
9
|
+
delegate :user_input_in_time_zone, to: :subtype
|
10
|
+
|
11
|
+
def initialize(subtype, type = :range)
|
12
|
+
@subtype = subtype
|
13
|
+
@type = type
|
14
|
+
end
|
15
|
+
|
16
|
+
def type_cast_for_schema(value)
|
17
|
+
value.inspect.gsub("Infinity", "::Float::INFINITY")
|
18
|
+
end
|
19
|
+
|
20
|
+
def cast_value(value)
|
21
|
+
return if ["empty", ""].include? value
|
22
|
+
return value unless value.is_a?(::String)
|
23
|
+
|
24
|
+
extracted = extract_bounds(value)
|
25
|
+
from = type_cast_single extracted[:from]
|
26
|
+
to = type_cast_single extracted[:to]
|
27
|
+
|
28
|
+
if !infinity?(from) && extracted[:exclude_start]
|
29
|
+
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
|
30
|
+
end
|
31
|
+
::Range.new(*sanitize_bounds(from, to), extracted[:exclude_end])
|
32
|
+
end
|
33
|
+
|
34
|
+
def serialize(value)
|
35
|
+
if value.is_a?(::Range)
|
36
|
+
from = type_cast_single_for_database(value.begin)
|
37
|
+
to = type_cast_single_for_database(value.end)
|
38
|
+
::Range.new(from, to, value.exclude_end?)
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
other.is_a?(Range) &&
|
46
|
+
other.subtype == subtype &&
|
47
|
+
other.type == type
|
48
|
+
end
|
49
|
+
|
50
|
+
def map(value) # :nodoc:
|
51
|
+
new_begin = yield(value.begin)
|
52
|
+
new_end = yield(value.end)
|
53
|
+
::Range.new(new_begin, new_end, value.exclude_end?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def force_equality?(value)
|
57
|
+
value.is_a?(::Range)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def type_cast_single(value)
|
62
|
+
infinity?(value) ? value : @subtype.deserialize(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def type_cast_single_for_database(value)
|
66
|
+
infinity?(value) ? value : @subtype.serialize(@subtype.cast(value))
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_bounds(value)
|
70
|
+
from, to = value[1..-2].split(",", 2)
|
71
|
+
{
|
72
|
+
from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
|
73
|
+
to: (to == "" || to == "infinity") ? infinity : unquote(to),
|
74
|
+
exclude_start: value.start_with?("("),
|
75
|
+
exclude_end: value.end_with?(")")
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
INFINITE_FLOAT_RANGE = (-::Float::INFINITY)..(::Float::INFINITY) # :nodoc:
|
80
|
+
|
81
|
+
def sanitize_bounds(from, to)
|
82
|
+
[
|
83
|
+
(from == -::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(to)) ? nil : from,
|
84
|
+
(to == ::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(from)) ? nil : to
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
# When formatting the bound values of range types, PostgreSQL quotes
|
89
|
+
# the bound value using double-quotes in certain conditions. Within
|
90
|
+
# a double-quoted string, literal " and \ characters are themselves
|
91
|
+
# escaped. In input, PostgreSQL accepts multiple escape styles for "
|
92
|
+
# (either \" or "") but in output always uses "".
|
93
|
+
# See:
|
94
|
+
# * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
|
95
|
+
# * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
|
96
|
+
def unquote(value)
|
97
|
+
if value.start_with?('"') && value.end_with?('"')
|
98
|
+
unquoted_value = value[1..-2]
|
99
|
+
unquoted_value.gsub!('""', '"')
|
100
|
+
unquoted_value.gsub!("\\\\", "\\")
|
101
|
+
unquoted_value
|
102
|
+
else
|
103
|
+
value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def infinity(negative: false)
|
108
|
+
if subtype.respond_to?(:infinity)
|
109
|
+
subtype.infinity(negative: negative)
|
110
|
+
elsif negative
|
111
|
+
-::Float::INFINITY
|
112
|
+
else
|
113
|
+
::Float::INFINITY
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def infinity?(value)
|
118
|
+
value.respond_to?(:infinite?) && value.infinite?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class SpecializedString < Type::String # :nodoc:
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
def initialize(type, **options)
|
11
|
+
@type = type
|
12
|
+
super(**options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Timestamp < DateTime # :nodoc:
|
8
|
+
def type
|
9
|
+
real_type_unless_aliased(:timestamp)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class TimestampWithTimeZone < DateTime # :nodoc:
|
8
|
+
def type
|
9
|
+
real_type_unless_aliased(:timestamptz)
|
10
|
+
end
|
11
|
+
|
12
|
+
def cast_value(value)
|
13
|
+
return if value.blank?
|
14
|
+
|
15
|
+
time = super
|
16
|
+
return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time)
|
17
|
+
|
18
|
+
# While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to Postgres in UTC.
|
19
|
+
# We prefer times always in UTC, so here we convert back.
|
20
|
+
if is_utc?
|
21
|
+
time.getutc
|
22
|
+
else
|
23
|
+
time.getlocal
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|