activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +141 -24
  3. data/.github/workflows/ruby.yml +12 -12
  4. data/.gitignore +7 -3
  5. data/.solargraph.yml +15 -0
  6. data/Gemfile +17 -4
  7. data/README.md +7 -3
  8. data/RUNNING_TESTS.md +36 -0
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  11. data/lib/arjdbc/abstract/connection_management.rb +26 -10
  12. data/lib/arjdbc/abstract/core.rb +5 -12
  13. data/lib/arjdbc/abstract/database_statements.rb +35 -25
  14. data/lib/arjdbc/abstract/statement_cache.rb +2 -7
  15. data/lib/arjdbc/abstract/transaction_support.rb +37 -22
  16. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  17. data/lib/arjdbc/jdbc/column.rb +0 -34
  18. data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
  19. data/lib/arjdbc/mssql/adapter.rb +101 -79
  20. data/lib/arjdbc/mssql/column.rb +1 -0
  21. data/lib/arjdbc/mssql/connection_methods.rb +7 -55
  22. data/lib/arjdbc/mssql/database_statements.rb +182 -71
  23. data/lib/arjdbc/mssql/explain_support.rb +8 -5
  24. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  25. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  26. data/lib/arjdbc/mssql/schema_statements.rb +25 -14
  27. data/lib/arjdbc/mssql/server_version.rb +56 -0
  28. data/lib/arjdbc/mssql/utils.rb +23 -9
  29. data/lib/arjdbc/mysql/adapter.rb +104 -27
  30. data/lib/arjdbc/postgresql/adapter.rb +71 -44
  31. data/lib/arjdbc/postgresql/oid_types.rb +8 -27
  32. data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
  33. data/lib/arjdbc/sqlite3/adapter.rb +205 -147
  34. data/lib/arjdbc/sqlite3/column.rb +103 -0
  35. data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
  36. data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
  37. data/lib/arjdbc/version.rb +1 -1
  38. data/rakelib/02-test.rake +1 -1
  39. data/rakelib/rails.rake +2 -0
  40. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3 -1
  41. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +11 -0
  42. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
  43. metadata +10 -12
  44. data/lib/arel/visitors/sql_server/ng42.rb +0 -294
  45. data/lib/arel/visitors/sql_server.rb +0 -124
  46. data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
  47. data/lib/arjdbc/mssql/lock_methods.rb +0 -77
  48. data/lib/arjdbc/mssql/old_adapter.rb +0 -804
  49. data/lib/arjdbc/mssql/old_column.rb +0 -200
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  class Mysql2Adapter < AbstractMysqlAdapter
24
24
  ADAPTER_NAME = 'Mysql2'
25
25
 
26
- include Jdbc::ConnectionPoolCallbacks
26
+ # include Jdbc::ConnectionPoolCallbacks
27
27
 
28
28
  include ArJdbc::Abstract::ConnectionManagement
29
29
  include ArJdbc::Abstract::DatabaseStatements
@@ -33,11 +33,47 @@ module ActiveRecord
33
33
 
34
34
  include ArJdbc::MySQL
35
35
 
36
- def initialize(connection, logger, connection_options, config)
37
- superclass_config = config.reverse_merge(prepared_statements: false)
38
- super(connection, logger, connection_options, superclass_config)
36
+ class << self
37
+ def jdbc_connection_class
38
+ ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
39
+ end
40
+
41
+ def new_client(conn_params, adapter_instance)
42
+ jdbc_connection_class.new(conn_params, adapter_instance)
43
+ end
44
+
45
+ private
46
+ def initialize_type_map(m)
47
+ super
48
+
49
+ m.register_type(%r(char)i) do |sql_type|
50
+ limit = extract_limit(sql_type)
51
+ Type.lookup(:string, adapter: :mysql2, limit: limit)
52
+ end
53
+
54
+ m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
55
+ m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
56
+ end
57
+ end
58
+
59
+ # NOTE: redefines constant defined in abstract class however this time
60
+ # will use methods defined in the mysql abstract class and map properly
61
+ # mysql types.
62
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
63
+
64
+ def initialize(...)
65
+ super
39
66
 
40
- # configure_connection taken care of at ArJdbc::Abstract::Core
67
+ @config[:flags] ||= 0
68
+
69
+ # JDBC mysql appears to use found rows by default: https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-connection.html
70
+ # if @config[:flags].kind_of? Array
71
+ # @config[:flags].push "FOUND_ROWS"
72
+ # else
73
+ # @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
74
+ # end
75
+
76
+ @connection_parameters ||= @config
41
77
  end
42
78
 
43
79
  def self.database_exists?(config)
@@ -49,13 +85,6 @@ module ActiveRecord
49
85
  conn.disconnect! if conn
50
86
  end
51
87
 
52
- def check_version
53
- # for JNDI, don't check version as the whole connection should be lazy
54
- return if ::ActiveRecord::ConnectionAdapters::JdbcConnection.jndi_config?(config)
55
-
56
- super
57
- end
58
-
59
88
  def supports_json?
60
89
  !mariadb? && database_version >= '5.7.8'
61
90
  end
@@ -96,20 +125,25 @@ module ActiveRecord
96
125
  !READ_QUERY.match?(sql)
97
126
  end
98
127
 
99
- def explain(arel, binds = [])
100
- sql = "EXPLAIN #{to_sql(arel, binds)}"
101
- start = Concurrent.monotonic_time
102
- result = exec_query(sql, "EXPLAIN", binds)
103
- elapsed = Concurrent.monotonic_time - start
128
+ def explain(arel, binds = [], options = [])
129
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
130
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
131
+ result = internal_exec_query(sql, "EXPLAIN", binds)
132
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
104
133
 
105
134
  MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
106
135
  end
107
136
 
108
- # Reloading the type map in abstract/statement_cache.rb blows up postgres
109
- def clear_cache!
110
- # FIXME: This seems to have disappeared in Rails 7?
111
- # reload_type_map
112
- super
137
+ def build_explain_clause(options = [])
138
+ return "EXPLAIN" if options.empty?
139
+
140
+ explain_clause = "EXPLAIN #{options.join(" ").upcase}"
141
+
142
+ if analyze_without_explain? && explain_clause.include?("ANALYZE")
143
+ explain_clause.sub("EXPLAIN ", "")
144
+ else
145
+ explain_clause
146
+ end
113
147
  end
114
148
 
115
149
  def each_hash(result) # :nodoc:
@@ -164,11 +198,47 @@ module ActiveRecord
164
198
  # CONNECTION MANAGEMENT ====================================
165
199
  #++
166
200
 
201
+ def active?
202
+ !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
203
+ end
204
+
167
205
  alias :reset! :reconnect!
168
206
 
207
+ # Disconnects from the database if already connected.
208
+ # Otherwise, this method does nothing.
209
+ def disconnect!
210
+ @lock.synchronize do
211
+ super
212
+ @raw_connection&.close
213
+ @raw_connection = nil
214
+ end
215
+ end
216
+
217
+ def discard! # :nodoc:
218
+ @lock.synchronize do
219
+ super
220
+ @raw_connection&.automatic_close = false
221
+ @raw_connection = nil
222
+ end
223
+ end
224
+
169
225
  #
170
226
 
171
227
  private
228
+ # https://mariadb.com/kb/en/analyze-statement/
229
+ def analyze_without_explain?
230
+ mariadb? && database_version >= "10.1.0"
231
+ end
232
+
233
+ def text_type?(type)
234
+ TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
235
+ end
236
+
237
+ def configure_connection
238
+ # @raw_connection.query_options[:as] = :array
239
+ # @raw_connection.query_options[:database_timezone] = default_timezone
240
+ super
241
+ end
172
242
 
173
243
  # e.g. "5.7.20-0ubuntu0.16.04.1"
174
244
  def full_version
@@ -176,17 +246,24 @@ module ActiveRecord
176
246
  end
177
247
 
178
248
  def get_full_version
179
- @full_version ||= @connection.full_version
180
- end
181
-
182
- def jdbc_connection_class(spec)
183
- ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
249
+ @full_version ||= any_raw_connection.full_version
184
250
  end
185
251
 
186
252
  def jdbc_column_class
187
253
  ::ActiveRecord::ConnectionAdapters::MySQL::Column
188
254
  end
189
255
 
256
+ def translate_exception(exception, message:, sql:, binds:)
257
+ case message
258
+ when /Table .* doesn't exist/i
259
+ StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
260
+ when /BLOB, TEXT, GEOMETRY or JSON column .* can't have a default value/i
261
+ StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
262
+ else
263
+ super
264
+ end
265
+ end
266
+
190
267
  # defined in MySQL::DatabaseStatements which is not included
191
268
  def default_insert_value(column)
192
269
  super unless column.auto_increment?
@@ -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,6 +22,8 @@ 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/schema_statements'
26
+
24
27
  require 'active_model'
25
28
 
26
29
  module ArJdbc
@@ -35,11 +38,6 @@ module ArJdbc
35
38
  # @private
36
39
  Type = ::ActiveRecord::Type
37
40
 
38
- # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
39
- def self.jdbc_connection_class
40
- ::ActiveRecord::ConnectionAdapters::PostgreSQLJdbcConnection
41
- end
42
-
43
41
  # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_column_class
44
42
  def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn end
45
43
 
@@ -52,8 +50,8 @@ module ArJdbc
52
50
  def redshift?
53
51
  # SELECT version() :
54
52
  # 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')
53
+ if (redshift = @config[:redshift]).nil?
54
+ redshift = !! (valid_raw_connection.database_product || '').index('Redshift')
57
55
  end
58
56
  redshift
59
57
  end
@@ -73,8 +71,8 @@ module ArJdbc
73
71
  # see http://jdbc.postgresql.org/documentation/91/connect.html
74
72
  # self.set_client_encoding(encoding)
75
73
  #end
76
- self.client_min_messages = config[:min_messages] || 'warning'
77
- self.schema_search_path = config[:schema_search_path] || config[:schema_order]
74
+ self.client_min_messages = @config[:min_messages] || 'warning'
75
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
78
76
 
79
77
  # Use standard-conforming strings if available so we don't have to do the E'...' dance.
80
78
  set_standard_conforming_strings
@@ -93,7 +91,7 @@ module ArJdbc
93
91
 
94
92
  # SET statements from :variables config hash
95
93
  # http://www.postgresql.org/docs/8.3/static/sql-set.html
96
- (config[:variables] || {}).map do |k, v|
94
+ (@config[:variables] || {}).map do |k, v|
97
95
  if v == ':default' || v == :default
98
96
  # Sets the value to the global or compile default
99
97
  execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
@@ -101,6 +99,8 @@ module ArJdbc
101
99
  execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
102
100
  end
103
101
  end
102
+
103
+ reload_type_map
104
104
  end
105
105
 
106
106
  # @private
@@ -127,7 +127,7 @@ module ArJdbc
127
127
  inet: { name: 'inet' },
128
128
  int4range: { name: 'int4range' },
129
129
  int8range: { name: 'int8range' },
130
- integer: { name: 'integer' },
130
+ integer: { name: 'integer', limit: 4 },
131
131
  interval: { name: 'interval' },
132
132
  json: { name: 'json' },
133
133
  jsonb: { name: 'jsonb' },
@@ -232,6 +232,10 @@ module ArJdbc
232
232
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
233
233
  alias supports_insert_conflict_target? supports_insert_on_conflict?
234
234
 
235
+ def supports_identity_columns? # :nodoc:
236
+ database_version >= 10_00_00 # >= 10.0
237
+ end
238
+
235
239
  def index_algorithms
236
240
  { concurrently: 'CONCURRENTLY' }
237
241
  end
@@ -297,14 +301,21 @@ module ArJdbc
297
301
  query_value("SELECT pg_advisory_unlock(#{lock_id})")
298
302
  end
299
303
 
300
- def enable_extension(name)
301
- exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
302
- reload_type_map
303
- }
304
+ def enable_extension(name, **)
305
+ schema, name = name.to_s.split(".").values_at(-2, -1)
306
+ sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
307
+ sql << " SCHEMA #{schema}" if schema
308
+
309
+ internal_exec_query(sql).tap { reload_type_map }
304
310
  end
305
311
 
306
- def disable_extension(name)
307
- exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
312
+ # Removes an extension from the database.
313
+ #
314
+ # [<tt>:force</tt>]
315
+ # Set to +:cascade+ to drop dependent objects as well.
316
+ # Defaults to false.
317
+ def disable_extension(name, force: false)
318
+ internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
308
319
  reload_type_map
309
320
  }
310
321
  end
@@ -318,7 +329,7 @@ module ArJdbc
318
329
  end
319
330
 
320
331
  def extensions
321
- exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
332
+ internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
322
333
  end
323
334
 
324
335
  # Returns a list of defined enum types, and their values.
@@ -370,7 +381,7 @@ module ArJdbc
370
381
 
371
382
  def get_database_version # :nodoc:
372
383
  begin
373
- version = @connection.database_product
384
+ version = valid_raw_connection.database_product
374
385
  if match = version.match(/([\d\.]*\d).*?/)
375
386
  version = match[1].split('.').map(&:to_i)
376
387
  # PostgreSQL version representation does not have more than 4 digits
@@ -426,8 +437,7 @@ module ArJdbc
426
437
  end
427
438
  end
428
439
 
429
-
430
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
440
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
431
441
  val = super
432
442
  if !use_insert_returning? && pk
433
443
  unless sequence_name
@@ -464,18 +474,23 @@ module ArJdbc
464
474
  # since apparently calling close on the statement object
465
475
  # doesn't always free the server resources and calling
466
476
  # '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
477
+ # def clear_cache!
478
+ # super
479
+ # # Make sure all query plans are *really* gone
480
+ # @connection.execute 'DEALLOCATE ALL' if active?
481
+ # end
472
482
 
473
483
  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
484
+ @lock.synchronize do
485
+ return connect! unless @raw_connection
486
+
487
+ # Have to deal with rollbacks differently than the AR adapter
488
+ @raw_connection.rollback
489
+
490
+ @raw_connection.execute("DISCARD ALL")
491
+
492
+ super
493
+ end
479
494
  end
480
495
 
481
496
  def default_sequence_name(table_name, pk = "id") #:nodoc:
@@ -660,6 +675,8 @@ module ArJdbc
660
675
  ::ActiveRecord::LockWaitTimeout.new(message, sql: sql, binds: binds)
661
676
  when /canceling statement/ # This needs to come after lock timeout because the lock timeout message also contains "canceling statement"
662
677
  ::ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
678
+ when /relation "animals" does not exist/i
679
+ ::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
663
680
  else
664
681
  super
665
682
  end
@@ -742,7 +759,7 @@ module ActiveRecord::ConnectionAdapters
742
759
  include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
743
760
  include ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting
744
761
 
745
- include Jdbc::ConnectionPoolCallbacks
762
+ # include Jdbc::ConnectionPoolCallbacks
746
763
 
747
764
  include ArJdbc::Abstract::Core
748
765
  include ArJdbc::Abstract::ConnectionManagement
@@ -753,6 +770,7 @@ module ActiveRecord::ConnectionAdapters
753
770
 
754
771
  require 'arjdbc/postgresql/oid_types'
755
772
  include ::ArJdbc::PostgreSQL::OIDTypes
773
+ include ::ArJdbc::PostgreSQL::SchemaStatements
756
774
 
757
775
  include ::ArJdbc::PostgreSQL::ColumnHelpers
758
776
 
@@ -761,16 +779,27 @@ module ActiveRecord::ConnectionAdapters
761
779
  # AR expects OID to be available on the adapter
762
780
  OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
763
781
 
764
- def initialize(connection, logger = nil, connection_parameters = nil, config = {})
782
+ class << self
783
+ def jdbc_connection_class
784
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLJdbcConnection
785
+ end
786
+
787
+ def new_client(conn_params, adapter_instance)
788
+ jdbc_connection_class.new(conn_params, adapter_instance)
789
+ end
790
+ end
791
+
792
+ def initialize(...)
793
+ super
794
+
795
+ conn_params = @config.compact
796
+
797
+ @connection_parameters = conn_params
798
+
765
799
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
766
800
  @local_tz = nil
767
801
  @max_identifier_length = nil
768
802
 
769
- super(connection, logger, config) # configure_connection happens in super
770
-
771
- @type_map = Type::HashLookupTypeMap.new
772
- initialize_type_map
773
-
774
803
  @use_insert_returning = @config.key?(:insert_returning) ?
775
804
  self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
776
805
  end
@@ -793,10 +822,6 @@ module ActiveRecord::ConnectionAdapters
793
822
  public :sql_for_insert
794
823
  alias :postgresql_version :database_version
795
824
 
796
- def jdbc_connection_class(spec)
797
- ::ArJdbc::PostgreSQL.jdbc_connection_class
798
- end
799
-
800
825
  private
801
826
 
802
827
  FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
@@ -829,8 +854,10 @@ module ActiveRecord::ConnectionAdapters
829
854
 
830
855
  type_casted_binds = type_casted_binds(binds)
831
856
  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)
857
+ with_raw_connection do |conn|
858
+ result = conn.exec_params(sql, type_casted_binds)
859
+ verified!
860
+ result
834
861
  end
835
862
  end
836
863
  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)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module PostgreSQL
5
+ module SchemaStatements
6
+ ForeignKeyDefinition = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
7
+ Utils = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
8
+
9
+ def foreign_keys(table_name)
10
+ scope = quoted_scope(table_name)
11
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
12
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
13
+ FROM pg_constraint c
14
+ JOIN pg_class t1 ON c.conrelid = t1.oid
15
+ JOIN pg_class t2 ON c.confrelid = t2.oid
16
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
17
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
18
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
19
+ WHERE c.contype = 'f'
20
+ AND t1.relname = #{scope[:name]}
21
+ AND t3.nspname = #{scope[:schema]}
22
+ ORDER BY c.conname
23
+ SQL
24
+
25
+ fk_info.map do |row|
26
+ to_table = Utils.unquote_identifier(row["to_table"])
27
+ # conkey = row["conkey"].scan(/\d+/).map(&:to_i)
28
+ # confkey = row["confkey"].scan(/\d+/).map(&:to_i)
29
+ conkey = row["conkey"]
30
+ confkey = row["confkey"]
31
+
32
+ if conkey.size > 1
33
+ column = column_names_from_column_numbers(row["conrelid"], conkey)
34
+ primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
35
+ else
36
+ column = Utils.unquote_identifier(row["column"])
37
+ primary_key = row["primary_key"]
38
+ end
39
+
40
+ options = {
41
+ column: column,
42
+ name: row["name"],
43
+ primary_key: primary_key
44
+ }
45
+
46
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
47
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
48
+ options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
49
+
50
+ options[:validate] = row["valid"]
51
+
52
+ ForeignKeyDefinition.new(table_name, to_table, options)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end