activerecord-jdbc-alt-adapter 61.2.0-java → 70.0.0.rc2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +118 -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 +4 -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/quoting.rb +26 -27
  27. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  28. data/lib/arjdbc/mssql/schema_definitions.rb +32 -17
  29. data/lib/arjdbc/mssql/schema_dumper.rb +13 -1
  30. data/lib/arjdbc/mssql/schema_statements.rb +61 -36
  31. data/lib/arjdbc/mssql/transaction.rb +2 -2
  32. data/lib/arjdbc/mssql/types/date_and_time_types.rb +6 -6
  33. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -2
  34. data/lib/arjdbc/mssql.rb +1 -1
  35. data/lib/arjdbc/mysql/adapter.rb +2 -1
  36. data/lib/arjdbc/oracle/adapter.rb +4 -23
  37. data/lib/arjdbc/postgresql/adapter.rb +152 -4
  38. data/lib/arjdbc/postgresql/oid_types.rb +142 -106
  39. data/lib/arjdbc/sqlite3/adapter.rb +132 -88
  40. data/lib/arjdbc/tasks/database_tasks.rb +0 -12
  41. data/lib/arjdbc/util/serialized_attributes.rb +0 -22
  42. data/lib/arjdbc/util/table_copier.rb +2 -1
  43. data/lib/arjdbc/version.rb +1 -1
  44. data/rakelib/02-test.rake +3 -18
  45. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +17 -2
  46. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +5 -0
  47. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +33 -0
  48. metadata +9 -40
  49. data/lib/active_record/connection_adapters/as400_adapter.rb +0 -2
  50. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -1
  51. data/lib/active_record/connection_adapters/derby_adapter.rb +0 -1
  52. data/lib/active_record/connection_adapters/informix_adapter.rb +0 -1
  53. data/lib/arel/visitors/db2.rb +0 -137
  54. data/lib/arel/visitors/derby.rb +0 -112
  55. data/lib/arel/visitors/firebird.rb +0 -79
  56. data/lib/arjdbc/db2/adapter.rb +0 -808
  57. data/lib/arjdbc/db2/as400.rb +0 -142
  58. data/lib/arjdbc/db2/column.rb +0 -131
  59. data/lib/arjdbc/db2/connection_methods.rb +0 -48
  60. data/lib/arjdbc/db2.rb +0 -4
  61. data/lib/arjdbc/derby/active_record_patch.rb +0 -13
  62. data/lib/arjdbc/derby/adapter.rb +0 -521
  63. data/lib/arjdbc/derby/connection_methods.rb +0 -20
  64. data/lib/arjdbc/derby/schema_creation.rb +0 -15
  65. data/lib/arjdbc/derby.rb +0 -3
  66. data/lib/arjdbc/firebird/adapter.rb +0 -413
  67. data/lib/arjdbc/firebird/connection_methods.rb +0 -23
  68. data/lib/arjdbc/firebird.rb +0 -4
  69. data/lib/arjdbc/informix/adapter.rb +0 -139
  70. data/lib/arjdbc/informix/connection_methods.rb +0 -9
  71. data/lib/arjdbc/sybase/adapter.rb +0 -47
  72. data/lib/arjdbc/sybase.rb +0 -2
  73. data/lib/arjdbc/tasks/db2_database_tasks.rb +0 -104
  74. data/lib/arjdbc/tasks/derby_database_tasks.rb +0 -95
  75. data/src/java/arjdbc/derby/DerbyModule.java +0 -178
  76. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +0 -152
  77. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +0 -174
  78. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +0 -75
@@ -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
@@ -81,7 +81,7 @@ module ArJdbc
81
81
  # If using Active Record's time zone support configure the connection to return
82
82
  # TIMESTAMP WITH ZONE types in UTC.
83
83
  # (SET TIME ZONE does not use an equals sign like other SET variables)
84
- if ActiveRecord::Base.default_timezone == :utc
84
+ if ActiveRecord.default_timezone == :utc
85
85
  execute("SET time zone 'UTC'", 'SCHEMA')
86
86
  elsif tz = local_tz
87
87
  execute("SET time zone '#{tz}'", 'SCHEMA')
@@ -320,6 +320,38 @@ module ArJdbc
320
320
  exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
321
321
  end
322
322
 
323
+ # Returns a list of defined enum types, and their values.
324
+ def enum_types
325
+ query = <<~SQL
326
+ SELECT
327
+ type.typname AS name,
328
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
329
+ FROM pg_enum AS enum
330
+ JOIN pg_type AS type
331
+ ON (type.oid = enum.enumtypid)
332
+ GROUP BY type.typname;
333
+ SQL
334
+ exec_query(query, "SCHEMA").cast_values
335
+ end
336
+
337
+ # Given a name and an array of values, creates an enum type.
338
+ def create_enum(name, values)
339
+ sql_values = values.map { |s| "'#{s}'" }.join(", ")
340
+ query = <<~SQL
341
+ DO $$
342
+ BEGIN
343
+ IF NOT EXISTS (
344
+ SELECT 1 FROM pg_type t
345
+ WHERE t.typname = '#{name}'
346
+ ) THEN
347
+ CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
348
+ END IF;
349
+ END
350
+ $$;
351
+ SQL
352
+ exec_query(query)
353
+ end
354
+
323
355
  # Returns the configured supported identifier length supported by PostgreSQL
324
356
  def max_identifier_length
325
357
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
@@ -672,6 +704,37 @@ module ActiveRecord::ConnectionAdapters
672
704
  class PostgreSQLAdapter < AbstractAdapter
673
705
  class_attribute :create_unlogged_tables, default: false
674
706
 
707
+ ##
708
+ # :singleton-method:
709
+ # PostgreSQL allows the creation of "unlogged" tables, which do not record
710
+ # data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
711
+ # but significantly increases the risk of data loss if the database
712
+ # crashes. As a result, this should not be used in production
713
+ # environments. If you would like all created tables to be unlogged in
714
+ # the test environment you can add the following line to your test.rb
715
+ # file:
716
+ #
717
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
718
+ class_attribute :create_unlogged_tables, default: false
719
+
720
+ ##
721
+ # :singleton-method:
722
+ # PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+
723
+ # in migrations, Rails will translate this to a PostgreSQL "timestamp without time zone".
724
+ # Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
725
+ # store DateTimes as "timestamp with time zone":
726
+ #
727
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
728
+ #
729
+ # Or if you are adding a custom type:
730
+ #
731
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "my_custom_type_name" }
732
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type
733
+ #
734
+ # If you're using +:ruby+ as your +config.active_record.schema_format+ and you change this
735
+ # setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
736
+ class_attribute :datetime_type, default: :timestamp
737
+
675
738
  # Try to use as much of the built in postgres logic as possible
676
739
  # maybe someday we can extend the actual adapter
677
740
  include ActiveRecord::ConnectionAdapters::PostgreSQL::ReferentialIntegrity
@@ -735,11 +798,96 @@ module ActiveRecord::ConnectionAdapters
735
798
 
736
799
  private
737
800
 
738
- # Prepared statements aren't schema aware so we need to make sure we
739
- # store different PreparedStatement objects for different schemas
801
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
802
+
803
+ def execute_and_clear(sql, name, binds, prepare: false, async: false)
804
+ sql = transform_query(sql)
805
+ check_if_write_query(sql)
806
+
807
+ if !prepare || without_prepared_statement?(binds)
808
+ result = exec_no_cache(sql, name, binds, async: async)
809
+ else
810
+ result = exec_cache(sql, name, binds, async: async)
811
+ end
812
+ begin
813
+ ret = yield result
814
+ ensure
815
+ # Is this really result in AR PG?
816
+ # result.clear
817
+ end
818
+ ret
819
+ end
820
+
821
+ def exec_no_cache(sql, name, binds, async: false)
822
+ materialize_transactions
823
+ mark_transaction_written_if_write(sql)
824
+
825
+ # make sure we carry over any changes to ActiveRecord.default_timezone that have been
826
+ # made since we established the connection
827
+ update_typemap_for_default_timezone
828
+
829
+ type_casted_binds = type_casted_binds(binds)
830
+ log(sql, name, binds, type_casted_binds, async: async) do
831
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
832
+ @connection.exec_params(sql, type_casted_binds)
833
+ end
834
+ end
835
+ end
836
+
837
+ def exec_cache(sql, name, binds, async: false)
838
+ materialize_transactions
839
+ mark_transaction_written_if_write(sql)
840
+ update_typemap_for_default_timezone
841
+
842
+ stmt_key = prepare_statement(sql, binds)
843
+ type_casted_binds = type_casted_binds(binds)
844
+
845
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
846
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
847
+ @connection.exec_prepared(stmt_key, type_casted_binds)
848
+ end
849
+ end
850
+ rescue ActiveRecord::StatementInvalid => e
851
+ raise unless is_cached_plan_failure?(e)
852
+
853
+ # Nothing we can do if we are in a transaction because all commands
854
+ # will raise InFailedSQLTransaction
855
+ if in_transaction?
856
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
857
+ else
858
+ @lock.synchronize do
859
+ # outside of transactions we can simply flush this query and retry
860
+ @statements.delete sql_key(sql)
861
+ end
862
+ retry
863
+ end
864
+ end
865
+
866
+ # Annoyingly, the code for prepared statements whose return value may
867
+ # have changed is FEATURE_NOT_SUPPORTED.
868
+ #
869
+ # This covers various different error types so we need to do additional
870
+ # work to classify the exception definitively as a
871
+ # ActiveRecord::PreparedStatementCacheExpired
872
+ #
873
+ # Check here for more details:
874
+ # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
875
+ def is_cached_plan_failure?(e)
876
+ pgerror = e.cause
877
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
878
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
879
+ rescue
880
+ false
881
+ end
882
+
883
+ def in_transaction?
884
+ open_transactions > 0
885
+ end
886
+
887
+ # Returns the statement identifier for the client side cache
888
+ # of statements
740
889
  def sql_key(sql)
741
890
  "#{schema_search_path}-#{sql}"
742
891
  end
743
-
744
892
  end
745
893
  end
@@ -92,7 +92,7 @@ module ArJdbc
92
92
 
93
93
  def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
94
94
  if !type_map.key?(oid)
95
- load_additional_types(type_map, oid)
95
+ load_additional_types([oid])
96
96
  end
97
97
 
98
98
  type_map.fetch(oid, fmod, sql_type) {
@@ -103,132 +103,168 @@ module ArJdbc
103
103
  }
104
104
  end
105
105
 
106
+ def reload_type_map
107
+ type_map.clear
108
+ initialize_type_map
109
+ end
110
+
111
+ def initialize_type_map_inner(m)
112
+ m.register_type "int2", Type::Integer.new(limit: 2)
113
+ m.register_type "int4", Type::Integer.new(limit: 4)
114
+ m.register_type "int8", Type::Integer.new(limit: 8)
115
+ m.register_type "oid", OID::Oid.new
116
+ m.register_type "float4", Type::Float.new
117
+ m.alias_type "float8", "float4"
118
+ m.register_type "text", Type::Text.new
119
+ register_class_with_limit m, "varchar", Type::String
120
+ m.alias_type "char", "varchar"
121
+ m.alias_type "name", "varchar"
122
+ m.alias_type "bpchar", "varchar"
123
+ m.register_type "bool", Type::Boolean.new
124
+ register_class_with_limit m, "bit", OID::Bit
125
+ register_class_with_limit m, "varbit", OID::BitVarying
126
+ m.register_type "date", OID::Date.new
127
+
128
+ m.register_type "money", OID::Money.new
129
+ m.register_type "bytea", OID::Bytea.new
130
+ m.register_type "point", OID::Point.new
131
+ m.register_type "hstore", OID::Hstore.new
132
+ m.register_type "json", Type::Json.new
133
+ m.register_type "jsonb", OID::Jsonb.new
134
+ m.register_type "cidr", OID::Cidr.new
135
+ m.register_type "inet", OID::Inet.new
136
+ m.register_type "uuid", OID::Uuid.new
137
+ m.register_type "xml", OID::Xml.new
138
+ m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
139
+ m.register_type "macaddr", OID::Macaddr.new
140
+ m.register_type "citext", OID::SpecializedString.new(:citext)
141
+ m.register_type "ltree", OID::SpecializedString.new(:ltree)
142
+ m.register_type "line", OID::SpecializedString.new(:line)
143
+ m.register_type "lseg", OID::SpecializedString.new(:lseg)
144
+ m.register_type "box", OID::SpecializedString.new(:box)
145
+ m.register_type "path", OID::SpecializedString.new(:path)
146
+ m.register_type "polygon", OID::SpecializedString.new(:polygon)
147
+ m.register_type "circle", OID::SpecializedString.new(:circle)
148
+ m.register_type "regproc", OID::Enum.new
149
+ # FIXME: adding this vector type leads to quoting not handlign Array data in quoting.
150
+ #m.register_type "_int4", OID::Vector.new(",", m.lookup("int4"))
151
+ register_class_with_precision m, "time", Type::Time
152
+ register_class_with_precision m, "timestamp", OID::Timestamp
153
+ register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
154
+
155
+ m.register_type "numeric" do |_, fmod, sql_type|
156
+ precision = extract_precision(sql_type)
157
+ scale = extract_scale(sql_type)
158
+
159
+ # The type for the numeric depends on the width of the field,
160
+ # so we'll do something special here.
161
+ #
162
+ # When dealing with decimal columns:
163
+ #
164
+ # places after decimal = fmod - 4 & 0xffff
165
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
166
+ if fmod && (fmod - 4 & 0xffff).zero?
167
+ # FIXME: Remove this class, and the second argument to
168
+ # lookups on PG
169
+ Type::DecimalWithoutScale.new(precision: precision)
170
+ else
171
+ OID::Decimal.new(precision: precision, scale: scale)
172
+ end
173
+ end
174
+
175
+ m.register_type "interval" do |*args, sql_type|
176
+ precision = extract_precision(sql_type)
177
+ OID::Interval.new(precision: precision)
178
+ end
179
+
180
+ # pgjdbc returns these if the column is auto-incrmenting
181
+ m.alias_type 'serial', 'int4'
182
+ m.alias_type 'bigserial', 'int8'
183
+ end
184
+
185
+
186
+ # We differ from AR here because we will initialize type_map when adapter initializes
106
187
  def type_map
107
188
  @type_map
108
189
  end
109
190
 
110
- def reload_type_map
111
- if ( @type_map ||= nil )
112
- @type_map.clear
113
- initialize_type_map(@type_map)
114
- end
191
+ def initialize_type_map(m = type_map)
192
+ initialize_type_map_inner(m)
193
+ load_additional_types
115
194
  end
116
195
 
117
196
  private
118
197
 
119
- def initialize_type_map(m = type_map)
120
- register_class_with_limit m, 'int2', Type::Integer
121
- register_class_with_limit m, 'int4', Type::Integer
122
- register_class_with_limit m, 'int8', Type::Integer
123
- m.register_type 'oid', OID::Oid.new
124
- m.register_type 'float4', Type::Float.new
125
- m.alias_type 'float8', 'float4'
126
- m.register_type 'text', Type::Text.new
127
- register_class_with_limit m, 'varchar', Type::String
128
- m.alias_type 'char', 'varchar'
129
- m.alias_type 'name', 'varchar'
130
- m.alias_type 'bpchar', 'varchar'
131
- m.register_type 'bool', Type::Boolean.new
132
- register_class_with_limit m, 'bit', OID::Bit
133
- register_class_with_limit m, 'varbit', OID::BitVarying
134
- m.alias_type 'timestamptz', 'timestamp'
135
- m.register_type 'date', OID::Date.new
136
-
137
- m.register_type 'money', OID::Money.new
138
- m.register_type 'bytea', OID::Bytea.new
139
- m.register_type 'point', OID::Point.new
140
- m.register_type 'hstore', OID::Hstore.new
141
- m.register_type 'json', Type::Json.new
142
- m.register_type 'jsonb', OID::Jsonb.new
143
- m.register_type 'cidr', OID::Cidr.new
144
- m.register_type 'inet', OID::Inet.new
145
- m.register_type 'uuid', OID::Uuid.new
146
- m.register_type 'xml', OID::Xml.new
147
- m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
148
- m.register_type 'macaddr', OID::Macaddr.new
149
- m.register_type 'citext', OID::SpecializedString.new(:citext)
150
- m.register_type 'ltree', OID::SpecializedString.new(:ltree)
151
- m.register_type 'line', OID::SpecializedString.new(:line)
152
- m.register_type 'lseg', OID::SpecializedString.new(:lseg)
153
- m.register_type 'box', OID::SpecializedString.new(:box)
154
- m.register_type 'path', OID::SpecializedString.new(:path)
155
- m.register_type 'polygon', OID::SpecializedString.new(:polygon)
156
- m.register_type 'circle', OID::SpecializedString.new(:circle)
157
-
158
- m.register_type 'interval' do |*args, sql_type|
159
- precision = extract_precision(sql_type)
160
- OID::Interval.new(precision: precision)
198
+ def register_class_with_limit(...)
199
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:register_class_with_limit, ...)
200
+ end
201
+
202
+ def register_class_with_precision(...)
203
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:register_class_with_precision, ...)
204
+ end
205
+
206
+ def load_additional_types(oids = nil) # :nodoc:
207
+ initializer = ArjdbcTypeMapInitializer.new(type_map)
208
+ load_types_queries(initializer, oids) do |query|
209
+ execute_and_clear(query, "SCHEMA", []) do |records|
210
+ #puts "RECORDS: #{records.to_a}"
211
+ initializer.run(records)
212
+ end
161
213
  end
214
+ end
162
215
 
163
- register_class_with_precision m, 'time', Type::Time
164
- register_class_with_precision m, 'timestamp', OID::DateTime
165
-
166
- m.register_type 'numeric' do |_, fmod, sql_type|
167
- precision = extract_precision(sql_type)
168
- scale = extract_scale(sql_type)
169
-
170
- # The type for the numeric depends on the width of the field,
171
- # so we'll do something special here.
172
- #
173
- # When dealing with decimal columns:
174
- #
175
- # places after decimal = fmod - 4 & 0xffff
176
- # places before decimal = (fmod - 4) >> 16 & 0xffff
177
- if fmod && (fmod - 4 & 0xffff).zero?
178
- # FIXME: Remove this class, and the second argument to
179
- # lookups on PG
180
- Type::DecimalWithoutScale.new(precision: precision)
216
+ def load_types_queries(initializer, oids)
217
+ query = <<~SQL
218
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
219
+ FROM pg_type as t
220
+ LEFT JOIN pg_range as r ON oid = rngtypid
221
+ SQL
222
+ if oids
223
+ if oids.all? { |e| e.kind_of? Numeric }
224
+ yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
181
225
  else
182
- OID::Decimal.new(precision: precision, scale: scale)
226
+ in_list = oids.map { |e| %Q{'#{e}'} }.join(", ")
227
+ #puts caller[0..40]
228
+ puts "IN_LIST = #{in_list}"
229
+ yield query + "WHERE t.typname IN (%s)" % in_list
183
230
  end
231
+ else
232
+ yield query + initializer.query_conditions_for_known_type_names
233
+ yield query + initializer.query_conditions_for_known_type_types
234
+ yield query + initializer.query_conditions_for_array_types
184
235
  end
185
-
186
- load_additional_types(m)
187
-
188
- # pgjdbc returns these if the column is auto-incrmenting
189
- m.alias_type 'serial', 'int4'
190
- m.alias_type 'bigserial', 'int8'
191
236
  end
192
237
 
193
- def load_additional_types(type_map, oid = nil) # :nodoc:
194
- initializer = ArjdbcTypeMapInitializer.new(type_map)
238
+ def update_typemap_for_default_timezone
239
+ if @default_timezone != ActiveRecord.default_timezone && @timestamp_decoder
240
+ decoder_class = ActiveRecord.default_timezone == :utc ?
241
+ PG::TextDecoder::TimestampUtc :
242
+ PG::TextDecoder::TimestampWithoutTimeZone
195
243
 
196
- if supports_ranges?
197
- query = <<-SQL
198
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype,
199
- ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
200
- FROM pg_type as t
201
- LEFT JOIN pg_range as r ON oid = rngtypid
202
- JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
203
- SQL
204
- else
205
- query = <<-SQL
206
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype,
207
- ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
208
- FROM pg_type as t
209
- JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
210
- SQL
211
- end
244
+ @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
245
+ @connection.type_map_for_results.add_coder(@timestamp_decoder)
212
246
 
213
- if oid
214
- if oid.is_a? Numeric || oid.match(/^\d+$/)
215
- # numeric OID
216
- query += "WHERE t.oid = %s" % oid
247
+ @default_timezone = ActiveRecord.default_timezone
217
248
 
218
- elsif m = oid.match(/"?(\w+)"?\."?(\w+)"?/)
219
- # namespace and type name
220
- query += "WHERE ns.nspname = '%s' AND t.typname = '%s'" % [m[1], m[2]]
249
+ # if default timezone has changed, we need to reconfigure the connection
250
+ # (specifically, the session time zone)
251
+ configure_connection
252
+ end
253
+ end
221
254
 
222
- else
223
- # only type name
224
- query += "WHERE t.typname = '%s' AND ns.nspname = ANY(current_schemas(true))" % oid
225
- end
226
- else
227
- query += initializer.query_conditions_for_initial_load
255
+ def extract_scale(sql_type)
256
+ case sql_type
257
+ when /\((\d+)\)/ then 0
258
+ when /\((\d+)(,(\d+))\)/ then $3.to_i
228
259
  end
260
+ end
261
+
262
+ def extract_precision(sql_type)
263
+ $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
264
+ end
229
265
 
230
- records = execute(query, 'SCHEMA')
231
- initializer.run(records)
266
+ def extract_limit(sql_type)
267
+ $1.to_i if sql_type =~ /\((.*)\)/
232
268
  end
233
269
 
234
270
  # Support arrays/ranges for defining attributes that don't exist in the db