activerecord-jdbc-alt-adapter 52.2.3-java → 60.1.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/.travis.yml +80 -52
  4. data/Gemfile +10 -3
  5. data/README.md +55 -37
  6. data/Rakefile +31 -5
  7. data/Rakefile.jdbc +8 -1
  8. data/activerecord-jdbc-adapter.gemspec +6 -9
  9. data/activerecord-jdbc-alt-adapter.gemspec +6 -9
  10. data/lib/arel/visitors/sqlserver.rb +33 -23
  11. data/lib/arjdbc/abstract/connection_management.rb +7 -0
  12. data/lib/arjdbc/abstract/core.rb +17 -24
  13. data/lib/arjdbc/abstract/database_statements.rb +31 -20
  14. data/lib/arjdbc/abstract/statement_cache.rb +2 -5
  15. data/lib/arjdbc/abstract/transaction_support.rb +5 -3
  16. data/lib/arjdbc/db2/column.rb +0 -39
  17. data/lib/arjdbc/derby/adapter.rb +1 -20
  18. data/lib/arjdbc/firebird/adapter.rb +0 -21
  19. data/lib/arjdbc/h2/adapter.rb +0 -15
  20. data/lib/arjdbc/hsqldb/adapter.rb +0 -14
  21. data/lib/arjdbc/informix/adapter.rb +0 -23
  22. data/lib/arjdbc/jdbc.rb +0 -4
  23. data/lib/arjdbc/jdbc/adapter.rb +3 -1
  24. data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
  25. data/lib/arjdbc/jdbc/base_ext.rb +3 -1
  26. data/lib/arjdbc/jdbc/callbacks.rb +2 -0
  27. data/lib/arjdbc/jdbc/column.rb +3 -5
  28. data/lib/arjdbc/jdbc/connection.rb +2 -0
  29. data/lib/arjdbc/jdbc/connection_methods.rb +2 -0
  30. data/lib/arjdbc/jdbc/error.rb +2 -0
  31. data/lib/arjdbc/jdbc/extension.rb +2 -0
  32. data/lib/arjdbc/jdbc/java.rb +3 -1
  33. data/lib/arjdbc/jdbc/railtie.rb +3 -1
  34. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -1
  35. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -1
  36. data/lib/arjdbc/jdbc/type_cast.rb +2 -0
  37. data/lib/arjdbc/jdbc/type_converter.rb +2 -0
  38. data/lib/arjdbc/mssql.rb +3 -1
  39. data/lib/arjdbc/mssql/adapter.rb +112 -46
  40. data/lib/arjdbc/mssql/column.rb +5 -1
  41. data/lib/arjdbc/mssql/connection_methods.rb +13 -2
  42. data/lib/arjdbc/mssql/database_limits.rb +2 -0
  43. data/lib/arjdbc/mssql/database_statements.rb +44 -6
  44. data/lib/arjdbc/mssql/errors.rb +2 -0
  45. data/lib/arjdbc/mssql/explain_support.rb +3 -1
  46. data/lib/arjdbc/mssql/extensions/attribute_methods.rb +5 -1
  47. data/lib/arjdbc/mssql/extensions/calculations.rb +2 -0
  48. data/lib/arjdbc/mssql/quoting.rb +38 -0
  49. data/lib/arjdbc/mssql/schema_creation.rb +24 -2
  50. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  51. data/lib/arjdbc/mssql/schema_dumper.rb +2 -0
  52. data/lib/arjdbc/mssql/schema_statements.rb +63 -21
  53. data/lib/arjdbc/mssql/transaction.rb +2 -0
  54. data/lib/arjdbc/mssql/types.rb +2 -0
  55. data/lib/arjdbc/mssql/types/binary_types.rb +2 -0
  56. data/lib/arjdbc/mssql/types/date_and_time_types.rb +2 -0
  57. data/lib/arjdbc/mssql/types/deprecated_types.rb +2 -0
  58. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -0
  59. data/lib/arjdbc/mssql/types/string_types.rb +2 -0
  60. data/lib/arjdbc/mssql/utils.rb +2 -0
  61. data/lib/arjdbc/mysql/adapter.rb +47 -18
  62. data/lib/arjdbc/mysql/connection_methods.rb +13 -7
  63. data/lib/arjdbc/postgresql/adapter.rb +240 -214
  64. data/lib/arjdbc/postgresql/base/array_decoder.rb +2 -0
  65. data/lib/arjdbc/postgresql/base/array_encoder.rb +4 -2
  66. data/lib/arjdbc/postgresql/base/array_parser.rb +4 -2
  67. data/lib/arjdbc/postgresql/base/pgconn.rb +2 -0
  68. data/lib/arjdbc/postgresql/column.rb +11 -6
  69. data/lib/arjdbc/postgresql/connection_methods.rb +3 -1
  70. data/lib/arjdbc/postgresql/name.rb +2 -0
  71. data/lib/arjdbc/postgresql/oid_types.rb +3 -1
  72. data/lib/arjdbc/sqlite3/adapter.rb +188 -180
  73. data/lib/arjdbc/sqlite3/connection_methods.rb +16 -4
  74. data/lib/arjdbc/tasks/databases.rake +13 -10
  75. data/lib/arjdbc/tasks/mssql_database_tasks.rb +49 -5
  76. data/lib/arjdbc/util/quoted_cache.rb +3 -1
  77. data/lib/arjdbc/util/serialized_attributes.rb +3 -1
  78. data/lib/arjdbc/util/table_copier.rb +3 -1
  79. data/lib/arjdbc/version.rb +1 -1
  80. data/pom.xml +4 -4
  81. data/rakelib/01-tomcat.rake +2 -2
  82. data/rakelib/02-test.rake +0 -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 +468 -637
  87. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +319 -38
  88. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +13 -23
  89. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +44 -31
  90. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +94 -99
  91. data/src/java/arjdbc/util/DateTimeUtils.java +34 -12
  92. metadata +7 -17
@@ -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
 
@@ -32,21 +34,30 @@ module ActiveRecord
32
34
  include ArJdbc::MySQL
33
35
 
34
36
  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
38
-
39
37
  super
40
38
 
41
- # set to nil to have it lazy-load the real value when required
42
- @version = nil if is_jndi
43
-
44
39
  @prepared_statements = false unless config.key?(:prepared_statements)
45
40
  # configure_connection taken care of at ArJdbc::Abstract::Core
46
41
  end
47
42
 
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
51
+
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
+
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,16 @@ 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
+ :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :describe, :desc
92
+ ) # :nodoc:
93
+ private_constant :READ_QUERY
94
+
95
+ def write_query?(sql) # :nodoc:
96
+ !READ_QUERY.match?(sql)
97
+ end
98
+
74
99
  # Reloading the type map in abstract/statement_cache.rb blows up postgres
75
100
  def clear_cache!
76
101
  reload_type_map
@@ -136,7 +161,13 @@ module ActiveRecord
136
161
  private
137
162
 
138
163
  # e.g. "5.7.20-0ubuntu0.16.04.1"
139
- def full_version; @full_version ||= @connection.full_version end
164
+ def full_version
165
+ schema_cache.database_version.full_version_string
166
+ end
167
+
168
+ def get_full_version
169
+ @full_version ||= @connection.full_version
170
+ end
140
171
 
141
172
  def jdbc_connection_class(spec)
142
173
  ::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
@@ -152,15 +183,13 @@ module ActiveRecord
152
183
  end
153
184
 
154
185
  # 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
186
 
163
- def discard_remaining_results
187
+ def combine_multi_statements(total_sql)
188
+ if total_sql.length == 1
189
+ total_sql.first
190
+ else
191
+ total_sql
192
+ end
164
193
  end
165
194
  end
166
195
  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)
@@ -10,11 +11,9 @@ ArJdbc::ConnectionMethods.module_eval do
10
11
 
11
12
  return jndi_connection(config) if jndi_config?(config)
12
13
 
13
- driver = config[:driver] ||=
14
- defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
15
-
16
- mysql_driver = driver.start_with?('com.mysql.')
17
- mariadb_driver = ! mysql_driver && driver.start_with?('org.mariadb.')
14
+ driver = config[:driver]
15
+ mysql_driver = driver.nil? || driver.to_s.start_with?('com.mysql.')
16
+ mariadb_driver = ! mysql_driver && driver.to_s.start_with?('org.mariadb.')
18
17
 
19
18
  begin
20
19
  require 'jdbc/mysql'
@@ -22,6 +21,11 @@ ArJdbc::ConnectionMethods.module_eval do
22
21
  rescue LoadError # assuming driver.jar is on the class-path
23
22
  end if mysql_driver
24
23
 
24
+ if driver.nil?
25
+ config[:driver] ||=
26
+ defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
27
+ end
28
+
25
29
  config[:username] = 'root' unless config.key?(:username)
26
30
  # jdbc:mysql://[host][,failoverhost...][:port]/[database]
27
31
  # - if the host name is not specified, it defaults to 127.0.0.1
@@ -36,7 +40,8 @@ ArJdbc::ConnectionMethods.module_eval do
36
40
 
37
41
  properties = ( config[:properties] ||= {} )
38
42
  if mysql_driver
39
- properties['zeroDateTimeBehavior'] ||= 'convertToNull'
43
+ properties['zeroDateTimeBehavior'] ||=
44
+ config[:driver].to_s.start_with?('com.mysql.cj.') ? 'CONVERT_TO_NULL' : 'convertToNull'
40
45
  properties['jdbcCompliantTruncation'] ||= false
41
46
  # NOTE: this is "better" than passing what users are used to set on MRI
42
47
  # e.g. 'utf8mb4' will fail cause the driver will check for a Java charset
@@ -108,7 +113,8 @@ ArJdbc::ConnectionMethods.module_eval do
108
113
  rescue LoadError # assuming driver.jar is on the class-path
109
114
  end
110
115
 
111
- config[:driver] ||= 'org.mariadb.jdbc.Driver'
116
+ config[:driver] ||=
117
+ defined?(::Jdbc::MariaDB.driver_name) ? ::Jdbc::MariaDB.driver_name : 'org.mariadb.jdbc.Driver'
112
118
 
113
119
  mysql_connection(config)
114
120
  end
@@ -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,52 @@ 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
51
+ def get_database_version # :nodoc:
52
+ begin
53
+ version = @connection.database_product
54
+ if match = version.match(/([\d\.]*\d).*?/)
55
+ version = match[1].split('.').map(&:to_i)
56
+ # PostgreSQL version representation does not have more than 4 digits
57
+ # From version 10 onwards, PG has changed its versioning policy to
58
+ # limit it to only 2 digits. i.e. in 10.x, 10 being the major
59
+ # version and x representing the patch release
60
+ # Refer to:
61
+ # https://www.postgresql.org/support/versioning/
62
+ # https://www.postgresql.org/docs/10/static/libpq-status.html -> PQserverVersion()
63
+ # for more info
64
+
65
+ if version.size >= 3
66
+ (version[0] * 100 + version[1]) * 100 + version[2]
67
+ elsif version.size == 2
68
+ if version[0] >= 10
69
+ version[0] * 100 * 100 + version[1]
76
70
  else
77
- 0
71
+ (version[0] * 100 + version[1]) * 100
78
72
  end
73
+ elsif version.size == 1
74
+ version[0] * 100 * 100
79
75
  else
80
76
  0
81
77
  end
78
+ else
79
+ 0
82
80
  end
81
+ end
82
+ end
83
+
84
+ def check_version # :nodoc:
85
+ if database_version < 90300
86
+ raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
87
+ end
83
88
  end
84
89
 
90
+
85
91
  def redshift?
86
92
  # SELECT version() :
87
93
  # 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 +98,6 @@ module ArJdbc
92
98
  end
93
99
  private :redshift?
94
100
 
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
101
  def set_client_encoding(encoding)
103
102
  ActiveRecord::Base.logger.warn "client_encoding is set by the driver and should not be altered, ('#{encoding}' ignored)"
104
103
  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."
@@ -165,7 +164,7 @@ module ArJdbc
165
164
  int4range: { name: 'int4range' },
166
165
  int8range: { name: 'int8range' },
167
166
  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 ...
167
+ interval: { name: 'interval' },
169
168
  json: { name: 'json' },
170
169
  jsonb: { name: 'jsonb' },
171
170
  line: { name: 'line' },
@@ -198,107 +197,118 @@ module ArJdbc
198
197
  !native_database_types[type].nil?
199
198
  end
200
199
 
201
- # Enable standard-conforming strings if available.
202
200
  def set_standard_conforming_strings
203
- self.standard_conforming_strings=(true)
201
+ execute("SET standard_conforming_strings = on", "SCHEMA")
204
202
  end
205
203
 
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
204
+ def supports_bulk_alter?
205
+ true
219
206
  end
220
207
 
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
208
+ def supports_index_sort_order?
209
+ true
235
210
  end
236
211
 
237
- def supports_ddl_transactions?; true end
212
+ def supports_partial_index?
213
+ true
214
+ end
238
215
 
239
- def supports_advisory_locks?; true end
216
+ def supports_expression_index?
217
+ true
218
+ end
240
219
 
241
- def supports_explain?; true end
220
+ def supports_transaction_isolation?
221
+ true
222
+ end
242
223
 
243
- def supports_expression_index?; true end
224
+ def supports_foreign_keys?
225
+ true
226
+ end
244
227
 
245
- def supports_foreign_keys?; true end
228
+ def supports_validate_constraints?
229
+ true
230
+ end
246
231
 
247
- def supports_validate_constraints?; true end
232
+ def supports_views?
233
+ true
234
+ end
248
235
 
249
- def supports_index_sort_order?; true end
236
+ def supports_datetime_with_precision?
237
+ true
238
+ end
250
239
 
251
- def supports_partial_index?; true end
240
+ def supports_json?
241
+ database_version >= 90200
242
+ end
252
243
 
253
- def supports_savepoints?; true end
244
+ def supports_comments?
245
+ true
246
+ end
254
247
 
255
- def supports_transaction_isolation?; true end
248
+ def supports_savepoints?
249
+ true
250
+ end
256
251
 
257
- def supports_views?; true end
252
+ def supports_insert_returning?
253
+ true
254
+ end
258
255
 
259
- def supports_bulk_alter?; true end
256
+ def supports_insert_on_conflict?
257
+ database_version >= 90500
258
+ end
259
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
260
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
261
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
260
262
 
261
- def supports_datetime_with_precision?; true end
263
+ def index_algorithms
264
+ { concurrently: 'CONCURRENTLY' }
265
+ end
262
266
 
263
- def supports_comments?; true end
267
+ def supports_ddl_transactions?
268
+ true
269
+ end
264
270
 
265
- # Does PostgreSQL support standard conforming strings?
266
- def supports_standard_conforming_strings?
267
- standard_conforming_strings?
268
- @standard_conforming_strings != :unsupported
271
+ def supports_advisory_locks?
272
+ true
269
273
  end
270
274
 
271
- def supports_foreign_tables? # we don't really support this yet, its a reminder :)
272
- postgresql_version >= 90300
275
+ def supports_explain?
276
+ true
273
277
  end
274
278
 
275
- def supports_hex_escaped_bytea?
276
- postgresql_version >= 90000
279
+ def supports_extensions?
280
+ database_version >= 90200
277
281
  end
278
282
 
279
- def supports_materialized_views?
280
- postgresql_version >= 90300
283
+ def supports_ranges?
284
+ database_version >= 90200
281
285
  end
282
286
 
283
- def supports_json?
284
- postgresql_version >= 90200
287
+ def supports_materialized_views?
288
+ database_version >= 90300
285
289
  end
286
290
 
287
- def supports_insert_with_returning?
288
- postgresql_version >= 80200
291
+ def supports_foreign_tables? # we don't really support this yet, its a reminder :)
292
+ database_version >= 90300
289
293
  end
290
294
 
291
295
  def supports_pgcrypto_uuid?
292
- postgresql_version >= 90400
296
+ database_version >= 90400
293
297
  end
294
298
 
295
- # Range data-types weren't introduced until PostgreSQL 9.2.
296
- def supports_ranges?
297
- postgresql_version >= 90200
299
+ def supports_optimizer_hints?
300
+ unless defined?(@has_pg_hint_plan)
301
+ @has_pg_hint_plan = extension_available?("pg_hint_plan")
302
+ end
303
+ @has_pg_hint_plan
298
304
  end
299
305
 
300
- def supports_extensions?
301
- postgresql_version >= 90200
306
+ def supports_common_table_expressions?
307
+ true
308
+ end
309
+
310
+ def supports_lazy_transactions?
311
+ true
302
312
  end
303
313
 
304
314
  # From AR 5.1 postgres_adapter.rb
@@ -306,65 +316,60 @@ module ArJdbc
306
316
  index.using == :btree || super
307
317
  end
308
318
 
319
+ def get_advisory_lock(lock_id) # :nodoc:
320
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
321
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
322
+ end
323
+ query_value("SELECT pg_try_advisory_lock(#{lock_id})")
324
+ end
325
+
326
+ def release_advisory_lock(lock_id) # :nodoc:
327
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
328
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
329
+ end
330
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
331
+ end
332
+
309
333
  def enable_extension(name)
310
- execute("CREATE EXTENSION IF NOT EXISTS \"#{name}\"")
334
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
335
+ reload_type_map
336
+ }
311
337
  end
312
338
 
313
339
  def disable_extension(name)
314
- execute("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE")
340
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
341
+ reload_type_map
342
+ }
343
+ end
344
+
345
+ def extension_available?(name)
346
+ query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
315
347
  end
316
348
 
317
349
  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
350
+ query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
323
351
  end
324
352
 
325
353
  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
354
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
332
355
  end
333
356
 
334
- def index_algorithms
335
- { :concurrently => 'CONCURRENTLY' }
357
+ # Returns the configured supported identifier length supported by PostgreSQL
358
+ def max_identifier_length
359
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
336
360
  end
337
361
 
338
- # Set the authorized user for this session.
362
+ # Set the authorized user for this session
339
363
  def session_auth=(user)
340
364
  clear_cache!
341
- execute "SET SESSION AUTHORIZATION #{user}"
342
- end
343
-
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")
348
- end
349
- select_value("SELECT pg_try_advisory_lock(#{lock_id});")
365
+ execute("SET SESSION AUTHORIZATION #{user}")
350
366
  end
351
367
 
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")
356
- end
357
- select_value("SELECT pg_advisory_unlock(#{lock_id})")
358
- end
359
-
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
368
+ def use_insert_returning?
369
+ @use_insert_returning
363
370
  end
364
- alias table_alias_length max_identifier_length
365
- alias index_name_length max_identifier_length
366
371
 
367
- def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
372
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
368
373
  val = super
369
374
  if !use_insert_returning? && pk
370
375
  unless sequence_name
@@ -378,25 +383,27 @@ module ArJdbc
378
383
  end
379
384
  end
380
385
 
386
+ def execute_batch(statements, name = nil)
387
+ if statements.is_a? Array
388
+ execute(combine_multi_statements(statements), name)
389
+ else
390
+ execute(statements, name)
391
+ end
392
+ end
393
+
381
394
  def explain(arel, binds = [])
382
395
  sql, binds = to_sql_and_binds(arel, binds)
383
396
  ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query("EXPLAIN #{sql}", 'EXPLAIN', binds))
384
397
  end
385
398
 
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
399
+ # from ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements
400
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
401
+ :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :with
402
+ ) # :nodoc:
403
+ private_constant :READ_QUERY
404
+
405
+ def write_query?(sql) # :nodoc:
406
+ !READ_QUERY.match?(sql)
400
407
  end
401
408
 
402
409
  # We need to make sure to deallocate all the prepared statements
@@ -427,6 +434,29 @@ module ArJdbc
427
434
  exec_query("SELECT currval('#{sequence_name}')", 'SQL')
428
435
  end
429
436
 
437
+ def build_insert_sql(insert) # :nodoc:
438
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
439
+
440
+ if insert.skip_duplicates?
441
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
442
+ elsif insert.update_duplicates?
443
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
444
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
445
+ end
446
+
447
+ sql << " RETURNING #{insert.returning}" if insert.returning
448
+ sql
449
+ end
450
+
451
+ def build_truncate_statements(*table_names)
452
+ ["TRUNCATE TABLE #{table_names.flatten.map(&method(:quote_table_name)).join(", ")}"]
453
+ end
454
+
455
+ def truncate(table_name, name = nil)
456
+ ActiveRecord::Base.clear_query_caches_for_current_thread if @query_cache_enabled
457
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)}", name)
458
+ end
459
+
430
460
  def all_schemas
431
461
  select('SELECT nspname FROM pg_namespace').map { |row| row["nspname"] }
432
462
  end
@@ -463,13 +493,7 @@ module ArJdbc
463
493
 
464
494
  def escape_bytea(string)
465
495
  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
496
+ "\\x#{string.unpack("H*")[0]}"
473
497
  end
474
498
 
475
499
  # @override
@@ -488,7 +512,7 @@ module ArJdbc
488
512
  alias_method :quote_schema_name, :quote_column_name
489
513
 
490
514
  # 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 = {})
515
+ def remove_column(table_name, column_name, type = nil, **options)
492
516
  super
493
517
  clear_cache!
494
518
  end
@@ -502,6 +526,19 @@ module ArJdbc
502
526
  nil
503
527
  end
504
528
 
529
+
530
+ # @private
531
+ def column_name_for_operation(operation, node)
532
+ case operation
533
+ when 'maximum' then 'max'
534
+ when 'minimum' then 'min'
535
+ when 'average' then 'avg'
536
+ else operation.downcase
537
+ end
538
+ end
539
+
540
+ private
541
+
505
542
  # Returns the list of a table's column names, data types, and default values.
506
543
  #
507
544
  # If the table name is not prefixed with a schema, the database will
@@ -511,82 +548,73 @@ module ArJdbc
511
548
  # - format_type includes the column size constraint, e.g. varchar(50)
512
549
  # - ::regclass is a function that gives the id for a table name
513
550
  def column_definitions(table_name)
514
- select_rows(<<-end_sql, 'SCHEMA')
551
+ select_rows(<<~SQL, 'SCHEMA')
515
552
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
516
553
  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
554
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
521
555
  FROM pg_attribute a
522
556
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
557
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
558
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
523
559
  WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
524
560
  AND a.attnum > 0 AND NOT a.attisdropped
525
561
  ORDER BY a.attnum
526
- end_sql
562
+ SQL
527
563
  end
528
- private :column_definitions
529
564
 
530
- def truncate(table_name, name = nil)
531
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
565
+ def extract_table_ref_from_insert_sql(sql)
566
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
567
+ $1.strip if $1
532
568
  end
533
569
 
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
570
+ def arel_visitor
571
+ Arel::Visitors::PostgreSQL.new(self)
542
572
  end
543
573
 
544
- private
545
-
546
574
  # Pulled from ActiveRecord's Postgres adapter and modified to use execute
547
575
  def can_perform_case_insensitive_comparison_for?(column)
548
576
  @case_insensitive_cache ||= {}
549
577
  @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
578
+ sql = <<~SQL
579
+ SELECT exists(
580
+ SELECT * FROM pg_proc
581
+ WHERE proname = 'lower'
582
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
583
+ ) OR exists(
584
+ SELECT * FROM pg_proc
585
+ INNER JOIN pg_cast
586
+ ON ARRAY[casttarget]::oidvector = proargtypes
587
+ WHERE proname = 'lower'
588
+ AND castsource = #{quote column.sql_type}::regtype
589
+ )
590
+ SQL
563
591
  select_value(sql, 'SCHEMA')
564
592
  end
565
593
  end
566
594
 
567
- def translate_exception(exception, message)
595
+ def translate_exception(exception, message:, sql:, binds:)
568
596
  return super unless exception.is_a?(ActiveRecord::JDBCError)
569
597
 
570
598
  # TODO: Can we base these on an error code of some kind?
571
599
  case exception.message
572
600
  when /duplicate key value violates unique constraint/
573
- ::ActiveRecord::RecordNotUnique.new(message)
601
+ ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
574
602
  when /violates not-null constraint/
575
- ::ActiveRecord::NotNullViolation.new(message)
603
+ ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
576
604
  when /violates foreign key constraint/
577
- ::ActiveRecord::InvalidForeignKey.new(message)
605
+ ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
578
606
  when /value too long/
579
- ::ActiveRecord::ValueTooLong.new(message)
607
+ ::ActiveRecord::ValueTooLong.new(message, sql: sql, binds: binds)
580
608
  when /out of range/
581
- ::ActiveRecord::RangeError.new(message)
609
+ ::ActiveRecord::RangeError.new(message, sql: sql, binds: binds)
582
610
  when /could not serialize/
583
- ::ActiveRecord::SerializationFailure.new(message)
611
+ ::ActiveRecord::SerializationFailure.new(message, sql: sql, binds: binds)
584
612
  when /deadlock detected/
585
- ::ActiveRecord::Deadlocked.new(message)
613
+ ::ActiveRecord::Deadlocked.new(message, sql: sql, binds: binds)
586
614
  when /lock timeout/
587
- ::ActiveRecord::LockWaitTimeout.new(message)
615
+ ::ActiveRecord::LockWaitTimeout.new(message, sql: sql, binds: binds)
588
616
  when /canceling statement/ # This needs to come after lock timeout because the lock timeout message also contains "canceling statement"
589
- ::ActiveRecord::QueryCanceled.new(message)
617
+ ::ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
590
618
  else
591
619
  super
592
620
  end
@@ -610,11 +638,6 @@ module ArJdbc
610
638
  end
611
639
  end
612
640
 
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
641
  def local_tz
619
642
  @local_tz ||= execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
620
643
  end
@@ -635,6 +658,7 @@ module ActiveRecord::ConnectionAdapters
635
658
  remove_const(:PostgreSQLAdapter) if const_defined?(:PostgreSQLAdapter)
636
659
 
637
660
  class PostgreSQLAdapter < AbstractAdapter
661
+ class_attribute :create_unlogged_tables, default: false
638
662
 
639
663
  # Try to use as much of the built in postgres logic as possible
640
664
  # maybe someday we can extend the actual adapter
@@ -672,11 +696,16 @@ module ActiveRecord::ConnectionAdapters
672
696
  initialize_type_map
673
697
 
674
698
  @use_insert_returning = @config.key?(:insert_returning) ?
675
- self.class.type_cast_config_to_boolean(@config[:insert_returning]) : nil
699
+ self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
676
700
  end
677
701
 
678
- def arel_visitor # :nodoc:
679
- Arel::Visitors::PostgreSQL.new(self)
702
+ def self.database_exists?(config)
703
+ conn = ActiveRecord::Base.postgresql_connection(config)
704
+ conn && conn.really_valid?
705
+ rescue ActiveRecord::NoDatabaseError
706
+ false
707
+ ensure
708
+ conn.disconnect! if conn
680
709
  end
681
710
 
682
711
  require 'active_record/connection_adapters/postgresql/schema_definitions'
@@ -685,11 +714,8 @@ module ActiveRecord::ConnectionAdapters
685
714
  TableDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
686
715
  Table = ActiveRecord::ConnectionAdapters::PostgreSQL::Table
687
716
 
688
- def create_table_definition(*args) # :nodoc:
689
- TableDefinition.new(*args)
690
- end
691
-
692
717
  public :sql_for_insert
718
+ alias :postgresql_version :database_version
693
719
 
694
720
  def jdbc_connection_class(spec)
695
721
  ::ArJdbc::PostgreSQL.jdbc_connection_class