activerecord-jdbc-alt-adapter 61.1.0-java → 70.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +166 -0
  3. data/.github/workflows/ruby.yml +273 -0
  4. data/.gitignore +1 -0
  5. data/.travis.yml +3 -4
  6. data/Gemfile +9 -7
  7. data/README.md +5 -1
  8. data/Rakefile +1 -1
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +2 -2
  11. data/lib/arel/visitors/compat.rb +5 -33
  12. data/lib/arel/visitors/h2.rb +1 -13
  13. data/lib/arel/visitors/hsqldb.rb +1 -21
  14. data/lib/arel/visitors/sql_server.rb +2 -103
  15. data/lib/arjdbc/abstract/core.rb +8 -9
  16. data/lib/arjdbc/abstract/database_statements.rb +12 -4
  17. data/lib/arjdbc/discover.rb +0 -67
  18. data/lib/arjdbc/hsqldb/adapter.rb +2 -2
  19. data/lib/arjdbc/jdbc/adapter.rb +3 -3
  20. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  21. data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
  22. data/lib/arjdbc/jdbc/column.rb +1 -26
  23. data/lib/arjdbc/jdbc/type_cast.rb +2 -2
  24. data/lib/arjdbc/jdbc.rb +0 -7
  25. data/lib/arjdbc/mssql/adapter.rb +138 -108
  26. data/lib/arjdbc/mssql/connection_methods.rb +3 -0
  27. data/lib/arjdbc/mssql/quoting.rb +26 -27
  28. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  29. data/lib/arjdbc/mssql/schema_definitions.rb +32 -17
  30. data/lib/arjdbc/mssql/schema_dumper.rb +13 -1
  31. data/lib/arjdbc/mssql/schema_statements.rb +61 -36
  32. data/lib/arjdbc/mssql/transaction.rb +2 -2
  33. data/lib/arjdbc/mssql/types/date_and_time_types.rb +6 -6
  34. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -2
  35. data/lib/arjdbc/mssql.rb +1 -1
  36. data/lib/arjdbc/mysql/adapter.rb +2 -1
  37. data/lib/arjdbc/oracle/adapter.rb +4 -23
  38. data/lib/arjdbc/postgresql/adapter.rb +153 -4
  39. data/lib/arjdbc/postgresql/oid_types.rb +155 -108
  40. data/lib/arjdbc/sqlite3/adapter.rb +152 -99
  41. data/lib/arjdbc/tasks/database_tasks.rb +0 -12
  42. data/lib/arjdbc/tasks/mssql_database_tasks.rb +1 -1
  43. data/lib/arjdbc/util/serialized_attributes.rb +0 -22
  44. data/lib/arjdbc/util/table_copier.rb +2 -1
  45. data/lib/arjdbc/version.rb +1 -1
  46. data/rakelib/02-test.rake +3 -18
  47. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +17 -2
  48. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +5 -0
  49. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +33 -0
  50. metadata +7 -38
  51. data/lib/active_record/connection_adapters/as400_adapter.rb +0 -2
  52. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -1
  53. data/lib/active_record/connection_adapters/derby_adapter.rb +0 -1
  54. data/lib/active_record/connection_adapters/informix_adapter.rb +0 -1
  55. data/lib/arel/visitors/db2.rb +0 -137
  56. data/lib/arel/visitors/derby.rb +0 -112
  57. data/lib/arel/visitors/firebird.rb +0 -79
  58. data/lib/arjdbc/db2/adapter.rb +0 -808
  59. data/lib/arjdbc/db2/as400.rb +0 -142
  60. data/lib/arjdbc/db2/column.rb +0 -131
  61. data/lib/arjdbc/db2/connection_methods.rb +0 -48
  62. data/lib/arjdbc/db2.rb +0 -4
  63. data/lib/arjdbc/derby/active_record_patch.rb +0 -13
  64. data/lib/arjdbc/derby/adapter.rb +0 -521
  65. data/lib/arjdbc/derby/connection_methods.rb +0 -20
  66. data/lib/arjdbc/derby/schema_creation.rb +0 -15
  67. data/lib/arjdbc/derby.rb +0 -3
  68. data/lib/arjdbc/firebird/adapter.rb +0 -413
  69. data/lib/arjdbc/firebird/connection_methods.rb +0 -23
  70. data/lib/arjdbc/firebird.rb +0 -4
  71. data/lib/arjdbc/informix/adapter.rb +0 -139
  72. data/lib/arjdbc/informix/connection_methods.rb +0 -9
  73. data/lib/arjdbc/sybase/adapter.rb +0 -47
  74. data/lib/arjdbc/sybase.rb +0 -2
  75. data/lib/arjdbc/tasks/db2_database_tasks.rb +0 -104
  76. data/lib/arjdbc/tasks/derby_database_tasks.rb +0 -95
  77. data/src/java/arjdbc/derby/DerbyModule.java +0 -178
  78. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +0 -152
  79. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +0 -174
  80. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +0 -75
@@ -7,36 +7,36 @@ module ActiveRecord
7
7
 
8
8
  NATIVE_DATABASE_TYPES = {
9
9
  # Logical Rails types to SQL Server types
10
- primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
11
- integer: { name: 'int', limit: 4 },
12
- boolean: { name: 'bit' },
13
- decimal: { name: 'decimal' },
14
- float: { name: 'float' },
15
- date: { name: 'date' },
16
- time: { name: 'time' },
17
- datetime: { name: 'datetime2' },
18
- string: { name: 'nvarchar', limit: 4000 },
19
- text: { name: 'nvarchar(max)' },
20
- binary: { name: 'varbinary(max)' },
10
+ primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
11
+ integer: { name: 'int', limit: 4 },
12
+ boolean: { name: 'bit' },
13
+ decimal: { name: 'decimal' },
14
+ float: { name: 'float' },
15
+ date: { name: 'date' },
16
+ time: { name: 'time' },
17
+ datetime: { name: 'datetime2' },
18
+ string: { name: 'nvarchar', limit: 4000 },
19
+ text: { name: 'nvarchar(max)' },
20
+ binary: { name: 'varbinary(max)' },
21
21
  # Other types or SQL Server specific
22
- bigint: { name: 'bigint' },
23
- smalldatetime: { name: 'smalldatetime' },
22
+ bigint: { name: 'bigint' },
23
+ smalldatetime: { name: 'smalldatetime' },
24
24
  datetime_basic: { name: 'datetime' },
25
- timestamp: { name: 'datetime' },
26
- real: { name: 'real' },
27
- money: { name: 'money' },
28
- smallmoney: { name: 'smallmoney' },
29
- char: { name: 'char' },
30
- nchar: { name: 'nchar' },
31
- varchar: { name: 'varchar', limit: 8000 },
32
- varchar_max: { name: 'varchar(max)' },
33
- uuid: { name: 'uniqueidentifier' },
34
- binary_basic: { name: 'binary' },
35
- varbinary: { name: 'varbinary', limit: 8000 },
25
+ timestamp: { name: 'datetime' },
26
+ real: { name: 'real' },
27
+ money: { name: 'money' },
28
+ smallmoney: { name: 'smallmoney' },
29
+ char: { name: 'char' },
30
+ nchar: { name: 'nchar' },
31
+ varchar: { name: 'varchar', limit: 8000 },
32
+ varchar_max: { name: 'varchar(max)' },
33
+ uuid: { name: 'uniqueidentifier' },
34
+ binary_basic: { name: 'binary' },
35
+ varbinary: { name: 'varbinary', limit: 8000 },
36
36
  # Deprecated SQL Server types
37
- image: { name: 'image' },
38
- ntext: { name: 'ntext' },
39
- text_basic: { name: 'text' }
37
+ image: { name: 'image' },
38
+ ntext: { name: 'ntext' },
39
+ text_basic: { name: 'text' }
40
40
  }.freeze
41
41
 
42
42
  def native_database_types
@@ -127,9 +127,17 @@ module ActiveRecord
127
127
  create_database(name, options)
128
128
  end
129
129
 
130
- def remove_column(table_name, column_name, type = nil, options = {})
131
- raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
130
+ def remove_columns(table_name, *column_names, type: nil, **options)
131
+ if column_names.empty?
132
+ raise ArgumentError.new('You must specify at least one column name. Example: remove_columns(:people, :first_name)')
133
+ end
134
+
135
+ column_names.each do |column_name|
136
+ remove_column(table_name, column_name, type, **options)
137
+ end
138
+ end
132
139
 
140
+ def remove_column(table_name, column_name, _type = nil, **options)
133
141
  return if options[:if_exists] == true && !column_exists?(table_name, column_name)
134
142
 
135
143
  remove_check_constraints(table_name, column_name)
@@ -138,7 +146,7 @@ module ActiveRecord
138
146
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
139
147
  end
140
148
 
141
- def drop_table(table_name, options = {})
149
+ def drop_table(table_name, **options)
142
150
  # mssql cannot recreate referenced table with force: :cascade
143
151
  # https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-table-transact-sql?view=sql-server-2017
144
152
  if options[:force] == :cascade
@@ -248,7 +256,7 @@ module ActiveRecord
248
256
  (order_columns << super).join(', ')
249
257
  end
250
258
 
251
- def add_timestamps(table_name, options = {})
259
+ def add_timestamps(table_name, **options)
252
260
  if !options.key?(:precision) && supports_datetime_with_precision?
253
261
  options[:precision] = 7
254
262
  end
@@ -256,6 +264,16 @@ module ActiveRecord
256
264
  super
257
265
  end
258
266
 
267
+ def add_column(table_name, column_name, type, **options)
268
+ if supports_datetime_with_precision?
269
+ if type == :datetime && !options.key?(:precision)
270
+ options[:precision] = 7
271
+ end
272
+ end
273
+
274
+ super
275
+ end
276
+
259
277
  def create_schema_dumper(options)
260
278
  MSSQL::SchemaDumper.create(self, options)
261
279
  end
@@ -323,16 +341,20 @@ module ActiveRecord
323
341
  quoted_table = quote_table_name(table_name)
324
342
  quoted_column = quote_column_name(column_name)
325
343
  quoted_default = quote(default)
344
+
326
345
  unless null || default.nil?
327
346
  execute("UPDATE #{quoted_table} SET #{quoted_column}=#{quoted_default} WHERE #{quoted_column} IS NULL")
328
347
  end
348
+
349
+ options = { limit: column.limit, precision: column.precision, scale: column.scale }
350
+
329
351
  sql_alter = [
330
352
  "ALTER TABLE #{quoted_table}",
331
- "ALTER COLUMN #{quoted_column} #{type_to_sql(column.type, limit: column.limit, precision: column.precision, scale: column.scale)}",
332
- (' NOT NULL' unless null)
353
+ "ALTER COLUMN #{quoted_column} #{type_to_sql(column.type, **options)}",
354
+ ('NOT NULL' unless null)
333
355
  ]
334
356
 
335
- execute(sql_alter.join(' '))
357
+ execute(sql_alter.compact.join(' '))
336
358
  end
337
359
 
338
360
  def update_table_definition(table_name, base) #:nodoc:
@@ -345,11 +367,14 @@ module ActiveRecord
345
367
  MSSQL::SchemaCreation.new(self)
346
368
  end
347
369
 
348
- def create_table_definition(*args)
349
- MSSQL::TableDefinition.new(self, *args)
370
+ def create_table_definition(name, **options)
371
+ MSSQL::TableDefinition.new(self, name, **options)
350
372
  end
351
373
 
352
374
  def new_column_from_field(table_name, field)
375
+ # NOTE: this method is used by the columns method in the abstract Class
376
+ # to map column_definitions. It would be good if column_definitions is
377
+ # implemented in ruby
353
378
  field
354
379
  end
355
380
 
@@ -27,10 +27,10 @@ module ActiveRecord
27
27
  module RealTransactionExt
28
28
  attr_reader :initial_transaction_isolation
29
29
 
30
- def initialize(connection, options, *args)
30
+ def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
31
31
  @connection = connection
32
32
 
33
- if options[:isolation]
33
+ if isolation
34
34
  @initial_transaction_isolation = current_transaction_isolation
35
35
  end
36
36
 
@@ -13,9 +13,9 @@ module ActiveRecord
13
13
  return %("#{value}") if value.acts_like?(:string)
14
14
 
15
15
  if value.usec > 0
16
- %("#{value.to_s(:db)}.#{value.usec.to_s.remove(/0+$/)}")
16
+ %("#{value.to_fs(:db)}.#{value.usec.to_s.remove(/0+$/)}")
17
17
  else
18
- %("#{value.to_s(:db)}")
18
+ %("#{value.to_fs(:db)}")
19
19
  end
20
20
  end
21
21
 
@@ -54,9 +54,9 @@ module ActiveRecord
54
54
  return %("#{value}") if value.acts_like?(:string)
55
55
 
56
56
  if value.usec > 0
57
- %("#{value.to_s(:db)}.#{value.usec.to_s.remove(/0+$/)}")
57
+ %("#{value.to_fs(:db)}.#{value.usec.to_s.remove(/0+$/)}")
58
58
  else
59
- %("#{value.to_s(:db)}")
59
+ %("#{value.to_fs(:db)}")
60
60
  end
61
61
  end
62
62
 
@@ -100,9 +100,9 @@ module ActiveRecord
100
100
  return %("#{value}") if value.acts_like?(:string)
101
101
 
102
102
  if value.usec > 0
103
- %("#{value.to_s(:db)}.#{value.usec.to_s.remove(/0+$/)}")
103
+ %("#{value.to_fs(:db)}.#{value.usec.to_s.remove(/0+$/)}")
104
104
  else
105
- %("#{value.to_s(:db)}")
105
+ %("#{value.to_fs(:db)}")
106
106
  end
107
107
  end
108
108
 
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  end
36
36
 
37
37
  class Money < Decimal
38
- def initialize(options = {})
38
+ def initialize(precision: nil, limit: nil, scale: nil)
39
39
  super
40
40
  @precision = 19
41
41
  @scale = 4
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  end
47
47
 
48
48
  class SmallMoney < Decimal
49
- def initialize(options = {})
49
+ def initialize(precision: nil, limit: nil, scale: nil)
50
50
  super
51
51
  @precision = 10
52
52
  @scale = 4
data/lib/arjdbc/mssql.rb CHANGED
@@ -6,4 +6,4 @@ module ArJdbc
6
6
  MsSQL = MSSQL # compatibility with 1.2
7
7
  end
8
8
 
9
- ArJdbc.warn_unsupported_adapter 'mssql', [6, 1] # warns on AR >= 4.2
9
+ ArJdbc.warn_unsupported_adapter 'mssql', [7, 0] # warns on AR >= 4.2
@@ -107,7 +107,8 @@ module ActiveRecord
107
107
 
108
108
  # Reloading the type map in abstract/statement_cache.rb blows up postgres
109
109
  def clear_cache!
110
- reload_type_map
110
+ # FIXME: This seems to have disappeared in Rails 7?
111
+ # reload_type_map
111
112
  super
112
113
  end
113
114
 
@@ -40,7 +40,7 @@ module ArJdbc
40
40
  return if @@_initialized; @@_initialized = true
41
41
 
42
42
  require 'arjdbc/util/serialized_attributes'
43
- Util::SerializedAttributes.setup /LOB\(|LOB$/i, 'after_save_with_oracle_lob'
43
+ Util::SerializedAttributes.setup %r{LOB\(|LOB$}i, 'after_save_with_oracle_lob'
44
44
 
45
45
  unless ActiveRecord::ConnectionAdapters::AbstractAdapter.
46
46
  instance_methods(false).detect { |m| m.to_s == "prefetch_primary_key?" }
@@ -285,7 +285,7 @@ module ArJdbc
285
285
  execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})"
286
286
  end
287
287
  end
288
- end if AR42
288
+ end
289
289
 
290
290
  # @private
291
291
  def add_index_options(table_name, column_name, options = {})
@@ -309,7 +309,7 @@ module ArJdbc
309
309
 
310
310
  quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
311
311
  [ index_name, index_type, quoted_column_names, tablespace, index_options ]
312
- end if AR42
312
+ end
313
313
 
314
314
  # @override
315
315
  def remove_index(table_name, options = {})
@@ -327,12 +327,7 @@ module ArJdbc
327
327
  end
328
328
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(index_name)}" rescue nil
329
329
  execute "DROP INDEX #{quote_column_name(index_name)}"
330
- end if AR42
331
-
332
- # @private
333
- def remove_index(table_name, options = {})
334
- execute "DROP INDEX #{index_name(table_name, options)}"
335
- end unless AR42
330
+ end
336
331
 
337
332
  def change_column_default(table_name, column_name, default)
338
333
  execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
@@ -361,25 +356,11 @@ module ArJdbc
361
356
  "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
362
357
  end
363
358
 
364
- if ActiveRecord::VERSION::MAJOR >= 4
365
-
366
359
  # @override
367
360
  def remove_column(table_name, column_name, type = nil, options = {})
368
361
  do_remove_column(table_name, column_name)
369
362
  end
370
363
 
371
- else
372
-
373
- # @override
374
- def remove_column(table_name, *column_names)
375
- for column_name in column_names.flatten
376
- do_remove_column(table_name, column_name)
377
- end
378
- end
379
- alias remove_columns remove_column
380
-
381
- end
382
-
383
364
  def do_remove_column(table_name, column_name)
384
365
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
385
366
  end
@@ -21,6 +21,7 @@ require 'arjdbc/abstract/transaction_support'
21
21
  require 'arjdbc/postgresql/base/array_decoder'
22
22
  require 'arjdbc/postgresql/base/array_encoder'
23
23
  require 'arjdbc/postgresql/name'
24
+ require 'active_model'
24
25
 
25
26
  module ArJdbc
26
27
  # Strives to provide Rails built-in PostgreSQL adapter (API) compatibility.
@@ -81,7 +82,7 @@ module ArJdbc
81
82
  # If using Active Record's time zone support configure the connection to return
82
83
  # TIMESTAMP WITH ZONE types in UTC.
83
84
  # (SET TIME ZONE does not use an equals sign like other SET variables)
84
- if ActiveRecord::Base.default_timezone == :utc
85
+ if ActiveRecord.default_timezone == :utc
85
86
  execute("SET time zone 'UTC'", 'SCHEMA')
86
87
  elsif tz = local_tz
87
88
  execute("SET time zone '#{tz}'", 'SCHEMA')
@@ -320,6 +321,38 @@ module ArJdbc
320
321
  exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
321
322
  end
322
323
 
324
+ # Returns a list of defined enum types, and their values.
325
+ def enum_types
326
+ query = <<~SQL
327
+ SELECT
328
+ type.typname AS name,
329
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
330
+ FROM pg_enum AS enum
331
+ JOIN pg_type AS type
332
+ ON (type.oid = enum.enumtypid)
333
+ GROUP BY type.typname;
334
+ SQL
335
+ exec_query(query, "SCHEMA").cast_values
336
+ end
337
+
338
+ # Given a name and an array of values, creates an enum type.
339
+ def create_enum(name, values)
340
+ sql_values = values.map { |s| "'#{s}'" }.join(", ")
341
+ query = <<~SQL
342
+ DO $$
343
+ BEGIN
344
+ IF NOT EXISTS (
345
+ SELECT 1 FROM pg_type t
346
+ WHERE t.typname = '#{name}'
347
+ ) THEN
348
+ CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
349
+ END IF;
350
+ END
351
+ $$;
352
+ SQL
353
+ exec_query(query)
354
+ end
355
+
323
356
  # Returns the configured supported identifier length supported by PostgreSQL
324
357
  def max_identifier_length
325
358
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
@@ -672,6 +705,37 @@ module ActiveRecord::ConnectionAdapters
672
705
  class PostgreSQLAdapter < AbstractAdapter
673
706
  class_attribute :create_unlogged_tables, default: false
674
707
 
708
+ ##
709
+ # :singleton-method:
710
+ # PostgreSQL allows the creation of "unlogged" tables, which do not record
711
+ # data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
712
+ # but significantly increases the risk of data loss if the database
713
+ # crashes. As a result, this should not be used in production
714
+ # environments. If you would like all created tables to be unlogged in
715
+ # the test environment you can add the following line to your test.rb
716
+ # file:
717
+ #
718
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
719
+ class_attribute :create_unlogged_tables, default: false
720
+
721
+ ##
722
+ # :singleton-method:
723
+ # PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+
724
+ # in migrations, Rails will translate this to a PostgreSQL "timestamp without time zone".
725
+ # Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
726
+ # store DateTimes as "timestamp with time zone":
727
+ #
728
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
729
+ #
730
+ # Or if you are adding a custom type:
731
+ #
732
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "my_custom_type_name" }
733
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type
734
+ #
735
+ # If you're using +:ruby+ as your +config.active_record.schema_format+ and you change this
736
+ # setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
737
+ class_attribute :datetime_type, default: :timestamp
738
+
675
739
  # Try to use as much of the built in postgres logic as possible
676
740
  # maybe someday we can extend the actual adapter
677
741
  include ActiveRecord::ConnectionAdapters::PostgreSQL::ReferentialIntegrity
@@ -735,11 +799,96 @@ module ActiveRecord::ConnectionAdapters
735
799
 
736
800
  private
737
801
 
738
- # Prepared statements aren't schema aware so we need to make sure we
739
- # store different PreparedStatement objects for different schemas
802
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
803
+
804
+ def execute_and_clear(sql, name, binds, prepare: false, async: false)
805
+ sql = transform_query(sql)
806
+ check_if_write_query(sql)
807
+
808
+ if !prepare || without_prepared_statement?(binds)
809
+ result = exec_no_cache(sql, name, binds, async: async)
810
+ else
811
+ result = exec_cache(sql, name, binds, async: async)
812
+ end
813
+ begin
814
+ ret = yield result
815
+ ensure
816
+ # Is this really result in AR PG?
817
+ # result.clear
818
+ end
819
+ ret
820
+ end
821
+
822
+ def exec_no_cache(sql, name, binds, async: false)
823
+ materialize_transactions
824
+ mark_transaction_written_if_write(sql)
825
+
826
+ # make sure we carry over any changes to ActiveRecord.default_timezone that have been
827
+ # made since we established the connection
828
+ update_typemap_for_default_timezone
829
+
830
+ type_casted_binds = type_casted_binds(binds)
831
+ log(sql, name, binds, type_casted_binds, async: async) do
832
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
833
+ @connection.exec_params(sql, type_casted_binds)
834
+ end
835
+ end
836
+ end
837
+
838
+ def exec_cache(sql, name, binds, async: false)
839
+ materialize_transactions
840
+ mark_transaction_written_if_write(sql)
841
+ update_typemap_for_default_timezone
842
+
843
+ stmt_key = prepare_statement(sql, binds)
844
+ type_casted_binds = type_casted_binds(binds)
845
+
846
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
847
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
848
+ @connection.exec_prepared(stmt_key, type_casted_binds)
849
+ end
850
+ end
851
+ rescue ActiveRecord::StatementInvalid => e
852
+ raise unless is_cached_plan_failure?(e)
853
+
854
+ # Nothing we can do if we are in a transaction because all commands
855
+ # will raise InFailedSQLTransaction
856
+ if in_transaction?
857
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
858
+ else
859
+ @lock.synchronize do
860
+ # outside of transactions we can simply flush this query and retry
861
+ @statements.delete sql_key(sql)
862
+ end
863
+ retry
864
+ end
865
+ end
866
+
867
+ # Annoyingly, the code for prepared statements whose return value may
868
+ # have changed is FEATURE_NOT_SUPPORTED.
869
+ #
870
+ # This covers various different error types so we need to do additional
871
+ # work to classify the exception definitively as a
872
+ # ActiveRecord::PreparedStatementCacheExpired
873
+ #
874
+ # Check here for more details:
875
+ # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
876
+ def is_cached_plan_failure?(e)
877
+ pgerror = e.cause
878
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
879
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
880
+ rescue
881
+ false
882
+ end
883
+
884
+ def in_transaction?
885
+ open_transactions > 0
886
+ end
887
+
888
+ # Returns the statement identifier for the client side cache
889
+ # of statements
740
890
  def sql_key(sql)
741
891
  "#{schema_search_path}-#{sql}"
742
892
  end
743
-
744
893
  end
745
894
  end