activerecord-jdbc-alt-adapter 52.6.0-java → 60.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.travis.yml +58 -37
  4. data/Gemfile +9 -2
  5. data/README.md +30 -14
  6. data/Rakefile +1 -1
  7. data/Rakefile.jdbc +8 -1
  8. data/activerecord-jdbc-adapter.gemspec +5 -8
  9. data/activerecord-jdbc-alt-adapter.gemspec +5 -8
  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 +16 -23
  13. data/lib/arjdbc/abstract/database_statements.rb +24 -0
  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/adapter.rb +3 -1
  23. data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
  24. data/lib/arjdbc/jdbc/base_ext.rb +3 -1
  25. data/lib/arjdbc/jdbc/callbacks.rb +2 -0
  26. data/lib/arjdbc/jdbc/column.rb +2 -0
  27. data/lib/arjdbc/jdbc/connection.rb +2 -0
  28. data/lib/arjdbc/jdbc/connection_methods.rb +2 -0
  29. data/lib/arjdbc/jdbc/error.rb +2 -0
  30. data/lib/arjdbc/jdbc/extension.rb +2 -0
  31. data/lib/arjdbc/jdbc/java.rb +3 -1
  32. data/lib/arjdbc/jdbc/railtie.rb +3 -1
  33. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -1
  34. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -1
  35. data/lib/arjdbc/jdbc/type_cast.rb +2 -0
  36. data/lib/arjdbc/jdbc/type_converter.rb +2 -0
  37. data/lib/arjdbc/mssql.rb +3 -1
  38. data/lib/arjdbc/mssql/adapter.rb +105 -36
  39. data/lib/arjdbc/mssql/column.rb +5 -1
  40. data/lib/arjdbc/mssql/connection_methods.rb +8 -2
  41. data/lib/arjdbc/mssql/database_limits.rb +2 -0
  42. data/lib/arjdbc/mssql/database_statements.rb +43 -5
  43. data/lib/arjdbc/mssql/errors.rb +2 -0
  44. data/lib/arjdbc/mssql/explain_support.rb +3 -1
  45. data/lib/arjdbc/mssql/extensions/attribute_methods.rb +5 -1
  46. data/lib/arjdbc/mssql/extensions/calculations.rb +2 -0
  47. data/lib/arjdbc/mssql/quoting.rb +38 -0
  48. data/lib/arjdbc/mssql/schema_creation.rb +24 -2
  49. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  50. data/lib/arjdbc/mssql/schema_dumper.rb +2 -0
  51. data/lib/arjdbc/mssql/schema_statements.rb +63 -21
  52. data/lib/arjdbc/mssql/transaction.rb +2 -0
  53. data/lib/arjdbc/mssql/types.rb +2 -0
  54. data/lib/arjdbc/mssql/types/binary_types.rb +2 -0
  55. data/lib/arjdbc/mssql/types/date_and_time_types.rb +2 -0
  56. data/lib/arjdbc/mssql/types/deprecated_types.rb +2 -0
  57. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -0
  58. data/lib/arjdbc/mssql/types/string_types.rb +2 -0
  59. data/lib/arjdbc/mssql/utils.rb +2 -0
  60. data/lib/arjdbc/mysql/adapter.rb +47 -18
  61. data/lib/arjdbc/postgresql/adapter.rb +220 -213
  62. data/lib/arjdbc/postgresql/base/array_decoder.rb +2 -0
  63. data/lib/arjdbc/postgresql/base/array_encoder.rb +4 -2
  64. data/lib/arjdbc/postgresql/base/array_parser.rb +4 -2
  65. data/lib/arjdbc/postgresql/base/pgconn.rb +2 -0
  66. data/lib/arjdbc/postgresql/column.rb +6 -4
  67. data/lib/arjdbc/postgresql/name.rb +2 -0
  68. data/lib/arjdbc/postgresql/oid_types.rb +2 -0
  69. data/lib/arjdbc/sqlite3/adapter.rb +175 -180
  70. data/lib/arjdbc/sqlite3/connection_methods.rb +15 -4
  71. data/lib/arjdbc/tasks/databases.rake +13 -10
  72. data/lib/arjdbc/tasks/mssql_database_tasks.rb +49 -5
  73. data/lib/arjdbc/util/quoted_cache.rb +3 -1
  74. data/lib/arjdbc/util/serialized_attributes.rb +3 -1
  75. data/lib/arjdbc/util/table_copier.rb +3 -1
  76. data/lib/arjdbc/version.rb +1 -1
  77. data/pom.xml +4 -4
  78. data/rakelib/01-tomcat.rake +2 -2
  79. data/src/java/arjdbc/ArJdbcModule.java +5 -5
  80. data/src/java/arjdbc/jdbc/DriverWrapper.java +1 -9
  81. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +406 -629
  82. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +37 -51
  83. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +13 -23
  84. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +31 -24
  85. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +94 -99
  86. data/src/java/arjdbc/util/DateTimeUtils.java +12 -4
  87. metadata +8 -16
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/relation'
2
4
  require 'active_record/version'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -64,6 +66,42 @@ module ActiveRecord
64
66
  # @see #quote in old adapter
65
67
  BLOB_VALUE_MARKER = "''"
66
68
 
69
+ def column_name_matcher
70
+ COLUMN_NAME
71
+ end
72
+
73
+ def column_name_with_order_matcher
74
+ COLUMN_NAME_WITH_ORDER
75
+ end
76
+
77
+ COLUMN_NAME = /
78
+ \A
79
+ (
80
+ (?:
81
+ # \[table_name\].\[column_name\] | function(one or no argument)
82
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
83
+ )
84
+ (?:\s+AS\s+(?:\w+|\[\w+\]))?
85
+ )
86
+ (?:\s*,\s*\g<1>)*
87
+ \z
88
+ /ix
89
+
90
+ COLUMN_NAME_WITH_ORDER = /
91
+ \A
92
+ (
93
+ (?:
94
+ # \[table_name\].\[column_name\] | function(one or no argument)
95
+ ((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
96
+ )
97
+ (?:\s+ASC|\s+DESC)?
98
+ )
99
+ (?:\s*,\s*\g<1>)*
100
+ \z
101
+ /ix
102
+
103
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
104
+
67
105
  private
68
106
 
69
107
  def time_with_db_timezone(value)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -11,8 +13,28 @@ module ActiveRecord
11
13
  projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
12
14
  select_into = "SELECT #{projections} INTO #{table_name} FROM #{source}"
13
15
  else
14
- o.instance_variable_set :@as, nil
15
- super
16
+ # o.instance_variable_set :@as, nil
17
+ # super
18
+ create_sql = +''
19
+
20
+ create_sql << "IF NOT EXISTS (SELECT 1 FROM sysobjects WHERE name='#{o.name}' and xtype='U') " if o.if_not_exists
21
+ create_sql << "CREATE#{table_modifier_in_create(o)} TABLE "
22
+ create_sql << "#{quote_table_name(o.name)} "
23
+
24
+ statements = o.columns.map { |c| accept c }
25
+ statements << accept(o.primary_keys) if o.primary_keys
26
+
27
+ if supports_indexes_in_create?
28
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
29
+ end
30
+
31
+ if supports_foreign_keys?
32
+ statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
33
+ end
34
+
35
+ create_sql << "(#{statements.join(', ')})" if statements.present?
36
+ add_table_options!(create_sql, table_options(o))
37
+ create_sql
16
38
  end
17
39
  end
18
40
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -81,6 +83,14 @@ module ActiveRecord
81
83
 
82
84
  super
83
85
  end
86
+
87
+ def timestamps(**options)
88
+ if !options.key?(:precision) && @conn.supports_datetime_with_precision?
89
+ options[:precision] = 7
90
+ end
91
+
92
+ super
93
+ end
84
94
  end
85
95
 
86
96
  class Table < ActiveRecord::ConnectionAdapters::Table
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module MSSQL
4
- module SchemaStatements
6
+ module SchemaStatements # :nodoc:
5
7
 
6
8
  NATIVE_DATABASE_TYPES = {
7
9
  # Logical Rails types to SQL Server types
@@ -41,23 +43,36 @@ module ActiveRecord
41
43
  NATIVE_DATABASE_TYPES
42
44
  end
43
45
 
44
- # Returns an array of Column objects for the table specified by +table_name+.
45
- # See the concrete implementation for details on the expected parameter values.
46
- # NOTE: This is ready, all implemented in the java part of adapter,
47
- # it uses MSSQLColumn, SqlTypeMetadata, etc.
48
- def columns(table_name)
49
- log('JDBC: GETCOLUMNS', 'SCHEMA') { @connection.columns(table_name) }
50
- rescue => e
51
- # raise translate_exception_class(e, nil)
52
- # FIXME: this breaks one arjdbc test but fixes activerecord tests
53
- # (table name alias). Also it behaves similarly to the CRuby adapter
54
- # which returns an empty array too. (postgres throws a exception)
55
- []
56
- end
57
-
58
46
  # Returns an array of indexes for the given table.
59
- def indexes(table_name, name = nil)
60
- @connection.indexes(table_name, name)
47
+ def indexes(table_name)
48
+ data = select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA") rescue []
49
+
50
+ data.reduce([]) do |indexes, index|
51
+ index = index.with_indifferent_access
52
+
53
+ if index[:index_description] =~ /primary key/
54
+ indexes
55
+ else
56
+ name = index[:index_name]
57
+ unique = index[:index_description].to_s.match?(/unique/)
58
+ where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
59
+ orders = {}
60
+ columns = []
61
+
62
+ index[:index_keys].split(',').each do |column|
63
+ column.strip!
64
+
65
+ if column.ends_with?('(-)')
66
+ column.gsub! '(-)', ''
67
+ orders[column] = :desc
68
+ end
69
+
70
+ columns << column
71
+ end
72
+
73
+ indexes << IndexDefinition.new(table_name, name, unique, columns, where: where, orders: orders)
74
+ end
75
+ end
61
76
  end
62
77
 
63
78
  def primary_keys(table_name)
@@ -124,7 +139,7 @@ module ActiveRecord
124
139
  end
125
140
  end
126
141
 
127
- if options[:if_exists] && @mssql_major_version < 13
142
+ if options[:if_exists] && mssql_major_version < 13
128
143
  # this is for sql server 2012 and 2014
129
144
  execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
130
145
  else
@@ -193,7 +208,7 @@ module ActiveRecord
193
208
  column_type_sql << "(#{precision})"
194
209
  else
195
210
  raise(
196
- ActiveRecordError,
211
+ ArgumentError,
197
212
  "No #{native[:name]} type has precision of #{precision}. The " \
198
213
  'allowed range of precision is from 0 to 7, even though the ' \
199
214
  'sql type precision is 7 this adapter will persist up to 6 ' \
@@ -220,6 +235,14 @@ module ActiveRecord
220
235
  (order_columns << super).join(', ')
221
236
  end
222
237
 
238
+ def add_timestamps(table_name, options = {})
239
+ if !options.key?(:precision) && supports_datetime_with_precision?
240
+ options[:precision] = 7
241
+ end
242
+
243
+ super
244
+ end
245
+
223
246
  def create_schema_dumper(options)
224
247
  MSSQL::SchemaDumper.create(self, options)
225
248
  end
@@ -299,13 +322,30 @@ module ActiveRecord
299
322
  execute(sql_alter.join(' '))
300
323
  end
301
324
 
325
+ def update_table_definition(table_name, base) #:nodoc:
326
+ MSSQL::Table.new(table_name, base)
327
+ end
328
+
302
329
  private
303
330
 
331
+ def schema_creation
332
+ MSSQL::SchemaCreation.new(self)
333
+ end
334
+
335
+ def create_table_definition(*args)
336
+ MSSQL::TableDefinition.new(self, *args)
337
+ end
338
+
339
+ def new_column_from_field(table_name, field)
340
+ field
341
+ end
342
+
304
343
  def data_source_sql(name = nil, type: nil)
305
344
  scope = quoted_scope(name, type: type)
306
345
  table_name = 'TABLE_NAME'
307
346
 
308
- sql = "SELECT #{table_name}"
347
+ sql = ''.dup
348
+ sql << "SELECT #{table_name}"
309
349
  sql << ' FROM INFORMATION_SCHEMA.TABLES'
310
350
  sql << ' WHERE TABLE_CATALOG = DB_NAME()'
311
351
  sql << " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
@@ -327,7 +367,9 @@ module ActiveRecord
327
367
  end
328
368
 
329
369
  def change_column_type(table_name, column_name, type, options = {})
330
- sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
370
+ sql = ''.dup
371
+
372
+ sql << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
331
373
  sql << (options[:null] ? " NULL" : " NOT NULL") if options.has_key?(:null)
332
374
  result = execute(sql)
333
375
  result
@@ -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,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,114 @@ 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_lazy_transactions?
307
+ true
302
308
  end
303
309
 
304
310
  # From AR 5.1 postgres_adapter.rb
@@ -306,65 +312,60 @@ module ArJdbc
306
312
  index.using == :btree || super
307
313
  end
308
314
 
315
+ def get_advisory_lock(lock_id) # :nodoc:
316
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
317
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
318
+ end
319
+ query_value("SELECT pg_try_advisory_lock(#{lock_id})")
320
+ end
321
+
322
+ def release_advisory_lock(lock_id) # :nodoc:
323
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
324
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
325
+ end
326
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
327
+ end
328
+
309
329
  def enable_extension(name)
310
- execute("CREATE EXTENSION IF NOT EXISTS \"#{name}\"")
330
+ exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
331
+ reload_type_map
332
+ }
311
333
  end
312
334
 
313
335
  def disable_extension(name)
314
- execute("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE")
336
+ exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
337
+ reload_type_map
338
+ }
339
+ end
340
+
341
+ def extension_available?(name)
342
+ query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
315
343
  end
316
344
 
317
345
  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
346
+ query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
323
347
  end
324
348
 
325
349
  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
350
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
332
351
  end
333
352
 
334
- def index_algorithms
335
- { :concurrently => 'CONCURRENTLY' }
353
+ # Returns the configured supported identifier length supported by PostgreSQL
354
+ def max_identifier_length
355
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
336
356
  end
337
357
 
338
- # Set the authorized user for this session.
358
+ # Set the authorized user for this session
339
359
  def session_auth=(user)
340
360
  clear_cache!
341
- execute "SET SESSION AUTHORIZATION #{user}"
361
+ execute("SET SESSION AUTHORIZATION #{user}")
342
362
  end
343
363
 
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});")
350
- end
351
-
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
364
+ def use_insert_returning?
365
+ @use_insert_returning
363
366
  end
364
- alias table_alias_length max_identifier_length
365
- alias index_name_length max_identifier_length
366
367
 
367
- def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
368
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
368
369
  val = super
369
370
  if !use_insert_returning? && pk
370
371
  unless sequence_name
@@ -383,20 +384,12 @@ module ArJdbc
383
384
  ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query("EXPLAIN #{sql}", 'EXPLAIN', binds))
384
385
  end
385
386
 
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
387
+ # from ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements
388
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback) # :nodoc:
389
+ private_constant :READ_QUERY
390
+
391
+ def write_query?(sql) # :nodoc:
392
+ !READ_QUERY.match?(sql)
400
393
  end
401
394
 
402
395
  # We need to make sure to deallocate all the prepared statements
@@ -427,6 +420,24 @@ module ArJdbc
427
420
  exec_query("SELECT currval('#{sequence_name}')", 'SQL')
428
421
  end
429
422
 
423
+ def build_insert_sql(insert) # :nodoc:
424
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
425
+
426
+ if insert.skip_duplicates?
427
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
428
+ elsif insert.update_duplicates?
429
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
430
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
431
+ end
432
+
433
+ sql << " RETURNING #{insert.returning}" if insert.returning
434
+ sql
435
+ end
436
+
437
+ def build_truncate_statements(*table_names)
438
+ "TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}"
439
+ end
440
+
430
441
  def all_schemas
431
442
  select('SELECT nspname FROM pg_namespace').map { |row| row["nspname"] }
432
443
  end
@@ -463,13 +474,7 @@ module ArJdbc
463
474
 
464
475
  def escape_bytea(string)
465
476
  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
477
+ "\\x#{string.unpack("H*")[0]}"
473
478
  end
474
479
 
475
480
  # @override
@@ -502,6 +507,19 @@ module ArJdbc
502
507
  nil
503
508
  end
504
509
 
510
+
511
+ # @private
512
+ def column_name_for_operation(operation, node)
513
+ case operation
514
+ when 'maximum' then 'max'
515
+ when 'minimum' then 'min'
516
+ when 'average' then 'avg'
517
+ else operation.downcase
518
+ end
519
+ end
520
+
521
+ private
522
+
505
523
  # Returns the list of a table's column names, data types, and default values.
506
524
  #
507
525
  # If the table name is not prefixed with a schema, the database will
@@ -511,82 +529,73 @@ module ArJdbc
511
529
  # - format_type includes the column size constraint, e.g. varchar(50)
512
530
  # - ::regclass is a function that gives the id for a table name
513
531
  def column_definitions(table_name)
514
- select_rows(<<-end_sql, 'SCHEMA')
532
+ select_rows(<<~SQL, 'SCHEMA')
515
533
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
516
534
  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
535
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
521
536
  FROM pg_attribute a
522
537
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
538
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
539
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
523
540
  WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
524
541
  AND a.attnum > 0 AND NOT a.attisdropped
525
542
  ORDER BY a.attnum
526
- end_sql
543
+ SQL
527
544
  end
528
- private :column_definitions
529
545
 
530
- def truncate(table_name, name = nil)
531
- execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
546
+ def extract_table_ref_from_insert_sql(sql)
547
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
548
+ $1.strip if $1
532
549
  end
533
550
 
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
551
+ def arel_visitor
552
+ Arel::Visitors::PostgreSQL.new(self)
542
553
  end
543
554
 
544
- private
545
-
546
555
  # Pulled from ActiveRecord's Postgres adapter and modified to use execute
547
556
  def can_perform_case_insensitive_comparison_for?(column)
548
557
  @case_insensitive_cache ||= {}
549
558
  @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
559
+ sql = <<~SQL
560
+ SELECT exists(
561
+ SELECT * FROM pg_proc
562
+ WHERE proname = 'lower'
563
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
564
+ ) OR exists(
565
+ SELECT * FROM pg_proc
566
+ INNER JOIN pg_cast
567
+ ON ARRAY[casttarget]::oidvector = proargtypes
568
+ WHERE proname = 'lower'
569
+ AND castsource = #{quote column.sql_type}::regtype
570
+ )
571
+ SQL
563
572
  select_value(sql, 'SCHEMA')
564
573
  end
565
574
  end
566
575
 
567
- def translate_exception(exception, message)
576
+ def translate_exception(exception, message:, sql:, binds:)
568
577
  return super unless exception.is_a?(ActiveRecord::JDBCError)
569
578
 
570
579
  # TODO: Can we base these on an error code of some kind?
571
580
  case exception.message
572
581
  when /duplicate key value violates unique constraint/
573
- ::ActiveRecord::RecordNotUnique.new(message)
582
+ ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
574
583
  when /violates not-null constraint/
575
- ::ActiveRecord::NotNullViolation.new(message)
584
+ ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
576
585
  when /violates foreign key constraint/
577
- ::ActiveRecord::InvalidForeignKey.new(message)
586
+ ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
578
587
  when /value too long/
579
- ::ActiveRecord::ValueTooLong.new(message)
588
+ ::ActiveRecord::ValueTooLong.new(message, sql: sql, binds: binds)
580
589
  when /out of range/
581
- ::ActiveRecord::RangeError.new(message)
590
+ ::ActiveRecord::RangeError.new(message, sql: sql, binds: binds)
582
591
  when /could not serialize/
583
- ::ActiveRecord::SerializationFailure.new(message)
592
+ ::ActiveRecord::SerializationFailure.new(message, sql: sql, binds: binds)
584
593
  when /deadlock detected/
585
- ::ActiveRecord::Deadlocked.new(message)
594
+ ::ActiveRecord::Deadlocked.new(message, sql: sql, binds: binds)
586
595
  when /lock timeout/
587
- ::ActiveRecord::LockWaitTimeout.new(message)
596
+ ::ActiveRecord::LockWaitTimeout.new(message, sql: sql, binds: binds)
588
597
  when /canceling statement/ # This needs to come after lock timeout because the lock timeout message also contains "canceling statement"
589
- ::ActiveRecord::QueryCanceled.new(message)
598
+ ::ActiveRecord::QueryCanceled.new(message, sql: sql, binds: binds)
590
599
  else
591
600
  super
592
601
  end
@@ -610,11 +619,6 @@ module ArJdbc
610
619
  end
611
620
  end
612
621
 
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
622
  def local_tz
619
623
  @local_tz ||= execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
620
624
  end
@@ -635,6 +639,7 @@ module ActiveRecord::ConnectionAdapters
635
639
  remove_const(:PostgreSQLAdapter) if const_defined?(:PostgreSQLAdapter)
636
640
 
637
641
  class PostgreSQLAdapter < AbstractAdapter
642
+ class_attribute :create_unlogged_tables, default: false
638
643
 
639
644
  # Try to use as much of the built in postgres logic as possible
640
645
  # maybe someday we can extend the actual adapter
@@ -672,11 +677,16 @@ module ActiveRecord::ConnectionAdapters
672
677
  initialize_type_map
673
678
 
674
679
  @use_insert_returning = @config.key?(:insert_returning) ?
675
- self.class.type_cast_config_to_boolean(@config[:insert_returning]) : nil
680
+ self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
676
681
  end
677
682
 
678
- def arel_visitor # :nodoc:
679
- Arel::Visitors::PostgreSQL.new(self)
683
+ def self.database_exists?(config)
684
+ conn = ActiveRecord::Base.postgresql_connection(config)
685
+ conn && conn.really_valid?
686
+ rescue ActiveRecord::NoDatabaseError
687
+ false
688
+ ensure
689
+ conn.disconnect! if conn
680
690
  end
681
691
 
682
692
  require 'active_record/connection_adapters/postgresql/schema_definitions'
@@ -685,11 +695,8 @@ module ActiveRecord::ConnectionAdapters
685
695
  TableDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
686
696
  Table = ActiveRecord::ConnectionAdapters::PostgreSQL::Table
687
697
 
688
- def create_table_definition(*args) # :nodoc:
689
- TableDefinition.new(*args)
690
- end
691
-
692
698
  public :sql_for_insert
699
+ alias :postgresql_version :database_version
693
700
 
694
701
  def jdbc_connection_class(spec)
695
702
  ::ArJdbc::PostgreSQL.jdbc_connection_class