activerecord-cipherstash-pg-adapter 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/README.md +13 -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/cipherstash_pg/database_extensions.rb +3 -1
  45. data/lib/active_record/connection_adapters/cipherstash_pg/database_tasks.rb +4 -2
  46. data/lib/active_record/connection_adapters/postgres_cipherstash_adapter.rb +42 -12
  47. data/lib/activerecord-cipherstash-pg-adapter.rb +17 -23
  48. data/lib/cipherstash_tasks.rake +34 -0
  49. data/lib/version.rb +1 -1
  50. metadata +89 -45
  51. data/lib/active_record/connection_adapters/cipherstash_pg/oid.rb +0 -38
  52. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/explain_pretty_printer.rb +0 -0
  53. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit.rb +0 -0
  54. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bit_varying.rb +0 -0
  55. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/bytea.rb +0 -0
  56. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/cidr.rb +0 -0
  57. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/decimal.rb +0 -0
  58. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/enum.rb +0 -0
  59. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/inet.rb +0 -0
  60. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/interval.rb +0 -0
  61. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/jsonb.rb +0 -0
  62. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/legacy_point.rb +0 -0
  63. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/macaddr.rb +0 -0
  64. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/money.rb +0 -0
  65. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/oid.rb +0 -0
  66. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/point.rb +0 -0
  67. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/range.rb +0 -0
  68. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/specialized_string.rb +0 -0
  69. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/uuid.rb +0 -0
  70. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/vector.rb +0 -0
  71. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/oid/xml.rb +0 -0
  72. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/type_metadata.rb +0 -0
  73. /data/lib/active_record/connection_adapters/{cipherstash_pg → 6.1/cipherstash_pg}/utils.rb +0 -0
  74. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/column.rb +0 -0
  75. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/database_statements.rb +0 -0
  76. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/array.rb +0 -0
  77. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date.rb +0 -0
  78. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/date_time.rb +0 -0
  79. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/hstore.rb +0 -0
  80. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp.rb +0 -0
  81. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/timestamp_with_time_zone.rb +0 -0
  82. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/oid/type_map_initializer.rb +0 -0
  83. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/quoting.rb +0 -0
  84. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/referential_integrity.rb +0 -0
  85. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_creation.rb +0 -0
  86. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_definitions.rb +0 -0
  87. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_dumper.rb +0 -0
  88. /data/lib/active_record/connection_adapters/{cipherstash_pg → 7.0/cipherstash_pg}/schema_statements.rb +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