activerecord-jdbc-adapter 70.2-java → 72.0-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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +26 -26
- data/.gitignore +8 -0
- data/Gemfile +17 -4
- data/README.md +10 -5
- data/RUNNING_TESTS.md +36 -0
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/lib/arjdbc/abstract/connection_management.rb +25 -10
- data/lib/arjdbc/abstract/core.rb +15 -13
- data/lib/arjdbc/abstract/database_statements.rb +36 -36
- data/lib/arjdbc/abstract/relation_query_attribute_monkey_patch.rb +24 -0
- data/lib/arjdbc/abstract/statement_cache.rb +2 -7
- data/lib/arjdbc/abstract/transaction_support.rb +39 -22
- data/lib/arjdbc/jdbc/adapter.rb +0 -1
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/column.rb +0 -34
- data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
- data/lib/arjdbc/mysql/adapter.rb +108 -32
- data/lib/arjdbc/mysql/adapter_hash_config.rb +159 -0
- data/lib/arjdbc/mysql.rb +1 -1
- data/lib/arjdbc/postgresql/adapter.rb +267 -114
- data/lib/arjdbc/postgresql/adapter_hash_config.rb +98 -0
- data/lib/arjdbc/postgresql/base/array_encoder.rb +3 -1
- data/lib/arjdbc/postgresql/database_statements.rb +20 -0
- data/lib/arjdbc/postgresql/oid_types.rb +10 -29
- data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
- data/lib/arjdbc/postgresql.rb +1 -1
- data/lib/arjdbc/sqlite3/adapter.rb +343 -172
- data/lib/arjdbc/sqlite3/adapter_hash_config.rb +91 -0
- data/lib/arjdbc/sqlite3/column.rb +117 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/sqlite3/pragmas.rb +105 -0
- data/lib/arjdbc/sqlite3.rb +1 -1
- data/lib/arjdbc/version.rb +1 -1
- data/lib/arjdbc.rb +13 -1
- data/rakelib/02-test.rake +2 -2
- data/rakelib/rails.rake +2 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +9 -2
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +12 -5
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +14 -3
- metadata +15 -11
- data/lib/arjdbc/jdbc/base_ext.rb +0 -17
@@ -16,6 +16,11 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
|
|
16
16
|
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
17
17
|
require "active_record/connection_adapters/sqlite3/schema_statements"
|
18
18
|
require "active_support/core_ext/class/attribute"
|
19
|
+
require "arjdbc/sqlite3/column"
|
20
|
+
require "arjdbc/sqlite3/adapter_hash_config"
|
21
|
+
require "arjdbc/sqlite3/pragmas"
|
22
|
+
|
23
|
+
require "arjdbc/abstract/relation_query_attribute_monkey_patch"
|
19
24
|
|
20
25
|
module SQLite3
|
21
26
|
module Constants
|
@@ -55,18 +60,12 @@ module ArJdbc
|
|
55
60
|
# DIFFERENCE: Some common constant names to reduce differences in rest of this module from AR5 version
|
56
61
|
ConnectionAdapters = ::ActiveRecord::ConnectionAdapters
|
57
62
|
IndexDefinition = ::ActiveRecord::ConnectionAdapters::IndexDefinition
|
63
|
+
ForeignKeyDefinition = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
|
58
64
|
Quoting = ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
|
59
65
|
RecordNotUnique = ::ActiveRecord::RecordNotUnique
|
60
66
|
SchemaCreation = ConnectionAdapters::SQLite3::SchemaCreation
|
61
67
|
SQLite3Adapter = ConnectionAdapters::AbstractAdapter
|
62
68
|
|
63
|
-
ADAPTER_NAME = 'SQLite'
|
64
|
-
|
65
|
-
# DIFFERENCE: FQN
|
66
|
-
include ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
|
67
|
-
include ::ActiveRecord::ConnectionAdapters::SQLite3::SchemaStatements
|
68
|
-
include ::ActiveRecord::ConnectionAdapters::SQLite3::DatabaseStatements
|
69
|
-
|
70
69
|
NATIVE_DATABASE_TYPES = {
|
71
70
|
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
|
72
71
|
string: { name: "varchar" },
|
@@ -81,7 +80,16 @@ module ArJdbc
|
|
81
80
|
boolean: { name: "boolean" },
|
82
81
|
json: { name: "json" },
|
83
82
|
}
|
84
|
-
|
83
|
+
|
84
|
+
DEFAULT_PRAGMAS = {
|
85
|
+
"foreign_keys" => true,
|
86
|
+
"journal_mode" => :wal,
|
87
|
+
"synchronous" => :normal,
|
88
|
+
"mmap_size" => 134217728, # 128 megabytes
|
89
|
+
"journal_size_limit" => 67108864, # 64 megabytes
|
90
|
+
"cache_size" => 2000
|
91
|
+
}
|
92
|
+
|
85
93
|
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
86
94
|
private
|
87
95
|
def dealloc(stmt)
|
@@ -89,20 +97,8 @@ module ArJdbc
|
|
89
97
|
end
|
90
98
|
end
|
91
99
|
|
92
|
-
def initialize(connection, logger, connection_options, config)
|
93
|
-
@memory_database = config[:database] == ":memory:"
|
94
|
-
super(connection, logger, config)
|
95
|
-
configure_connection
|
96
|
-
end
|
97
|
-
|
98
100
|
def self.database_exists?(config)
|
99
|
-
config
|
100
|
-
if config[:database] == ":memory:"
|
101
|
-
true
|
102
|
-
else
|
103
|
-
database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
|
104
|
-
File.exist?(database_file)
|
105
|
-
end
|
101
|
+
@config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
|
106
102
|
end
|
107
103
|
|
108
104
|
def supports_ddl_transactions?
|
@@ -153,6 +149,10 @@ module ArJdbc
|
|
153
149
|
database_version >= "3.8.3"
|
154
150
|
end
|
155
151
|
|
152
|
+
def supports_insert_returning?
|
153
|
+
database_version >= "3.35.0"
|
154
|
+
end
|
155
|
+
|
156
156
|
def supports_insert_on_conflict?
|
157
157
|
database_version >= "3.24.0"
|
158
158
|
end
|
@@ -165,20 +165,39 @@ module ArJdbc
|
|
165
165
|
!@memory_database
|
166
166
|
end
|
167
167
|
|
168
|
+
def supports_virtual_columns?
|
169
|
+
database_version >= "3.31.0"
|
170
|
+
end
|
171
|
+
|
172
|
+
def connected?
|
173
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
174
|
+
end
|
175
|
+
|
168
176
|
def active?
|
169
|
-
|
177
|
+
if connected?
|
178
|
+
@lock.synchronize do
|
179
|
+
if @raw_connection&.active?
|
180
|
+
verified!
|
181
|
+
true
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end || false
|
170
185
|
end
|
171
186
|
|
172
|
-
def
|
173
|
-
|
174
|
-
connect if @connection.closed?
|
187
|
+
def return_value_after_insert?(column) # :nodoc:
|
188
|
+
column.auto_populated?
|
175
189
|
end
|
176
190
|
|
191
|
+
# MISSING: alias :reset! :reconnect!
|
192
|
+
|
177
193
|
# Disconnects from the database if already connected. Otherwise, this
|
178
194
|
# method does nothing.
|
179
195
|
def disconnect!
|
180
|
-
|
181
|
-
|
196
|
+
@lock.synchronize do
|
197
|
+
super
|
198
|
+
@raw_connection&.close rescue nil
|
199
|
+
@raw_connection = nil
|
200
|
+
end
|
182
201
|
end
|
183
202
|
|
184
203
|
def supports_index_sort_order?
|
@@ -191,7 +210,7 @@ module ArJdbc
|
|
191
210
|
|
192
211
|
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
193
212
|
def encoding
|
194
|
-
|
213
|
+
any_raw_connection.encoding.to_s
|
195
214
|
end
|
196
215
|
|
197
216
|
def supports_explain?
|
@@ -218,8 +237,14 @@ module ArJdbc
|
|
218
237
|
end
|
219
238
|
end
|
220
239
|
|
221
|
-
def
|
222
|
-
|
240
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
241
|
+
sql = "PRAGMA foreign_key_check"
|
242
|
+
result = execute(sql)
|
243
|
+
|
244
|
+
unless result.blank?
|
245
|
+
tables = result.map { |row| row["table"] }
|
246
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
247
|
+
end
|
223
248
|
end
|
224
249
|
|
225
250
|
# SCHEMA STATEMENTS ========================================
|
@@ -234,18 +259,18 @@ module ArJdbc
|
|
234
259
|
|
235
260
|
index_name = index_name_for_remove(table_name, column_name, options)
|
236
261
|
|
237
|
-
|
262
|
+
internal_exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
238
263
|
end
|
239
264
|
|
240
|
-
|
241
265
|
# Renames a table.
|
242
266
|
#
|
243
267
|
# Example:
|
244
268
|
# rename_table('octopuses', 'octopi')
|
245
|
-
def rename_table(table_name, new_name)
|
269
|
+
def rename_table(table_name, new_name, **options)
|
270
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
246
271
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
247
272
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
248
|
-
|
273
|
+
internal_exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
249
274
|
rename_table_indexes(table_name, new_name)
|
250
275
|
end
|
251
276
|
|
@@ -285,8 +310,10 @@ module ArJdbc
|
|
285
310
|
end
|
286
311
|
|
287
312
|
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
313
|
+
validate_change_column_null_argument!(null)
|
314
|
+
|
288
315
|
unless null || default.nil?
|
289
|
-
|
316
|
+
internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
290
317
|
end
|
291
318
|
alter_table(table_name) do |definition|
|
292
319
|
definition[column_name].null = null
|
@@ -295,10 +322,7 @@ module ArJdbc
|
|
295
322
|
|
296
323
|
def change_column(table_name, column_name, type, **options) #:nodoc:
|
297
324
|
alter_table(table_name) do |definition|
|
298
|
-
definition
|
299
|
-
self.type = aliased_types(type.to_s, type)
|
300
|
-
self.options.merge!(options)
|
301
|
-
end
|
325
|
+
definition.change_column(column_name, type, **options)
|
302
326
|
end
|
303
327
|
end
|
304
328
|
|
@@ -308,22 +332,59 @@ module ArJdbc
|
|
308
332
|
rename_column_indexes(table_name, column.name, new_column_name)
|
309
333
|
end
|
310
334
|
|
335
|
+
def add_timestamps(table_name, **options)
|
336
|
+
options[:null] = false if options[:null].nil?
|
337
|
+
|
338
|
+
if !options.key?(:precision)
|
339
|
+
options[:precision] = 6
|
340
|
+
end
|
341
|
+
|
342
|
+
alter_table(table_name) do |definition|
|
343
|
+
definition.column :created_at, :datetime, **options
|
344
|
+
definition.column :updated_at, :datetime, **options
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
311
348
|
def add_reference(table_name, ref_name, **options) # :nodoc:
|
312
349
|
super(table_name, ref_name, type: :integer, **options)
|
313
350
|
end
|
314
351
|
alias :add_belongs_to :add_reference
|
315
352
|
|
353
|
+
FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
|
354
|
+
DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
|
316
355
|
def foreign_keys(table_name)
|
317
|
-
|
318
|
-
fk_info
|
356
|
+
# SQLite returns 1 row for each column of composite foreign keys.
|
357
|
+
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
358
|
+
# Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
|
359
|
+
fk_defs = table_structure_sql(table_name)
|
360
|
+
.select do |column_string|
|
361
|
+
column_string.start_with?("CONSTRAINT") &&
|
362
|
+
column_string.include?("FOREIGN KEY")
|
363
|
+
end
|
364
|
+
.to_h do |fk_string|
|
365
|
+
_, from, table, to = fk_string.match(FK_REGEX).to_a
|
366
|
+
_, mode = fk_string.match(DEFERRABLE_REGEX).to_a
|
367
|
+
deferred = mode&.downcase&.to_sym || false
|
368
|
+
[[table, from, to], deferred]
|
369
|
+
end
|
370
|
+
|
371
|
+
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
372
|
+
grouped_fk.map do |group|
|
373
|
+
row = group.first
|
319
374
|
options = {
|
320
|
-
column: row["from"],
|
321
|
-
primary_key: row["to"],
|
322
375
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
323
|
-
on_update: extract_foreign_key_action(row["on_update"])
|
376
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
377
|
+
deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
|
324
378
|
}
|
325
|
-
|
326
|
-
|
379
|
+
|
380
|
+
if group.one?
|
381
|
+
options[:column] = row["from"]
|
382
|
+
options[:primary_key] = row["to"]
|
383
|
+
else
|
384
|
+
options[:column] = group.map { |row| row["from"] }
|
385
|
+
options[:primary_key] = group.map { |row| row["to"] }
|
386
|
+
end
|
387
|
+
ForeignKeyDefinition.new(table_name, row["table"], options)
|
327
388
|
end
|
328
389
|
end
|
329
390
|
|
@@ -342,6 +403,7 @@ module ArJdbc
|
|
342
403
|
end
|
343
404
|
end
|
344
405
|
|
406
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
345
407
|
sql
|
346
408
|
end
|
347
409
|
|
@@ -349,6 +411,10 @@ module ArJdbc
|
|
349
411
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
350
412
|
end
|
351
413
|
|
414
|
+
def use_insert_returning?
|
415
|
+
@use_insert_returning
|
416
|
+
end
|
417
|
+
|
352
418
|
def get_database_version # :nodoc:
|
353
419
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
354
420
|
end
|
@@ -359,6 +425,35 @@ module ArJdbc
|
|
359
425
|
end
|
360
426
|
end
|
361
427
|
|
428
|
+
# DIFFERENCE: here to
|
429
|
+
def new_column_from_field(table_name, field, definitions)
|
430
|
+
default = field["dflt_value"]
|
431
|
+
|
432
|
+
type_metadata = fetch_type_metadata(field["type"])
|
433
|
+
default_value = extract_value_from_default(default)
|
434
|
+
generated_type = extract_generated_type(field)
|
435
|
+
|
436
|
+
if generated_type.present?
|
437
|
+
default_function = default
|
438
|
+
else
|
439
|
+
default_function = extract_default_function(default_value, default)
|
440
|
+
end
|
441
|
+
|
442
|
+
rowid = is_column_the_rowid?(field, definitions)
|
443
|
+
|
444
|
+
ActiveRecord::ConnectionAdapters::SQLite3Column.new(
|
445
|
+
field["name"],
|
446
|
+
default_value,
|
447
|
+
type_metadata,
|
448
|
+
field["notnull"].to_i == 0,
|
449
|
+
default_function,
|
450
|
+
collation: field["collation"],
|
451
|
+
auto_increment: field["auto_increment"],
|
452
|
+
rowid: rowid,
|
453
|
+
generated_type: generated_type
|
454
|
+
)
|
455
|
+
end
|
456
|
+
|
362
457
|
private
|
363
458
|
# See https://www.sqlite.org/limits.html,
|
364
459
|
# the default value is 999 when not configured.
|
@@ -367,7 +462,12 @@ module ArJdbc
|
|
367
462
|
end
|
368
463
|
|
369
464
|
def table_structure(table_name)
|
370
|
-
structure =
|
465
|
+
structure = if supports_virtual_columns?
|
466
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
467
|
+
else
|
468
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
469
|
+
end
|
470
|
+
|
371
471
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
372
472
|
table_structure_with_collation(table_name, structure)
|
373
473
|
end
|
@@ -377,15 +477,18 @@ module ArJdbc
|
|
377
477
|
case default
|
378
478
|
when /^null$/i
|
379
479
|
nil
|
380
|
-
|
381
|
-
when /^'(
|
480
|
+
# Quoted types
|
481
|
+
when /^'([^|]*)'$/m
|
382
482
|
$1.gsub("''", "'")
|
383
|
-
|
384
|
-
when /^"(
|
483
|
+
# Quoted types
|
484
|
+
when /^"([^|]*)"$/m
|
385
485
|
$1.gsub('""', '"')
|
386
|
-
|
486
|
+
# Numeric types
|
387
487
|
when /\A-?\d+(\.\d*)?\z/
|
388
488
|
$&
|
489
|
+
# Binary columns
|
490
|
+
when /x'(.*)'/
|
491
|
+
[ $1 ].pack("H*")
|
389
492
|
else
|
390
493
|
# Anything else is blank or some function
|
391
494
|
# and we can't know the value of that, so return nil.
|
@@ -398,14 +501,15 @@ module ArJdbc
|
|
398
501
|
end
|
399
502
|
|
400
503
|
def has_default_function?(default_value, default)
|
401
|
-
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
|
504
|
+
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
|
402
505
|
end
|
403
506
|
|
404
507
|
# See: https://www.sqlite.org/lang_altertable.html
|
405
508
|
# SQLite has an additional restriction on the ALTER TABLE statement
|
406
509
|
def invalid_alter_table_type?(type, options)
|
407
|
-
type
|
408
|
-
options[:null] == false && options[:default].nil?
|
510
|
+
type == :primary_key || options[:primary_key] ||
|
511
|
+
options[:null] == false && options[:default].nil? ||
|
512
|
+
(type == :virtual && options[:stored])
|
409
513
|
end
|
410
514
|
|
411
515
|
def alter_table(
|
@@ -461,24 +565,40 @@ module ArJdbc
|
|
461
565
|
options[:rename][column.name.to_sym] ||
|
462
566
|
column.name) : column.name
|
463
567
|
|
464
|
-
|
568
|
+
column_options = {
|
569
|
+
limit: column.limit,
|
570
|
+
precision: column.precision,
|
571
|
+
scale: column.scale,
|
572
|
+
null: column.null,
|
573
|
+
collation: column.collation,
|
574
|
+
primary_key: column_name == from_primary_key
|
575
|
+
}
|
576
|
+
|
577
|
+
if column.virtual?
|
578
|
+
column_options[:as] = column.default_function
|
579
|
+
column_options[:stored] = column.virtual_stored?
|
580
|
+
column_options[:type] = column.type
|
581
|
+
elsif column.has_default?
|
465
582
|
type = lookup_cast_type_from_column(column)
|
466
583
|
default = type.deserialize(column.default)
|
584
|
+
default = -> { column.default_function } if default.nil?
|
585
|
+
|
586
|
+
unless column.auto_increment?
|
587
|
+
column_options[:default] = default
|
588
|
+
end
|
467
589
|
end
|
468
590
|
|
469
|
-
|
470
|
-
|
471
|
-
precision: column.precision, scale: column.scale,
|
472
|
-
null: column.null, collation: column.collation,
|
473
|
-
primary_key: column_name == from_primary_key
|
474
|
-
)
|
591
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
592
|
+
@definition.column(column_name, column_type, **column_options)
|
475
593
|
end
|
476
594
|
|
477
595
|
yield @definition if block_given?
|
478
596
|
end
|
479
597
|
copy_table_indexes(from, to, options[:rename] || {})
|
598
|
+
|
599
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
480
600
|
copy_table_contents(from, to,
|
481
|
-
|
601
|
+
columns_to_copy,
|
482
602
|
options[:rename] || {})
|
483
603
|
end
|
484
604
|
|
@@ -519,8 +639,8 @@ module ArJdbc
|
|
519
639
|
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
520
640
|
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
521
641
|
|
522
|
-
|
523
|
-
|
642
|
+
internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
643
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
524
644
|
end
|
525
645
|
|
526
646
|
def translate_exception(exception, message:, sql:, binds:)
|
@@ -530,46 +650,44 @@ module ArJdbc
|
|
530
650
|
# column *column_name* is not unique
|
531
651
|
if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
|
532
652
|
# DIFFERENCE: FQN
|
533
|
-
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
|
653
|
+
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
534
654
|
elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
|
535
655
|
# DIFFERENCE: FQN
|
536
|
-
::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
|
656
|
+
::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
537
657
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
538
658
|
# DIFFERENCE: FQN
|
539
|
-
::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
|
659
|
+
::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
540
660
|
elsif exception.message.match?(/called on a closed database/i)
|
541
661
|
# DIFFERENCE: FQN
|
542
|
-
::ActiveRecord::ConnectionNotEstablished.new(exception)
|
662
|
+
::ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
663
|
+
elsif exception.message.match?(/sql error/i)
|
664
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
665
|
+
elsif exception.message.match?(/write a readonly database/i)
|
666
|
+
message = message.sub('org.sqlite.SQLiteException', 'SQLite3::ReadOnlyException')
|
667
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
543
668
|
else
|
544
669
|
super
|
545
670
|
end
|
546
671
|
end
|
547
672
|
|
548
673
|
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
|
674
|
+
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*\"(\w+)\".+PRIMARY KEY AUTOINCREMENT/i
|
675
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
549
676
|
|
550
677
|
def table_structure_with_collation(table_name, basic_structure)
|
551
678
|
collation_hash = {}
|
552
|
-
|
553
|
-
|
554
|
-
(SELECT * FROM sqlite_master UNION ALL
|
555
|
-
SELECT * FROM sqlite_temp_master)
|
556
|
-
WHERE type = 'table' AND name = #{quote(table_name)}
|
557
|
-
SQL
|
679
|
+
auto_increments = {}
|
680
|
+
generated_columns = {}
|
558
681
|
|
559
|
-
|
560
|
-
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
561
|
-
# "password_digest" varchar COLLATE "NOCASE");
|
562
|
-
result = query_value(sql, "SCHEMA")
|
682
|
+
column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
|
563
683
|
|
564
|
-
if
|
565
|
-
|
566
|
-
# columns separated with comma(,).
|
567
|
-
columns_string = result.split("(", 2).last
|
568
|
-
|
569
|
-
columns_string.split(",").each do |column_string|
|
684
|
+
if column_strings.any?
|
685
|
+
column_strings.each do |column_string|
|
570
686
|
# This regex will match the column name and collation type and will save
|
571
687
|
# the value in $1 and $2 respectively.
|
572
688
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
689
|
+
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
690
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
573
691
|
end
|
574
692
|
|
575
693
|
basic_structure.map do |column|
|
@@ -579,6 +697,14 @@ module ArJdbc
|
|
579
697
|
column["collation"] = collation_hash[column_name]
|
580
698
|
end
|
581
699
|
|
700
|
+
if auto_increments.has_key?(column_name)
|
701
|
+
column["auto_increment"] = true
|
702
|
+
end
|
703
|
+
|
704
|
+
if generated_columns.has_key?(column_name)
|
705
|
+
column["dflt_value"] = generated_columns[column_name]
|
706
|
+
end
|
707
|
+
|
582
708
|
column
|
583
709
|
end
|
584
710
|
else
|
@@ -586,107 +712,96 @@ module ArJdbc
|
|
586
712
|
end
|
587
713
|
end
|
588
714
|
|
589
|
-
|
590
|
-
|
591
|
-
end
|
715
|
+
UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
|
716
|
+
FINAL_CLOSE_PARENS_REGEX = /\);*\z/
|
592
717
|
|
593
|
-
def
|
594
|
-
|
595
|
-
|
718
|
+
def table_structure_sql(table_name, column_names = nil)
|
719
|
+
unless column_names
|
720
|
+
column_info = table_info(table_name)
|
721
|
+
column_names = column_info.map { |column| column["name"] }
|
722
|
+
end
|
596
723
|
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
724
|
+
sql = <<~SQL
|
725
|
+
SELECT sql FROM
|
726
|
+
(SELECT * FROM sqlite_master UNION ALL
|
727
|
+
SELECT * FROM sqlite_temp_master)
|
728
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
729
|
+
SQL
|
603
730
|
|
604
|
-
|
605
|
-
#
|
606
|
-
#
|
731
|
+
# Result will have following sample string
|
732
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
733
|
+
# "password_digest" varchar COLLATE "NOCASE",
|
734
|
+
# "o_id" integer,
|
735
|
+
# CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
|
736
|
+
result = query_value(sql, "SCHEMA")
|
607
737
|
|
608
|
-
|
609
|
-
end
|
738
|
+
return [] unless result
|
610
739
|
|
611
|
-
|
612
|
-
|
613
|
-
|
740
|
+
# Splitting with left parentheses and discarding the first part will return all
|
741
|
+
# columns separated with comma(,).
|
742
|
+
result.partition(UNQUOTED_OPEN_PARENS_REGEX)
|
743
|
+
.last
|
744
|
+
.sub(FINAL_CLOSE_PARENS_REGEX, "")
|
745
|
+
# column definitions can have a comma in them, so split on commas followed
|
746
|
+
# by a space and a column name in quotes or followed by the keyword CONSTRAINT
|
747
|
+
.split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
|
748
|
+
.map(&:strip)
|
749
|
+
end
|
614
750
|
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
if Hash === name
|
619
|
-
super
|
751
|
+
def table_info(table_name)
|
752
|
+
if supports_virtual_columns?
|
753
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
620
754
|
else
|
621
|
-
|
755
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
622
756
|
end
|
623
757
|
end
|
624
758
|
|
625
|
-
def
|
626
|
-
|
759
|
+
def arel_visitor
|
760
|
+
Arel::Visitors::SQLite.new(self)
|
627
761
|
end
|
628
762
|
|
629
|
-
def
|
630
|
-
|
631
|
-
value = value.force_encoding(Encoding::ASCII_8BIT)
|
632
|
-
end
|
633
|
-
value
|
763
|
+
def build_statement_pool
|
764
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
634
765
|
end
|
635
766
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
@default = nil
|
767
|
+
def reconnect
|
768
|
+
if active?
|
769
|
+
@raw_connection.rollback rescue nil
|
640
770
|
else
|
641
|
-
|
771
|
+
connect
|
642
772
|
end
|
643
773
|
end
|
644
774
|
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
case type
|
657
|
-
when :string then value
|
658
|
-
when :primary_key
|
659
|
-
value.respond_to?(:to_i) ? value.to_i : ( value ? 1 : 0 )
|
660
|
-
when :float then value.to_f
|
661
|
-
when :decimal then self.class.value_to_decimal(value)
|
662
|
-
when :boolean then self.class.value_to_boolean(value)
|
663
|
-
else super
|
775
|
+
def configure_connection
|
776
|
+
if @config[:timeout] && @config[:retries]
|
777
|
+
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
778
|
+
elsif @config[:timeout]
|
779
|
+
# FIXME: missing from adapter
|
780
|
+
# @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
|
781
|
+
elsif @config[:retries]
|
782
|
+
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
783
|
+
raw_connection.busy_handler do |count|
|
784
|
+
count <= retries
|
785
|
+
end
|
664
786
|
end
|
665
|
-
end
|
666
|
-
|
667
|
-
private
|
668
787
|
|
669
|
-
# @override {ActiveRecord::ConnectionAdapters::Column#extract_limit}
|
670
|
-
def extract_limit(sql_type)
|
671
|
-
return nil if sql_type =~ /^(real)\(\d+/i
|
672
788
|
super
|
673
|
-
end
|
674
789
|
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
case sql_type
|
684
|
-
when /^(real)\((\d+)\)/i then 0
|
685
|
-
when /^(real)\((\d+)(,(\d+))\)/i then $4.to_i
|
686
|
-
else super
|
790
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
791
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
792
|
+
if ::SQLite3::Pragmas.respond_to?(pragma)
|
793
|
+
stmt = ::SQLite3::Pragmas.public_send(pragma, value)
|
794
|
+
raw_execute(stmt, "SCHEMA")
|
795
|
+
else
|
796
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
797
|
+
end
|
687
798
|
end
|
688
799
|
end
|
689
800
|
end
|
801
|
+
# DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
|
802
|
+
end
|
803
|
+
|
804
|
+
module ActiveRecord::ConnectionAdapters
|
690
805
|
|
691
806
|
remove_const(:SQLite3Adapter) if const_defined?(:SQLite3Adapter)
|
692
807
|
|
@@ -695,13 +810,77 @@ module ActiveRecord::ConnectionAdapters
|
|
695
810
|
# ActiveRecord::ConnectionAdapters::SQLite3Adapter. Once we can do that we can remove the
|
696
811
|
# module SQLite3 above and remove a majority of this file.
|
697
812
|
class SQLite3Adapter < AbstractAdapter
|
813
|
+
ADAPTER_NAME = "SQLite"
|
814
|
+
|
815
|
+
class << self
|
816
|
+
def new_client(conn_params, adapter_instance)
|
817
|
+
jdbc_connection_class.new(conn_params, adapter_instance)
|
818
|
+
end
|
819
|
+
|
820
|
+
def dbconsole(config, options = {})
|
821
|
+
args = []
|
822
|
+
|
823
|
+
args << "-#{options[:mode]}" if options[:mode]
|
824
|
+
args << "-header" if options[:header]
|
825
|
+
args << File.expand_path(config.database, const_defined?(:Rails) && Rails.respond_to?(:root) ? Rails.root : nil)
|
826
|
+
|
827
|
+
find_cmd_and_exec("sqlite3", *args)
|
828
|
+
end
|
829
|
+
|
830
|
+
def jdbc_connection_class
|
831
|
+
::ActiveRecord::ConnectionAdapters::SQLite3JdbcConnection
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
# NOTE: include these modules before all then override some methods with the
|
836
|
+
# ones defined in ArJdbc::SQLite3 java part and ArJdbc::Abstract
|
837
|
+
include ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
|
838
|
+
include ::ActiveRecord::ConnectionAdapters::SQLite3::SchemaStatements
|
839
|
+
include ::ActiveRecord::ConnectionAdapters::SQLite3::DatabaseStatements
|
840
|
+
|
698
841
|
include ArJdbc::Abstract::Core
|
699
842
|
include ArJdbc::SQLite3
|
843
|
+
include ArJdbc::SQLite3Config
|
844
|
+
|
700
845
|
include ArJdbc::Abstract::ConnectionManagement
|
701
846
|
include ArJdbc::Abstract::DatabaseStatements
|
702
847
|
include ArJdbc::Abstract::StatementCache
|
703
848
|
include ArJdbc::Abstract::TransactionSupport
|
704
849
|
|
850
|
+
|
851
|
+
##
|
852
|
+
# :singleton-method:
|
853
|
+
# Configure the SQLite3Adapter to be used in a strict strings mode.
|
854
|
+
# This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
|
855
|
+
# For example, it is possible to create an index for a non existing column.
|
856
|
+
# If you wish to enable this mode you can add the following line to your application.rb file:
|
857
|
+
#
|
858
|
+
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
859
|
+
class_attribute :strict_strings_by_default, default: false # Does not actually do anything right now
|
860
|
+
|
861
|
+
def initialize(...)
|
862
|
+
super
|
863
|
+
|
864
|
+
@memory_database = false
|
865
|
+
case @config[:database].to_s
|
866
|
+
when ""
|
867
|
+
raise ArgumentError, "No database file specified. Missing argument: database"
|
868
|
+
when ":memory:"
|
869
|
+
@memory_database = true
|
870
|
+
end
|
871
|
+
|
872
|
+
# assign arjdbc extra connection params
|
873
|
+
conn_params = build_connection_config(@config.compact)
|
874
|
+
|
875
|
+
# NOTE: strict strings is not supported by the jdbc driver yet,
|
876
|
+
# hope it will supported soon, I open a issue in their repository.
|
877
|
+
# https://github.com/xerial/sqlite-jdbc/issues/1153
|
878
|
+
#
|
879
|
+
# @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
880
|
+
|
881
|
+
@connection_parameters = conn_params
|
882
|
+
end
|
883
|
+
|
705
884
|
def self.represent_boolean_as_integer=(value) # :nodoc:
|
706
885
|
if value == false
|
707
886
|
raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
|
@@ -736,23 +915,15 @@ module ActiveRecord::ConnectionAdapters
|
|
736
915
|
# SQLite driver doesn't support all types of insert statements with executeUpdate so
|
737
916
|
# make it act like a regular query and the ids will be returned from #last_inserted_id
|
738
917
|
# example: INSERT INTO "aircraft" DEFAULT VALUES
|
739
|
-
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
740
|
-
|
918
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
919
|
+
sql, binds = sql_for_insert(sql, pk, binds, returning)
|
920
|
+
internal_exec_query(sql, name, binds)
|
741
921
|
end
|
742
922
|
|
743
923
|
def jdbc_column_class
|
744
924
|
::ActiveRecord::ConnectionAdapters::SQLite3Column
|
745
925
|
end
|
746
926
|
|
747
|
-
def jdbc_connection_class(spec)
|
748
|
-
self.class.jdbc_connection_class
|
749
|
-
end
|
750
|
-
|
751
|
-
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
|
752
|
-
def self.jdbc_connection_class
|
753
|
-
::ActiveRecord::ConnectionAdapters::SQLite3JdbcConnection
|
754
|
-
end
|
755
|
-
|
756
927
|
# 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).
|
757
928
|
# ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
758
929
|
|