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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +11 -1
  4. data/activerecord-cipherstash-pg-adapter.gemspec +1 -1
  5. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/column.rb +55 -0
  6. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/database_statements.rb +149 -0
  7. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/array.rb +91 -0
  8. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/date.rb +23 -0
  9. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/date_time.rb +31 -0
  10. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/hstore.rb +72 -0
  11. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid/type_map_initializer.rb +113 -0
  12. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/oid.rb +36 -0
  13. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/quoting.rb +205 -0
  14. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/referential_integrity.rb +43 -0
  15. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_creation.rb +80 -0
  16. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_definitions.rb +222 -0
  17. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_dumper.rb +49 -0
  18. data/lib/active_record/connection_adapters/6.1/cipherstash_pg/schema_statements.rb +794 -0
  19. data/lib/active_record/connection_adapters/6.1/postgres_cipherstash_adapter.rb +958 -0
  20. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/explain_pretty_printer.rb +44 -0
  21. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bit.rb +53 -0
  22. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bit_varying.rb +15 -0
  23. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/bytea.rb +17 -0
  24. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/cidr.rb +48 -0
  25. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/decimal.rb +15 -0
  26. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/enum.rb +20 -0
  27. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/inet.rb +15 -0
  28. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/interval.rb +49 -0
  29. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/jsonb.rb +15 -0
  30. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/legacy_point.rb +44 -0
  31. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/macaddr.rb +25 -0
  32. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/money.rb +41 -0
  33. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/oid.rb +15 -0
  34. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/point.rb +64 -0
  35. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/range.rb +115 -0
  36. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/specialized_string.rb +18 -0
  37. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/uuid.rb +35 -0
  38. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/vector.rb +28 -0
  39. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid/xml.rb +30 -0
  40. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/oid.rb +38 -0
  41. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/type_metadata.rb +44 -0
  42. data/lib/active_record/connection_adapters/7.0/cipherstash_pg/utils.rb +80 -0
  43. data/lib/active_record/connection_adapters/{cipherstash_pg_adapter.rb → 7.0/postgres_cipherstash_adapter.rb} +16 -48
  44. data/lib/active_record/connection_adapters/postgres_cipherstash_adapter.rb +42 -12
  45. data/lib/activerecord-cipherstash-pg-adapter.rb +1 -1
  46. data/lib/version.rb +1 -1
  47. metadata +89 -46
  48. data/lib/active_record/connection_adapters/cipherstash_pg/oid.rb +0 -38
  49. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/explain_pretty_printer.rb +0 -0
  50. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit.rb +0 -0
  51. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit_varying.rb +0 -0
  52. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bytea.rb +0 -0
  53. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/cidr.rb +0 -0
  54. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/decimal.rb +0 -0
  55. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/enum.rb +0 -0
  56. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/inet.rb +0 -0
  57. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/interval.rb +0 -0
  58. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/jsonb.rb +0 -0
  59. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/legacy_point.rb +0 -0
  60. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/macaddr.rb +0 -0
  61. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/money.rb +0 -0
  62. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/oid.rb +0 -0
  63. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/point.rb +0 -0
  64. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/range.rb +0 -0
  65. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/specialized_string.rb +0 -0
  66. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/uuid.rb +0 -0
  67. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/vector.rb +0 -0
  68. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/xml.rb +0 -0
  69. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/type_metadata.rb +0 -0
  70. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/utils.rb +0 -0
  71. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/column.rb +0 -0
  72. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/database_statements.rb +0 -0
  73. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/array.rb +0 -0
  74. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date.rb +0 -0
  75. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date_time.rb +0 -0
  76. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/hstore.rb +0 -0
  77. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp.rb +0 -0
  78. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp_with_time_zone.rb +0 -0
  79. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/type_map_initializer.rb +0 -0
  80. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/quoting.rb +0 -0
  81. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/referential_integrity.rb +0 -0
  82. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_creation.rb +0 -0
  83. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_definitions.rb +0 -0
  84. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_dumper.rb +0 -0
  85. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_statements.rb +0 -0
  86. /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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module CipherStashPG
6
+ module OID # :nodoc:
7
+ class BitVarying < OID::Bit # :nodoc:
8
+ def type
9
+ :bit_varying
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module CipherStashPG
6
+ module OID # :nodoc:
7
+ class Inet < Cidr # :nodoc:
8
+ def type
9
+ :inet
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module CipherStashPG
6
+ module OID # :nodoc:
7
+ class Jsonb < Type::Json # :nodoc:
8
+ def type
9
+ :jsonb
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module CipherStashPG
6
+ module OID # :nodoc:
7
+ class Oid < Type::UnsignedInteger # :nodoc:
8
+ def type
9
+ :oid
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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