activerecord-jdbc-alt-adapter 52.4.0-java → 61.0.0-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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.nvimlog +0 -0
  4. data/.travis.yml +63 -39
  5. data/Gemfile +11 -4
  6. data/README.md +55 -35
  7. data/Rakefile +1 -1
  8. data/Rakefile.jdbc +8 -1
  9. data/activerecord-jdbc-adapter.gemspec +6 -9
  10. data/activerecord-jdbc-alt-adapter.gemspec +9 -12
  11. data/lib/arel/visitors/postgresql_jdbc.rb +1 -1
  12. data/lib/arel/visitors/sqlserver.rb +49 -23
  13. data/lib/arjdbc/abstract/connection_management.rb +7 -0
  14. data/lib/arjdbc/abstract/core.rb +17 -23
  15. data/lib/arjdbc/abstract/database_statements.rb +30 -2
  16. data/lib/arjdbc/abstract/statement_cache.rb +2 -5
  17. data/lib/arjdbc/abstract/transaction_support.rb +22 -7
  18. data/lib/arjdbc/db2/column.rb +0 -39
  19. data/lib/arjdbc/derby/adapter.rb +1 -20
  20. data/lib/arjdbc/firebird/adapter.rb +0 -21
  21. data/lib/arjdbc/h2/adapter.rb +0 -15
  22. data/lib/arjdbc/hsqldb/adapter.rb +0 -14
  23. data/lib/arjdbc/informix/adapter.rb +0 -23
  24. data/lib/arjdbc/jdbc/adapter.rb +3 -1
  25. data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
  26. data/lib/arjdbc/jdbc/base_ext.rb +3 -1
  27. data/lib/arjdbc/jdbc/callbacks.rb +2 -0
  28. data/lib/arjdbc/jdbc/column.rb +2 -0
  29. data/lib/arjdbc/jdbc/connection.rb +2 -0
  30. data/lib/arjdbc/jdbc/connection_methods.rb +2 -0
  31. data/lib/arjdbc/jdbc/error.rb +2 -0
  32. data/lib/arjdbc/jdbc/extension.rb +2 -0
  33. data/lib/arjdbc/jdbc/java.rb +3 -1
  34. data/lib/arjdbc/jdbc/railtie.rb +3 -1
  35. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -1
  36. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -1
  37. data/lib/arjdbc/jdbc/type_cast.rb +2 -0
  38. data/lib/arjdbc/jdbc/type_converter.rb +2 -0
  39. data/lib/arjdbc/mssql.rb +3 -1
  40. data/lib/arjdbc/mssql/adapter.rb +114 -36
  41. data/lib/arjdbc/mssql/column.rb +19 -1
  42. data/lib/arjdbc/mssql/connection_methods.rb +10 -2
  43. data/lib/arjdbc/mssql/database_limits.rb +9 -0
  44. data/lib/arjdbc/mssql/database_statements.rb +44 -6
  45. data/lib/arjdbc/mssql/errors.rb +2 -0
  46. data/lib/arjdbc/mssql/explain_support.rb +3 -1
  47. data/lib/arjdbc/mssql/extensions/attribute_methods.rb +6 -2
  48. data/lib/arjdbc/mssql/extensions/calculations.rb +2 -0
  49. data/lib/arjdbc/mssql/quoting.rb +38 -0
  50. data/lib/arjdbc/mssql/schema_creation.rb +25 -3
  51. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  52. data/lib/arjdbc/mssql/schema_dumper.rb +2 -0
  53. data/lib/arjdbc/mssql/schema_statements.rb +92 -22
  54. data/lib/arjdbc/mssql/transaction.rb +2 -0
  55. data/lib/arjdbc/mssql/types.rb +2 -0
  56. data/lib/arjdbc/mssql/types/binary_types.rb +2 -0
  57. data/lib/arjdbc/mssql/types/date_and_time_types.rb +2 -0
  58. data/lib/arjdbc/mssql/types/deprecated_types.rb +2 -0
  59. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -0
  60. data/lib/arjdbc/mssql/types/string_types.rb +2 -0
  61. data/lib/arjdbc/mssql/utils.rb +2 -0
  62. data/lib/arjdbc/mysql/adapter.rb +59 -21
  63. data/lib/arjdbc/mysql/connection_methods.rb +6 -1
  64. data/lib/arjdbc/postgresql/adapter.rb +257 -219
  65. data/lib/arjdbc/postgresql/base/array_decoder.rb +2 -0
  66. data/lib/arjdbc/postgresql/base/array_encoder.rb +4 -2
  67. data/lib/arjdbc/postgresql/base/array_parser.rb +4 -2
  68. data/lib/arjdbc/postgresql/base/pgconn.rb +2 -0
  69. data/lib/arjdbc/postgresql/column.rb +6 -4
  70. data/lib/arjdbc/postgresql/connection_methods.rb +1 -0
  71. data/lib/arjdbc/postgresql/name.rb +2 -0
  72. data/lib/arjdbc/postgresql/oid_types.rb +7 -4
  73. data/lib/arjdbc/sqlite3/adapter.rb +266 -221
  74. data/lib/arjdbc/sqlite3/connection_methods.rb +26 -4
  75. data/lib/arjdbc/tasks/databases.rake +21 -13
  76. data/lib/arjdbc/tasks/mssql_database_tasks.rb +126 -25
  77. data/lib/arjdbc/util/quoted_cache.rb +3 -1
  78. data/lib/arjdbc/util/serialized_attributes.rb +3 -1
  79. data/lib/arjdbc/util/table_copier.rb +3 -1
  80. data/lib/arjdbc/version.rb +3 -1
  81. data/pom.xml +4 -4
  82. data/rakelib/01-tomcat.rake +2 -2
  83. data/rakelib/rails.rake +1 -1
  84. data/src/java/arjdbc/ArJdbcModule.java +5 -5
  85. data/src/java/arjdbc/jdbc/DriverWrapper.java +1 -9
  86. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +549 -691
  87. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +88 -0
  88. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +13 -23
  89. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +125 -53
  90. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +97 -103
  91. data/src/java/arjdbc/util/DateTimeUtils.java +12 -4
  92. metadata +10 -18
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/connection_adapters/abstract/transaction'
2
4
 
3
5
  # MSSQL doe not restore the initial transaction isolation when the transaction
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'arjdbc/mssql/types/numeric_types'
2
4
  require 'arjdbc/mssql/types/string_types'
3
5
  require 'arjdbc/mssql/types/binary_types'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # MSSQL binary types definitions
2
4
  module ActiveRecord
3
5
  module ConnectionAdapters
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # MSSQL date and time types definitions
2
4
  module ActiveRecord
3
5
  module ConnectionAdapters
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # MSSQL deprecated type definitions
2
4
  module ActiveRecord
3
5
  module ConnectionAdapters
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # MSSQL numeric types definitions
2
4
  module ActiveRecord
3
5
  module ConnectionAdapters
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # MSSQL string types definitions
2
4
  module ActiveRecord
3
5
  module ConnectionAdapters
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # NOTE: file contains code adapted from **sqlserver** adapter, license follows
2
4
  =begin
3
5
  Copyright (c) 2008-2015
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ArJdbc.load_java_part :MySQL
2
4
 
3
5
  require 'bigdecimal'
@@ -19,7 +21,7 @@ module ActiveRecord
19
21
  remove_const(:Mysql2Adapter) if const_defined?(:Mysql2Adapter)
20
22
 
21
23
  class Mysql2Adapter < AbstractMysqlAdapter
22
- ADAPTER_NAME = 'Mysql2'.freeze
24
+ ADAPTER_NAME = 'Mysql2'
23
25
 
24
26
  include Jdbc::ConnectionPoolCallbacks
25
27
 
@@ -31,22 +33,31 @@ module ActiveRecord
31
33
 
32
34
  include ArJdbc::MySQL
33
35
 
34
- def initialize(connection, logger, connection_parameters, config)
35
- # workaround to skip version check on JNDI to be lazy, dummy version is high enough for Rails 5.0 - 6.0
36
- is_jndi = ::ActiveRecord::ConnectionAdapters::JdbcConnection.jndi_config?(config)
37
- @version = '8.1.5' if is_jndi
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)
38
39
 
39
- super
40
+ # configure_connection taken care of at ArJdbc::Abstract::Core
41
+ end
40
42
 
41
- # set to nil to have it lazy-load the real value when required
42
- @version = nil if is_jndi
43
+ def self.database_exists?(config)
44
+ conn = ActiveRecord::Base.mysql2_connection(config)
45
+ conn && conn.really_valid?
46
+ rescue ActiveRecord::NoDatabaseError
47
+ false
48
+ ensure
49
+ conn.disconnect! if conn
50
+ end
43
51
 
44
- @prepared_statements = false unless config.key?(:prepared_statements)
45
- # configure_connection taken care of at ArJdbc::Abstract::Core
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
46
57
  end
47
58
 
48
59
  def supports_json?
49
- !mariadb? && version >= '5.7.8'
60
+ !mariadb? && database_version >= '5.7.8'
50
61
  end
51
62
 
52
63
  def supports_comments?
@@ -61,6 +72,10 @@ module ActiveRecord
61
72
  true
62
73
  end
63
74
 
75
+ def supports_lazy_transactions?
76
+ true
77
+ end
78
+
64
79
  def supports_transaction_isolation?
65
80
  true
66
81
  end
@@ -71,6 +86,25 @@ module ActiveRecord
71
86
 
72
87
  # HELPER METHODS ===========================================
73
88
 
89
+ # from MySQL::DatabaseStatements
90
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
91
+ :desc, :describe, :set, :show, :use
92
+ ) # :nodoc:
93
+ private_constant :READ_QUERY
94
+
95
+ def write_query?(sql) # :nodoc:
96
+ !READ_QUERY.match?(sql)
97
+ end
98
+
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
104
+
105
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
106
+ end
107
+
74
108
  # Reloading the type map in abstract/statement_cache.rb blows up postgres
75
109
  def clear_cache!
76
110
  reload_type_map
@@ -136,7 +170,13 @@ module ActiveRecord
136
170
  private
137
171
 
138
172
  # e.g. "5.7.20-0ubuntu0.16.04.1"
139
- def full_version; @full_version ||= @connection.full_version end
173
+ def full_version
174
+ schema_cache.database_version.full_version_string
175
+ end
176
+
177
+ def get_full_version
178
+ @full_version ||= @connection.full_version
179
+ end
140
180
 
141
181
  def jdbc_connection_class(spec)
142
182
  ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
@@ -148,19 +188,17 @@ module ActiveRecord
148
188
 
149
189
  # defined in MySQL::DatabaseStatements which is not included
150
190
  def default_insert_value(column)
151
- Arel.sql("DEFAULT") unless column.auto_increment?
191
+ super unless column.auto_increment?
152
192
  end
153
193
 
154
194
  # FIXME: optimize insert_fixtures_set by using JDBC Statement.addBatch()/executeBatch()
155
- def combine_multi_statements(total_sql)
156
- total_sql
157
- end
158
-
159
- def with_multi_statements
160
- yield
161
- end
162
195
 
163
- def discard_remaining_results
196
+ def combine_multi_statements(total_sql)
197
+ if total_sql.length == 1
198
+ total_sql.first
199
+ else
200
+ total_sql
201
+ end
164
202
  end
165
203
  end
166
204
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  ArJdbc::ConnectionMethods.module_eval do
3
3
  def mysql_connection(config)
4
+ config = config.deep_dup
4
5
  # NOTE: this isn't "really" necessary but Rails (in tests) assumes being able to :
5
6
  # ActiveRecord::Base.mysql2_connection ActiveRecord::Base.configurations['arunit'].merge(database: ...)
6
7
  config = symbolize_keys_if_necessary(config)
@@ -67,6 +68,7 @@ ArJdbc::ConnectionMethods.module_eval do
67
68
  # with reconnect fail-over sets connection read-only (by default)
68
69
  # properties['failOverReadOnly'] ||= 'false'
69
70
  end
71
+ properties['noDatetimeStringSync'] = true unless properties.key?('noDatetimeStringSync')
70
72
  end
71
73
  if config[:sslkey] || sslcert = config[:sslcert] # || config[:use_ssl]
72
74
  properties['useSSL'] ||= true # supported by MariaDB as well
@@ -89,11 +91,12 @@ ArJdbc::ConnectionMethods.module_eval do
89
91
  properties['localSocket'] ||= socket if mariadb_driver
90
92
  end
91
93
 
94
+ # properties['useJDBCCompliantTimezoneShift'] ||= true
92
95
  # for the Connector/J 5.1 line this is true by default - but it requires some really nasty
93
96
  # quirks to get casted Time values extracted properly according for AR's default_timezone
94
97
  # - thus we're turning it off (should be off in newer driver versions >= 6 anyway)
95
98
  # + also MariaDB driver is compilant and we would need to branch out based on driver
96
- properties['useLegacyDatetimeCode'] = false
99
+ properties['useLegacyDatetimeCode'] = false # disables the effect of 'useTimezone'
97
100
 
98
101
  jdbc_connection(config)
99
102
  end
@@ -101,6 +104,8 @@ ArJdbc::ConnectionMethods.module_eval do
101
104
  alias_method :mysql2_connection, :mysql_connection
102
105
 
103
106
  def mariadb_connection(config)
107
+ config = config.deep_dup
108
+
104
109
  config[:adapter_spec] ||= ::ArJdbc::MySQL
105
110
  config[:adapter_class] = ActiveRecord::ConnectionAdapters::Mysql2Adapter unless config.key?(:adapter_class)
106
111
 
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
2
  ArJdbc.load_java_part :PostgreSQL
3
3
 
4
4
  require 'ipaddr'
@@ -42,46 +42,12 @@ module ArJdbc
42
42
  # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_column_class
43
43
  def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn end
44
44
 
45
- ADAPTER_NAME = 'PostgreSQL'.freeze
45
+ ADAPTER_NAME = 'PostgreSQL'
46
46
 
47
47
  def adapter_name
48
48
  ADAPTER_NAME
49
49
  end
50
50
 
51
- def postgresql_version
52
- @postgresql_version ||=
53
- begin
54
- version = @connection.database_product
55
- if match = version.match(/([\d\.]*\d).*?/)
56
- version = match[1].split('.').map(&:to_i)
57
- # PostgreSQL version representation does not have more than 4 digits
58
- # From version 10 onwards, PG has changed its versioning policy to
59
- # limit it to only 2 digits. i.e. in 10.x, 10 being the major
60
- # version and x representing the patch release
61
- # Refer to:
62
- # https://www.postgresql.org/support/versioning/
63
- # https://www.postgresql.org/docs/10/static/libpq-status.html -> PQserverVersion()
64
- # for more info
65
-
66
- if version.size >= 3
67
- (version[0] * 100 + version[1]) * 100 + version[2]
68
- elsif version.size == 2
69
- if version[0] >= 10
70
- version[0] * 100 * 100 + version[1]
71
- else
72
- (version[0] * 100 + version[1]) * 100
73
- end
74
- elsif version.size == 1
75
- version[0] * 100 * 100
76
- else
77
- 0
78
- end
79
- else
80
- 0
81
- end
82
- end
83
- end
84
-
85
51
  def redshift?
86
52
  # SELECT version() :
87
53
  # 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
@@ -92,13 +58,6 @@ module ArJdbc
92
58
  end
93
59
  private :redshift?
94
60
 
95
- def use_insert_returning?
96
- if @use_insert_returning.nil?
97
- @use_insert_returning = supports_insert_with_returning?
98
- end
99
- @use_insert_returning
100
- end
101
-
102
61
  def set_client_encoding(encoding)
103
62
  ActiveRecord::Base.logger.warn "client_encoding is set by the driver and should not be altered, ('#{encoding}' ignored)"
104
63
  ActiveRecord::Base.logger.debug "Set the 'allowEncodingChanges' driver property (e.g. using config[:properties]) if you need to override the client encoding when doing a copy."
@@ -128,6 +87,9 @@ module ArJdbc
128
87
  execute("SET time zone '#{tz}'", 'SCHEMA')
129
88
  end unless redshift?
130
89
 
90
+ # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
91
+ execute("SET intervalstyle = iso_8601", "SCHEMA")
92
+
131
93
  # SET statements from :variables config hash
132
94
  # http://www.postgresql.org/docs/8.3/static/sql-set.html
133
95
  (config[:variables] || {}).map do |k, v|
@@ -165,7 +127,7 @@ module ArJdbc
165
127
  int4range: { name: 'int4range' },
166
128
  int8range: { name: 'int8range' },
167
129
  integer: { name: 'integer' },
168
- interval: { name: 'interval' }, # This doesn't get added to AR's postgres adapter until 5.1 but it fixes broken tests in 5.0 ...
130
+ interval: { name: 'interval' },
169
131
  json: { name: 'json' },
170
132
  jsonb: { name: 'jsonb' },
171
133
  line: { name: 'line' },
@@ -198,173 +160,241 @@ module ArJdbc
198
160
  !native_database_types[type].nil?
199
161
  end
200
162
 
201
- # Enable standard-conforming strings if available.
202
163
  def set_standard_conforming_strings
203
- self.standard_conforming_strings=(true)
164
+ execute("SET standard_conforming_strings = on", "SCHEMA")
204
165
  end
205
166
 
206
- # Enable standard-conforming strings if available.
207
- def standard_conforming_strings=(enable)
208
- client_min_messages = self.client_min_messages
209
- begin
210
- self.client_min_messages = 'panic'
211
- value = enable ? "on" : "off"
212
- execute("SET standard_conforming_strings = #{value}", 'SCHEMA')
213
- @standard_conforming_strings = ( value == "on" )
214
- rescue
215
- @standard_conforming_strings = :unsupported
216
- ensure
217
- self.client_min_messages = client_min_messages
218
- end
167
+ def supports_bulk_alter?
168
+ true
219
169
  end
220
170
 
221
- def standard_conforming_strings?
222
- if @standard_conforming_strings.nil?
223
- client_min_messages = self.client_min_messages
224
- begin
225
- self.client_min_messages = 'panic'
226
- value = select_one('SHOW standard_conforming_strings', 'SCHEMA')['standard_conforming_strings']
227
- @standard_conforming_strings = ( value == "on" )
228
- rescue
229
- @standard_conforming_strings = :unsupported
230
- ensure
231
- self.client_min_messages = client_min_messages
232
- end
233
- end
234
- @standard_conforming_strings == true # return false if :unsupported
171
+ def supports_index_sort_order?
172
+ true
235
173
  end
236
174
 
237
- def supports_ddl_transactions?; true end
175
+ def supports_partitioned_indexes?
176
+ database_version >= 110_000
177
+ end
238
178
 
239
- def supports_advisory_locks?; true end
179
+ def supports_partial_index?
180
+ true
181
+ end
240
182
 
241
- def supports_explain?; true end
183
+ def supports_expression_index?
184
+ true
185
+ end
242
186
 
243
- def supports_expression_index?; true end
187
+ def supports_transaction_isolation?
188
+ true
189
+ end
244
190
 
245
- def supports_foreign_keys?; true end
191
+ def supports_foreign_keys?
192
+ true
193
+ end
246
194
 
247
- def supports_validate_constraints?; true end
195
+ def supports_check_constraints?
196
+ true
197
+ end
248
198
 
249
- def supports_index_sort_order?; true end
199
+ def supports_validate_constraints?
200
+ true
201
+ end
250
202
 
251
- def supports_partial_index?; true end
203
+ def supports_views?
204
+ true
205
+ end
252
206
 
253
- def supports_savepoints?; true end
207
+ def supports_datetime_with_precision?
208
+ true
209
+ end
210
+
211
+ def supports_json?
212
+ database_version >= 90200
213
+ end
214
+
215
+ def supports_comments?
216
+ true
217
+ end
218
+
219
+ def supports_savepoints?
220
+ true
221
+ end
254
222
 
255
- def supports_transaction_isolation?; true end
223
+ def supports_insert_returning?
224
+ true
225
+ end
256
226
 
257
- def supports_views?; true end
227
+ def supports_insert_on_conflict?
228
+ database_version >= 90500
229
+ end
230
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
231
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
232
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
258
233
 
259
- def supports_bulk_alter?; true end
234
+ def index_algorithms
235
+ { concurrently: 'CONCURRENTLY' }
236
+ end
260
237
 
261
- def supports_datetime_with_precision?; true end
238
+ def supports_ddl_transactions?
239
+ true
240
+ end
262
241
 
263
- def supports_comments?; true end
242
+ def supports_advisory_locks?
243
+ true
244
+ end
264
245
 
265
- # Does PostgreSQL support standard conforming strings?
266
- def supports_standard_conforming_strings?
267
- standard_conforming_strings?
268
- @standard_conforming_strings != :unsupported
246
+ def supports_explain?
247
+ true
269
248
  end
270
249
 
271
- def supports_foreign_tables? # we don't really support this yet, its a reminder :)
272
- postgresql_version >= 90300
250
+ def supports_extensions?
251
+ database_version >= 90200
273
252
  end
274
253
 
275
- def supports_hex_escaped_bytea?
276
- postgresql_version >= 90000
254
+ def supports_ranges?
255
+ database_version >= 90200
277
256
  end
278
257
 
279
258
  def supports_materialized_views?
280
- postgresql_version >= 90300
259
+ database_version >= 90300
281
260
  end
282
261
 
283
- def supports_json?
284
- postgresql_version >= 90200
262
+ def supports_foreign_tables?
263
+ database_version >= 90300
285
264
  end
286
265
 
287
- def supports_insert_with_returning?
288
- postgresql_version >= 80200
266
+ def supports_pgcrypto_uuid?
267
+ database_version >= 90400
289
268
  end
290
269
 
291
- def supports_pgcrypto_uuid?
292
- postgresql_version >= 90400
270
+ def supports_optimizer_hints?
271
+ unless defined?(@has_pg_hint_plan)
272
+ @has_pg_hint_plan = extension_available?("pg_hint_plan")
273
+ end
274
+ @has_pg_hint_plan
293
275
  end
294
276
 
295
- # Range data-types weren't introduced until PostgreSQL 9.2.
296
- def supports_ranges?
297
- postgresql_version >= 90200
277
+ def supports_common_table_expressions?
278
+ true
298
279
  end
299
280
 
300
- def supports_extensions?
301
- postgresql_version >= 90200
281
+ def supports_lazy_transactions?
282
+ true
302
283
  end
303
284
 
304
- # From AR 5.1 postgres_adapter.rb
305
- def default_index_type?(index) # :nodoc:
306
- index.using == :btree || super
285
+ def get_advisory_lock(lock_id) # :nodoc:
286
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
287
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
288
+ end
289
+ query_value("SELECT pg_try_advisory_lock(#{lock_id})")
290
+ end
291
+
292
+ def release_advisory_lock(lock_id) # :nodoc:
293
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
294
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
295
+ end
296
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
307
297
  end
308
298
 
309
299
  def enable_extension(name)
310
- execute("CREATE EXTENSION IF NOT EXISTS \"#{name}\"")
300
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
301
+ reload_type_map
302
+ }
311
303
  end
312
304
 
313
305
  def disable_extension(name)
314
- execute("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE")
306
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
307
+ reload_type_map
308
+ }
309
+ end
310
+
311
+ def extension_available?(name)
312
+ query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
315
313
  end
316
314
 
317
315
  def extension_enabled?(name)
318
- if supports_extensions?
319
- rows = select_rows("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)", 'SCHEMA')
320
- available = rows.first.first # true/false or 't'/'f'
321
- available == true || available == 't'
322
- end
316
+ query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
323
317
  end
324
318
 
325
319
  def extensions
326
- if supports_extensions?
327
- rows = select_rows "SELECT extname from pg_extension", "SCHEMA"
328
- rows.map { |row| row.first }
329
- else
330
- []
331
- end
320
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
332
321
  end
333
322
 
334
- def index_algorithms
335
- { :concurrently => 'CONCURRENTLY' }
323
+ # Returns the configured supported identifier length supported by PostgreSQL
324
+ def max_identifier_length
325
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
336
326
  end
337
327
 
338
- # Set the authorized user for this session.
328
+ # Set the authorized user for this session
339
329
  def session_auth=(user)
340
330
  clear_cache!
341
- execute "SET SESSION AUTHORIZATION #{user}"
331
+ execute("SET SESSION AUTHORIZATION #{user}")
342
332
  end
343
333
 
344
- # Came from postgres_adapter
345
- def get_advisory_lock(lock_id) # :nodoc:
346
- unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
347
- raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
334
+ def use_insert_returning?
335
+ @use_insert_returning
336
+ end
337
+
338
+ def get_database_version # :nodoc:
339
+ begin
340
+ version = @connection.database_product
341
+ if match = version.match(/([\d\.]*\d).*?/)
342
+ version = match[1].split('.').map(&:to_i)
343
+ # PostgreSQL version representation does not have more than 4 digits
344
+ # From version 10 onwards, PG has changed its versioning policy to
345
+ # limit it to only 2 digits. i.e. in 10.x, 10 being the major
346
+ # version and x representing the patch release
347
+ # Refer to:
348
+ # https://www.postgresql.org/support/versioning/
349
+ # https://www.postgresql.org/docs/10/static/libpq-status.html -> PQserverVersion()
350
+ # for more info
351
+
352
+ if version.size >= 3
353
+ (version[0] * 100 + version[1]) * 100 + version[2]
354
+ elsif version.size == 2
355
+ if version[0] >= 10
356
+ version[0] * 100 * 100 + version[1]
357
+ else
358
+ (version[0] * 100 + version[1]) * 100
359
+ end
360
+ elsif version.size == 1
361
+ version[0] * 100 * 100
362
+ else
363
+ 0
364
+ end
365
+ else
366
+ 0
367
+ end
348
368
  end
349
- select_value("SELECT pg_try_advisory_lock(#{lock_id});")
350
369
  end
351
370
 
352
- # Came from postgres_adapter
353
- def release_advisory_lock(lock_id) # :nodoc:
354
- unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
355
- raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
371
+ def default_index_type?(index) # :nodoc:
372
+ index.using == :btree || super
373
+ end
374
+
375
+ def build_insert_sql(insert) # :nodoc:
376
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
377
+
378
+ if insert.skip_duplicates?
379
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
380
+ elsif insert.update_duplicates?
381
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
382
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
383
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
356
384
  end
357
- select_value("SELECT pg_advisory_unlock(#{lock_id})")
385
+
386
+ sql << " RETURNING #{insert.returning}" if insert.returning
387
+ sql
358
388
  end
359
389
 
360
- # Returns the max identifier length supported by PostgreSQL
361
- def max_identifier_length
362
- @max_identifier_length ||= select_one('SHOW max_identifier_length', 'SCHEMA'.freeze)['max_identifier_length'].to_i
390
+ def check_version # :nodoc:
391
+ if database_version < 90300
392
+ raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
393
+ end
363
394
  end
364
- alias table_alias_length max_identifier_length
365
- alias index_name_length max_identifier_length
366
395
 
367
- def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
396
+
397
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
368
398
  val = super
369
399
  if !use_insert_returning? && pk
370
400
  unless sequence_name
@@ -378,25 +408,23 @@ module ArJdbc
378
408
  end
379
409
  end
380
410
 
411
+ def execute_batch(statements, name = nil)
412
+ execute(combine_multi_statements(statements), name)
413
+ end
414
+
381
415
  def explain(arel, binds = [])
382
416
  sql, binds = to_sql_and_binds(arel, binds)
383
417
  ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query("EXPLAIN #{sql}", 'EXPLAIN', binds))
384
418
  end
385
419
 
386
- # @note Only for "better" AR 4.0 compatibility.
387
- # @private
388
- def query(sql, name = nil)
389
- log(sql, name) do
390
- result = []
391
- @connection.execute_query_raw(sql, []) do |*values|
392
- # We need to use #deep_dup here because it appears that
393
- # the java method is reusing an object in some cases
394
- # which makes all of the entries in the "result"
395
- # array end up with the same values as the last row
396
- result << values.deep_dup
397
- end
398
- result
399
- end
420
+ # from ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements
421
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
422
+ :close, :declare, :fetch, :move, :set, :show
423
+ ) # :nodoc:
424
+ private_constant :READ_QUERY
425
+
426
+ def write_query?(sql) # :nodoc:
427
+ !READ_QUERY.match?(sql)
400
428
  end
401
429
 
402
430
  # We need to make sure to deallocate all the prepared statements
@@ -427,6 +455,10 @@ module ArJdbc
427
455
  exec_query("SELECT currval('#{sequence_name}')", 'SQL')
428
456
  end
429
457
 
458
+ def build_truncate_statements(table_names)
459
+ ["TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}"]
460
+ end
461
+
430
462
  def all_schemas
431
463
  select('SELECT nspname FROM pg_namespace').map { |row| row["nspname"] }
432
464
  end
@@ -463,13 +495,7 @@ module ArJdbc
463
495
 
464
496
  def escape_bytea(string)
465
497
  return unless string
466
- if supports_hex_escaped_bytea?
467
- "\\x#{string.unpack("H*")[0]}"
468
- else
469
- result = ''
470
- string.each_byte { |c| result << sprintf('\\\\%03o', c) }
471
- result
472
- end
498
+ "\\x#{string.unpack("H*")[0]}"
473
499
  end
474
500
 
475
501
  # @override
@@ -488,7 +514,7 @@ module ArJdbc
488
514
  alias_method :quote_schema_name, :quote_column_name
489
515
 
490
516
  # Need to clear the cache even though the AR adapter doesn't for some reason
491
- def remove_column(table_name, column_name, type = nil, options = {})
517
+ def remove_column(table_name, column_name, type = nil, **options)
492
518
  super
493
519
  clear_cache!
494
520
  end
@@ -502,8 +528,31 @@ module ArJdbc
502
528
  nil
503
529
  end
504
530
 
531
+
532
+ # @private
533
+ def column_name_for_operation(operation, node)
534
+ case operation
535
+ when 'maximum' then 'max'
536
+ when 'minimum' then 'min'
537
+ when 'average' then 'avg'
538
+ else operation.downcase
539
+ end
540
+ end
541
+
542
+ private
543
+
505
544
  # Returns the list of a table's column names, data types, and default values.
506
545
  #
546
+ # The underlying query is roughly:
547
+ # SELECT column.name, column.type, default.value, column.comment
548
+ # FROM column LEFT JOIN default
549
+ # ON column.table_id = default.table_id
550
+ # AND column.num = default.column_num
551
+ # WHERE column.table_id = get_table_id('table_name')
552
+ # AND column.num > 0
553
+ # AND NOT column.is_dropped
554
+ # ORDER BY column.num
555
+ #
507
556
  # If the table name is not prefixed with a schema, the database will
508
557
  # take the first match from the schema search path.
509
558
  #
@@ -511,82 +560,73 @@ module ArJdbc
511
560
  # - format_type includes the column size constraint, e.g. varchar(50)
512
561
  # - ::regclass is a function that gives the id for a table name
513
562
  def column_definitions(table_name)
514
- select_rows(<<-end_sql, 'SCHEMA')
563
+ select_rows(<<~SQL, 'SCHEMA')
515
564
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
516
565
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
517
- (SELECT c.collname FROM pg_collation c, pg_type t
518
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid
519
- AND a.attcollation <> t.typcollation),
520
- col_description(a.attrelid, a.attnum) AS comment
566
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
521
567
  FROM pg_attribute a
522
568
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
569
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
570
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
523
571
  WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
524
572
  AND a.attnum > 0 AND NOT a.attisdropped
525
573
  ORDER BY a.attnum
526
- end_sql
574
+ SQL
527
575
  end
528
- private :column_definitions
529
576
 
530
- def truncate(table_name, name = nil)
531
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
577
+ def extract_table_ref_from_insert_sql(sql)
578
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
579
+ $1.strip if $1
532
580
  end
533
581
 
534
- # @private
535
- def column_name_for_operation(operation, node)
536
- case operation
537
- when 'maximum' then 'max'
538
- when 'minimum' then 'min'
539
- when 'average' then 'avg'
540
- else operation.downcase
541
- end
582
+ def arel_visitor
583
+ Arel::Visitors::PostgreSQL.new(self)
542
584
  end
543
585
 
544
- private
545
-
546
586
  # Pulled from ActiveRecord's Postgres adapter and modified to use execute
547
587
  def can_perform_case_insensitive_comparison_for?(column)
548
588
  @case_insensitive_cache ||= {}
549
589
  @case_insensitive_cache[column.sql_type] ||= begin
550
- sql = <<-end_sql
551
- SELECT exists(
552
- SELECT * FROM pg_proc
553
- WHERE proname = 'lower'
554
- AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
555
- ) OR exists(
556
- SELECT * FROM pg_proc
557
- INNER JOIN pg_cast
558
- ON ARRAY[casttarget]::oidvector = proargtypes
559
- WHERE proname = 'lower'
560
- AND castsource = #{quote column.sql_type}::regtype
561
- )
562
- end_sql
590
+ sql = <<~SQL
591
+ SELECT exists(
592
+ SELECT * FROM pg_proc
593
+ WHERE proname = 'lower'
594
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
595
+ ) OR exists(
596
+ SELECT * FROM pg_proc
597
+ INNER JOIN pg_cast
598
+ ON ARRAY[casttarget]::oidvector = proargtypes
599
+ WHERE proname = 'lower'
600
+ AND castsource = #{quote column.sql_type}::regtype
601
+ )
602
+ SQL
563
603
  select_value(sql, 'SCHEMA')
564
604
  end
565
605
  end
566
606
 
567
- def translate_exception(exception, message)
607
+ def translate_exception(exception, message:, sql:, binds:)
568
608
  return super unless exception.is_a?(ActiveRecord::JDBCError)
569
609
 
570
610
  # TODO: Can we base these on an error code of some kind?
571
611
  case exception.message
572
612
  when /duplicate key value violates unique constraint/
573
- ::ActiveRecord::RecordNotUnique.new(message)
613
+ ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
574
614
  when /violates not-null constraint/
575
- ::ActiveRecord::NotNullViolation.new(message)
615
+ ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
576
616
  when /violates foreign key constraint/
577
- ::ActiveRecord::InvalidForeignKey.new(message)
617
+ ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
578
618
  when /value too long/
579
- ::ActiveRecord::ValueTooLong.new(message)
619
+ ::ActiveRecord::ValueTooLong.new(message, sql: sql, binds: binds)
580
620
  when /out of range/
581
- ::ActiveRecord::RangeError.new(message)
621
+ ::ActiveRecord::RangeError.new(message, sql: sql, binds: binds)
582
622
  when /could not serialize/
583
- ::ActiveRecord::SerializationFailure.new(message)
623
+ ::ActiveRecord::SerializationFailure.new(message, sql: sql, binds: binds)
584
624
  when /deadlock detected/
585
- ::ActiveRecord::Deadlocked.new(message)
625
+ ::ActiveRecord::Deadlocked.new(message, sql: sql, binds: binds)
586
626
  when /lock timeout/
587
- ::ActiveRecord::LockWaitTimeout.new(message)
627
+ ::ActiveRecord::LockWaitTimeout.new(message, sql: sql, binds: binds)
588
628
  when /canceling statement/ # This needs to come after lock timeout because the lock timeout message also contains "canceling statement"
589
- ::ActiveRecord::QueryCanceled.new(message)
629
+ ::ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
590
630
  else
591
631
  super
592
632
  end
@@ -610,11 +650,6 @@ module ArJdbc
610
650
  end
611
651
  end
612
652
 
613
- def extract_table_ref_from_insert_sql(sql)
614
- sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
615
- $1.strip if $1
616
- end
617
-
618
653
  def local_tz
619
654
  @local_tz ||= execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
620
655
  end
@@ -635,6 +670,7 @@ module ActiveRecord::ConnectionAdapters
635
670
  remove_const(:PostgreSQLAdapter) if const_defined?(:PostgreSQLAdapter)
636
671
 
637
672
  class PostgreSQLAdapter < AbstractAdapter
673
+ class_attribute :create_unlogged_tables, default: false
638
674
 
639
675
  # Try to use as much of the built in postgres logic as possible
640
676
  # maybe someday we can extend the actual adapter
@@ -672,11 +708,16 @@ module ActiveRecord::ConnectionAdapters
672
708
  initialize_type_map
673
709
 
674
710
  @use_insert_returning = @config.key?(:insert_returning) ?
675
- self.class.type_cast_config_to_boolean(@config[:insert_returning]) : nil
711
+ self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
676
712
  end
677
713
 
678
- def arel_visitor # :nodoc:
679
- Arel::Visitors::PostgreSQL.new(self)
714
+ def self.database_exists?(config)
715
+ conn = ActiveRecord::Base.postgresql_connection(config)
716
+ conn && conn.really_valid?
717
+ rescue ActiveRecord::NoDatabaseError
718
+ false
719
+ ensure
720
+ conn.disconnect! if conn
680
721
  end
681
722
 
682
723
  require 'active_record/connection_adapters/postgresql/schema_definitions'
@@ -685,11 +726,8 @@ module ActiveRecord::ConnectionAdapters
685
726
  TableDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
686
727
  Table = ActiveRecord::ConnectionAdapters::PostgreSQL::Table
687
728
 
688
- def create_table_definition(*args) # :nodoc:
689
- TableDefinition.new(*args)
690
- end
691
-
692
729
  public :sql_for_insert
730
+ alias :postgresql_version :database_version
693
731
 
694
732
  def jdbc_connection_class(spec)
695
733
  ::ArJdbc::PostgreSQL.jdbc_connection_class