activerecord-cipherstash-pg-adapter 0.2.0 → 0.3.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.md +15 -0
- data/README.md +11 -1
- data/activerecord-cipherstash-pg-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/column.rb +55 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/database_statements.rb +149 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/date_time.rb +31 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/hstore.rb +72 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/type_map_initializer.rb +113 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid.rb +36 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/quoting.rb +205 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_definitions.rb +222 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_dumper.rb +49 -0
- data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_statements.rb +794 -0
- data/lib/active_record/connection_adapters/6.1/postgres_cipherstash_adapter.rb +958 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/point.rb +64 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/range.rb +115 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/uuid.rb +35 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid.rb +38 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/7.0/cipherstash_pg/utils.rb +80 -0
- data/lib/active_record/connection_adapters/{cipherstash_pg_adapter.rb → 7.0/postgres_cipherstash_adapter.rb} +16 -48
- data/lib/active_record/connection_adapters/postgres_cipherstash_adapter.rb +42 -12
- data/lib/activerecord-cipherstash-pg-adapter.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +89 -46
- data/lib/active_record/connection_adapters/cipherstash_pg/oid.rb +0 -38
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/explain_pretty_printer.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit_varying.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bytea.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/cidr.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/decimal.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/enum.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/inet.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/interval.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/jsonb.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/legacy_point.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/macaddr.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/money.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/oid.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/point.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/range.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/specialized_string.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/uuid.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/vector.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/xml.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/type_metadata.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/utils.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/column.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/database_statements.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/array.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date_time.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/hstore.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp_with_time_zone.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/type_map_initializer.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/quoting.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/referential_integrity.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_creation.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_definitions.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_dumper.rb +0 -0
- /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_statements.rb +0 -0
- /data/lib/{active_record/connection_adapters/cipherstash_pg/cipherstash_tasks.rake → cipherstash_tasks.rake} +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
class ExplainPrettyPrinter # :nodoc:
|
|
7
|
+
# Pretty prints the result of an EXPLAIN in a way that resembles the output of the
|
|
8
|
+
# PostgreSQL shell:
|
|
9
|
+
#
|
|
10
|
+
# QUERY PLAN
|
|
11
|
+
# ------------------------------------------------------------------------------
|
|
12
|
+
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
|
13
|
+
# Join Filter: (posts.user_id = users.id)
|
|
14
|
+
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
|
15
|
+
# Index Cond: (id = 1)
|
|
16
|
+
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
|
17
|
+
# Filter: (posts.user_id = 1)
|
|
18
|
+
# (6 rows)
|
|
19
|
+
#
|
|
20
|
+
def pp(result)
|
|
21
|
+
header = result.columns.first
|
|
22
|
+
lines = result.rows.map(&:first)
|
|
23
|
+
|
|
24
|
+
# We add 2 because there's one char of padding at both sides, note
|
|
25
|
+
# the extra hyphens in the example above.
|
|
26
|
+
width = [header, *lines].map(&:length).max + 2
|
|
27
|
+
|
|
28
|
+
pp = []
|
|
29
|
+
|
|
30
|
+
pp << header.center(width).rstrip
|
|
31
|
+
pp << "-" * width
|
|
32
|
+
|
|
33
|
+
pp += lines.map { |line| " #{line}" }
|
|
34
|
+
|
|
35
|
+
nrows = result.rows.length
|
|
36
|
+
rows_label = nrows == 1 ? "row" : "rows"
|
|
37
|
+
pp << "(#{nrows} #{rows_label})"
|
|
38
|
+
|
|
39
|
+
pp.join("\n") + "\n"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class Bit < Type::Value # :nodoc:
|
|
8
|
+
def type
|
|
9
|
+
:bit
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cast_value(value)
|
|
13
|
+
if ::String === value
|
|
14
|
+
case value
|
|
15
|
+
when /^0x/i
|
|
16
|
+
value[2..-1].hex.to_s(2) # Hexadecimal notation
|
|
17
|
+
else
|
|
18
|
+
value # Bit-string notation
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
value.to_s
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def serialize(value)
|
|
26
|
+
Data.new(super) if value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Data
|
|
30
|
+
def initialize(value)
|
|
31
|
+
@value = value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_s
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def binary?
|
|
39
|
+
/\A[01]*\Z/.match?(value)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def hex?
|
|
43
|
+
/\A[0-9A-F]*\Z/i.match?(value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
attr_reader :value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class Bytea < Type::Binary # :nodoc:
|
|
8
|
+
def deserialize(value)
|
|
9
|
+
return if value.nil?
|
|
10
|
+
return value.to_s if value.is_a?(Type::Binary::Data)
|
|
11
|
+
PG::Connection.unescape_bytea(super)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ipaddr"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module ConnectionAdapters
|
|
7
|
+
module CipherStashPG
|
|
8
|
+
module OID # :nodoc:
|
|
9
|
+
class Cidr < Type::Value # :nodoc:
|
|
10
|
+
def type
|
|
11
|
+
:cidr
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def type_cast_for_schema(value)
|
|
15
|
+
# If the subnet mask is equal to /32, don't output it
|
|
16
|
+
if value.prefix == 32
|
|
17
|
+
"\"#{value}\""
|
|
18
|
+
else
|
|
19
|
+
"\"#{value}/#{value.prefix}\""
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialize(value)
|
|
24
|
+
if IPAddr === value
|
|
25
|
+
"#{value}/#{value.prefix}"
|
|
26
|
+
else
|
|
27
|
+
value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cast_value(value)
|
|
32
|
+
if value.nil?
|
|
33
|
+
nil
|
|
34
|
+
elsif String === value
|
|
35
|
+
begin
|
|
36
|
+
IPAddr.new(value)
|
|
37
|
+
rescue ArgumentError
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
value
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
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 Decimal < Type::Decimal # :nodoc:
|
|
8
|
+
def infinity(options = {})
|
|
9
|
+
BigDecimal("Infinity") * (options[:negative] ? -1 : 1)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class Enum < Type::Value # :nodoc:
|
|
8
|
+
def type
|
|
9
|
+
:enum
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def cast_value(value)
|
|
14
|
+
value.to_s
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
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,115 @@
|
|
|
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 value == "empty"
|
|
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(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
|
+
# When formatting the bound values of range types, PostgreSQL quotes
|
|
80
|
+
# the bound value using double-quotes in certain conditions. Within
|
|
81
|
+
# a double-quoted string, literal " and \ characters are themselves
|
|
82
|
+
# escaped. In input, PostgreSQL accepts multiple escape styles for "
|
|
83
|
+
# (either \" or "") but in output always uses "".
|
|
84
|
+
# See:
|
|
85
|
+
# * https://www.cipherstash_pg.org/docs/current/rangetypes.html#RANGETYPES-IO
|
|
86
|
+
# * https://www.cipherstash_pg.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
|
|
87
|
+
def unquote(value)
|
|
88
|
+
if value.start_with?('"') && value.end_with?('"')
|
|
89
|
+
unquoted_value = value[1..-2]
|
|
90
|
+
unquoted_value.gsub!('""', '"')
|
|
91
|
+
unquoted_value.gsub!("\\\\", "\\")
|
|
92
|
+
unquoted_value
|
|
93
|
+
else
|
|
94
|
+
value
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def infinity(negative: false)
|
|
99
|
+
if subtype.respond_to?(:infinity)
|
|
100
|
+
subtype.infinity(negative: negative)
|
|
101
|
+
elsif negative
|
|
102
|
+
-::Float::INFINITY
|
|
103
|
+
else
|
|
104
|
+
::Float::INFINITY
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def infinity?(value)
|
|
109
|
+
value.respond_to?(:infinite?) && value.infinite?
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
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,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class Uuid < Type::Value # :nodoc:
|
|
8
|
+
ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
|
|
9
|
+
|
|
10
|
+
alias :serialize :deserialize
|
|
11
|
+
|
|
12
|
+
def type
|
|
13
|
+
:uuid
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
|
17
|
+
old_value.class != new_value.class ||
|
|
18
|
+
new_value && old_value.casecmp(new_value) != 0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def changed_in_place?(raw_old_value, new_value)
|
|
22
|
+
raw_old_value.class != new_value.class ||
|
|
23
|
+
new_value && raw_old_value.casecmp(new_value) != 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
def cast_value(value)
|
|
28
|
+
casted = value.to_s
|
|
29
|
+
casted if casted.match?(ACCEPTABLE_UUID)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module CipherStashPG
|
|
6
|
+
module OID # :nodoc:
|
|
7
|
+
class Vector < Type::Value # :nodoc:
|
|
8
|
+
attr_reader :delim, :subtype
|
|
9
|
+
|
|
10
|
+
# +delim+ corresponds to the `typdelim` column in the pg_types
|
|
11
|
+
# table. +subtype+ is derived from the `typelem` column in the
|
|
12
|
+
# pg_types table.
|
|
13
|
+
def initialize(delim, subtype)
|
|
14
|
+
@delim = delim
|
|
15
|
+
@subtype = subtype
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# FIXME: this should probably split on +delim+ and use +subtype+
|
|
19
|
+
# to cast the values. Unfortunately, the current Rails behavior
|
|
20
|
+
# is to just return the string.
|
|
21
|
+
def cast(value)
|
|
22
|
+
value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|