activerecord-yugabytedb-adapter 7.0.4.1

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