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

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 (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