activerecord-jdbc-adapter 70.2-java → 71.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,6 +13,7 @@ require 'active_record/connection_adapters/postgresql/schema_dumper'
13
13
  require 'active_record/connection_adapters/postgresql/schema_statements'
14
14
  require 'active_record/connection_adapters/postgresql/type_metadata'
15
15
  require 'active_record/connection_adapters/postgresql/utils'
16
+
16
17
  require 'arjdbc/abstract/core'
17
18
  require 'arjdbc/abstract/connection_management'
18
19
  require 'arjdbc/abstract/database_statements'
@@ -21,8 +22,13 @@ require 'arjdbc/abstract/transaction_support'
21
22
  require 'arjdbc/postgresql/base/array_decoder'
22
23
  require 'arjdbc/postgresql/base/array_encoder'
23
24
  require 'arjdbc/postgresql/name'
25
+ require 'arjdbc/postgresql/database_statements'
26
+ require 'arjdbc/postgresql/schema_statements'
27
+
24
28
  require 'active_model'
25
29
 
30
+ require "arjdbc/abstract/relation_query_attribute_monkey_patch"
31
+
26
32
  module ArJdbc
27
33
  # Strives to provide Rails built-in PostgreSQL adapter (API) compatibility.
28
34
  module PostgreSQL
@@ -35,11 +41,6 @@ module ArJdbc
35
41
  # @private
36
42
  Type = ::ActiveRecord::Type
37
43
 
38
- # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
39
- def self.jdbc_connection_class
40
- ::ActiveRecord::ConnectionAdapters::PostgreSQLJdbcConnection
41
- end
42
-
43
44
  # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_column_class
44
45
  def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn end
45
46
 
@@ -52,8 +53,8 @@ module ArJdbc
52
53
  def redshift?
53
54
  # SELECT version() :
54
55
  # PostgreSQL 8.0.2 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3), Redshift 1.0.647
55
- if ( redshift = config[:redshift] ).nil?
56
- redshift = !! (@connection.database_product || '').index('Redshift')
56
+ if (redshift = @config[:redshift]).nil?
57
+ redshift = !! (valid_raw_connection.database_product || '').index('Redshift')
57
58
  end
58
59
  redshift
59
60
  end
@@ -73,8 +74,8 @@ module ArJdbc
73
74
  # see http://jdbc.postgresql.org/documentation/91/connect.html
74
75
  # self.set_client_encoding(encoding)
75
76
  #end
76
- self.client_min_messages = config[:min_messages] || 'warning'
77
- self.schema_search_path = config[:schema_search_path] || config[:schema_order]
77
+ self.client_min_messages = @config[:min_messages] || 'warning'
78
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
78
79
 
79
80
  # Use standard-conforming strings if available so we don't have to do the E'...' dance.
80
81
  set_standard_conforming_strings
@@ -93,7 +94,7 @@ module ArJdbc
93
94
 
94
95
  # SET statements from :variables config hash
95
96
  # http://www.postgresql.org/docs/8.3/static/sql-set.html
96
- (config[:variables] || {}).map do |k, v|
97
+ (@config[:variables] || {}).map do |k, v|
97
98
  if v == ':default' || v == :default
98
99
  # Sets the value to the global or compile default
99
100
  execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
@@ -101,6 +102,8 @@ module ArJdbc
101
102
  execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
102
103
  end
103
104
  end
105
+
106
+ reload_type_map
104
107
  end
105
108
 
106
109
  # @private
@@ -120,14 +123,15 @@ module ArJdbc
120
123
  citext: { name: 'citext' },
121
124
  date: { name: 'date' },
122
125
  daterange: { name: 'daterange' },
123
- datetime: { name: 'timestamp' },
126
+ datetime: {}, # set dynamically based on datetime_type
127
+ timestamptz: { name: 'timestamptz' },
124
128
  decimal: { name: 'decimal' }, # :limit => 1000
125
129
  float: { name: 'float' },
126
130
  hstore: { name: 'hstore' },
127
131
  inet: { name: 'inet' },
128
132
  int4range: { name: 'int4range' },
129
133
  int8range: { name: 'int8range' },
130
- integer: { name: 'integer' },
134
+ integer: { name: 'integer', limit: 4 },
131
135
  interval: { name: 'interval' },
132
136
  json: { name: 'json' },
133
137
  jsonb: { name: 'jsonb' },
@@ -150,17 +154,10 @@ module ArJdbc
150
154
  tstzrange: { name: 'tstzrange' },
151
155
  tsvector: { name: 'tsvector' },
152
156
  uuid: { name: 'uuid' },
153
- xml: { name: 'xml' }
157
+ xml: { name: 'xml' },
158
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
154
159
  }
155
160
 
156
- def native_database_types
157
- NATIVE_DATABASE_TYPES
158
- end
159
-
160
- def valid_type?(type)
161
- !native_database_types[type].nil?
162
- end
163
-
164
161
  def set_standard_conforming_strings
165
162
  execute("SET standard_conforming_strings = on", "SCHEMA")
166
163
  end
@@ -232,6 +229,18 @@ module ArJdbc
232
229
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
233
230
  alias supports_insert_conflict_target? supports_insert_on_conflict?
234
231
 
232
+ def supports_virtual_columns?
233
+ database_version >= 12_00_00 # >= 12.0
234
+ end
235
+
236
+ def supports_identity_columns? # :nodoc:
237
+ database_version >= 10_00_00 # >= 10.0
238
+ end
239
+
240
+ def supports_nulls_not_distinct?
241
+ database_version >= 15_00_00 # >= 15.0
242
+ end
243
+
235
244
  def index_algorithms
236
245
  { concurrently: 'CONCURRENTLY' }
237
246
  end
@@ -297,14 +306,21 @@ module ArJdbc
297
306
  query_value("SELECT pg_advisory_unlock(#{lock_id})")
298
307
  end
299
308
 
300
- def enable_extension(name)
301
- exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
302
- reload_type_map
303
- }
309
+ def enable_extension(name, **)
310
+ schema, name = name.to_s.split(".").values_at(-2, -1)
311
+ sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
312
+ sql << " SCHEMA #{schema}" if schema
313
+
314
+ internal_exec_query(sql).tap { reload_type_map }
304
315
  end
305
316
 
306
- def disable_extension(name)
307
- exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
317
+ # Removes an extension from the database.
318
+ #
319
+ # [<tt>:force</tt>]
320
+ # Set to +:cascade+ to drop dependent objects as well.
321
+ # Defaults to false.
322
+ def disable_extension(name, force: false)
323
+ internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
308
324
  reload_type_map
309
325
  }
310
326
  end
@@ -318,39 +334,106 @@ module ArJdbc
318
334
  end
319
335
 
320
336
  def extensions
321
- exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
337
+ internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
322
338
  end
323
339
 
324
340
  # Returns a list of defined enum types, and their values.
325
341
  def enum_types
326
342
  query = <<~SQL
327
- SELECT
328
- type.typname AS name,
329
- string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
330
- FROM pg_enum AS enum
331
- JOIN pg_type AS type
332
- ON (type.oid = enum.enumtypid)
333
- GROUP BY type.typname;
343
+ SELECT
344
+ type.typname AS name,
345
+ type.OID AS oid,
346
+ n.nspname AS schema,
347
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
348
+ FROM pg_enum AS enum
349
+ JOIN pg_type AS type ON (type.oid = enum.enumtypid)
350
+ JOIN pg_namespace n ON type.typnamespace = n.oid
351
+ WHERE n.nspname = ANY (current_schemas(false))
352
+ GROUP BY type.OID, n.nspname, type.typname;
334
353
  SQL
335
- exec_query(query, "SCHEMA").cast_values
354
+
355
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
356
+ name, schema = row[0], row[2]
357
+ schema = nil if schema == current_schema
358
+ full_name = [schema, name].compact.join(".")
359
+ memo[full_name] = row.last
360
+ end.to_a
336
361
  end
337
362
 
338
363
  # Given a name and an array of values, creates an enum type.
339
- def create_enum(name, values)
340
- sql_values = values.map { |s| "'#{s}'" }.join(", ")
364
+ def create_enum(name, values, **options)
365
+ sql_values = values.map { |s| quote(s) }.join(", ")
366
+ scope = quoted_scope(name)
367
+ query = <<~SQL
368
+ DO $$
369
+ BEGIN
370
+ IF NOT EXISTS (
371
+ SELECT 1
372
+ FROM pg_type t
373
+ JOIN pg_namespace n ON t.typnamespace = n.oid
374
+ WHERE t.typname = #{scope[:name]}
375
+ AND n.nspname = #{scope[:schema]}
376
+ ) THEN
377
+ CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
378
+ END IF;
379
+ END
380
+ $$;
381
+ SQL
382
+
383
+ internal_exec_query(query).tap { reload_type_map }
384
+ end
385
+
386
+ # Drops an enum type.
387
+ #
388
+ # If the <tt>if_exists: true</tt> option is provided, the enum is dropped
389
+ # only if it exists. Otherwise, if the enum doesn't exist, an error is
390
+ # raised.
391
+ #
392
+ # The +values+ parameter will be ignored if present. It can be helpful
393
+ # to provide this in a migration's +change+ method so it can be reverted.
394
+ # In that case, +values+ will be used by #create_enum.
395
+ def drop_enum(name, values = nil, **options)
341
396
  query = <<~SQL
342
- DO $$
343
- BEGIN
344
- IF NOT EXISTS (
345
- SELECT 1 FROM pg_type t
346
- WHERE t.typname = '#{name}'
347
- ) THEN
348
- CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
349
- END IF;
350
- END
351
- $$;
397
+ DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
352
398
  SQL
353
- exec_query(query)
399
+ internal_exec_query(query).tap { reload_type_map }
400
+ end
401
+
402
+ # Rename an existing enum type to something else.
403
+ def rename_enum(name, options = {})
404
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
405
+
406
+ exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
407
+ end
408
+
409
+ # Add enum value to an existing enum type.
410
+ def add_enum_value(type_name, value, options = {})
411
+ before, after = options.values_at(:before, :after)
412
+ sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
413
+
414
+ if before && after
415
+ raise ArgumentError, "Cannot have both :before and :after at the same time"
416
+ elsif before
417
+ sql << " BEFORE '#{before}'"
418
+ elsif after
419
+ sql << " AFTER '#{after}'"
420
+ end
421
+
422
+ execute(sql).tap { reload_type_map }
423
+ end
424
+
425
+ # Rename enum value on an existing enum type.
426
+ def rename_enum_value(type_name, options = {})
427
+ unless database_version >= 10_00_00 # >= 10.0
428
+ raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
429
+ end
430
+
431
+ from = options.fetch(:from) { raise ArgumentError, ":from is required" }
432
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
433
+
434
+ execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
435
+ reload_type_map
436
+ }
354
437
  end
355
438
 
356
439
  # Returns the configured supported identifier length supported by PostgreSQL
@@ -370,7 +453,7 @@ module ArJdbc
370
453
 
371
454
  def get_database_version # :nodoc:
372
455
  begin
373
- version = @connection.database_product
456
+ version = valid_raw_connection.database_product
374
457
  if match = version.match(/([\d\.]*\d).*?/)
375
458
  version = match[1].split('.').map(&:to_i)
376
459
  # PostgreSQL version representation does not have more than 4 digits
@@ -426,8 +509,7 @@ module ArJdbc
426
509
  end
427
510
  end
428
511
 
429
-
430
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
512
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
431
513
  val = super
432
514
  if !use_insert_returning? && pk
433
515
  unless sequence_name
@@ -445,11 +527,6 @@ module ArJdbc
445
527
  execute(combine_multi_statements(statements), name)
446
528
  end
447
529
 
448
- def explain(arel, binds = [])
449
- sql, binds = to_sql_and_binds(arel, binds)
450
- ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query("EXPLAIN #{sql}", 'EXPLAIN', binds))
451
- end
452
-
453
530
  # from ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements
454
531
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
455
532
  :close, :declare, :fetch, :move, :set, :show
@@ -464,18 +541,33 @@ module ArJdbc
464
541
  # since apparently calling close on the statement object
465
542
  # doesn't always free the server resources and calling
466
543
  # 'DISCARD ALL' fails if we are inside a transaction
467
- def clear_cache!
468
- super
469
- # Make sure all query plans are *really* gone
470
- @connection.execute 'DEALLOCATE ALL' if active?
471
- end
544
+ # def clear_cache!
545
+ # super
546
+ # # Make sure all query plans are *really* gone
547
+ # @connection.execute 'DEALLOCATE ALL' if active?
548
+ # end
472
549
 
473
550
  def reset!
474
- clear_cache!
475
- reset_transaction
476
- @connection.rollback # Have to deal with rollbacks differently than the AR adapter
477
- @connection.execute 'DISCARD ALL'
478
- @connection.configure_connection
551
+ @lock.synchronize do
552
+ return connect! unless @raw_connection
553
+
554
+ # Have to deal with rollbacks differently than the AR adapter
555
+ @raw_connection.rollback
556
+
557
+ @raw_connection.execute("DISCARD ALL")
558
+
559
+ super
560
+ end
561
+ end
562
+
563
+ # Disconnects from the database if already connected. Otherwise, this
564
+ # method does nothing.
565
+ def disconnect!
566
+ @lock.synchronize do
567
+ super
568
+ @raw_connection&.close
569
+ @raw_connection = nil
570
+ end
479
571
  end
480
572
 
481
573
  def default_sequence_name(table_name, pk = "id") #:nodoc:
@@ -593,17 +685,19 @@ module ArJdbc
593
685
  # - format_type includes the column size constraint, e.g. varchar(50)
594
686
  # - ::regclass is a function that gives the id for a table name
595
687
  def column_definitions(table_name)
596
- select_rows(<<~SQL, 'SCHEMA')
597
- SELECT a.attname, format_type(a.atttypid, a.atttypmod),
598
- pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
599
- c.collname, col_description(a.attrelid, a.attnum) AS comment
600
- FROM pg_attribute a
601
- LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
602
- LEFT JOIN pg_type t ON a.atttypid = t.oid
603
- LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
604
- WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
605
- AND a.attnum > 0 AND NOT a.attisdropped
606
- ORDER BY a.attnum
688
+ query(<<~SQL, "SCHEMA")
689
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
690
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
691
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
692
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
693
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
694
+ FROM pg_attribute a
695
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
696
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
697
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
698
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
699
+ AND a.attnum > 0 AND NOT a.attisdropped
700
+ ORDER BY a.attnum
607
701
  SQL
608
702
  end
609
703
 
@@ -618,22 +712,27 @@ module ArJdbc
618
712
 
619
713
  # Pulled from ActiveRecord's Postgres adapter and modified to use execute
620
714
  def can_perform_case_insensitive_comparison_for?(column)
621
- @case_insensitive_cache ||= {}
622
- @case_insensitive_cache[column.sql_type] ||= begin
623
- sql = <<~SQL
624
- SELECT exists(
625
- SELECT * FROM pg_proc
626
- WHERE proname = 'lower'
627
- AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
628
- ) OR exists(
629
- SELECT * FROM pg_proc
630
- INNER JOIN pg_cast
631
- ON ARRAY[casttarget]::oidvector = proargtypes
632
- WHERE proname = 'lower'
633
- AND castsource = #{quote column.sql_type}::regtype
634
- )
635
- SQL
636
- select_value(sql, 'SCHEMA')
715
+ # NOTE: citext is an exception. It is possible to perform a
716
+ # case-insensitive comparison using `LOWER()`, but it is
717
+ # unnecessary, as `citext` is case-insensitive by definition.
718
+ @case_insensitive_cache ||= { "citext" => false }
719
+ @case_insensitive_cache.fetch(column.sql_type) do
720
+ @case_insensitive_cache[column.sql_type] = begin
721
+ sql = <<~SQL
722
+ SELECT exists(
723
+ SELECT * FROM pg_proc
724
+ WHERE proname = 'lower'
725
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
726
+ ) OR exists(
727
+ SELECT * FROM pg_proc
728
+ INNER JOIN pg_cast
729
+ ON ARRAY[casttarget]::oidvector = proargtypes
730
+ WHERE proname = 'lower'
731
+ AND castsource = #{quote column.sql_type}::regtype
732
+ )
733
+ SQL
734
+ select_value(sql, 'SCHEMA')
735
+ end
637
736
  end
638
737
  end
639
738
 
@@ -642,6 +741,8 @@ module ArJdbc
642
741
 
643
742
  # TODO: Can we base these on an error code of some kind?
644
743
  case exception.message
744
+ when /could not create unique index/
745
+ ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
645
746
  when /duplicate key value violates unique constraint/
646
747
  ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
647
748
  when /violates not-null constraint/
@@ -660,6 +761,10 @@ module ArJdbc
660
761
  ::ActiveRecord::LockWaitTimeout.new(message, sql: sql, binds: binds)
661
762
  when /canceling statement/ # This needs to come after lock timeout because the lock timeout message also contains "canceling statement"
662
763
  ::ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
764
+ when /relation .* does not exist/i
765
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
766
+ when /syntax error at or near/i
767
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
663
768
  else
664
769
  super
665
770
  end
@@ -742,7 +847,7 @@ module ActiveRecord::ConnectionAdapters
742
847
  include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
743
848
  include ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting
744
849
 
745
- include Jdbc::ConnectionPoolCallbacks
850
+ # include Jdbc::ConnectionPoolCallbacks
746
851
 
747
852
  include ArJdbc::Abstract::Core
748
853
  include ArJdbc::Abstract::ConnectionManagement
@@ -753,6 +858,8 @@ module ActiveRecord::ConnectionAdapters
753
858
 
754
859
  require 'arjdbc/postgresql/oid_types'
755
860
  include ::ArJdbc::PostgreSQL::OIDTypes
861
+ include ::ArJdbc::PostgreSQL::DatabaseStatements
862
+ include ::ArJdbc::PostgreSQL::SchemaStatements
756
863
 
757
864
  include ::ArJdbc::PostgreSQL::ColumnHelpers
758
865
 
@@ -761,16 +868,46 @@ module ActiveRecord::ConnectionAdapters
761
868
  # AR expects OID to be available on the adapter
762
869
  OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
763
870
 
764
- def initialize(connection, logger = nil, connection_parameters = nil, config = {})
871
+ class << self
872
+ def jdbc_connection_class
873
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLJdbcConnection
874
+ end
875
+
876
+ def new_client(conn_params, adapter_instance)
877
+ jdbc_connection_class.new(conn_params, adapter_instance)
878
+ end
879
+
880
+ def dbconsole(config, options = {})
881
+ pg_config = config.configuration_hash
882
+
883
+ ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
884
+ ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
885
+ ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
886
+ ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
887
+ ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
888
+ ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
889
+ ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
890
+ ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
891
+ if pg_config[:variables]
892
+ ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
893
+ "-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
894
+ end.join(" ")
895
+ end
896
+ find_cmd_and_exec("psql", config.database)
897
+ end
898
+ end
899
+
900
+ def initialize(...)
901
+ super
902
+
903
+ conn_params = @config.compact
904
+
905
+ @connection_parameters = conn_params
906
+
765
907
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
766
908
  @local_tz = nil
767
909
  @max_identifier_length = nil
768
910
 
769
- super(connection, logger, config) # configure_connection happens in super
770
-
771
- @type_map = Type::HashLookupTypeMap.new
772
- initialize_type_map
773
-
774
911
  @use_insert_returning = @config.key?(:insert_returning) ?
775
912
  self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
776
913
  end
@@ -793,8 +930,16 @@ module ActiveRecord::ConnectionAdapters
793
930
  public :sql_for_insert
794
931
  alias :postgresql_version :database_version
795
932
 
796
- def jdbc_connection_class(spec)
797
- ::ArJdbc::PostgreSQL.jdbc_connection_class
933
+ def native_database_types # :nodoc:
934
+ self.class.native_database_types
935
+ end
936
+
937
+ def self.native_database_types # :nodoc:
938
+ @native_database_types ||= begin
939
+ types = NATIVE_DATABASE_TYPES.dup
940
+ types[:datetime] = types[datetime_type]
941
+ types
942
+ end
798
943
  end
799
944
 
800
945
  private
@@ -829,8 +974,10 @@ module ActiveRecord::ConnectionAdapters
829
974
 
830
975
  type_casted_binds = type_casted_binds(binds)
831
976
  log(sql, name, binds, type_casted_binds, async: async) do
832
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
833
- @connection.exec_params(sql, type_casted_binds)
977
+ with_raw_connection do |conn|
978
+ result = conn.exec_params(sql, type_casted_binds)
979
+ verified!
980
+ result
834
981
  end
835
982
  end
836
983
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module PostgreSQL
5
+ module DatabaseStatements
6
+ def explain(arel, binds = [], options = [])
7
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
8
+
9
+ result = internal_exec_query(sql, "EXPLAIN", binds)
10
+ ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(result)
11
+ end
12
+
13
+ def build_explain_clause(options = [])
14
+ return "EXPLAIN" if options.empty?
15
+
16
+ "EXPLAIN (#{options.join(", ").upcase})"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -67,28 +67,6 @@ module ArJdbc
67
67
 
68
68
  # @private
69
69
  module OIDTypes
70
-
71
- # @override
72
- def enable_extension(name)
73
- result = super(name)
74
- @extensions = nil
75
- reload_type_map
76
- result
77
- end
78
-
79
- # @override
80
- def disable_extension(name)
81
- result = super(name)
82
- @extensions = nil
83
- reload_type_map
84
- result
85
- end
86
-
87
- # @override
88
- def extensions
89
- @extensions ||= super
90
- end
91
-
92
70
  def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
93
71
  # Note: type_map is storing a bunch of oid type prefixed with a namespace even
94
72
  # if they are not namespaced (e.g. ""."oidvector"). builtin types which are
@@ -118,8 +96,15 @@ module ArJdbc
118
96
  end
119
97
 
120
98
  def reload_type_map
121
- type_map.clear
99
+ @lock.synchronize do
100
+ if @type_map
101
+ type_map.clear
102
+ else
103
+ @type_map = Type::HashLookupTypeMap.new
104
+ end
105
+
122
106
  initialize_type_map
107
+ end
123
108
  end
124
109
 
125
110
  def initialize_type_map_inner(m)
@@ -274,10 +259,6 @@ module ArJdbc
274
259
  $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
275
260
  end
276
261
 
277
- def extract_limit(sql_type)
278
- $1.to_i if sql_type =~ /\((.*)\)/
279
- end
280
-
281
262
  # Support arrays/ranges for defining attributes that don't exist in the db
282
263
  ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
283
264
  ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)