activerecord-jdbc-adapter 50.8-java → 51.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -2
  3. data/.travis.yml +26 -51
  4. data/Gemfile +3 -1
  5. data/README.md +9 -11
  6. data/Rakefile +15 -78
  7. data/activerecord-jdbc-adapter.gemspec +2 -2
  8. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  9. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  10. data/lib/arjdbc/abstract/core.rb +4 -12
  11. data/lib/arjdbc/abstract/database_statements.rb +4 -10
  12. data/lib/arjdbc/abstract/statement_cache.rb +4 -4
  13. data/lib/arjdbc/abstract/transaction_support.rb +2 -9
  14. data/lib/arjdbc/jdbc.rb +4 -0
  15. data/lib/arjdbc/jdbc/column.rb +11 -5
  16. data/lib/arjdbc/jdbc/connection_methods.rb +9 -2
  17. data/lib/arjdbc/jdbc/error.rb +1 -1
  18. data/lib/arjdbc/jdbc/jdbc.rake +4 -0
  19. data/lib/arjdbc/mssql.rb +7 -0
  20. data/lib/arjdbc/mssql/adapter.rb +804 -0
  21. data/lib/arjdbc/mssql/column.rb +200 -0
  22. data/lib/arjdbc/mssql/connection_methods.rb +79 -0
  23. data/lib/arjdbc/mssql/explain_support.rb +99 -0
  24. data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
  25. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  26. data/lib/arjdbc/mssql/types.rb +343 -0
  27. data/lib/arjdbc/mssql/utils.rb +82 -0
  28. data/lib/arjdbc/mysql/adapter.rb +14 -11
  29. data/lib/arjdbc/mysql/connection_methods.rb +9 -18
  30. data/lib/arjdbc/postgresql/adapter.rb +108 -59
  31. data/lib/arjdbc/postgresql/column.rb +3 -6
  32. data/lib/arjdbc/postgresql/connection_methods.rb +3 -12
  33. data/lib/arjdbc/postgresql/oid_types.rb +14 -93
  34. data/lib/arjdbc/sqlite3/adapter.rb +171 -140
  35. data/lib/arjdbc/sqlite3/connection_methods.rb +1 -2
  36. data/lib/arjdbc/tasks/database_tasks.rb +36 -16
  37. data/lib/arjdbc/tasks/databases.rake +75 -32
  38. data/lib/arjdbc/tasks/databases3.rake +215 -0
  39. data/lib/arjdbc/tasks/databases4.rake +39 -0
  40. data/lib/arjdbc/version.rb +1 -1
  41. data/rakelib/01-tomcat.rake +2 -2
  42. data/rakelib/02-test.rake +3 -0
  43. data/rakelib/compile.rake +70 -0
  44. data/rakelib/db.rake +7 -21
  45. data/rakelib/rails.rake +4 -5
  46. data/src/java/arjdbc/ArJdbcModule.java +15 -5
  47. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +2 -2
  48. data/src/java/arjdbc/jdbc/ConnectionFactory.java +87 -0
  49. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +1 -0
  50. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +41 -120
  51. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +14 -310
  52. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +2 -2
  53. data/src/java/arjdbc/postgresql/ByteaUtils.java +1 -0
  54. data/src/java/arjdbc/postgresql/PgResultSetMetaDataWrapper.java +23 -0
  55. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +13 -21
  56. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +46 -41
  57. data/src/java/arjdbc/util/DateTimeUtils.java +5 -141
  58. data/src/java/arjdbc/util/QuotingUtils.java +7 -6
  59. data/src/java/arjdbc/util/StringHelper.java +20 -6
  60. metadata +25 -16
  61. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +0 -61
  62. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +0 -52
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  ArJdbc::ConnectionMethods.module_eval do
3
3
  def postgresql_connection(config)
4
- config = config.deep_dup
5
4
  # NOTE: this isn't "really" necessary but Rails (in tests) assumes being able to :
6
5
  # ActiveRecord::Base.postgresql_connection ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)
7
6
  # ... while using symbols by default but than configurations returning string keys ;(
@@ -17,8 +16,7 @@ ArJdbc::ConnectionMethods.module_eval do
17
16
  ::Jdbc::Postgres.load_driver(:require) if defined?(::Jdbc::Postgres.load_driver)
18
17
  rescue LoadError # assuming driver.jar is on the class-path
19
18
  end
20
- driver = (config[:driver] ||=
21
- defined?(::Jdbc::Postgres.driver_name) ? ::Jdbc::Postgres.driver_name : 'org.postgresql.Driver')
19
+ driver = config[:driver] ||= 'org.postgresql.Driver'
22
20
 
23
21
  host = config[:host] ||= ( config[:hostaddr] || ENV['PGHOST'] || 'localhost' )
24
22
  port = config[:port] ||= ( ENV['PGPORT'] || 5432 )
@@ -53,15 +51,8 @@ ArJdbc::ConnectionMethods.module_eval do
53
51
  properties['tcpKeepAlive'] ||= config[:keepalives] if config.key?(:keepalives)
54
52
  properties['kerberosServerName'] ||= config[:krbsrvname] if config[:krbsrvname]
55
53
 
56
- prepared_statements = config.fetch(:prepared_statements) { true }
57
- prepared_statements = false if prepared_statements == 'false'
58
- if prepared_statements
59
- # this makes the pgjdbc driver handle hot compatibility internally
60
- properties['autosave'] ||= 'conservative'
61
- else
62
- # If prepared statements are off, lets make sure they are really *off*
63
- properties['prepareThreshold'] = 0
64
- end
54
+ # If prepared statements are off, lets make sure they are really *off*
55
+ properties['prepareThreshold'] ||= 0 unless config[:prepared_statements]
65
56
 
66
57
  jdbc_connection(config)
67
58
  end
@@ -9,61 +9,6 @@ module ArJdbc
9
9
  # @private
10
10
  OID = ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID
11
11
 
12
- # this version makes sure to register the types by name as well
13
- # we still need to version with OID since it's used from SchemaStatements as well
14
- class ArjdbcTypeMapInitializer < OID::TypeMapInitializer
15
- private
16
-
17
- def name_with_ns(row)
18
- if row['in_ns']
19
- row['typname']
20
- else
21
- %Q("#{row['nspname']}"."#{row['typname']}")
22
- end
23
- end
24
-
25
- def register_enum_type(row)
26
- super
27
- register name_with_ns(row), OID::Enum.new
28
- end
29
-
30
- def register_array_type(row)
31
- super
32
- register_with_subtype(name_with_ns(row), row['typelem'].to_i) do |subtype|
33
- OID::Array.new(subtype, row['typdelim'])
34
- end
35
- end
36
-
37
- def register_range_type(row)
38
- super
39
- name = name_with_ns(row)
40
- register_with_subtype(name, row['rngsubtype'].to_i) do |subtype|
41
- OID::Range.new(subtype, name.to_sym)
42
- end
43
- end
44
-
45
- def register_domain_type(row)
46
- if base_type = @store.lookup(row['typbasetype'].to_i)
47
- register row['oid'], base_type
48
- register name_with_ns(row), base_type
49
- else
50
- warn "unknown base type (OID: #{row['typbasetype']}) for domain #{row['typname']}."
51
- end
52
- end
53
-
54
- def register_composite_type(row)
55
- if subtype = @store.lookup(row['typelem'].to_i)
56
- register row['oid'], OID::Vector.new(row['typdelim'], subtype)
57
- register name_with_ns(row), OID::Vector.new(row['typdelim'], subtype)
58
- end
59
- end
60
-
61
- def assert_valid_registration(oid, oid_type)
62
- ret = super
63
- ret == 0 ? oid : ret
64
- end
65
- end
66
-
67
12
  # @private
68
13
  module OIDTypes
69
14
 
@@ -96,7 +41,7 @@ module ArJdbc
96
41
 
97
42
  def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
98
43
  if !type_map.key?(oid)
99
- load_additional_types(type_map, oid)
44
+ load_additional_types(type_map, [oid])
100
45
  end
101
46
 
102
47
  type_map.fetch(oid, fmod, sql_type) {
@@ -124,7 +69,7 @@ module ArJdbc
124
69
  register_class_with_limit m, 'int2', Type::Integer
125
70
  register_class_with_limit m, 'int4', Type::Integer
126
71
  register_class_with_limit m, 'int8', Type::Integer
127
- m.alias_type 'oid', 'int2'
72
+ m.register_type 'oid', OID::Oid.new
128
73
  m.register_type 'float4', Type::Float.new
129
74
  m.alias_type 'float8', 'float4'
130
75
  m.register_type 'text', Type::Text.new
@@ -159,15 +104,10 @@ module ArJdbc
159
104
  m.register_type 'polygon', OID::SpecializedString.new(:polygon)
160
105
  m.register_type 'circle', OID::SpecializedString.new(:circle)
161
106
 
162
- #m.alias_type 'interval', 'varchar' # in Rails 5.0
163
- # This is how Rails 5.1 handles it.
164
- # In 5.0 SpecializedString doesn't take a precision option 5.0 actually leaves it as a regular String
165
- # but we need it specialized to support prepared statements
166
- # m.register_type 'interval' do |_, _, sql_type|
167
- # precision = extract_precision(sql_type)
168
- # OID::SpecializedString.new(:interval, precision: precision)
169
- # end
170
- m.register_type 'interval', OID::SpecializedString.new(:interval)
107
+ m.register_type 'interval' do |_, _, sql_type|
108
+ precision = extract_precision(sql_type)
109
+ OID::SpecializedString.new(:interval, precision: precision)
110
+ end
171
111
 
172
112
  register_class_with_precision m, 'time', Type::Time
173
113
  register_class_with_precision m, 'timestamp', OID::DateTime
@@ -193,45 +133,26 @@ module ArJdbc
193
133
  end
194
134
 
195
135
  load_additional_types(m)
196
-
197
- # pgjdbc returns these if the column is auto-incrmenting
198
- m.alias_type 'serial', 'int4'
199
- m.alias_type 'bigserial', 'int8'
200
136
  end
201
137
 
202
- def load_additional_types(type_map, oid = nil) # :nodoc:
203
- initializer = ArjdbcTypeMapInitializer.new(type_map)
138
+ def load_additional_types(type_map, oids = nil) # :nodoc:
139
+ initializer = OID::TypeMapInitializer.new(type_map)
204
140
 
205
141
  if supports_ranges?
206
142
  query = <<-SQL
207
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype,
208
- ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
143
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
209
144
  FROM pg_type as t
210
145
  LEFT JOIN pg_range as r ON oid = rngtypid
211
- JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
212
146
  SQL
213
147
  else
214
148
  query = <<-SQL
215
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype,
216
- ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
149
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
217
150
  FROM pg_type as t
218
- JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
219
151
  SQL
220
152
  end
221
153
 
222
- if oid
223
- if oid.is_a? Numeric || oid.match(/^\d+$/)
224
- # numeric OID
225
- query += "WHERE t.oid::integer = %s" % oid
226
-
227
- elsif m = oid.match(/"?(\w+)"?\."?(\w+)"?/)
228
- # namespace and type name
229
- query += "WHERE ns.nspname = '%s' AND t.typname = '%s'" % [m[1], m[2]]
230
-
231
- else
232
- # only type name
233
- query += "WHERE t.typname = '%s' AND ns.nspname = ANY(current_schemas(true))" % oid
234
- end
154
+ if oids
155
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
235
156
  else
236
157
  query += initializer.query_conditions_for_initial_load(type_map)
237
158
  end
@@ -255,8 +176,8 @@ module ArJdbc
255
176
  ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql)
256
177
  ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
257
178
  ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
258
- ActiveRecord::Type.register(:point, OID::Rails51Point, adapter: :postgresql)
259
- ActiveRecord::Type.register(:legacy_point, OID::Point, adapter: :postgresql)
179
+ ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
180
+ ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
260
181
  ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
261
182
  ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
262
183
  ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
@@ -4,11 +4,14 @@ require "arjdbc/abstract/core"
4
4
  require "arjdbc/abstract/database_statements"
5
5
  require 'arjdbc/abstract/statement_cache'
6
6
  require "arjdbc/abstract/transaction_support"
7
+ require "active_record/connection_adapters/abstract_adapter"
7
8
  require "active_record/connection_adapters/statement_pool"
8
- require "active_record/connection_adapters/abstract/database_statements"
9
9
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
10
10
  require "active_record/connection_adapters/sqlite3/quoting"
11
11
  require "active_record/connection_adapters/sqlite3/schema_creation"
12
+ require "active_record/connection_adapters/sqlite3/schema_definitions"
13
+ require "active_record/connection_adapters/sqlite3/schema_dumper"
14
+ require "active_record/connection_adapters/sqlite3/schema_statements"
12
15
 
13
16
  module ArJdbc
14
17
  # All the code in this module is a copy of ConnectionAdapters::SQLite3Adapter from active_record 5.
@@ -28,7 +31,10 @@ module ArJdbc
28
31
 
29
32
  ADAPTER_NAME = 'SQLite'.freeze
30
33
 
31
- include Quoting
34
+ # DIFFERENCE: FQN
35
+ include ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
36
+ include ::ActiveRecord::ConnectionAdapters::SQLite3::ColumnDumper
37
+ include ::ActiveRecord::ConnectionAdapters::SQLite3::SchemaStatements
32
38
 
33
39
  NATIVE_DATABASE_TYPES = {
34
40
  primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -52,19 +58,28 @@ module ArJdbc
52
58
  end
53
59
  end
54
60
 
61
+ def update_table_definition(table_name, base) # :nodoc:
62
+ # DIFFERENCE: FQN
63
+ ::ActiveRecord::ConnectionAdapters::SQLite3::Table.new(table_name, base)
64
+ end
65
+
55
66
  def schema_creation # :nodoc:
56
- SQLite3::SchemaCreation.new self
67
+ # DIFFERENCE: FQN
68
+ ::ActiveRecord::ConnectionAdapters::SQLite3::SchemaCreation.new self
57
69
  end
58
70
 
59
71
  def arel_visitor # :nodoc:
60
72
  Arel::Visitors::SQLite.new(self)
61
73
  end
62
74
 
63
- def initialize(connection, logger, connection_options, config)
75
+ # DIFFERENCE: we remove connection_options because we are not using it.
76
+ def initialize(connection, logger, config)
64
77
  super(connection, logger, config)
65
78
 
66
79
  @active = nil
67
80
  @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
81
+
82
+ configure_connection
68
83
  end
69
84
 
70
85
  def supports_ddl_transactions?
@@ -85,17 +100,12 @@ module ArJdbc
85
100
  true
86
101
  end
87
102
 
88
- # Returns true, since this connection adapter supports migrations.
89
- def supports_migrations? #:nodoc:
90
- true
91
- end
92
-
93
- def supports_primary_key? #:nodoc:
103
+ def requires_reloading?
94
104
  true
95
105
  end
96
106
 
97
- def requires_reloading?
98
- true
107
+ def supports_foreign_keys_in_create?
108
+ sqlite_version >= "3.6.19"
99
109
  end
100
110
 
101
111
  def supports_views?
@@ -131,10 +141,6 @@ module ArJdbc
131
141
  true
132
142
  end
133
143
 
134
- def valid_type?(type)
135
- true
136
- end
137
-
138
144
  # Returns 62. SQLite supports index names up to 64
139
145
  # characters. The rest is used by Rails internally to perform
140
146
  # temporary rename operations
@@ -155,43 +161,59 @@ module ArJdbc
155
161
  true
156
162
  end
157
163
 
164
+ # REFERENTIAL INTEGRITY ====================================
165
+
166
+ def disable_referential_integrity # :nodoc:
167
+ old = query_value("PRAGMA foreign_keys")
168
+
169
+ begin
170
+ execute("PRAGMA foreign_keys = OFF")
171
+ yield
172
+ ensure
173
+ execute("PRAGMA foreign_keys = #{old}")
174
+ end
175
+ end
176
+
158
177
  #--
159
178
  # DATABASE STATEMENTS ======================================
160
179
  #++
161
180
 
162
181
  def explain(arel, binds = [])
163
182
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
183
+ # DIFFERENCE: FQN
164
184
  ::ActiveRecord::ConnectionAdapters::SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
165
185
  end
166
186
 
167
187
  def exec_query(sql, name = nil, binds = [], prepare: false)
168
- type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
169
-
170
- log(sql, name, binds) do
171
- # Don't cache statements if they are not prepared
172
- unless prepare
173
- stmt = @connection.prepare(sql)
174
- begin
175
- cols = stmt.columns
176
- unless without_prepared_statement?(binds)
177
- stmt.bind_params(type_casted_binds)
188
+ type_casted_binds = type_casted_binds(binds)
189
+
190
+ log(sql, name, binds, type_casted_binds) do
191
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
192
+ # Don't cache statements if they are not prepared
193
+ unless prepare
194
+ stmt = @connection.prepare(sql)
195
+ begin
196
+ cols = stmt.columns
197
+ unless without_prepared_statement?(binds)
198
+ stmt.bind_params(type_casted_binds)
199
+ end
200
+ records = stmt.to_a
201
+ ensure
202
+ stmt.close
178
203
  end
204
+ else
205
+ cache = @statements[sql] ||= {
206
+ stmt: @connection.prepare(sql)
207
+ }
208
+ stmt = cache[:stmt]
209
+ cols = cache[:cols] ||= stmt.columns
210
+ stmt.reset!
211
+ stmt.bind_params(type_casted_binds)
179
212
  records = stmt.to_a
180
- ensure
181
- stmt.close
182
213
  end
183
- stmt = records
184
- else
185
- cache = @statements[sql] ||= {
186
- :stmt => @connection.prepare(sql)
187
- }
188
- stmt = cache[:stmt]
189
- cols = cache[:cols] ||= stmt.columns
190
- stmt.reset!
191
- stmt.bind_params(type_casted_binds)
192
- end
193
214
 
194
- ActiveRecord::Result.new(cols, stmt.to_a)
215
+ ActiveRecord::Result.new(cols, records)
216
+ end
195
217
  end
196
218
  end
197
219
 
@@ -206,7 +228,11 @@ module ArJdbc
206
228
  end
207
229
 
208
230
  def execute(sql, name = nil) #:nodoc:
209
- log(sql, name) { @connection.execute(sql) }
231
+ log(sql, name) do
232
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
233
+ @connection.execute(sql)
234
+ end
235
+ end
210
236
  end
211
237
 
212
238
  def begin_db_transaction #:nodoc:
@@ -223,80 +249,30 @@ module ArJdbc
223
249
 
224
250
  # SCHEMA STATEMENTS ========================================
225
251
 
226
- def tables(name = nil) # :nodoc:
227
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
228
- #tables currently returns both tables and views.
229
- This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
230
- Use #data_sources instead.
231
- MSG
232
-
233
- if name
234
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
235
- Passing arguments to #tables is deprecated without replacement.
236
- MSG
252
+ def new_column_from_field(table_name, field) # :nondoc:
253
+ case field["dflt_value"]
254
+ when /^null$/i
255
+ field["dflt_value"] = nil
256
+ when /^'(.*)'$/m
257
+ field["dflt_value"] = $1.gsub("''", "'")
258
+ when /^"(.*)"$/m
259
+ field["dflt_value"] = $1.gsub('""', '"')
237
260
  end
238
261
 
239
- data_sources
240
- end
241
-
242
- def data_sources
243
- select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")
244
- end
245
-
246
- def table_exists?(table_name)
247
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
248
- #table_exists? currently checks both tables and views.
249
- This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
250
- Use #data_source_exists? instead.
251
- MSG
252
-
253
- data_source_exists?(table_name)
254
- end
255
-
256
- def data_source_exists?(table_name)
257
- return false unless table_name.present?
258
-
259
- sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
260
- sql << " AND name = #{quote(table_name)}"
261
-
262
- select_values(sql, "SCHEMA").any?
263
- end
264
-
265
- def views # :nodoc:
266
- select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
267
- end
268
-
269
- def view_exists?(view_name) # :nodoc:
270
- return false unless view_name.present?
271
-
272
- sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
273
- sql << " AND name = #{quote(view_name)}"
274
-
275
- select_values(sql, "SCHEMA").any?
276
- end
277
-
278
- # Returns an array of +Column+ objects for the table specified by +table_name+.
279
- def columns(table_name) # :nodoc:
280
- table_name = table_name.to_s
281
- table_structure(table_name).map do |field|
282
- case field["dflt_value"]
283
- when /^null$/i
284
- field["dflt_value"] = nil
285
- when /^'(.*)'$/m
286
- field["dflt_value"] = $1.gsub("''", "'")
287
- when /^"(.*)"$/m
288
- field["dflt_value"] = $1.gsub('""', '"')
289
- end
290
-
291
- collation = field["collation"]
292
- sql_type = field["type"]
293
- type_metadata = fetch_type_metadata(sql_type)
294
- new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
295
- end
262
+ collation = field["collation"]
263
+ sql_type = field["type"]
264
+ type_metadata = fetch_type_metadata(sql_type)
265
+ new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
296
266
  end
297
267
 
298
268
  # Returns an array of indexes for the given table.
299
269
  def indexes(table_name, name = nil) #:nodoc:
270
+ if name
271
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
272
+ Passing name to #indexes is deprecated without replacement.
273
+ MSG
274
+ end
275
+
300
276
  exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
301
277
  sql = <<-SQL
302
278
  SELECT sql
@@ -306,17 +282,17 @@ module ArJdbc
306
282
  SELECT sql
307
283
  FROM sqlite_temp_master
308
284
  WHERE name=#{quote(row['name'])} AND type='index'
309
- SQL
285
+ SQL
310
286
  index_sql = exec_query(sql).first["sql"]
311
287
  match = /\sWHERE\s+(.+)$/i.match(index_sql)
312
288
  where = match[1] if match
313
289
  IndexDefinition.new(
314
- table_name,
315
- row["name"],
316
- row["unique"] != 0,
317
- exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
318
- col["name"]
319
- }, nil, nil, where)
290
+ table_name,
291
+ row["name"],
292
+ row["unique"] != 0,
293
+ exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
294
+ col["name"]
295
+ }, nil, nil, where)
320
296
  end
321
297
  end
322
298
 
@@ -346,7 +322,7 @@ module ArJdbc
346
322
  end
347
323
 
348
324
  def add_column(table_name, column_name, type, options = {}) #:nodoc:
349
- if valid_alter_table_type?(type)
325
+ if valid_alter_table_type?(type) && !options[:primary_key]
350
326
  super(table_name, column_name, type, options)
351
327
  else
352
328
  alter_table(table_name) do |definition|
@@ -380,11 +356,10 @@ module ArJdbc
380
356
 
381
357
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
382
358
  alter_table(table_name) do |definition|
383
- include_default = options_include_default?(options)
384
359
  definition[column_name].instance_eval do
385
360
  self.type = type
386
361
  self.limit = options[:limit] if options.include?(:limit)
387
- self.default = options[:default] if include_default
362
+ self.default = options[:default] if options.include?(:default)
388
363
  self.null = options[:null] if options.include?(:null)
389
364
  self.precision = options[:precision] if options.include?(:precision)
390
365
  self.scale = options[:scale] if options.include?(:scale)
@@ -399,14 +374,34 @@ module ArJdbc
399
374
  rename_column_indexes(table_name, column.name, new_column_name)
400
375
  end
401
376
 
402
- protected
377
+ def add_reference(table_name, ref_name, **options) # :nodoc:
378
+ super(table_name, ref_name, type: :integer, **options)
379
+ end
380
+ alias :add_belongs_to :add_reference
381
+
382
+ def foreign_keys(table_name)
383
+ fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
384
+ fk_info.map do |row|
385
+ options = {
386
+ column: row["from"],
387
+ primary_key: row["to"],
388
+ on_delete: extract_foreign_key_action(row["on_delete"]),
389
+ on_update: extract_foreign_key_action(row["on_update"])
390
+ }
391
+ # DIFFERENCE: FQN
392
+ ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
393
+ end
394
+ end
395
+
396
+ private
403
397
 
404
398
  def table_structure(table_name)
405
399
  structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
406
400
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
407
401
  table_structure_with_collation(table_name, structure)
408
402
  end
409
-
403
+ alias column_definitions table_structure
404
+
410
405
  def alter_table(table_name, options = {}) #:nodoc:
411
406
  altered_table_name = "a#{table_name}"
412
407
  caller = lambda { |definition| yield definition if block_given? }
@@ -418,28 +413,31 @@ module ArJdbc
418
413
  end
419
414
  end
420
415
 
421
- def move_table(from, to, options = {}, &block) #:nodoc:
416
+ def move_table(from, to, options = {}, &block)
422
417
  copy_table(from, to, options, &block)
423
418
  drop_table(from)
424
419
  end
425
420
 
426
- def copy_table(from, to, options = {}) #:nodoc:
421
+ def copy_table(from, to, options = {})
427
422
  from_primary_key = primary_key(from)
428
423
  options[:id] = false
429
424
  create_table(to, options) do |definition|
430
425
  @definition = definition
431
- @definition.primary_key(from_primary_key) if from_primary_key.present?
426
+ if from_primary_key.is_a?(Array)
427
+ @definition.primary_keys from_primary_key
428
+ end
432
429
  columns(from).each do |column|
433
430
  column_name = options[:rename] ?
434
431
  (options[:rename][column.name] ||
435
432
  options[:rename][column.name.to_sym] ||
436
433
  column.name) : column.name
437
- next if column_name == from_primary_key
438
434
 
439
435
  @definition.column(column_name, column.type,
440
436
  limit: column.limit, default: column.default,
441
437
  precision: column.precision, scale: column.scale,
442
- null: column.null, collation: column.collation)
438
+ null: column.null, collation: column.collation,
439
+ primary_key: column_name == from_primary_key
440
+ )
443
441
  end
444
442
  yield @definition if block_given?
445
443
  end
@@ -449,9 +447,12 @@ module ArJdbc
449
447
  options[:rename] || {})
450
448
  end
451
449
 
452
- def copy_table_indexes(from, to, rename = {}) #:nodoc:
450
+ def copy_table_indexes(from, to, rename = {})
453
451
  indexes(from).each do |index|
454
452
  name = index.name
453
+ # indexes sqlite creates for internal use start with `sqlite_` and
454
+ # don't need to be copied
455
+ next if name.starts_with?("sqlite_")
455
456
  if to == "a#{from}"
456
457
  name = "t#{name}"
457
458
  elsif from == "a#{to}"
@@ -467,12 +468,13 @@ module ArJdbc
467
468
  # index name can't be the same
468
469
  opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
469
470
  opts[:unique] = true if index.unique
471
+ opts[:where] = index.where if index.where
470
472
  add_index(to, columns, opts)
471
473
  end
472
474
  end
473
475
  end
474
476
 
475
- def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
477
+ def copy_table_contents(from, to, columns, rename = {})
476
478
  column_mappings = Hash[columns.map { |name| [name, name] }]
477
479
  rename.each { |a| column_mappings[a.last] = a.first }
478
480
  from_columns = columns(from).collect(&:name)
@@ -496,43 +498,51 @@ module ArJdbc
496
498
  # Older versions of SQLite return:
497
499
  # column *column_name* is not unique
498
500
  when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
499
- RecordNotUnique.new(message)
501
+ # DIFFERENCE: FQN
502
+ ::ActiveRecord::RecordNotUnique.new(message)
503
+ when /.* may not be NULL/, /NOT NULL constraint failed: .*/
504
+ # DIFFERENCE: FQN
505
+ ::ActiveRecord::NotNullViolation.new(message)
506
+ when /FOREIGN KEY constraint failed/i
507
+ # DIFFERENCE: FQN
508
+ ::ActiveRecord::InvalidForeignKey.new(message)
500
509
  else
501
510
  super
502
511
  end
503
512
  end
504
513
 
505
- private
506
514
  COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
507
515
 
508
516
  def table_structure_with_collation(table_name, basic_structure)
509
517
  collation_hash = {}
510
- sql = "SELECT sql FROM
511
- (SELECT * FROM sqlite_master UNION ALL
512
- SELECT * FROM sqlite_temp_master)
513
- WHERE type='table' and name='#{ table_name }' \;"
518
+ sql = <<-SQL
519
+ SELECT sql FROM
520
+ (SELECT * FROM sqlite_master UNION ALL
521
+ SELECT * FROM sqlite_temp_master)
522
+ WHERE type = 'table' AND name = #{quote(table_name)}
523
+ SQL
514
524
 
515
525
  # Result will have following sample string
516
526
  # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
517
527
  # "password_digest" varchar COLLATE "NOCASE");
518
- result = exec_query(sql, 'SCHEMA').first
528
+ result = exec_query(sql, "SCHEMA").first
519
529
 
520
530
  if result
521
531
  # Splitting with left parentheses and picking up last will return all
522
532
  # columns separated with comma(,).
523
- columns_string = result["sql"].split('(').last
533
+ columns_string = result["sql"].split("(").last
524
534
 
525
- columns_string.split(',').each do |column_string|
535
+ columns_string.split(",").each do |column_string|
526
536
  # This regex will match the column name and collation type and will save
527
537
  # the value in $1 and $2 respectively.
528
- collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
538
+ collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
529
539
  end
530
540
 
531
541
  basic_structure.map! do |column|
532
- column_name = column['name']
542
+ column_name = column["name"]
533
543
 
534
544
  if collation_hash.has_key? column_name
535
- column['collation'] = collation_hash[column_name]
545
+ column["collation"] = collation_hash[column_name]
536
546
  end
537
547
 
538
548
  column
@@ -541,6 +551,23 @@ module ArJdbc
541
551
  basic_structure.to_hash
542
552
  end
543
553
  end
554
+
555
+ def create_table_definition(*args)
556
+ # DIFFERENCE: FQN
557
+ ::ActiveRecord::ConnectionAdapters::SQLite3::TableDefinition.new(*args)
558
+ end
559
+
560
+ def extract_foreign_key_action(specifier)
561
+ case specifier
562
+ when "CASCADE"; :cascade
563
+ when "SET NULL"; :nullify
564
+ when "RESTRICT"; :restrict
565
+ end
566
+ end
567
+
568
+ def configure_connection
569
+ execute("PRAGMA foreign_keys = ON", "SCHEMA")
570
+ end
544
571
  end
545
572
  end
546
573
 
@@ -651,10 +678,14 @@ module ActiveRecord::ConnectionAdapters
651
678
  include ArJdbc::Abstract::StatementCache
652
679
  include ArJdbc::Abstract::TransactionSupport
653
680
 
681
+ def supports_transaction_isolation?
682
+ false
683
+ end
684
+
654
685
  def begin_isolated_db_transaction(isolation)
655
686
  raise ActiveRecord::TransactionIsolationError, 'adapter does not support setting transaction isolation'
656
687
  end
657
-
688
+
658
689
  # SQLite driver doesn't support all types of insert statements with executeUpdate so
659
690
  # make it act like a regular query and the ids will be returned from #last_inserted_id
660
691
  # example: INSERT INTO "aircraft" DEFAULT VALUES