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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/activerecord-jdbc-adapter.gemspec +1 -3
  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/core.rb +8 -9
  9. data/lib/arjdbc/abstract/database_statements.rb +8 -0
  10. data/lib/arjdbc/discover.rb +0 -67
  11. data/lib/arjdbc/jdbc/adapter.rb +1 -1
  12. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  13. data/lib/arjdbc/jdbc/column.rb +1 -26
  14. data/lib/arjdbc/jdbc.rb +0 -7
  15. data/lib/arjdbc/mysql/adapter.rb +2 -1
  16. data/lib/arjdbc/oracle/adapter.rb +4 -23
  17. data/lib/arjdbc/postgresql/adapter.rb +152 -3
  18. data/lib/arjdbc/postgresql/oid_types.rb +155 -108
  19. data/lib/arjdbc/sqlite3/adapter.rb +54 -36
  20. data/lib/arjdbc/tasks/database_tasks.rb +0 -15
  21. data/lib/arjdbc/util/serialized_attributes.rb +0 -22
  22. data/lib/arjdbc/util/table_copier.rb +2 -1
  23. data/lib/arjdbc/version.rb +1 -1
  24. data/rakelib/02-test.rake +3 -18
  25. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +5 -0
  26. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +5 -0
  27. metadata +5 -38
  28. data/lib/active_record/connection_adapters/as400_adapter.rb +0 -2
  29. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -1
  30. data/lib/active_record/connection_adapters/derby_adapter.rb +0 -1
  31. data/lib/active_record/connection_adapters/informix_adapter.rb +0 -1
  32. data/lib/arel/visitors/db2.rb +0 -137
  33. data/lib/arel/visitors/derby.rb +0 -112
  34. data/lib/arel/visitors/firebird.rb +0 -79
  35. data/lib/arjdbc/db2/adapter.rb +0 -808
  36. data/lib/arjdbc/db2/as400.rb +0 -142
  37. data/lib/arjdbc/db2/column.rb +0 -131
  38. data/lib/arjdbc/db2/connection_methods.rb +0 -48
  39. data/lib/arjdbc/db2.rb +0 -4
  40. data/lib/arjdbc/derby/active_record_patch.rb +0 -13
  41. data/lib/arjdbc/derby/adapter.rb +0 -521
  42. data/lib/arjdbc/derby/connection_methods.rb +0 -20
  43. data/lib/arjdbc/derby/schema_creation.rb +0 -15
  44. data/lib/arjdbc/derby.rb +0 -3
  45. data/lib/arjdbc/firebird/adapter.rb +0 -413
  46. data/lib/arjdbc/firebird/connection_methods.rb +0 -23
  47. data/lib/arjdbc/firebird.rb +0 -4
  48. data/lib/arjdbc/informix/adapter.rb +0 -139
  49. data/lib/arjdbc/informix/connection_methods.rb +0 -9
  50. data/lib/arjdbc/sybase/adapter.rb +0 -47
  51. data/lib/arjdbc/sybase.rb +0 -2
  52. data/lib/arjdbc/tasks/db2_database_tasks.rb +0 -104
  53. data/lib/arjdbc/tasks/derby_database_tasks.rb +0 -95
  54. data/src/java/arjdbc/derby/DerbyModule.java +0 -178
  55. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +0 -152
  56. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +0 -174
  57. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +0 -75
@@ -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
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  require 'thread'
4
3
 
5
4
  module ArJdbc
@@ -91,8 +90,23 @@ module ArJdbc
91
90
  end
92
91
 
93
92
  def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
94
- if !type_map.key?(oid)
95
- load_additional_types(type_map, oid)
93
+ # Note: type_map is storing a bunch of oid type prefixed with a namespace even
94
+ # if they are not namespaced (e.g. ""."oidvector"). builtin types which are
95
+ # common seem to not be prefixed (e.g. "varchar"). OID numbers are also keys
96
+ # but JDBC never returns those. So the current scheme is to check with
97
+ # what we got and that covers number and plain strings and otherwise we will
98
+ # wrap with the namespace form.
99
+ found = type_map.key?(oid)
100
+
101
+ if !found
102
+ key = oid.kind_of?(String) && oid != "oid" ? "\"\".\"#{oid}\"" : oid
103
+ found = type_map.key?(key)
104
+
105
+ if !found
106
+ load_additional_types([oid])
107
+ else
108
+ oid = key
109
+ end
96
110
  end
97
111
 
98
112
  type_map.fetch(oid, fmod, sql_type) {
@@ -103,132 +117,165 @@ module ArJdbc
103
117
  }
104
118
  end
105
119
 
120
+ def reload_type_map
121
+ type_map.clear
122
+ initialize_type_map
123
+ end
124
+
125
+ def initialize_type_map_inner(m)
126
+ m.register_type "int2", Type::Integer.new(limit: 2)
127
+ m.register_type "int4", Type::Integer.new(limit: 4)
128
+ m.register_type "int8", Type::Integer.new(limit: 8)
129
+ m.register_type "oid", OID::Oid.new
130
+ m.register_type "float4", Type::Float.new
131
+ m.alias_type "float8", "float4"
132
+ m.register_type "text", Type::Text.new
133
+ register_class_with_limit m, "varchar", Type::String
134
+ m.alias_type "char", "varchar"
135
+ m.alias_type "name", "varchar"
136
+ m.alias_type "bpchar", "varchar"
137
+ m.register_type "bool", Type::Boolean.new
138
+ register_class_with_limit m, "bit", OID::Bit
139
+ register_class_with_limit m, "varbit", OID::BitVarying
140
+ m.register_type "date", OID::Date.new
141
+
142
+ m.register_type "money", OID::Money.new
143
+ m.register_type "bytea", OID::Bytea.new
144
+ m.register_type "point", OID::Point.new
145
+ m.register_type "hstore", OID::Hstore.new
146
+ m.register_type "json", Type::Json.new
147
+ m.register_type "jsonb", OID::Jsonb.new
148
+ m.register_type "cidr", OID::Cidr.new
149
+ m.register_type "inet", OID::Inet.new
150
+ m.register_type "uuid", OID::Uuid.new
151
+ m.register_type "xml", OID::Xml.new
152
+ m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
153
+ m.register_type "macaddr", OID::Macaddr.new
154
+ m.register_type "citext", OID::SpecializedString.new(:citext)
155
+ m.register_type "ltree", OID::SpecializedString.new(:ltree)
156
+ m.register_type "line", OID::SpecializedString.new(:line)
157
+ m.register_type "lseg", OID::SpecializedString.new(:lseg)
158
+ m.register_type "box", OID::SpecializedString.new(:box)
159
+ m.register_type "path", OID::SpecializedString.new(:path)
160
+ m.register_type "polygon", OID::SpecializedString.new(:polygon)
161
+ m.register_type "circle", OID::SpecializedString.new(:circle)
162
+ m.register_type "regproc", OID::Enum.new
163
+ # FIXME: adding this vector type leads to quoting not handlign Array data in quoting.
164
+ #m.register_type "_int4", OID::Vector.new(",", m.lookup("int4"))
165
+ register_class_with_precision m, "time", Type::Time
166
+ register_class_with_precision m, "timestamp", OID::Timestamp
167
+ register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
168
+
169
+ m.register_type "numeric" do |_, fmod, sql_type|
170
+ precision = extract_precision(sql_type)
171
+ scale = extract_scale(sql_type)
172
+
173
+ # The type for the numeric depends on the width of the field,
174
+ # so we'll do something special here.
175
+ #
176
+ # When dealing with decimal columns:
177
+ #
178
+ # places after decimal = fmod - 4 & 0xffff
179
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
180
+ if fmod && (fmod - 4 & 0xffff).zero?
181
+ # FIXME: Remove this class, and the second argument to
182
+ # lookups on PG
183
+ Type::DecimalWithoutScale.new(precision: precision)
184
+ else
185
+ OID::Decimal.new(precision: precision, scale: scale)
186
+ end
187
+ end
188
+
189
+ m.register_type "interval" do |*args, sql_type|
190
+ precision = extract_precision(sql_type)
191
+ OID::Interval.new(precision: precision)
192
+ end
193
+
194
+ # pgjdbc returns these if the column is auto-incrmenting
195
+ m.alias_type 'serial', 'int4'
196
+ m.alias_type 'bigserial', 'int8'
197
+ end
198
+
199
+
200
+ # We differ from AR here because we will initialize type_map when adapter initializes
106
201
  def type_map
107
202
  @type_map
108
203
  end
109
204
 
110
- def reload_type_map
111
- if ( @type_map ||= nil )
112
- @type_map.clear
113
- initialize_type_map(@type_map)
114
- end
205
+ def initialize_type_map(m = type_map)
206
+ initialize_type_map_inner(m)
207
+ load_additional_types
115
208
  end
116
209
 
117
210
  private
118
211
 
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)
212
+ def register_class_with_limit(...)
213
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:register_class_with_limit, ...)
214
+ end
215
+
216
+ def register_class_with_precision(...)
217
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:register_class_with_precision, ...)
218
+ end
219
+
220
+ def load_additional_types(oids = nil) # :nodoc:
221
+ initializer = ArjdbcTypeMapInitializer.new(type_map)
222
+ load_types_queries(initializer, oids) do |query|
223
+ execute_and_clear(query, "SCHEMA", []) do |records|
224
+ initializer.run(records)
225
+ end
161
226
  end
227
+ end
162
228
 
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)
229
+ def load_types_queries(initializer, oids)
230
+ query = <<~SQL
231
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
232
+ FROM pg_type as t
233
+ LEFT JOIN pg_range as r ON oid = rngtypid
234
+ SQL
235
+ if oids
236
+ if oids.all? { |e| e.kind_of? Numeric }
237
+ yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
181
238
  else
182
- OID::Decimal.new(precision: precision, scale: scale)
239
+ in_list = oids.map { |e| %Q{'#{e}'} }.join(", ")
240
+ yield query + "WHERE t.typname IN (%s)" % in_list
183
241
  end
242
+ else
243
+ yield query + initializer.query_conditions_for_known_type_names
244
+ yield query + initializer.query_conditions_for_known_type_types
245
+ yield query + initializer.query_conditions_for_array_types
184
246
  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
247
  end
192
248
 
193
- def load_additional_types(type_map, oid = nil) # :nodoc:
194
- initializer = ArjdbcTypeMapInitializer.new(type_map)
249
+ def update_typemap_for_default_timezone
250
+ if @default_timezone != ActiveRecord.default_timezone && @timestamp_decoder
251
+ decoder_class = ActiveRecord.default_timezone == :utc ?
252
+ PG::TextDecoder::TimestampUtc :
253
+ PG::TextDecoder::TimestampWithoutTimeZone
195
254
 
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
255
+ @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
256
+ @connection.type_map_for_results.add_coder(@timestamp_decoder)
212
257
 
213
- if oid
214
- if oid.is_a? Numeric || oid.match(/^\d+$/)
215
- # numeric OID
216
- query += "WHERE t.oid = %s" % oid
258
+ @default_timezone = ActiveRecord.default_timezone
217
259
 
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]]
260
+ # if default timezone has changed, we need to reconfigure the connection
261
+ # (specifically, the session time zone)
262
+ configure_connection
263
+ end
264
+ end
221
265
 
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
266
+ def extract_scale(sql_type)
267
+ case sql_type
268
+ when /\((\d+)\)/ then 0
269
+ when /\((\d+)(,(\d+))\)/ then $3.to_i
228
270
  end
271
+ end
272
+
273
+ def extract_precision(sql_type)
274
+ $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
275
+ end
229
276
 
230
- records = execute(query, 'SCHEMA')
231
- initializer.run(records)
277
+ def extract_limit(sql_type)
278
+ $1.to_i if sql_type =~ /\((.*)\)/
232
279
  end
233
280
 
234
281
  # Support arrays/ranges for defining attributes that don't exist in the db
@@ -262,9 +262,7 @@ module ArJdbc
262
262
  def remove_column(table_name, column_name, type = nil, **options) #:nodoc:
263
263
  alter_table(table_name) do |definition|
264
264
  definition.remove_column column_name
265
- definition.foreign_keys.delete_if do |_, fk_options|
266
- fk_options[:column] == column_name.to_s
267
- end
265
+ definition.foreign_keys.delete_if { |fk| fk.column == column_name.to_s }
268
266
  end
269
267
  end
270
268
 
@@ -298,8 +296,8 @@ module ArJdbc
298
296
  def change_column(table_name, column_name, type, **options) #:nodoc:
299
297
  alter_table(table_name) do |definition|
300
298
  definition[column_name].instance_eval do
301
- self.type = type
302
- self.options.merge!(options)
299
+ self.type = aliased_types(type.to_s, type)
300
+ self.options.merge!(options)
303
301
  end
304
302
  end
305
303
  end
@@ -336,8 +334,12 @@ module ArJdbc
336
334
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
337
335
  elsif insert.update_duplicates?
338
336
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
339
- sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
340
- sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
337
+ if insert.raw_update_sql?
338
+ sql << insert.raw_update_sql
339
+ else
340
+ sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
341
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
342
+ end
341
343
  end
342
344
 
343
345
  sql
@@ -348,7 +350,7 @@ module ArJdbc
348
350
  end
349
351
 
350
352
  def get_database_version # :nodoc:
351
- SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
353
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
352
354
  end
353
355
 
354
356
  def check_version
@@ -459,8 +461,13 @@ module ArJdbc
459
461
  options[:rename][column.name.to_sym] ||
460
462
  column.name) : column.name
461
463
 
464
+ if column.has_default?
465
+ type = lookup_cast_type_from_column(column)
466
+ default = type.deserialize(column.default)
467
+ end
468
+
462
469
  @definition.column(column_name, column.type,
463
- limit: column.limit, default: column.default,
470
+ limit: column.limit, default: default,
464
471
  precision: column.precision, scale: column.scale,
465
472
  null: column.null, collation: column.collation,
466
473
  primary_key: column_name == from_primary_key
@@ -478,9 +485,6 @@ module ArJdbc
478
485
  def copy_table_indexes(from, to, rename = {})
479
486
  indexes(from).each do |index|
480
487
  name = index.name
481
- # indexes sqlite creates for internal use start with `sqlite_` and
482
- # don't need to be copied
483
- next if name.start_with?("sqlite_")
484
488
  if to == "a#{from}"
485
489
  name = "t#{name}"
486
490
  elsif from == "a#{to}"
@@ -520,20 +524,22 @@ module ArJdbc
520
524
  end
521
525
 
522
526
  def translate_exception(exception, message:, sql:, binds:)
523
- case exception.message
524
527
  # SQLite 3.8.2 returns a newly formatted error message:
525
528
  # UNIQUE constraint failed: *table_name*.*column_name*
526
529
  # Older versions of SQLite return:
527
530
  # column *column_name* is not unique
528
- when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
531
+ if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
529
532
  # DIFFERENCE: FQN
530
533
  ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
531
- when /.* may not be NULL/, /NOT NULL constraint failed: .*/
534
+ elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
532
535
  # DIFFERENCE: FQN
533
536
  ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
534
- when /FOREIGN KEY constraint failed/i
537
+ elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
535
538
  # DIFFERENCE: FQN
536
539
  ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
540
+ elsif exception.message.match?(/called on a closed database/i)
541
+ # DIFFERENCE: FQN
542
+ ::ActiveRecord::ConnectionNotEstablished.new(exception)
537
543
  else
538
544
  super
539
545
  end
@@ -553,12 +559,12 @@ module ArJdbc
553
559
  # Result will have following sample string
554
560
  # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
555
561
  # "password_digest" varchar COLLATE "NOCASE");
556
- result = exec_query(sql, "SCHEMA").first
562
+ result = query_value(sql, "SCHEMA")
557
563
 
558
564
  if result
559
565
  # Splitting with left parentheses and discarding the first part will return all
560
566
  # columns separated with comma(,).
561
- columns_string = result["sql"].split("(", 2).last
567
+ columns_string = result.split("(", 2).last
562
568
 
563
569
  columns_string.split(",").each do |column_string|
564
570
  # This regex will match the column name and collation type and will save
@@ -596,6 +602,9 @@ module ArJdbc
596
602
  end
597
603
 
598
604
  def configure_connection
605
+ # FIXME: missing from adapter
606
+ # @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
607
+
599
608
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
600
609
  end
601
610
 
@@ -747,6 +756,29 @@ module ActiveRecord::ConnectionAdapters
747
756
  # Note: This is not an override of ours but a moved line from AR Sqlite3Adapter to register ours vs our copied module (which would be their class).
748
757
  # ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
749
758
 
759
+ # DIFFERENCE: FQN
760
+ class SQLite3Integer < ::ActiveRecord::Type::Integer # :nodoc:
761
+ private
762
+ def _limit
763
+ # INTEGER storage class can be stored 8 bytes value.
764
+ # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
765
+ limit || 8
766
+ end
767
+ end
768
+
769
+ # DIFFERENCE: FQN
770
+ ::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
771
+
772
+ class << self
773
+ private
774
+ def initialize_type_map(m)
775
+ super
776
+ register_class_with_limit m, %r(int)i, SQLite3Integer
777
+ end
778
+ end
779
+
780
+ TYPE_MAP = ActiveRecord::Type::TypeMap.new.tap { |m| initialize_type_map(m) }
781
+
750
782
  private
751
783
 
752
784
  # because the JDBC driver doesn't like multiple SQL statements in one JDBC statement
@@ -754,6 +786,10 @@ module ActiveRecord::ConnectionAdapters
754
786
  total_sql
755
787
  end
756
788
 
789
+ def type_map
790
+ TYPE_MAP
791
+ end
792
+
757
793
  # combine
758
794
  def write_query?(sql) # :nodoc:
759
795
  return sql.any? { |stmt| super(stmt) } if sql.kind_of? Array
@@ -761,23 +797,5 @@ module ActiveRecord::ConnectionAdapters
761
797
  rescue ArgumentError # Invalid encoding
762
798
  !READ_QUERY.match?(sql.b)
763
799
  end
764
-
765
- def initialize_type_map(m = type_map)
766
- super
767
- register_class_with_limit m, %r(int)i, SQLite3Integer
768
- end
769
-
770
- # DIFFERENCE: FQN
771
- class SQLite3Integer < ::ActiveRecord::Type::Integer # :nodoc:
772
- private
773
- def _limit
774
- # INTEGER storage class can be stored 8 bytes value.
775
- # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
776
- limit || 8
777
- end
778
- end
779
-
780
- # DIFFERENCE: FQN
781
- ::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
782
800
  end
783
801
  end