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,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ConnectionAdapters
|
7
|
+
module CipherStashPG
|
8
|
+
module OID # :nodoc:
|
9
|
+
# This class uses the data from PostgreSQL pg_type table to build
|
10
|
+
# the OID -> Type mapping.
|
11
|
+
# - OID is an integer representing the type.
|
12
|
+
# - Type is an OID::Type object.
|
13
|
+
# This class has side effects on the +store+ passed during initialization.
|
14
|
+
class TypeMapInitializer # :nodoc:
|
15
|
+
def initialize(store)
|
16
|
+
@store = store
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(records)
|
20
|
+
nodes = records.reject { |row| @store.key? row["oid"].to_i }
|
21
|
+
mapped = nodes.extract! { |row| @store.key? row["typname"] }
|
22
|
+
ranges = nodes.extract! { |row| row["typtype"] == "r" }
|
23
|
+
enums = nodes.extract! { |row| row["typtype"] == "e" }
|
24
|
+
domains = nodes.extract! { |row| row["typtype"] == "d" }
|
25
|
+
arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
|
26
|
+
composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
|
27
|
+
|
28
|
+
mapped.each { |row| register_mapped_type(row) }
|
29
|
+
enums.each { |row| register_enum_type(row) }
|
30
|
+
domains.each { |row| register_domain_type(row) }
|
31
|
+
arrays.each { |row| register_array_type(row) }
|
32
|
+
ranges.each { |row| register_range_type(row) }
|
33
|
+
composites.each { |row| register_composite_type(row) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def query_conditions_for_known_type_names
|
37
|
+
known_type_names = @store.keys.map { |n| "'#{n}'" }
|
38
|
+
<<~SQL % known_type_names.join(", ")
|
39
|
+
WHERE
|
40
|
+
t.typname IN (%s)
|
41
|
+
SQL
|
42
|
+
end
|
43
|
+
|
44
|
+
def query_conditions_for_known_type_types
|
45
|
+
known_type_types = %w('r' 'e' 'd')
|
46
|
+
<<~SQL % known_type_types.join(", ")
|
47
|
+
WHERE
|
48
|
+
t.typtype IN (%s)
|
49
|
+
SQL
|
50
|
+
end
|
51
|
+
|
52
|
+
def query_conditions_for_array_types
|
53
|
+
known_type_oids = @store.keys.reject { |k| k.is_a?(String) }
|
54
|
+
<<~SQL % [known_type_oids.join(", ")]
|
55
|
+
WHERE
|
56
|
+
t.typelem IN (%s)
|
57
|
+
SQL
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def register_mapped_type(row)
|
62
|
+
alias_type row["oid"], row["typname"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def register_enum_type(row)
|
66
|
+
register row["oid"], OID::Enum.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def register_array_type(row)
|
70
|
+
register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype|
|
71
|
+
OID::Array.new(subtype, row["typdelim"])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def register_range_type(row)
|
76
|
+
register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype|
|
77
|
+
OID::Range.new(subtype, row["typname"].to_sym)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def register_domain_type(row)
|
82
|
+
if base_type = @store.lookup(row["typbasetype"].to_i)
|
83
|
+
register row["oid"], base_type
|
84
|
+
else
|
85
|
+
warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def register_composite_type(row)
|
90
|
+
if subtype = @store.lookup(row["typelem"].to_i)
|
91
|
+
register row["oid"], OID::Vector.new(row["typdelim"], subtype)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def register(oid, oid_type = nil, &block)
|
96
|
+
oid = assert_valid_registration(oid, oid_type || block)
|
97
|
+
if block_given?
|
98
|
+
@store.register_type(oid, &block)
|
99
|
+
else
|
100
|
+
@store.register_type(oid, oid_type)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def alias_type(oid, target)
|
105
|
+
oid = assert_valid_registration(oid, target)
|
106
|
+
@store.alias_type(oid, target)
|
107
|
+
end
|
108
|
+
|
109
|
+
def register_with_subtype(oid, target_oid)
|
110
|
+
if @store.key?(target_oid)
|
111
|
+
register(oid) do |_, *args|
|
112
|
+
yield @store.lookup(target_oid, *args)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def assert_valid_registration(oid, oid_type)
|
118
|
+
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
|
119
|
+
oid.to_i
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
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
|
@@ -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 Xml < Type::String # :nodoc:
|
8
|
+
def type
|
9
|
+
:xml
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(value)
|
13
|
+
return unless value
|
14
|
+
Data.new(super)
|
15
|
+
end
|
16
|
+
|
17
|
+
class Data # :nodoc:
|
18
|
+
def initialize(value)
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
@value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./oid/array"
|
4
|
+
require_relative "./oid/bit"
|
5
|
+
require_relative "./oid/bit_varying"
|
6
|
+
require_relative "./oid/bytea"
|
7
|
+
require_relative "./oid/cidr"
|
8
|
+
require_relative "./oid/date"
|
9
|
+
require_relative "./oid/date_time"
|
10
|
+
require_relative "./oid/decimal"
|
11
|
+
require_relative "./oid/enum"
|
12
|
+
require_relative "./oid/hstore"
|
13
|
+
require_relative "./oid/inet"
|
14
|
+
require_relative "./oid/interval"
|
15
|
+
require_relative "./oid/jsonb"
|
16
|
+
require_relative "./oid/macaddr"
|
17
|
+
require_relative "./oid/money"
|
18
|
+
require_relative "./oid/oid"
|
19
|
+
require_relative "./oid/point"
|
20
|
+
require_relative "./oid/legacy_point"
|
21
|
+
require_relative "./oid/range"
|
22
|
+
require_relative "./oid/specialized_string"
|
23
|
+
require_relative "./oid/timestamp"
|
24
|
+
require_relative "./oid/timestamp_with_time_zone"
|
25
|
+
require_relative "./oid/uuid"
|
26
|
+
require_relative "./oid/vector"
|
27
|
+
require_relative "./oid/xml"
|
28
|
+
|
29
|
+
require_relative "./oid/type_map_initializer"
|
30
|
+
|
31
|
+
module ActiveRecord
|
32
|
+
module ConnectionAdapters
|
33
|
+
module CipherStashPG
|
34
|
+
module OID # :nodoc:
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module Quoting
|
7
|
+
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
8
|
+
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
9
|
+
|
10
|
+
class IntegerOutOf64BitRange < StandardError
|
11
|
+
def initialize(msg)
|
12
|
+
super(msg)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Escapes binary strings for bytea input to the database.
|
17
|
+
def escape_bytea(value)
|
18
|
+
valid_raw_connection.escape_bytea(value) if value
|
19
|
+
end
|
20
|
+
|
21
|
+
# Unescapes bytea output from a database to the binary string it represents.
|
22
|
+
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
23
|
+
# on escaped binary output from database drive.
|
24
|
+
def unescape_bytea(value)
|
25
|
+
valid_raw_connection.unescape_bytea(value) if value
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_int_in_range(value)
|
29
|
+
if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808
|
30
|
+
exception = <<~ERROR
|
31
|
+
Provided value outside of the range of a signed 64bit integer.
|
32
|
+
|
33
|
+
PostgreSQL will treat the column type in question as a numeric.
|
34
|
+
This may result in a slow sequential scan due to a comparison
|
35
|
+
being performed between an integer or bigint value and a numeric value.
|
36
|
+
|
37
|
+
To allow for this potentially unwanted behavior, set
|
38
|
+
ActiveRecord.raise_int_wider_than_64bit to false.
|
39
|
+
ERROR
|
40
|
+
raise IntegerOutOf64BitRange.new exception
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def quote(value) # :nodoc:
|
45
|
+
if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer)
|
46
|
+
check_int_in_range(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
case value
|
50
|
+
when OID::Xml::Data
|
51
|
+
"xml '#{quote_string(value.to_s)}'"
|
52
|
+
when OID::Bit::Data
|
53
|
+
if value.binary?
|
54
|
+
"B'#{value}'"
|
55
|
+
elsif value.hex?
|
56
|
+
"X'#{value}'"
|
57
|
+
end
|
58
|
+
when Numeric
|
59
|
+
if value.finite?
|
60
|
+
super
|
61
|
+
else
|
62
|
+
"'#{value}'"
|
63
|
+
end
|
64
|
+
when OID::Array::Data
|
65
|
+
quote(encode_array(value))
|
66
|
+
when Range
|
67
|
+
quote(encode_range(value))
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Quotes strings for use in SQL input.
|
74
|
+
def quote_string(s) # :nodoc:
|
75
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
76
|
+
connection.escape(s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Checks the following cases:
|
81
|
+
#
|
82
|
+
# - table_name
|
83
|
+
# - "table.name"
|
84
|
+
# - schema_name.table_name
|
85
|
+
# - schema_name."table.name"
|
86
|
+
# - "schema.name".table_name
|
87
|
+
# - "schema.name"."table.name"
|
88
|
+
def quote_table_name(name) # :nodoc:
|
89
|
+
QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
90
|
+
end
|
91
|
+
|
92
|
+
# Quotes schema names for use in SQL queries.
|
93
|
+
def quote_schema_name(name)
|
94
|
+
::CipherStashPG::Connection.quote_ident(name)
|
95
|
+
end
|
96
|
+
|
97
|
+
def quote_table_name_for_assignment(table, attr)
|
98
|
+
quote_column_name(attr)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Quotes column names for use in SQL queries.
|
102
|
+
def quote_column_name(name) # :nodoc:
|
103
|
+
QUOTED_COLUMN_NAMES[name] ||= ::CipherStashPG::Connection.quote_ident(super).freeze
|
104
|
+
end
|
105
|
+
|
106
|
+
# Quote date/time values for use in SQL input.
|
107
|
+
def quoted_date(value) # :nodoc:
|
108
|
+
if value.year <= 0
|
109
|
+
bce_year = format("%04d", -value.year + 1)
|
110
|
+
super.sub(/^-?\d+/, bce_year) + " BC"
|
111
|
+
else
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def quoted_binary(value) # :nodoc:
|
117
|
+
"'#{escape_bytea(value.to_s)}'"
|
118
|
+
end
|
119
|
+
|
120
|
+
def quote_default_expression(value, column) # :nodoc:
|
121
|
+
if value.is_a?(Proc)
|
122
|
+
value.call
|
123
|
+
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
|
124
|
+
value # Does not quote function default values for UUID columns
|
125
|
+
elsif column.respond_to?(:array?)
|
126
|
+
type = lookup_cast_type_from_column(column)
|
127
|
+
quote(type.serialize(value))
|
128
|
+
else
|
129
|
+
super
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def type_cast(value) # :nodoc:
|
134
|
+
case value
|
135
|
+
when Type::Binary::Data
|
136
|
+
# Return a bind param hash with format as binary.
|
137
|
+
# See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
|
138
|
+
# for more information
|
139
|
+
{ value: value.to_s, format: 1 }
|
140
|
+
when OID::Xml::Data, OID::Bit::Data
|
141
|
+
value.to_s
|
142
|
+
when OID::Array::Data
|
143
|
+
encode_array(value)
|
144
|
+
when Range
|
145
|
+
encode_range(value)
|
146
|
+
else
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def lookup_cast_type_from_column(column) # :nodoc:
|
152
|
+
type_map.lookup(column.oid, column.fmod, column.sql_type)
|
153
|
+
end
|
154
|
+
|
155
|
+
def column_name_matcher
|
156
|
+
COLUMN_NAME
|
157
|
+
end
|
158
|
+
|
159
|
+
def column_name_with_order_matcher
|
160
|
+
COLUMN_NAME_WITH_ORDER
|
161
|
+
end
|
162
|
+
|
163
|
+
COLUMN_NAME = /
|
164
|
+
\A
|
165
|
+
(
|
166
|
+
(?:
|
167
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
168
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
169
|
+
)
|
170
|
+
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
171
|
+
)
|
172
|
+
(?:\s*,\s*\g<1>)*
|
173
|
+
\z
|
174
|
+
/ix
|
175
|
+
|
176
|
+
COLUMN_NAME_WITH_ORDER = /
|
177
|
+
\A
|
178
|
+
(
|
179
|
+
(?:
|
180
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
181
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
182
|
+
)
|
183
|
+
(?:\s+COLLATE\s+"\w+")?
|
184
|
+
(?:\s+ASC|\s+DESC)?
|
185
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
186
|
+
)
|
187
|
+
(?:\s*,\s*\g<1>)*
|
188
|
+
\z
|
189
|
+
/ix
|
190
|
+
|
191
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
192
|
+
|
193
|
+
private
|
194
|
+
def lookup_cast_type(sql_type)
|
195
|
+
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
196
|
+
end
|
197
|
+
|
198
|
+
def encode_array(array_data)
|
199
|
+
encoder = array_data.encoder
|
200
|
+
values = type_cast_array(array_data.values)
|
201
|
+
|
202
|
+
result = encoder.encode(values)
|
203
|
+
if encoding = determine_encoding_of_strings_in_array(values)
|
204
|
+
result.force_encoding(encoding)
|
205
|
+
end
|
206
|
+
result
|
207
|
+
end
|
208
|
+
|
209
|
+
def encode_range(range)
|
210
|
+
"[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}"
|
211
|
+
end
|
212
|
+
|
213
|
+
def determine_encoding_of_strings_in_array(value)
|
214
|
+
case value
|
215
|
+
when ::Array then determine_encoding_of_strings_in_array(value.first)
|
216
|
+
when ::String then value.encoding
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def type_cast_array(values)
|
221
|
+
case values
|
222
|
+
when ::Array then values.map { |item| type_cast_array(item) }
|
223
|
+
else type_cast(values)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def type_cast_range_value(value)
|
228
|
+
infinity?(value) ? "" : type_cast(value)
|
229
|
+
end
|
230
|
+
|
231
|
+
def infinity?(value)
|
232
|
+
value.respond_to?(:infinite?) && value.infinite?
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module ReferentialIntegrity # :nodoc:
|
7
|
+
def disable_referential_integrity # :nodoc:
|
8
|
+
original_exception = nil
|
9
|
+
|
10
|
+
begin
|
11
|
+
transaction(requires_new: true) do
|
12
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
13
|
+
end
|
14
|
+
rescue ActiveRecord::ActiveRecordError => e
|
15
|
+
original_exception = e
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
yield
|
20
|
+
rescue ActiveRecord::InvalidForeignKey => e
|
21
|
+
warn <<-WARNING
|
22
|
+
WARNING: Rails was not able to disable referential integrity.
|
23
|
+
|
24
|
+
This is most likely caused due to missing permissions.
|
25
|
+
Rails needs superuser privileges to disable referential integrity.
|
26
|
+
|
27
|
+
cause: #{original_exception&.message}
|
28
|
+
|
29
|
+
WARNING
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
transaction(requires_new: true) do
|
35
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
36
|
+
end
|
37
|
+
rescue ActiveRecord::ActiveRecordError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
42
|
+
sql = <<~SQL
|
43
|
+
do $$
|
44
|
+
declare r record;
|
45
|
+
BEGIN
|
46
|
+
FOR r IN (
|
47
|
+
SELECT FORMAT(
|
48
|
+
'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
|
49
|
+
constraint_name,
|
50
|
+
table_schema,
|
51
|
+
table_schema,
|
52
|
+
table_name,
|
53
|
+
constraint_name
|
54
|
+
) AS constraint_check
|
55
|
+
FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
|
56
|
+
)
|
57
|
+
LOOP
|
58
|
+
EXECUTE (r.constraint_check);
|
59
|
+
END LOOP;
|
60
|
+
END;
|
61
|
+
$$;
|
62
|
+
SQL
|
63
|
+
|
64
|
+
transaction(requires_new: true) do
|
65
|
+
execute(sql)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|