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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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