activerecord-jdbc-adapter 70.0-java → 70.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/activerecord-jdbc-adapter.gemspec +1 -1
  4. data/lib/arel/visitors/compat.rb +5 -33
  5. data/lib/arel/visitors/h2.rb +1 -13
  6. data/lib/arel/visitors/hsqldb.rb +1 -21
  7. data/lib/arel/visitors/sql_server.rb +2 -103
  8. data/lib/arjdbc/abstract/database_statements.rb +8 -0
  9. data/lib/arjdbc/discover.rb +0 -67
  10. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  11. data/lib/arjdbc/jdbc/column.rb +1 -26
  12. data/lib/arjdbc/jdbc.rb +0 -7
  13. data/lib/arjdbc/oracle/adapter.rb +3 -22
  14. data/lib/arjdbc/postgresql/adapter.rb +152 -3
  15. data/lib/arjdbc/postgresql/oid_types.rb +155 -108
  16. data/lib/arjdbc/sqlite3/adapter.rb +27 -18
  17. data/lib/arjdbc/tasks/database_tasks.rb +0 -15
  18. data/lib/arjdbc/util/serialized_attributes.rb +0 -22
  19. data/lib/arjdbc/version.rb +1 -1
  20. data/rakelib/02-test.rake +3 -18
  21. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +5 -0
  22. metadata +2 -35
  23. data/lib/active_record/connection_adapters/as400_adapter.rb +0 -2
  24. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -1
  25. data/lib/active_record/connection_adapters/derby_adapter.rb +0 -1
  26. data/lib/active_record/connection_adapters/informix_adapter.rb +0 -1
  27. data/lib/arel/visitors/db2.rb +0 -137
  28. data/lib/arel/visitors/derby.rb +0 -112
  29. data/lib/arel/visitors/firebird.rb +0 -79
  30. data/lib/arjdbc/db2/adapter.rb +0 -808
  31. data/lib/arjdbc/db2/as400.rb +0 -142
  32. data/lib/arjdbc/db2/column.rb +0 -131
  33. data/lib/arjdbc/db2/connection_methods.rb +0 -48
  34. data/lib/arjdbc/db2.rb +0 -4
  35. data/lib/arjdbc/derby/active_record_patch.rb +0 -13
  36. data/lib/arjdbc/derby/adapter.rb +0 -521
  37. data/lib/arjdbc/derby/connection_methods.rb +0 -20
  38. data/lib/arjdbc/derby/schema_creation.rb +0 -15
  39. data/lib/arjdbc/derby.rb +0 -3
  40. data/lib/arjdbc/firebird/adapter.rb +0 -413
  41. data/lib/arjdbc/firebird/connection_methods.rb +0 -23
  42. data/lib/arjdbc/firebird.rb +0 -4
  43. data/lib/arjdbc/informix/adapter.rb +0 -139
  44. data/lib/arjdbc/informix/connection_methods.rb +0 -9
  45. data/lib/arjdbc/sybase/adapter.rb +0 -47
  46. data/lib/arjdbc/sybase.rb +0 -2
  47. data/lib/arjdbc/tasks/db2_database_tasks.rb +0 -104
  48. data/lib/arjdbc/tasks/derby_database_tasks.rb +0 -95
  49. data/src/java/arjdbc/derby/DerbyModule.java +0 -178
  50. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +0 -152
  51. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +0 -174
  52. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79e198f6d1579290fb0eeb3597282367757655bcff41b3e848a788b557573706
4
- data.tar.gz: 935a5c332bf06b2d77ca47ae6fc92128cbf450e1c50d64a293ef02ef2a04ad07
3
+ metadata.gz: '095c3d28051b253315cdd1bec95a83683f3240b0f3315cfb8f9c3c4c0d124f41'
4
+ data.tar.gz: 13b5d48e4c1b0b16811da5a361cfd9bb687727b5bbad2f5b750540b0232408ee
5
5
  SHA512:
6
- metadata.gz: b124009ca502a4e24c88a5fc1ff0a8a307e1850db491ae0cf12f0388753cccb575fff87f2421ec725dc9a945e335d2f6f8de1c7734ee6887f887d380e6726a68
7
- data.tar.gz: 82faa67a43955a2ff946510c3a77826365eb6dbd3adc7e7c81eecd83da03ce10ca37f0100a32b7089a3ef88014797a2960561851c701d695fe943f11f2712aae
6
+ metadata.gz: bc4801f970c8651e0ce8076b6544bf56d0e4decd77a9463439a8d5692f767aab896786680197c25b52d510465837290604970689b1b6cba26708385038f1fa39
7
+ data.tar.gz: 47e1df847914baefffadb80537b1ffd9dfcb218b2f5bfd26addefad934be7b31a0ade446c5838bea88b81897c34b13a7cef8e6b840540e89f4627f950949549c
data/Rakefile CHANGED
@@ -26,7 +26,7 @@
26
26
 
27
27
  require 'rake/clean'
28
28
 
29
- CLEAN.include 'derby*', 'test.db.*', '*test.sqlite3', 'test/reports'
29
+ CLEAN.include 'test.db.*', '*test.sqlite3', 'test/reports'
30
30
  CLEAN.include 'lib/**/*.jar', 'MANIFEST.MF', '*.log', 'target/*'
31
31
 
32
32
  task :default => :jar # RubyGems extention will do a bare `rake' e.g. :
@@ -47,6 +47,6 @@ Gem::Specification.new do |gem|
47
47
  #gem.add_development_dependency 'test-unit-context', '>= 0.3.0'
48
48
  #gem.add_development_dependency 'mocha', '~> 0.13.1'
49
49
 
50
- gem.rdoc_options = ["--main", "README.md", "-SHN", "-f", "darkfish"]
50
+ gem.rdoc_options = ["--main", "README.md"]
51
51
  end
52
52
 
@@ -4,44 +4,16 @@ module Arel
4
4
 
5
5
  protected
6
6
 
7
- if ToSql.instance_method('visit').arity == 1
8
- def do_visit(x, a); visit(x); end # a = nil
9
- else # > AREL 4.0
10
- def do_visit(x, a); visit(x, a); end
11
- end
7
+ def do_visit(x, a); visit(x, a); end
12
8
 
13
- if ToSql.instance_method('visit_Arel_Nodes_SelectCore').arity == 1
14
- def do_visit_select_core(x, a) # a = nil
15
- visit_Arel_Nodes_SelectCore(x)
16
- end
17
- else # > AREL 4.0
18
- def do_visit_select_core(x, a)
19
- visit_Arel_Nodes_SelectCore(x, a)
20
- end
9
+ def do_visit_select_core(x, a)
10
+ visit_Arel_Nodes_SelectCore(x, a)
21
11
  end
22
12
 
23
13
  private
24
14
 
25
- if ArJdbc::AR42
26
- if Arel::VERSION < '6.0.2'
27
- def limit_for(limit_or_node)
28
- if limit_or_node.respond_to?(:expr)
29
- expr = limit_or_node.expr
30
- # NOTE(uwe): Different behavior for Arel 6.0.0 and 6.0.2
31
- expr.respond_to?(:value) ? expr.value.to_i : expr.to_i
32
- else
33
- limit_or_node
34
- end
35
- end
36
- else
37
- def limit_for(limit_or_node)
38
- limit_or_node.respond_to?(:expr) ? limit_or_node.expr.to_i : limit_or_node
39
- end
40
- end
41
- else
42
- def limit_for(limit_or_node)
43
- limit_or_node.respond_to?(:expr) ? limit_or_node.expr.to_i : limit_or_node
44
- end
15
+ def limit_for(limit_or_node)
16
+ limit_or_node.respond_to?(:expr) ? limit_or_node.expr.to_i : limit_or_node
45
17
  end
46
18
  module_function :limit_for
47
19
 
@@ -7,19 +7,7 @@ module Arel
7
7
  def visit_Arel_Nodes_SelectStatement(o, *)
8
8
  o.limit ||= Arel::Nodes::Limit.new(-1) if o.offset
9
9
  super
10
- end if ArJdbc::AR42
11
-
12
- def limit_offset sql, o
13
- offset = o.offset || 0
14
- offset = offset.expr unless (offset.nil? || offset == 0)
15
- if limit = o.limit
16
- "SELECT LIMIT #{offset} #{limit_for(limit)} #{sql[7..-1]}"
17
- elsif offset > 0
18
- "SELECT LIMIT #{offset} -1 #{sql[7..-1]}" # removes "SELECT "
19
- else
20
- sql
21
- end
22
- end unless ArJdbc::AR42
10
+ end
23
11
  end
24
12
  end
25
13
  end
@@ -6,27 +6,7 @@ module Arel
6
6
  def visit_Arel_Nodes_SelectStatement(o, *)
7
7
  o.limit ||= Arel::Nodes::Limit.new(0) if o.offset
8
8
  super
9
- end if ArJdbc::AR42
10
-
11
- def visit_Arel_Nodes_SelectStatement o, a = nil
12
- sql = limit_offset(o.cores.map { |x| do_visit_select_core x, a }.join, o)
13
- sql << " ORDER BY #{o.orders.map { |x| do_visit x, a }.join(', ')}" unless o.orders.empty?
14
- sql
15
- end unless ArJdbc::AR42
16
-
17
- private
18
-
19
- def limit_offset sql, o
20
- offset = o.offset || 0
21
- offset = offset.expr unless (offset.nil? || offset == 0)
22
- if limit = o.limit
23
- "SELECT LIMIT #{offset} #{limit_for(limit)} #{sql[7..-1]}"
24
- elsif offset > 0
25
- "SELECT LIMIT #{offset} 0 #{sql[7..-1]}" # removes "SELECT "
26
- else
27
- sql
28
- end
29
- end unless ArJdbc::AR42
9
+ end
30
10
  end
31
11
  end
32
12
  end
@@ -11,44 +11,6 @@ module Arel
11
11
 
12
12
  private
13
13
 
14
- def visit_Arel_Nodes_SelectStatement(*args) # [o] AR <= 4.0 [o, a] on 4.1
15
- o, a = args.first, args.last
16
-
17
- return _visit_Arel_Nodes_SelectStatement(*args) if ! o.limit && ! o.offset
18
-
19
- unless o.orders.empty?
20
- select_order_by = do_visit_columns o.orders, a, 'ORDER BY '
21
- end
22
-
23
- select_count = false; sql = ''
24
- o.cores.each do |x|
25
- x = x.dup
26
- core_order_by = select_order_by || determine_order_by(x, a)
27
- if select_count? x
28
- x.projections = [
29
- Arel::Nodes::SqlLiteral.new(core_order_by ? over_row_num(core_order_by) : '*')
30
- ]
31
- select_count = true
32
- else
33
- # NOTE: this should really be added here and we should built the
34
- # wrapping SQL but than #replace_limit_offset! assumes it does that
35
- # ... MS-SQL adapter code seems to be 'hacked' by a lot of people
36
- #x.projections << Arel::Nodes::SqlLiteral.new over_row_num(order_by)
37
- end
38
- sql << do_visit_select_core(x, a)
39
- end
40
-
41
- #sql = "SELECT _t.* FROM (#{sql}) as _t WHERE #{get_offset_limit_clause(o)}"
42
- select_order_by ||= "ORDER BY #{@connection.determine_order_clause(sql)}"
43
- replace_limit_offset!(sql, limit_for(o.limit), o.offset && o.offset.value.to_i, select_order_by)
44
-
45
- sql = "SELECT COUNT(*) AS count_id FROM (#{sql}) AS subquery" if select_count
46
-
47
- add_lock!(sql, :lock => o.lock && true)
48
-
49
- sql
50
- end unless ArJdbc::AR42
51
-
52
14
  # @private
53
15
  MAX_LIMIT_VALUE = 9_223_372_036_854_775_807
54
16
 
@@ -60,14 +22,6 @@ module Arel
60
22
  super
61
23
  end
62
24
 
63
- def visit_Arel_Nodes_Lock o, a = nil
64
- # MS-SQL doesn't support "SELECT...FOR UPDATE". Instead, it needs
65
- # WITH(ROWLOCK,UPDLOCK) specified after each table in the FROM clause.
66
- #
67
- # we return nothing here and add the appropriate stuff with #add_lock!
68
- #do_visit o.expr, a
69
- end unless ArJdbc::AR42
70
-
71
25
  def visit_Arel_Nodes_Top o, a = nil
72
26
  # `top` wouldn't really work here:
73
27
  # User.select("distinct first_name").limit(10)
@@ -76,29 +30,6 @@ module Arel
76
30
  a || ''
77
31
  end
78
32
 
79
- def visit_Arel_Nodes_Limit o, a = nil
80
- "TOP (#{do_visit o.expr, a})"
81
- end unless ArJdbc::AR42
82
-
83
- def visit_Arel_Nodes_Ordering o, a = nil
84
- expr = do_visit o.expr, a
85
- if o.respond_to?(:direction)
86
- "#{expr} #{o.ascending? ? 'ASC' : 'DESC'}"
87
- else
88
- expr
89
- end
90
- end unless ArJdbc::AR42
91
-
92
- def visit_Arel_Nodes_Bin o, a = nil
93
- expr = o.expr; sql = do_visit expr, a
94
- if expr.respond_to?(:val) && expr.val.is_a?(Numeric)
95
- sql
96
- else
97
- sql << " #{::ArJdbc::MSSQL.cs_equality_operator} "
98
- sql
99
- end
100
- end unless ArJdbc::AR42
101
-
102
33
  private
103
34
 
104
35
  def self.possibly_private_method_defined?(name)
@@ -140,25 +71,7 @@ module Arel
140
71
  visit(x, sql); sql << ', ' unless i == last
141
72
  end
142
73
  sql.value
143
- end if ArJdbc::AR42
144
-
145
- def do_visit_columns(colls, a, sql)
146
- non_simple_order = /\sASC|\sDESC|\sCASE|\sCOLLATE|[\.,\[\(]/i # MIN(width)
147
-
148
- last = colls.size - 1
149
- colls.each_with_index do |x, i|
150
- coll = do_visit(x, a)
151
-
152
- if coll !~ non_simple_order && coll.to_i == 0
153
- sql << @connection.quote_column_name(coll)
154
- else
155
- sql << coll
156
- end
157
-
158
- sql << ', ' unless i == last
159
- end
160
- sql
161
- end if Arel::VERSION < '4.0.0'
74
+ end
162
75
 
163
76
  def over_row_num order_by
164
77
  "ROW_NUMBER() OVER (#{order_by}) as _row_num"
@@ -174,20 +87,6 @@ module Arel
174
87
  end
175
88
  end
176
89
 
177
- def table_from_select_core core
178
- table_finder = lambda do |x|
179
- case x
180
- when Arel::Table
181
- x
182
- when Arel::Nodes::SqlLiteral
183
- Arel::Table.new(x, @engine)
184
- when Arel::Nodes::Join
185
- table_finder.call(x.left)
186
- end
187
- end
188
- table_finder.call(core.froms)
189
- end if ActiveRecord::VERSION::STRING < '3.2'
190
-
191
90
  def primary_key_from_table t
192
91
  return unless t
193
92
  return t.primary_key if t.primary_key
@@ -219,7 +118,7 @@ module Arel
219
118
  include ArJdbc::MSSQL::LimitHelpers::SqlServer2000ReplaceLimitOffset
220
119
  end
221
120
 
222
- load 'arel/visitors/sql_server/ng42.rb' if ArJdbc::AR42
121
+ load 'arel/visitors/sql_server/ng42.rb'
223
122
 
224
123
  end
225
124
  end
@@ -10,6 +10,8 @@ module ArJdbc
10
10
  NO_BINDS = [].freeze
11
11
 
12
12
  def exec_insert(sql, name = nil, binds = NO_BINDS, pk = nil, sequence_name = nil)
13
+ sql = transform_query(sql)
14
+
13
15
  if preventing_writes?
14
16
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
15
17
  end
@@ -31,6 +33,8 @@ module ArJdbc
31
33
  # It appears that at this point (AR 5.0) "prepare" should only ever be true
32
34
  # if prepared statements are enabled
33
35
  def exec_query(sql, name = nil, binds = NO_BINDS, prepare: false, async: false)
36
+ sql = transform_query(sql)
37
+
34
38
  if preventing_writes? && write_query?(sql)
35
39
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
36
40
  end
@@ -52,6 +56,8 @@ module ArJdbc
52
56
  end
53
57
 
54
58
  def exec_update(sql, name = nil, binds = NO_BINDS)
59
+ sql = transform_query(sql)
60
+
55
61
  if preventing_writes?
56
62
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
57
63
  end
@@ -70,6 +76,8 @@ module ArJdbc
70
76
  alias :exec_delete :exec_update
71
77
 
72
78
  def execute(sql, name = nil, async: false)
79
+ sql = transform_query(sql)
80
+
73
81
  if preventing_writes? && write_query?(sql)
74
82
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
75
83
  end
@@ -23,26 +23,6 @@ module ArJdbc
23
23
  require('arjdbc/sqlite3') || true if name =~ /sqlite/i
24
24
  end
25
25
 
26
- # Other supported adapters :
27
-
28
- extension :Derby do |name, config|
29
- if name =~ /derby/i
30
- require 'arjdbc/derby'
31
-
32
- if config && config[:username].nil? # set the database schema name (:username) :
33
- begin
34
- ArJdbc.with_meta_data_from_data_source_if_any(config) do
35
- |meta_data| config[:username] = meta_data.getUserName
36
- end
37
- rescue => e
38
- ArJdbc.warn("failed to set :username from (Derby) database meda-data: #{e.inspect}")
39
- end
40
- end
41
-
42
- true
43
- end
44
- end
45
-
46
26
  extension :H2 do |name|
47
27
  require('arjdbc/h2') || true if name =~ /\.h2\./i
48
28
  end
@@ -54,51 +34,4 @@ module ArJdbc
54
34
  extension :MSSQL do |name|
55
35
  require('arjdbc/mssql') || true if name =~ /sqlserver|tds|Microsoft SQL/i
56
36
  end
57
-
58
- extension :DB2 do |name, config|
59
- if name =~ /db2/i && name !~ /as\/?400/i && config[:url] !~ /^jdbc:derby:net:/
60
- require 'arjdbc/db2'
61
- true
62
- end
63
- end
64
-
65
- extension :AS400 do |name, config|
66
- # The native JDBC driver always returns "DB2 UDB for AS/400"
67
- if name =~ /as\/?400/i
68
- require 'arjdbc/db2'
69
- require 'arjdbc/db2/as400'
70
- true
71
- end
72
- end
73
-
74
- # NOTE: following ones are likely getting deprecated :
75
-
76
- extension :FireBird do |name|
77
- if name =~ /firebird/i
78
- require 'arjdbc/firebird'
79
- true
80
- end
81
- end
82
-
83
- extension :Sybase do |name|
84
- if name =~ /sybase|tds/i
85
- require 'arjdbc/sybase'
86
- true
87
- end
88
- end
89
-
90
- extension :Informix do |name|
91
- if name =~ /informix/i
92
- require 'arjdbc/informix'
93
- true
94
- end
95
- end
96
-
97
- extension :Mimer do |name|
98
- if name =~ /mimer/i
99
- require 'arjdbc/mimer'
100
- true
101
- end
102
- end
103
-
104
37
  end
Binary file
@@ -28,21 +28,6 @@ module ActiveRecord
28
28
  end
29
29
  end
30
30
 
31
- if ArJdbc::AR52
32
- # undefined method `cast' for #<ActiveRecord::ConnectionAdapters::SqlTypeMetadata> on AR52
33
- else
34
- default = args[0].cast(default)
35
-
36
- sql_type = args.delete_at(1)
37
- type = args.delete_at(0)
38
-
39
- args.unshift(SqlTypeMetadata.new(:sql_type => sql_type, :type => type))
40
- end
41
-
42
- # super <= 4.1: (name, default, sql_type = nil, null = true)
43
- # super >= 4.2: (name, default, cast_type, sql_type = nil, null = true)
44
- # super >= 5.0: (name, default, sql_type_metadata = nil, null = true)
45
-
46
31
  super(name, default, *args)
47
32
  init_column(name, default, *args)
48
33
  end
@@ -80,18 +65,8 @@ module ActiveRecord
80
65
  end
81
66
 
82
67
  class << self
83
-
84
- include Jdbc::TypeCast if ::ActiveRecord::VERSION::STRING >= '4.2'
85
-
86
- if ActiveRecord::VERSION::MAJOR > 3 && ActiveRecord::VERSION::STRING < '4.2'
87
-
88
- # @private provides compatibility between AR 3.x/4.0 API
89
- def string_to_date(value); value_to_date(value) end
90
-
91
- end
92
-
68
+ include Jdbc::TypeCast
93
69
  end
94
-
95
70
  end
96
71
  end
97
72
  end
data/lib/arjdbc/jdbc.rb CHANGED
@@ -1,13 +1,6 @@
1
1
  require 'active_support/deprecation'
2
2
 
3
3
  module ArJdbc
4
-
5
- # @private
6
- AR42 = ::ActiveRecord::VERSION::STRING >= '4.2'
7
-
8
- # @private
9
- AR52 = ::ActiveRecord::VERSION::STRING >= '5.2'
10
-
11
4
  class << self
12
5
 
13
6
  # @private Internal API
@@ -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.
@@ -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