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,958 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+ require "active_record/connection_adapters/abstract_adapter"
5
+ require "active_record/connection_adapters/statement_pool"
6
+
7
+ require_relative "./cipherstash_pg/column"
8
+ require_relative "./cipherstash_pg/database_statements"
9
+ require_relative "./cipherstash_pg/explain_pretty_printer"
10
+ require_relative "./cipherstash_pg/oid"
11
+ require_relative "./cipherstash_pg/quoting"
12
+ require_relative "./cipherstash_pg/referential_integrity"
13
+ require_relative "./cipherstash_pg/schema_creation"
14
+ require_relative "./cipherstash_pg/schema_definitions"
15
+ require_relative "./cipherstash_pg/schema_dumper"
16
+ require_relative "./cipherstash_pg/schema_statements"
17
+ require_relative "./cipherstash_pg/type_metadata"
18
+ require_relative "./cipherstash_pg/utils"
19
+
20
+ module ActiveRecord
21
+ module ConnectionHandling
22
+ ###########################################################################
23
+ # Everything from this point onwards is a copy-paste or inherit from Rails'
24
+ # activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
25
+ # 6-1-stable branch
26
+ # ... with the exception of adding the ore_64_8_v1 type support on line 569.
27
+ #
28
+ # (We can't just inherit from PostgreSQLAdapter because we cannot load the
29
+ # postgresql_adapter.rb file; that would load 'pg', and we cannot [right
30
+ # now] have both 'cipherstash-pg' and 'pg' loaded at the same time.)
31
+ ###########################################################################
32
+
33
+ def postgres_cipherstash_connection(config)
34
+ conn_params = config.symbolize_keys.compact
35
+
36
+ # Map ActiveRecords param names to PGs.
37
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
38
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
39
+
40
+ # Forward only valid config params to PG::Connection.connect.
41
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
42
+ conn_params.slice!(*valid_conn_param_keys)
43
+
44
+ ConnectionAdapters::CipherStashPGAdapter.new(
45
+ ConnectionAdapters::CipherStashPGAdapter.new_client(conn_params),
46
+ logger,
47
+ conn_params,
48
+ config,
49
+ )
50
+ end
51
+ end
52
+
53
+ module ConnectionAdapters
54
+ class CipherStashPGAdapter < AbstractAdapter
55
+ ADAPTER_NAME = "CipherStash PostgreSQL"
56
+
57
+ class << self
58
+ def new_client(conn_params)
59
+ PG.connect(**conn_params)
60
+ rescue ::PG::Error => error
61
+ if conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
62
+ raise ActiveRecord::NoDatabaseError
63
+ else
64
+ raise ActiveRecord::ConnectionNotEstablished, error.message
65
+ end
66
+ end
67
+ end
68
+
69
+ ##
70
+ # :singleton-method:
71
+ # PostgreSQL allows the creation of "unlogged" tables, which do not record
72
+ # data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
73
+ # but significantly increases the risk of data loss if the database
74
+ # crashes. As a result, this should not be used in production
75
+ # environments. If you would like all created tables to be unlogged in
76
+ # the test environment you can add the following line to your test.rb
77
+ # file:
78
+ #
79
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
80
+ class_attribute :create_unlogged_tables, default: false
81
+
82
+ NATIVE_DATABASE_TYPES = {
83
+ primary_key: "bigserial primary key",
84
+ string: { name: "character varying" },
85
+ text: { name: "text" },
86
+ integer: { name: "integer", limit: 4 },
87
+ float: { name: "float" },
88
+ decimal: { name: "decimal" },
89
+ datetime: { name: "timestamp" },
90
+ time: { name: "time" },
91
+ date: { name: "date" },
92
+ daterange: { name: "daterange" },
93
+ numrange: { name: "numrange" },
94
+ tsrange: { name: "tsrange" },
95
+ tstzrange: { name: "tstzrange" },
96
+ int4range: { name: "int4range" },
97
+ int8range: { name: "int8range" },
98
+ binary: { name: "bytea" },
99
+ boolean: { name: "boolean" },
100
+ xml: { name: "xml" },
101
+ tsvector: { name: "tsvector" },
102
+ hstore: { name: "hstore" },
103
+ inet: { name: "inet" },
104
+ cidr: { name: "cidr" },
105
+ macaddr: { name: "macaddr" },
106
+ uuid: { name: "uuid" },
107
+ json: { name: "json" },
108
+ jsonb: { name: "jsonb" },
109
+ ltree: { name: "ltree" },
110
+ citext: { name: "citext" },
111
+ point: { name: "point" },
112
+ line: { name: "line" },
113
+ lseg: { name: "lseg" },
114
+ box: { name: "box" },
115
+ path: { name: "path" },
116
+ polygon: { name: "polygon" },
117
+ circle: { name: "circle" },
118
+ bit: { name: "bit" },
119
+ bit_varying: { name: "bit varying" },
120
+ money: { name: "money" },
121
+ interval: { name: "interval" },
122
+ oid: { name: "oid" },
123
+ }
124
+
125
+ OID = CipherStashPG::OID # :nodoc:
126
+
127
+ include CipherStashPG::Quoting
128
+ include CipherStashPG::ReferentialIntegrity
129
+ include CipherStashPG::SchemaStatements
130
+ include CipherStashPG::DatabaseStatements
131
+
132
+ def supports_bulk_alter?
133
+ true
134
+ end
135
+
136
+ def supports_index_sort_order?
137
+ true
138
+ end
139
+
140
+ def supports_partitioned_indexes?
141
+ database_version >= 110_000
142
+ end
143
+
144
+ def supports_partial_index?
145
+ true
146
+ end
147
+
148
+ def supports_expression_index?
149
+ true
150
+ end
151
+
152
+ def supports_transaction_isolation?
153
+ true
154
+ end
155
+
156
+ def supports_foreign_keys?
157
+ true
158
+ end
159
+
160
+ def supports_check_constraints?
161
+ true
162
+ end
163
+
164
+ def supports_validate_constraints?
165
+ true
166
+ end
167
+
168
+ def supports_views?
169
+ true
170
+ end
171
+
172
+ def supports_datetime_with_precision?
173
+ true
174
+ end
175
+
176
+ def supports_json?
177
+ true
178
+ end
179
+
180
+ def supports_comments?
181
+ true
182
+ end
183
+
184
+ def supports_savepoints?
185
+ true
186
+ end
187
+
188
+ def supports_insert_returning?
189
+ true
190
+ end
191
+
192
+ def supports_insert_on_conflict?
193
+ database_version >= 90500
194
+ end
195
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
196
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
197
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
198
+
199
+ def index_algorithms
200
+ { concurrently: "CONCURRENTLY" }
201
+ end
202
+
203
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
204
+ def initialize(connection, max)
205
+ super(max)
206
+ @connection = connection
207
+ @counter = 0
208
+ end
209
+
210
+ def next_key
211
+ "a#{@counter += 1}"
212
+ end
213
+
214
+ private
215
+ def dealloc(key)
216
+ @connection.query "DEALLOCATE #{key}" if connection_active?
217
+ rescue PG::Error
218
+ end
219
+
220
+ def connection_active?
221
+ @connection.status == PG::CONNECTION_OK
222
+ rescue PG::Error
223
+ false
224
+ end
225
+ end
226
+
227
+ # Initializes and connects a CipherStashPG adapter.
228
+ def initialize(connection, logger, connection_parameters, config)
229
+ super(connection, logger, config)
230
+
231
+ @connection_parameters = connection_parameters || {}
232
+
233
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
234
+ @local_tz = nil
235
+ @max_identifier_length = nil
236
+
237
+ configure_connection
238
+ add_pg_encoders
239
+ add_pg_decoders
240
+
241
+ @type_map = Type::HashLookupTypeMap.new
242
+ initialize_type_map
243
+ @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
244
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
245
+ end
246
+
247
+ def self.database_exists?(config)
248
+ !!ActiveRecord::Base.postgres_cipherstash_conection(config)
249
+ rescue ActiveRecord::NoDatabaseError
250
+ false
251
+ end
252
+
253
+ # Is this connection alive and ready for queries?
254
+ def active?
255
+ @lock.synchronize do
256
+ @connection.query "SELECT 1"
257
+ end
258
+ true
259
+ rescue PG::Error
260
+ false
261
+ end
262
+
263
+ # Close then reopen the connection.
264
+ def reconnect!
265
+ @lock.synchronize do
266
+ super
267
+ @connection.reset
268
+ configure_connection
269
+ rescue PG::ConnectionBad
270
+ connect
271
+ end
272
+ end
273
+
274
+ def reset!
275
+ @lock.synchronize do
276
+ clear_cache!
277
+ reset_transaction
278
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
279
+ @connection.query "ROLLBACK"
280
+ end
281
+ @connection.query "DISCARD ALL"
282
+ configure_connection
283
+ end
284
+ end
285
+
286
+ # Disconnects from the database if already connected. Otherwise, this
287
+ # method does nothing.
288
+ def disconnect!
289
+ @lock.synchronize do
290
+ super
291
+ @connection.close rescue nil
292
+ end
293
+ end
294
+
295
+ def discard! # :nodoc:
296
+ super
297
+ @connection.socket_io.reopen(IO::NULL) rescue nil
298
+ @connection = nil
299
+ end
300
+
301
+ def native_database_types #:nodoc:
302
+ NATIVE_DATABASE_TYPES
303
+ end
304
+
305
+ def set_standard_conforming_strings
306
+ execute("SET standard_conforming_strings = on", "SCHEMA")
307
+ end
308
+
309
+ def supports_ddl_transactions?
310
+ true
311
+ end
312
+
313
+ def supports_advisory_locks?
314
+ true
315
+ end
316
+
317
+ def supports_explain?
318
+ true
319
+ end
320
+
321
+ def supports_extensions?
322
+ true
323
+ end
324
+
325
+ def supports_materialized_views?
326
+ true
327
+ end
328
+
329
+ def supports_foreign_tables?
330
+ true
331
+ end
332
+
333
+ def supports_pgcrypto_uuid?
334
+ database_version >= 90400
335
+ end
336
+
337
+ def supports_optimizer_hints?
338
+ unless defined?(@has_pg_hint_plan)
339
+ @has_pg_hint_plan = extension_available?("pg_hint_plan")
340
+ end
341
+ @has_pg_hint_plan
342
+ end
343
+
344
+ def supports_common_table_expressions?
345
+ true
346
+ end
347
+
348
+ def supports_lazy_transactions?
349
+ true
350
+ end
351
+
352
+ def get_advisory_lock(lock_id) # :nodoc:
353
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
354
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
355
+ end
356
+ query_value("SELECT pg_try_advisory_lock(#{lock_id})")
357
+ end
358
+
359
+ def release_advisory_lock(lock_id) # :nodoc:
360
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
361
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
362
+ end
363
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
364
+ end
365
+
366
+ def enable_extension(name)
367
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
368
+ reload_type_map
369
+ }
370
+ end
371
+
372
+ def disable_extension(name)
373
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
374
+ reload_type_map
375
+ }
376
+ end
377
+
378
+ def extension_available?(name)
379
+ query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
380
+ end
381
+
382
+ def extension_enabled?(name)
383
+ query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
384
+ end
385
+
386
+ def extensions
387
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
388
+ end
389
+
390
+ # Returns the configured supported identifier length supported by PostgreSQL
391
+ def max_identifier_length
392
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
393
+ end
394
+
395
+ # Set the authorized user for this session
396
+ def session_auth=(user)
397
+ clear_cache!
398
+ execute("SET SESSION AUTHORIZATION #{user}")
399
+ end
400
+
401
+ def use_insert_returning?
402
+ @use_insert_returning
403
+ end
404
+
405
+ # Returns the version of the connected PostgreSQL server.
406
+ def get_database_version # :nodoc:
407
+ @connection.server_version
408
+ end
409
+ alias :cipherstash_pg_version :database_version
410
+
411
+ def default_index_type?(index) # :nodoc:
412
+ index.using == :btree || super
413
+ end
414
+
415
+ def build_insert_sql(insert) # :nodoc:
416
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
417
+
418
+ if insert.skip_duplicates?
419
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
420
+ elsif insert.update_duplicates?
421
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
422
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
423
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
424
+ end
425
+
426
+ sql << " RETURNING #{insert.returning}" if insert.returning
427
+ sql
428
+ end
429
+
430
+ def check_version # :nodoc:
431
+ if database_version < 90300
432
+ raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
433
+ end
434
+ end
435
+
436
+ private
437
+ # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
438
+ VALUE_LIMIT_VIOLATION = "22001"
439
+ NUMERIC_VALUE_OUT_OF_RANGE = "22003"
440
+ NOT_NULL_VIOLATION = "23502"
441
+ FOREIGN_KEY_VIOLATION = "23503"
442
+ UNIQUE_VIOLATION = "23505"
443
+ SERIALIZATION_FAILURE = "40001"
444
+ DEADLOCK_DETECTED = "40P01"
445
+ DUPLICATE_DATABASE = "42P04"
446
+ LOCK_NOT_AVAILABLE = "55P03"
447
+ QUERY_CANCELED = "57014"
448
+
449
+ def translate_exception(exception, message:, sql:, binds:)
450
+ return exception unless exception.respond_to?(:result)
451
+
452
+ case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
453
+ when nil
454
+ if exception.message.match?(/connection is closed/i)
455
+ ConnectionNotEstablished.new(exception)
456
+ else
457
+ super
458
+ end
459
+ when UNIQUE_VIOLATION
460
+ RecordNotUnique.new(message, sql: sql, binds: binds)
461
+ when FOREIGN_KEY_VIOLATION
462
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
463
+ when VALUE_LIMIT_VIOLATION
464
+ ValueTooLong.new(message, sql: sql, binds: binds)
465
+ when NUMERIC_VALUE_OUT_OF_RANGE
466
+ RangeError.new(message, sql: sql, binds: binds)
467
+ when NOT_NULL_VIOLATION
468
+ NotNullViolation.new(message, sql: sql, binds: binds)
469
+ when SERIALIZATION_FAILURE
470
+ SerializationFailure.new(message, sql: sql, binds: binds)
471
+ when DEADLOCK_DETECTED
472
+ Deadlocked.new(message, sql: sql, binds: binds)
473
+ when DUPLICATE_DATABASE
474
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
475
+ when LOCK_NOT_AVAILABLE
476
+ LockWaitTimeout.new(message, sql: sql, binds: binds)
477
+ when QUERY_CANCELED
478
+ QueryCanceled.new(message, sql: sql, binds: binds)
479
+ else
480
+ super
481
+ end
482
+ end
483
+
484
+ def get_oid_type(oid, fmod, column_name, sql_type = "")
485
+ if !type_map.key?(oid)
486
+ load_additional_types([oid])
487
+ end
488
+
489
+ type_map.fetch(oid, fmod, sql_type) {
490
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
491
+ Type.default_value.tap do |cast_type|
492
+ type_map.register_type(oid, cast_type)
493
+ end
494
+ }
495
+ end
496
+
497
+ def initialize_type_map(m = type_map)
498
+ m.register_type "int2", Type::Integer.new(limit: 2)
499
+ m.register_type "int4", Type::Integer.new(limit: 4)
500
+ m.register_type "int8", Type::Integer.new(limit: 8)
501
+ m.register_type "oid", OID::Oid.new
502
+ m.register_type "float4", Type::Float.new
503
+ m.alias_type "float8", "float4"
504
+ m.register_type "text", Type::Text.new
505
+ register_class_with_limit m, "varchar", Type::String
506
+ m.alias_type "char", "varchar"
507
+ m.alias_type "name", "varchar"
508
+ m.alias_type "bpchar", "varchar"
509
+ m.register_type "bool", Type::Boolean.new
510
+ register_class_with_limit m, "bit", OID::Bit
511
+ register_class_with_limit m, "varbit", OID::BitVarying
512
+ m.alias_type "timestamptz", "timestamp"
513
+ m.register_type "date", OID::Date.new
514
+
515
+ m.register_type "money", OID::Money.new
516
+ m.register_type "bytea", OID::Bytea.new
517
+ m.register_type "point", OID::Point.new
518
+ m.register_type "hstore", OID::Hstore.new
519
+ m.register_type "json", Type::Json.new
520
+ m.register_type "jsonb", OID::Jsonb.new
521
+ m.register_type "cidr", OID::Cidr.new
522
+ m.register_type "inet", OID::Inet.new
523
+ m.register_type "uuid", OID::Uuid.new
524
+ m.register_type "xml", OID::Xml.new
525
+ m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
526
+ m.register_type "macaddr", OID::Macaddr.new
527
+ m.register_type "citext", OID::SpecializedString.new(:citext)
528
+ m.register_type "ltree", OID::SpecializedString.new(:ltree)
529
+ m.register_type "line", OID::SpecializedString.new(:line)
530
+ m.register_type "lseg", OID::SpecializedString.new(:lseg)
531
+ m.register_type "box", OID::SpecializedString.new(:box)
532
+ m.register_type "path", OID::SpecializedString.new(:path)
533
+ m.register_type "polygon", OID::SpecializedString.new(:polygon)
534
+ m.register_type "circle", OID::SpecializedString.new(:circle)
535
+
536
+ register_class_with_precision m, "time", Type::Time
537
+ register_class_with_precision m, "timestamp", OID::DateTime
538
+
539
+ m.register_type "numeric" do |_, fmod, sql_type|
540
+ precision = extract_precision(sql_type)
541
+ scale = extract_scale(sql_type)
542
+
543
+ # The type for the numeric depends on the width of the field,
544
+ # so we'll do something special here.
545
+ #
546
+ # When dealing with decimal columns:
547
+ #
548
+ # places after decimal = fmod - 4 & 0xffff
549
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
550
+ if fmod && (fmod - 4 & 0xffff).zero?
551
+ # FIXME: Remove this class, and the second argument to
552
+ # lookups on PG
553
+ Type::DecimalWithoutScale.new(precision: precision)
554
+ else
555
+ OID::Decimal.new(precision: precision, scale: scale)
556
+ end
557
+ end
558
+
559
+ m.register_type "interval" do |*args, sql_type|
560
+ precision = extract_precision(sql_type)
561
+ OID::Interval.new(precision: precision)
562
+ end
563
+
564
+ ############################################
565
+ # EDITED to add type mapping for ORE type.
566
+ ############################################
567
+ # TODO: Look into OID::Array for ore_64_8_v1. It doesn't work out of
568
+ # the box, FWIW, so will need a little digging.
569
+ m.register_type "ore_64_8_v1_term", OID::SpecializedString.new(:ore_64_8_v1_term)
570
+ m.register_type "ore_64_8_v1", OID::SpecializedString.new(:ore_64_8_v1)
571
+
572
+ load_additional_types
573
+ end
574
+
575
+ # Extracts the value from a PostgreSQL column default definition.
576
+ def extract_value_from_default(default)
577
+ case default
578
+ # Quoted types
579
+ when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
580
+ # The default 'now'::date is CURRENT_DATE
581
+ if $1 == "now" && $2 == "date"
582
+ nil
583
+ else
584
+ $1.gsub("''", "'")
585
+ end
586
+ # Boolean types
587
+ when "true", "false"
588
+ default
589
+ # Numeric types
590
+ when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
591
+ $1
592
+ # Object identifier types
593
+ when /\A-?\d+\z/
594
+ $1
595
+ else
596
+ # Anything else is blank, some user type, or some function
597
+ # and we can't know the value of that, so return nil.
598
+ nil
599
+ end
600
+ end
601
+
602
+ def extract_default_function(default_value, default)
603
+ default if has_default_function?(default_value, default)
604
+ end
605
+
606
+ def has_default_function?(default_value, default)
607
+ !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
608
+ end
609
+
610
+ def load_additional_types(oids = nil)
611
+ initializer = OID::TypeMapInitializer.new(type_map)
612
+
613
+ query = <<~SQL
614
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
615
+ FROM pg_type as t
616
+ LEFT JOIN pg_range as r ON oid = rngtypid
617
+ SQL
618
+
619
+ if oids
620
+ query += "WHERE t.oid IN (%s)" % oids.join(", ")
621
+ else
622
+ query += initializer.query_conditions_for_initial_load
623
+ end
624
+
625
+ execute_and_clear(query, "SCHEMA", []) do |records|
626
+ initializer.run(records)
627
+ end
628
+ end
629
+
630
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
631
+
632
+ def execute_and_clear(sql, name, binds, prepare: false)
633
+ if preventing_writes? && write_query?(sql)
634
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
635
+ end
636
+
637
+ if !prepare || without_prepared_statement?(binds)
638
+ result = exec_no_cache(sql, name, binds)
639
+ else
640
+ result = exec_cache(sql, name, binds)
641
+ end
642
+ begin
643
+ ret = yield result
644
+ ensure
645
+ result.clear
646
+ end
647
+ ret
648
+ end
649
+
650
+ def exec_no_cache(sql, name, binds)
651
+ materialize_transactions
652
+ mark_transaction_written_if_write(sql)
653
+
654
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
655
+ # made since we established the connection
656
+ update_typemap_for_default_timezone
657
+
658
+ type_casted_binds = type_casted_binds(binds)
659
+ log(sql, name, binds, type_casted_binds) do
660
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
661
+ @connection.exec_params(sql, type_casted_binds)
662
+ end
663
+ end
664
+ end
665
+
666
+ def exec_cache(sql, name, binds)
667
+ materialize_transactions
668
+ mark_transaction_written_if_write(sql)
669
+ update_typemap_for_default_timezone
670
+
671
+ stmt_key = prepare_statement(sql, binds)
672
+ type_casted_binds = type_casted_binds(binds)
673
+
674
+ log(sql, name, binds, type_casted_binds, stmt_key) do
675
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
676
+ @connection.exec_prepared(stmt_key, type_casted_binds)
677
+ end
678
+ end
679
+ rescue ActiveRecord::StatementInvalid => e
680
+ raise unless is_cached_plan_failure?(e)
681
+
682
+ # Nothing we can do if we are in a transaction because all commands
683
+ # will raise InFailedSQLTransaction
684
+ if in_transaction?
685
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
686
+ else
687
+ @lock.synchronize do
688
+ # outside of transactions we can simply flush this query and retry
689
+ @statements.delete sql_key(sql)
690
+ end
691
+ retry
692
+ end
693
+ end
694
+
695
+ # Annoyingly, the code for prepared statements whose return value may
696
+ # have changed is FEATURE_NOT_SUPPORTED.
697
+ #
698
+ # This covers various different error types so we need to do additional
699
+ # work to classify the exception definitively as a
700
+ # ActiveRecord::PreparedStatementCacheExpired
701
+ #
702
+ # Check here for more details:
703
+ # https://git.cipherstash_pg.org/gitweb/?p=cipherstash_pg.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
704
+ def is_cached_plan_failure?(e)
705
+ pgerror = e.cause
706
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
707
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
708
+ rescue
709
+ false
710
+ end
711
+
712
+ def in_transaction?
713
+ open_transactions > 0
714
+ end
715
+
716
+ # Returns the statement identifier for the client side cache
717
+ # of statements
718
+ def sql_key(sql)
719
+ "#{schema_search_path}-#{sql}"
720
+ end
721
+
722
+ # Prepare the statement if it hasn't been prepared, return
723
+ # the statement key.
724
+ def prepare_statement(sql, binds)
725
+ @lock.synchronize do
726
+ sql_key = sql_key(sql)
727
+ unless @statements.key? sql_key
728
+ nextkey = @statements.next_key
729
+ begin
730
+ @connection.prepare nextkey, sql
731
+ rescue => e
732
+ raise translate_exception_class(e, sql, binds)
733
+ end
734
+ # Clear the queue
735
+ @connection.get_last_result
736
+ @statements[sql_key] = nextkey
737
+ end
738
+ @statements[sql_key]
739
+ end
740
+ end
741
+
742
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
743
+ # connected server's characteristics.
744
+ def connect
745
+ @connection = self.class.new_client(@connection_parameters)
746
+ configure_connection
747
+ add_pg_encoders
748
+ add_pg_decoders
749
+ end
750
+
751
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
752
+ # This is called by #connect and should not be called manually.
753
+ def configure_connection
754
+ if @config[:encoding]
755
+ @connection.set_client_encoding(@config[:encoding])
756
+ end
757
+ self.client_min_messages = @config[:min_messages] || "warning"
758
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
759
+
760
+ # Use standard-conforming strings so we don't have to do the E'...' dance.
761
+ set_standard_conforming_strings
762
+
763
+ variables = @config.fetch(:variables, {}).stringify_keys
764
+
765
+ # If using Active Record's time zone support configure the connection to return
766
+ # TIMESTAMP WITH ZONE types in UTC.
767
+ unless variables["timezone"]
768
+ if ActiveRecord::Base.default_timezone == :utc
769
+ variables["timezone"] = "UTC"
770
+ elsif @local_tz
771
+ variables["timezone"] = @local_tz
772
+ end
773
+ end
774
+
775
+ # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
776
+ execute("SET intervalstyle = iso_8601", "SCHEMA")
777
+
778
+ # SET statements from :variables config hash
779
+ # https://www.cipherstash_pg.org/docs/current/static/sql-set.html
780
+ variables.map do |k, v|
781
+ if v == ":default" || v == :default
782
+ # Sets the value to the global or compile default
783
+ execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
784
+ elsif !v.nil?
785
+ execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
786
+ end
787
+ end
788
+ end
789
+
790
+ # Returns the list of a table's column names, data types, and default values.
791
+ #
792
+ # The underlying query is roughly:
793
+ # SELECT column.name, column.type, default.value, column.comment
794
+ # FROM column LEFT JOIN default
795
+ # ON column.table_id = default.table_id
796
+ # AND column.num = default.column_num
797
+ # WHERE column.table_id = get_table_id('table_name')
798
+ # AND column.num > 0
799
+ # AND NOT column.is_dropped
800
+ # ORDER BY column.num
801
+ #
802
+ # If the table name is not prefixed with a schema, the database will
803
+ # take the first match from the schema search path.
804
+ #
805
+ # Query implementation notes:
806
+ # - format_type includes the column size constraint, e.g. varchar(50)
807
+ # - ::regclass is a function that gives the id for a table name
808
+ def column_definitions(table_name)
809
+ query(<<~SQL, "SCHEMA")
810
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
811
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
812
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
813
+ FROM pg_attribute a
814
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
815
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
816
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
817
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
818
+ AND a.attnum > 0 AND NOT a.attisdropped
819
+ ORDER BY a.attnum
820
+ SQL
821
+ end
822
+
823
+ def extract_table_ref_from_insert_sql(sql)
824
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
825
+ $1.strip if $1
826
+ end
827
+
828
+ def arel_visitor
829
+ # Robin note: this is fine to leave as-is.
830
+ Arel::Visitors::PostgreSQL.new(self)
831
+ end
832
+
833
+ def build_statement_pool
834
+ StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
835
+ end
836
+
837
+ def can_perform_case_insensitive_comparison_for?(column)
838
+ @case_insensitive_cache ||= {}
839
+ @case_insensitive_cache[column.sql_type] ||= begin
840
+ sql = <<~SQL
841
+ SELECT exists(
842
+ SELECT * FROM pg_proc
843
+ WHERE proname = 'lower'
844
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
845
+ ) OR exists(
846
+ SELECT * FROM pg_proc
847
+ INNER JOIN pg_cast
848
+ ON ARRAY[casttarget]::oidvector = proargtypes
849
+ WHERE proname = 'lower'
850
+ AND castsource = #{quote column.sql_type}::regtype
851
+ )
852
+ SQL
853
+ execute_and_clear(sql, "SCHEMA", []) do |result|
854
+ result.getvalue(0, 0)
855
+ end
856
+ end
857
+ end
858
+
859
+ def add_pg_encoders
860
+ map = PG::TypeMapByClass.new
861
+ map[Integer] = PG::TextEncoder::Integer.new
862
+ map[TrueClass] = PG::TextEncoder::Boolean.new
863
+ map[FalseClass] = PG::TextEncoder::Boolean.new
864
+ @connection.type_map_for_queries = map
865
+ end
866
+
867
+ def update_typemap_for_default_timezone
868
+ if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
869
+ decoder_class = ActiveRecord::Base.default_timezone == :utc ?
870
+ PG::TextDecoder::TimestampUtc :
871
+ PG::TextDecoder::TimestampWithoutTimeZone
872
+
873
+ @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
874
+ @connection.type_map_for_results.add_coder(@timestamp_decoder)
875
+ @default_timezone = ActiveRecord::Base.default_timezone
876
+ end
877
+ end
878
+
879
+ def add_pg_decoders
880
+ @default_timezone = nil
881
+ @timestamp_decoder = nil
882
+
883
+ coders_by_name = {
884
+ "int2" => PG::TextDecoder::Integer,
885
+ "int4" => PG::TextDecoder::Integer,
886
+ "int8" => PG::TextDecoder::Integer,
887
+ "oid" => PG::TextDecoder::Integer,
888
+ "float4" => PG::TextDecoder::Float,
889
+ "float8" => PG::TextDecoder::Float,
890
+ "numeric" => PG::TextDecoder::Numeric,
891
+ "bool" => PG::TextDecoder::Boolean,
892
+ "timestamp" => PG::TextDecoder::TimestampUtc,
893
+ "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
894
+ }
895
+
896
+ known_coder_types = coders_by_name.keys.map { |n| quote(n) }
897
+ query = <<~SQL % known_coder_types.join(", ")
898
+ SELECT t.oid, t.typname
899
+ FROM pg_type as t
900
+ WHERE t.typname IN (%s)
901
+ SQL
902
+ coders = execute_and_clear(query, "SCHEMA", []) do |result|
903
+ result
904
+ .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
905
+ .compact
906
+ end
907
+
908
+ map = PG::TypeMapByOid.new
909
+ coders.each { |coder| map.add_coder(coder) }
910
+ @connection.type_map_for_results = map
911
+
912
+ @type_map_for_results = PG::TypeMapByOid.new
913
+ @type_map_for_results.default_type_map = map
914
+ @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
915
+ @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
916
+
917
+ # extract timestamp decoder for use in update_typemap_for_default_timezone
918
+ @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
919
+ update_typemap_for_default_timezone
920
+ end
921
+
922
+ def construct_coder(row, coder_class)
923
+ return unless coder_class
924
+ coder_class.new(oid: row["oid"].to_i, name: row["typname"])
925
+ end
926
+
927
+ class MoneyDecoder < PG::SimpleDecoder # :nodoc:
928
+ TYPE = OID::Money.new
929
+
930
+ def decode(value, tuple = nil, field = nil)
931
+ TYPE.deserialize(value)
932
+ end
933
+ end
934
+
935
+ ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :cipherstash_pg)
936
+ ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :cipherstash_pg)
937
+ ActiveRecord::Type.register(:bit, OID::Bit, adapter: :cipherstash_pg)
938
+ ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :cipherstash_pg)
939
+ ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :cipherstash_pg)
940
+ ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :cipherstash_pg)
941
+ ActiveRecord::Type.register(:date, OID::Date, adapter: :cipherstash_pg)
942
+ ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :cipherstash_pg)
943
+ ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :cipherstash_pg)
944
+ ActiveRecord::Type.register(:enum, OID::Enum, adapter: :cipherstash_pg)
945
+ ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :cipherstash_pg)
946
+ ActiveRecord::Type.register(:inet, OID::Inet, adapter: :cipherstash_pg)
947
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :cipherstash_pg)
948
+ ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :cipherstash_pg)
949
+ ActiveRecord::Type.register(:money, OID::Money, adapter: :cipherstash_pg)
950
+ ActiveRecord::Type.register(:point, OID::Point, adapter: :cipherstash_pg)
951
+ ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :cipherstash_pg)
952
+ ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :cipherstash_pg)
953
+ ActiveRecord::Type.register(:vector, OID::Vector, adapter: :cipherstash_pg)
954
+ ActiveRecord::Type.register(:xml, OID::Xml, adapter: :cipherstash_pg)
955
+ end
956
+ ActiveSupport.run_load_hooks(:active_record_cipherstash_pgadapter, CipherStashPGAdapter)
957
+ end
958
+ end