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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f91280c762358755ec87fbfb247164cff70519f1b8eb1befcd2a7cb65e18f61d
|
4
|
+
data.tar.gz: fe9e7177ade9fbf67e02048c6b511de3d949d2d4333b45dfc96181ffacca296a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7620f50195e83042060930382b7af7021e261e48ad6b0b6ef3f13e23e50f9bd69210ab9c4ec621097baf9402fe628fae5d4438c75d11dc6e6df2d1d412e62ea3
|
7
|
+
data.tar.gz: 1b1f84b66a3fe9b60ad596341cd1fba0949eab05e473e540baffd23bd3f0ad62c711f4b59201899c80ae6e59f837919552b5efe859f4f3da30d374b2d8482ee3
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [0.8.2] - 2023-09-25
|
10
|
+
|
11
|
+
- Update supported Active Record version to <= 7.1.
|
12
|
+
|
9
13
|
## [0.8.1] - 2023-09-14
|
10
14
|
|
11
15
|
- Update max supported Active Record dep to < version 7.1.
|
@@ -30,6 +30,6 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.require_paths = ["lib"]
|
31
31
|
|
32
32
|
# Runtime dependencies here; dev+test go in Gemfile.
|
33
|
-
spec.add_dependency "activerecord", ">= 6.0.0", "
|
33
|
+
spec.add_dependency "activerecord", ">= 6.0.0", "<= 7.1"
|
34
34
|
spec.add_dependency "cipherstash-pg", ">= 1.0.0.beta.17"
|
35
35
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
class Column < ConnectionAdapters::Column # :nodoc:
|
7
|
+
delegate :oid, :fmod, to: :sql_type_metadata
|
8
|
+
|
9
|
+
def initialize(*, serial: nil, generated: nil, **)
|
10
|
+
super
|
11
|
+
@serial = serial
|
12
|
+
@generated = generated
|
13
|
+
end
|
14
|
+
|
15
|
+
def serial?
|
16
|
+
@serial
|
17
|
+
end
|
18
|
+
alias_method :auto_incremented_by_db?, :serial?
|
19
|
+
|
20
|
+
def virtual?
|
21
|
+
# We assume every generated column is virtual, no matter the concrete type
|
22
|
+
@generated.present?
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_default?
|
26
|
+
super && !virtual?
|
27
|
+
end
|
28
|
+
|
29
|
+
def array
|
30
|
+
sql_type_metadata.sql_type.end_with?("[]")
|
31
|
+
end
|
32
|
+
alias :array? :array
|
33
|
+
|
34
|
+
def enum?
|
35
|
+
type == :enum
|
36
|
+
end
|
37
|
+
|
38
|
+
def sql_type
|
39
|
+
super.delete_suffix("[]")
|
40
|
+
end
|
41
|
+
|
42
|
+
def init_with(coder)
|
43
|
+
@serial = coder["serial"]
|
44
|
+
@generated = coder["generated"]
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def encode_with(coder)
|
49
|
+
coder["serial"] = @serial
|
50
|
+
coder["generated"] = @generated
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
other.is_a?(Column) &&
|
56
|
+
super &&
|
57
|
+
serial? == other.serial?
|
58
|
+
end
|
59
|
+
alias :eql? :==
|
60
|
+
|
61
|
+
def hash
|
62
|
+
Column.hash ^
|
63
|
+
super.hash ^
|
64
|
+
serial?.hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
CipherStashPGColumn = CipherStashPG::Column # :nodoc:
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module DatabaseStatements
|
7
|
+
def explain(arel, binds = [], options = [])
|
8
|
+
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
9
|
+
result = internal_exec_query(sql, "EXPLAIN", binds)
|
10
|
+
CipherStashPG::ExplainPrettyPrinter.new.pp(result)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Queries the database and returns the results in an Array-like object
|
14
|
+
def query(sql, name = nil) # :nodoc:
|
15
|
+
mark_transaction_written_if_write(sql)
|
16
|
+
|
17
|
+
log(sql, name) do
|
18
|
+
with_raw_connection do |conn|
|
19
|
+
conn.async_exec(sql).map_types!(@type_map_for_results).values
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
25
|
+
:close, :declare, :fetch, :move, :set, :show
|
26
|
+
) # :nodoc:
|
27
|
+
private_constant :READ_QUERY
|
28
|
+
|
29
|
+
def write_query?(sql) # :nodoc:
|
30
|
+
!READ_QUERY.match?(sql)
|
31
|
+
rescue ArgumentError # Invalid encoding
|
32
|
+
!READ_QUERY.match?(sql.b)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Executes an SQL statement, returning a ::CipherStashPG::Result object on success
|
36
|
+
# or raising a ::CipherStashPG::Error exception otherwise.
|
37
|
+
#
|
38
|
+
# Setting +allow_retry+ to true causes the db to reconnect and retry
|
39
|
+
# executing the SQL statement in case of a connection-related exception.
|
40
|
+
# This option should only be enabled for known idempotent queries.
|
41
|
+
#
|
42
|
+
# Note: the ::CipherStashPG::Result object is manually memory managed; if you don't
|
43
|
+
# need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
|
44
|
+
def execute(...) # :nodoc:
|
45
|
+
super
|
46
|
+
ensure
|
47
|
+
@notice_receiver_sql_warnings = []
|
48
|
+
end
|
49
|
+
|
50
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
51
|
+
log(sql, name, async: async) do
|
52
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
53
|
+
result = conn.async_exec(sql)
|
54
|
+
handle_warnings(result)
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
61
|
+
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
62
|
+
types = {}
|
63
|
+
fields = result.fields
|
64
|
+
fields.each_with_index do |fname, i|
|
65
|
+
ftype = result.ftype i
|
66
|
+
fmod = result.fmod i
|
67
|
+
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
68
|
+
end
|
69
|
+
build_result(columns: fields, rows: result.values, column_types: types)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
74
|
+
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
75
|
+
end
|
76
|
+
alias :exec_update :exec_delete
|
77
|
+
|
78
|
+
def sql_for_insert(sql, pk, binds, returning) # :nodoc:
|
79
|
+
if pk.nil?
|
80
|
+
# Extract the table from the insert sql. Yuck.
|
81
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
82
|
+
pk = primary_key(table_ref) if table_ref
|
83
|
+
end
|
84
|
+
|
85
|
+
returning_columns = returning || Array(pk)
|
86
|
+
|
87
|
+
returning_columns_statement = returning_columns.map { |c| quote_column_name(c) }.join(", ")
|
88
|
+
sql = "#{sql} RETURNING #{returning_columns_statement}" if returning_columns.any?
|
89
|
+
|
90
|
+
super
|
91
|
+
end
|
92
|
+
private :sql_for_insert
|
93
|
+
|
94
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
95
|
+
if use_insert_returning? || pk == false
|
96
|
+
super
|
97
|
+
else
|
98
|
+
result = internal_exec_query(sql, name, binds)
|
99
|
+
unless sequence_name
|
100
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
101
|
+
if table_ref
|
102
|
+
pk = primary_key(table_ref) if pk.nil?
|
103
|
+
pk = suppress_composite_primary_key(pk)
|
104
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
105
|
+
end
|
106
|
+
return result unless sequence_name
|
107
|
+
end
|
108
|
+
last_insert_id_result(sequence_name)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Begins a transaction.
|
113
|
+
def begin_db_transaction # :nodoc:
|
114
|
+
internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
115
|
+
end
|
116
|
+
|
117
|
+
def begin_isolated_db_transaction(isolation) # :nodoc:
|
118
|
+
internal_execute("BEGIN ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Commits a transaction.
|
122
|
+
def commit_db_transaction # :nodoc:
|
123
|
+
internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Aborts a transaction.
|
127
|
+
def exec_rollback_db_transaction # :nodoc:
|
128
|
+
cancel_any_running_query
|
129
|
+
internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
130
|
+
end
|
131
|
+
|
132
|
+
def exec_restart_db_transaction # :nodoc:
|
133
|
+
cancel_any_running_query
|
134
|
+
internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
135
|
+
end
|
136
|
+
|
137
|
+
# From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
138
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
|
139
|
+
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
140
|
+
|
141
|
+
def high_precision_current_timestamp
|
142
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP
|
143
|
+
end
|
144
|
+
|
145
|
+
def build_explain_clause(options = [])
|
146
|
+
return "EXPLAIN" if options.empty?
|
147
|
+
|
148
|
+
"EXPLAIN (#{options.join(", ").upcase})"
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
IDLE_TRANSACTION_STATUSES = [::CipherStashPG::PQTRANS_IDLE, ::CipherStashPG::PQTRANS_INTRANS, ::CipherStashPG::PQTRANS_INERROR]
|
153
|
+
private_constant :IDLE_TRANSACTION_STATUSES
|
154
|
+
|
155
|
+
def cancel_any_running_query
|
156
|
+
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
157
|
+
|
158
|
+
@raw_connection.cancel
|
159
|
+
@raw_connection.block
|
160
|
+
rescue ::CipherStashPG::Error
|
161
|
+
end
|
162
|
+
|
163
|
+
def execute_batch(statements, name = nil)
|
164
|
+
execute(combine_multi_statements(statements))
|
165
|
+
end
|
166
|
+
|
167
|
+
def build_truncate_statements(table_names)
|
168
|
+
["TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}"]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns the current ID of a table's sequence.
|
172
|
+
def last_insert_id_result(sequence_name)
|
173
|
+
internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
|
174
|
+
end
|
175
|
+
|
176
|
+
def returning_column_values(result)
|
177
|
+
result.rows.first
|
178
|
+
end
|
179
|
+
|
180
|
+
def suppress_composite_primary_key(pk)
|
181
|
+
pk unless pk.is_a?(Array)
|
182
|
+
end
|
183
|
+
|
184
|
+
def handle_warnings(sql)
|
185
|
+
@notice_receiver_sql_warnings.each do |warning|
|
186
|
+
next if warning_ignored?(warning)
|
187
|
+
|
188
|
+
warning.sql = sql
|
189
|
+
ActiveRecord.db_warnings_action.call(warning)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def warning_ignored?(warning)
|
194
|
+
["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -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,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module CipherStashPG
|
6
|
+
module OID # :nodoc:
|
7
|
+
class Array < Type::Value # :nodoc:
|
8
|
+
include ActiveModel::Type::Helpers::Mutable
|
9
|
+
|
10
|
+
Data = Struct.new(:encoder, :values) # :nodoc:
|
11
|
+
|
12
|
+
attr_reader :subtype, :delimiter
|
13
|
+
delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype
|
14
|
+
|
15
|
+
def initialize(subtype, delimiter = ",")
|
16
|
+
@subtype = subtype
|
17
|
+
@delimiter = delimiter
|
18
|
+
|
19
|
+
@pg_encoder = ::CipherStashPG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
|
20
|
+
@pg_decoder = ::CipherStashPG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
|
21
|
+
end
|
22
|
+
|
23
|
+
def deserialize(value)
|
24
|
+
case value
|
25
|
+
when ::String
|
26
|
+
type_cast_array(@pg_decoder.decode(value), :deserialize)
|
27
|
+
when Data
|
28
|
+
type_cast_array(value.values, :deserialize)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def cast(value)
|
35
|
+
if value.is_a?(::String)
|
36
|
+
value = begin
|
37
|
+
@pg_decoder.decode(value)
|
38
|
+
rescue TypeError
|
39
|
+
# malformed array string is treated as [], will raise in PG 2.0 gem
|
40
|
+
# this keeps a consistent implementation
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
type_cast_array(value, :cast)
|
45
|
+
end
|
46
|
+
|
47
|
+
def serialize(value)
|
48
|
+
if value.is_a?(::Array)
|
49
|
+
casted_values = type_cast_array(value, :serialize)
|
50
|
+
Data.new(@pg_encoder, casted_values)
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(other)
|
57
|
+
other.is_a?(Array) &&
|
58
|
+
subtype == other.subtype &&
|
59
|
+
delimiter == other.delimiter
|
60
|
+
end
|
61
|
+
|
62
|
+
def type_cast_for_schema(value)
|
63
|
+
return super unless value.is_a?(::Array)
|
64
|
+
"[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
|
65
|
+
end
|
66
|
+
|
67
|
+
def map(value, &block)
|
68
|
+
value.map { |v| subtype.map(v, &block) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def changed_in_place?(raw_old_value, new_value)
|
72
|
+
deserialize(raw_old_value) != new_value
|
73
|
+
end
|
74
|
+
|
75
|
+
def force_equality?(value)
|
76
|
+
value.is_a?(::Array)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def type_cast_array(value, method)
|
81
|
+
if value.is_a?(::Array)
|
82
|
+
value.map { |item| type_cast_array(item, method) }
|
83
|
+
else
|
84
|
+
@subtype.public_send(method, value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
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,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
|