activerecord-jdbc-adapter 50.8-java → 51.1-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 (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