activerecord-cipherstash-pg-adapter 0.1.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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.tool-versions +2 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +15 -0
  6. data/LICENSE +124 -0
  7. data/README.md +29 -0
  8. data/Rakefile +8 -0
  9. data/activerecord-cipherstash-pg-adapter.gemspec +35 -0
  10. data/lib/active_record/connection_adapters/cipherstash_pg/column.rb +69 -0
  11. data/lib/active_record/connection_adapters/cipherstash_pg/database_extensions.rb +31 -0
  12. data/lib/active_record/connection_adapters/cipherstash_pg/database_statements.rb +152 -0
  13. data/lib/active_record/connection_adapters/cipherstash_pg/database_tasks.rb +15 -0
  14. data/lib/active_record/connection_adapters/cipherstash_pg/explain_pretty_printer.rb +44 -0
  15. data/lib/active_record/connection_adapters/cipherstash_pg/oid/array.rb +91 -0
  16. data/lib/active_record/connection_adapters/cipherstash_pg/oid/bit.rb +53 -0
  17. data/lib/active_record/connection_adapters/cipherstash_pg/oid/bit_varying.rb +15 -0
  18. data/lib/active_record/connection_adapters/cipherstash_pg/oid/bytea.rb +17 -0
  19. data/lib/active_record/connection_adapters/cipherstash_pg/oid/cidr.rb +48 -0
  20. data/lib/active_record/connection_adapters/cipherstash_pg/oid/date.rb +31 -0
  21. data/lib/active_record/connection_adapters/cipherstash_pg/oid/date_time.rb +36 -0
  22. data/lib/active_record/connection_adapters/cipherstash_pg/oid/decimal.rb +15 -0
  23. data/lib/active_record/connection_adapters/cipherstash_pg/oid/enum.rb +20 -0
  24. data/lib/active_record/connection_adapters/cipherstash_pg/oid/hstore.rb +109 -0
  25. data/lib/active_record/connection_adapters/cipherstash_pg/oid/inet.rb +15 -0
  26. data/lib/active_record/connection_adapters/cipherstash_pg/oid/interval.rb +49 -0
  27. data/lib/active_record/connection_adapters/cipherstash_pg/oid/jsonb.rb +15 -0
  28. data/lib/active_record/connection_adapters/cipherstash_pg/oid/legacy_point.rb +44 -0
  29. data/lib/active_record/connection_adapters/cipherstash_pg/oid/macaddr.rb +25 -0
  30. data/lib/active_record/connection_adapters/cipherstash_pg/oid/money.rb +41 -0
  31. data/lib/active_record/connection_adapters/cipherstash_pg/oid/oid.rb +15 -0
  32. data/lib/active_record/connection_adapters/cipherstash_pg/oid/point.rb +64 -0
  33. data/lib/active_record/connection_adapters/cipherstash_pg/oid/range.rb +115 -0
  34. data/lib/active_record/connection_adapters/cipherstash_pg/oid/specialized_string.rb +18 -0
  35. data/lib/active_record/connection_adapters/cipherstash_pg/oid/timestamp.rb +15 -0
  36. data/lib/active_record/connection_adapters/cipherstash_pg/oid/timestamp_with_time_zone.rb +30 -0
  37. data/lib/active_record/connection_adapters/cipherstash_pg/oid/type_map_initializer.rb +125 -0
  38. data/lib/active_record/connection_adapters/cipherstash_pg/oid/uuid.rb +35 -0
  39. data/lib/active_record/connection_adapters/cipherstash_pg/oid/vector.rb +28 -0
  40. data/lib/active_record/connection_adapters/cipherstash_pg/oid/xml.rb +30 -0
  41. data/lib/active_record/connection_adapters/cipherstash_pg/oid.rb +38 -0
  42. data/lib/active_record/connection_adapters/cipherstash_pg/quoting.rb +231 -0
  43. data/lib/active_record/connection_adapters/cipherstash_pg/referential_integrity.rb +77 -0
  44. data/lib/active_record/connection_adapters/cipherstash_pg/schema_creation.rb +100 -0
  45. data/lib/active_record/connection_adapters/cipherstash_pg/schema_definitions.rb +243 -0
  46. data/lib/active_record/connection_adapters/cipherstash_pg/schema_dumper.rb +74 -0
  47. data/lib/active_record/connection_adapters/cipherstash_pg/schema_statements.rb +812 -0
  48. data/lib/active_record/connection_adapters/cipherstash_pg/type_metadata.rb +44 -0
  49. data/lib/active_record/connection_adapters/cipherstash_pg/utils.rb +80 -0
  50. data/lib/active_record/connection_adapters/cipherstash_pg_adapter.rb +1100 -0
  51. data/lib/active_record/connection_adapters/postgres_cipherstash_adapter.rb +13 -0
  52. data/lib/activerecord-cipherstash-pg-adapter.rb +33 -0
  53. data/lib/version.rb +3 -0
  54. metadata +126 -0
@@ -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,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module CipherStashPG
6
+ module OID # :nodoc:
7
+ class Date < Type::Date # :nodoc:
8
+ def cast_value(value)
9
+ case value
10
+ when "infinity" then ::Float::INFINITY
11
+ when "-infinity" then -::Float::INFINITY
12
+ when / BC$/
13
+ value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
14
+ super(value.delete_suffix!(" BC"))
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def type_cast_for_schema(value)
21
+ case value
22
+ when ::Float::INFINITY then "::Float::INFINITY"
23
+ when -::Float::INFINITY then "-::Float::INFINITY"
24
+ else super
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module CipherStashPG
6
+ module OID # :nodoc:
7
+ class DateTime < Type::DateTime # :nodoc:
8
+ def cast_value(value)
9
+ case value
10
+ when "infinity" then ::Float::INFINITY
11
+ when "-infinity" then -::Float::INFINITY
12
+ when / BC$/
13
+ value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) }
14
+ super(value.delete_suffix!(" BC"))
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def type_cast_for_schema(value)
21
+ case value
22
+ when ::Float::INFINITY then "::Float::INFINITY"
23
+ when -::Float::INFINITY then "-::Float::INFINITY"
24
+ else super
25
+ end
26
+ end
27
+
28
+ protected
29
+ def real_type_unless_aliased(real_type)
30
+ ActiveRecord::ConnectionAdapters::CipherStashPGAdapter.datetime_type == real_type ? :datetime : real_type
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ 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,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,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