activerecord-jdbc-adapter 70.1-java → 71.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 +18 -18
- data/.gitignore +8 -0
- data/Gemfile +17 -4
- data/README.md +8 -3
- 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 +5 -12
- data/lib/arjdbc/abstract/database_statements.rb +35 -35
- 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 +37 -22
- 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 +106 -27
- data/lib/arjdbc/mysql/connection_methods.rb +43 -42
- data/lib/arjdbc/postgresql/adapter.rb +252 -105
- data/lib/arjdbc/postgresql/database_statements.rb +20 -0
- data/lib/arjdbc/postgresql/oid_types.rb +8 -27
- data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
- data/lib/arjdbc/sqlite3/adapter.rb +213 -145
- data/lib/arjdbc/sqlite3/column.rb +103 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/02-test.rake +1 -1
- data/rakelib/rails.rake +2 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +11 -0
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +12 -11
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArJdbc
|
4
|
+
module PostgreSQL
|
5
|
+
module SchemaStatements
|
6
|
+
ForeignKeyDefinition = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
|
7
|
+
Utils = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
|
8
|
+
|
9
|
+
def foreign_keys(table_name)
|
10
|
+
scope = quoted_scope(table_name)
|
11
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
12
|
+
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
|
13
|
+
FROM pg_constraint c
|
14
|
+
JOIN pg_class t1 ON c.conrelid = t1.oid
|
15
|
+
JOIN pg_class t2 ON c.confrelid = t2.oid
|
16
|
+
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
17
|
+
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
18
|
+
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
19
|
+
WHERE c.contype = 'f'
|
20
|
+
AND t1.relname = #{scope[:name]}
|
21
|
+
AND t3.nspname = #{scope[:schema]}
|
22
|
+
ORDER BY c.conname
|
23
|
+
SQL
|
24
|
+
|
25
|
+
fk_info.map do |row|
|
26
|
+
to_table = Utils.unquote_identifier(row["to_table"])
|
27
|
+
# conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
28
|
+
# confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
29
|
+
conkey = row["conkey"]
|
30
|
+
confkey = row["confkey"]
|
31
|
+
|
32
|
+
if conkey.size > 1
|
33
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
34
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
35
|
+
else
|
36
|
+
column = Utils.unquote_identifier(row["column"])
|
37
|
+
primary_key = row["primary_key"]
|
38
|
+
end
|
39
|
+
|
40
|
+
options = {
|
41
|
+
column: column,
|
42
|
+
name: row["name"],
|
43
|
+
primary_key: primary_key
|
44
|
+
}
|
45
|
+
|
46
|
+
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
47
|
+
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
48
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
49
|
+
|
50
|
+
options[:validate] = row["valid"]
|
51
|
+
|
52
|
+
ForeignKeyDefinition.new(table_name, to_table, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -16,6 +16,9 @@ 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
|
+
|
21
|
+
require "arjdbc/abstract/relation_query_attribute_monkey_patch"
|
19
22
|
|
20
23
|
module SQLite3
|
21
24
|
module Constants
|
@@ -89,20 +92,8 @@ module ArJdbc
|
|
89
92
|
end
|
90
93
|
end
|
91
94
|
|
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
95
|
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
|
96
|
+
@config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
|
106
97
|
end
|
107
98
|
|
108
99
|
def supports_ddl_transactions?
|
@@ -153,6 +144,10 @@ module ArJdbc
|
|
153
144
|
database_version >= "3.8.3"
|
154
145
|
end
|
155
146
|
|
147
|
+
def supports_insert_returning?
|
148
|
+
database_version >= "3.35.0"
|
149
|
+
end
|
150
|
+
|
156
151
|
def supports_insert_on_conflict?
|
157
152
|
database_version >= "3.24.0"
|
158
153
|
end
|
@@ -166,19 +161,22 @@ module ArJdbc
|
|
166
161
|
end
|
167
162
|
|
168
163
|
def active?
|
169
|
-
!@raw_connection.closed?
|
164
|
+
@raw_connection && !@raw_connection.closed?
|
170
165
|
end
|
171
166
|
|
172
|
-
def
|
173
|
-
|
174
|
-
connect if @connection.closed?
|
167
|
+
def return_value_after_insert?(column) # :nodoc:
|
168
|
+
column.auto_populated?
|
175
169
|
end
|
176
170
|
|
171
|
+
# MISSING: alias :reset! :reconnect!
|
172
|
+
|
177
173
|
# Disconnects from the database if already connected. Otherwise, this
|
178
174
|
# method does nothing.
|
179
175
|
def disconnect!
|
180
176
|
super
|
181
|
-
|
177
|
+
|
178
|
+
@raw_connection&.close rescue nil
|
179
|
+
@raw_connection = nil
|
182
180
|
end
|
183
181
|
|
184
182
|
def supports_index_sort_order?
|
@@ -191,7 +189,7 @@ module ArJdbc
|
|
191
189
|
|
192
190
|
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
193
191
|
def encoding
|
194
|
-
|
192
|
+
any_raw_connection.encoding.to_s
|
195
193
|
end
|
196
194
|
|
197
195
|
def supports_explain?
|
@@ -218,8 +216,14 @@ module ArJdbc
|
|
218
216
|
end
|
219
217
|
end
|
220
218
|
|
221
|
-
def
|
222
|
-
|
219
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
220
|
+
sql = "PRAGMA foreign_key_check"
|
221
|
+
result = execute(sql)
|
222
|
+
|
223
|
+
unless result.blank?
|
224
|
+
tables = result.map { |row| row["table"] }
|
225
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
226
|
+
end
|
223
227
|
end
|
224
228
|
|
225
229
|
# SCHEMA STATEMENTS ========================================
|
@@ -234,7 +238,7 @@ module ArJdbc
|
|
234
238
|
|
235
239
|
index_name = index_name_for_remove(table_name, column_name, options)
|
236
240
|
|
237
|
-
|
241
|
+
internal_exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
238
242
|
end
|
239
243
|
|
240
244
|
|
@@ -242,10 +246,11 @@ module ArJdbc
|
|
242
246
|
#
|
243
247
|
# Example:
|
244
248
|
# rename_table('octopuses', 'octopi')
|
245
|
-
def rename_table(table_name, new_name)
|
249
|
+
def rename_table(table_name, new_name, **options)
|
250
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
246
251
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
247
252
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
248
|
-
|
253
|
+
internal_exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
249
254
|
rename_table_indexes(table_name, new_name)
|
250
255
|
end
|
251
256
|
|
@@ -285,8 +290,10 @@ module ArJdbc
|
|
285
290
|
end
|
286
291
|
|
287
292
|
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
293
|
+
validate_change_column_null_argument!(null)
|
294
|
+
|
288
295
|
unless null || default.nil?
|
289
|
-
|
296
|
+
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
297
|
end
|
291
298
|
alter_table(table_name) do |definition|
|
292
299
|
definition[column_name].null = null
|
@@ -295,10 +302,7 @@ module ArJdbc
|
|
295
302
|
|
296
303
|
def change_column(table_name, column_name, type, **options) #:nodoc:
|
297
304
|
alter_table(table_name) do |definition|
|
298
|
-
definition
|
299
|
-
self.type = aliased_types(type.to_s, type)
|
300
|
-
self.options.merge!(options)
|
301
|
-
end
|
305
|
+
definition.change_column(column_name, type, **options)
|
302
306
|
end
|
303
307
|
end
|
304
308
|
|
@@ -308,20 +312,42 @@ module ArJdbc
|
|
308
312
|
rename_column_indexes(table_name, column.name, new_column_name)
|
309
313
|
end
|
310
314
|
|
315
|
+
def add_timestamps(table_name, **options)
|
316
|
+
options[:null] = false if options[:null].nil?
|
317
|
+
|
318
|
+
if !options.key?(:precision)
|
319
|
+
options[:precision] = 6
|
320
|
+
end
|
321
|
+
|
322
|
+
alter_table(table_name) do |definition|
|
323
|
+
definition.column :created_at, :datetime, **options
|
324
|
+
definition.column :updated_at, :datetime, **options
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
311
328
|
def add_reference(table_name, ref_name, **options) # :nodoc:
|
312
329
|
super(table_name, ref_name, type: :integer, **options)
|
313
330
|
end
|
314
331
|
alias :add_belongs_to :add_reference
|
315
332
|
|
316
333
|
def foreign_keys(table_name)
|
317
|
-
|
318
|
-
fk_info
|
334
|
+
# SQLite returns 1 row for each column of composite foreign keys.
|
335
|
+
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
336
|
+
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
337
|
+
grouped_fk.map do |group|
|
338
|
+
row = group.first
|
319
339
|
options = {
|
320
|
-
column: row["from"],
|
321
|
-
primary_key: row["to"],
|
322
340
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
323
341
|
on_update: extract_foreign_key_action(row["on_update"])
|
324
342
|
}
|
343
|
+
|
344
|
+
if group.one?
|
345
|
+
options[:column] = row["from"]
|
346
|
+
options[:primary_key] = row["to"]
|
347
|
+
else
|
348
|
+
options[:column] = group.map { |row| row["from"] }
|
349
|
+
options[:primary_key] = group.map { |row| row["to"] }
|
350
|
+
end
|
325
351
|
# DIFFERENCE: FQN
|
326
352
|
::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
|
327
353
|
end
|
@@ -342,6 +368,7 @@ module ArJdbc
|
|
342
368
|
end
|
343
369
|
end
|
344
370
|
|
371
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
345
372
|
sql
|
346
373
|
end
|
347
374
|
|
@@ -349,6 +376,10 @@ module ArJdbc
|
|
349
376
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
350
377
|
end
|
351
378
|
|
379
|
+
def use_insert_returning?
|
380
|
+
@use_insert_returning
|
381
|
+
end
|
382
|
+
|
352
383
|
def get_database_version # :nodoc:
|
353
384
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
354
385
|
end
|
@@ -359,6 +390,27 @@ module ArJdbc
|
|
359
390
|
end
|
360
391
|
end
|
361
392
|
|
393
|
+
# DIFFERENCE: here to
|
394
|
+
def new_column_from_field(table_name, field, definitions)
|
395
|
+
default = field["dflt_value"]
|
396
|
+
|
397
|
+
type_metadata = fetch_type_metadata(field["type"])
|
398
|
+
default_value = extract_value_from_default(default)
|
399
|
+
default_function = extract_default_function(default_value, default)
|
400
|
+
rowid = is_column_the_rowid?(field, definitions)
|
401
|
+
|
402
|
+
ActiveRecord::ConnectionAdapters::SQLite3Column.new(
|
403
|
+
field["name"],
|
404
|
+
default_value,
|
405
|
+
type_metadata,
|
406
|
+
field["notnull"].to_i == 0,
|
407
|
+
default_function,
|
408
|
+
collation: field["collation"],
|
409
|
+
auto_increment: field["auto_increment"],
|
410
|
+
rowid: rowid
|
411
|
+
)
|
412
|
+
end
|
413
|
+
|
362
414
|
private
|
363
415
|
# See https://www.sqlite.org/limits.html,
|
364
416
|
# the default value is 999 when not configured.
|
@@ -367,7 +419,7 @@ module ArJdbc
|
|
367
419
|
end
|
368
420
|
|
369
421
|
def table_structure(table_name)
|
370
|
-
structure =
|
422
|
+
structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
371
423
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
372
424
|
table_structure_with_collation(table_name, structure)
|
373
425
|
end
|
@@ -377,15 +429,18 @@ module ArJdbc
|
|
377
429
|
case default
|
378
430
|
when /^null$/i
|
379
431
|
nil
|
380
|
-
|
381
|
-
when /^'(
|
432
|
+
# Quoted types
|
433
|
+
when /^'([^|]*)'$/m
|
382
434
|
$1.gsub("''", "'")
|
383
|
-
|
384
|
-
when /^"(
|
435
|
+
# Quoted types
|
436
|
+
when /^"([^|]*)"$/m
|
385
437
|
$1.gsub('""', '"')
|
386
|
-
|
438
|
+
# Numeric types
|
387
439
|
when /\A-?\d+(\.\d*)?\z/
|
388
440
|
$&
|
441
|
+
# Binary columns
|
442
|
+
when /x'(.*)'/
|
443
|
+
[ $1 ].pack("H*")
|
389
444
|
else
|
390
445
|
# Anything else is blank or some function
|
391
446
|
# and we can't know the value of that, so return nil.
|
@@ -398,7 +453,7 @@ module ArJdbc
|
|
398
453
|
end
|
399
454
|
|
400
455
|
def has_default_function?(default_value, default)
|
401
|
-
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
|
456
|
+
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
|
402
457
|
end
|
403
458
|
|
404
459
|
# See: https://www.sqlite.org/lang_altertable.html
|
@@ -464,14 +519,24 @@ module ArJdbc
|
|
464
519
|
if column.has_default?
|
465
520
|
type = lookup_cast_type_from_column(column)
|
466
521
|
default = type.deserialize(column.default)
|
522
|
+
default = -> { column.default_function } if default.nil?
|
467
523
|
end
|
468
524
|
|
469
|
-
|
470
|
-
limit: column.limit,
|
471
|
-
precision: column.precision,
|
472
|
-
|
525
|
+
column_options = {
|
526
|
+
limit: column.limit,
|
527
|
+
precision: column.precision,
|
528
|
+
scale: column.scale,
|
529
|
+
null: column.null,
|
530
|
+
collation: column.collation,
|
473
531
|
primary_key: column_name == from_primary_key
|
474
|
-
|
532
|
+
}
|
533
|
+
|
534
|
+
unless column.auto_increment?
|
535
|
+
column_options[:default] = default
|
536
|
+
end
|
537
|
+
|
538
|
+
column_type = column.bigint? ? :bigint : column.type
|
539
|
+
@definition.column(column_name, column_type, **column_options)
|
475
540
|
end
|
476
541
|
|
477
542
|
yield @definition if block_given?
|
@@ -519,8 +584,8 @@ module ArJdbc
|
|
519
584
|
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
520
585
|
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
521
586
|
|
522
|
-
|
523
|
-
|
587
|
+
internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
588
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
524
589
|
end
|
525
590
|
|
526
591
|
def translate_exception(exception, message:, sql:, binds:)
|
@@ -530,25 +595,32 @@ module ArJdbc
|
|
530
595
|
# column *column_name* is not unique
|
531
596
|
if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
|
532
597
|
# DIFFERENCE: FQN
|
533
|
-
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
|
598
|
+
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
534
599
|
elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
|
535
600
|
# DIFFERENCE: FQN
|
536
|
-
::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
|
601
|
+
::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
537
602
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
538
603
|
# DIFFERENCE: FQN
|
539
|
-
::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
|
604
|
+
::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
540
605
|
elsif exception.message.match?(/called on a closed database/i)
|
541
606
|
# DIFFERENCE: FQN
|
542
|
-
::ActiveRecord::ConnectionNotEstablished.new(exception)
|
607
|
+
::ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
608
|
+
elsif exception.message.match?(/sql error/i)
|
609
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
610
|
+
elsif exception.message.match?(/write a readonly database/i)
|
611
|
+
message = message.sub('org.sqlite.SQLiteException', 'SQLite3::ReadOnlyException')
|
612
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
543
613
|
else
|
544
614
|
super
|
545
615
|
end
|
546
616
|
end
|
547
617
|
|
548
618
|
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
|
619
|
+
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*\"(\w+)\".+PRIMARY KEY AUTOINCREMENT/i
|
549
620
|
|
550
621
|
def table_structure_with_collation(table_name, basic_structure)
|
551
622
|
collation_hash = {}
|
623
|
+
auto_increments = {}
|
552
624
|
sql = <<~SQL
|
553
625
|
SELECT sql FROM
|
554
626
|
(SELECT * FROM sqlite_master UNION ALL
|
@@ -570,6 +642,7 @@ module ArJdbc
|
|
570
642
|
# This regex will match the column name and collation type and will save
|
571
643
|
# the value in $1 and $2 respectively.
|
572
644
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
645
|
+
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
573
646
|
end
|
574
647
|
|
575
648
|
basic_structure.map do |column|
|
@@ -579,6 +652,10 @@ module ArJdbc
|
|
579
652
|
column["collation"] = collation_hash[column_name]
|
580
653
|
end
|
581
654
|
|
655
|
+
if auto_increments.has_key?(column_name)
|
656
|
+
column["auto_increment"] = true
|
657
|
+
end
|
658
|
+
|
582
659
|
column
|
583
660
|
end
|
584
661
|
else
|
@@ -594,99 +671,56 @@ module ArJdbc
|
|
594
671
|
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
595
672
|
end
|
596
673
|
|
597
|
-
def
|
598
|
-
|
599
|
-
@
|
600
|
-
|
601
|
-
|
674
|
+
def reconnect
|
675
|
+
if active?
|
676
|
+
@raw_connection.rollback rescue nil
|
677
|
+
else
|
678
|
+
connect
|
679
|
+
end
|
602
680
|
end
|
603
681
|
|
604
682
|
def configure_connection
|
605
|
-
|
606
|
-
|
683
|
+
if @config[:timeout] && @config[:retries]
|
684
|
+
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
685
|
+
elsif @config[:timeout]
|
686
|
+
# FIXME: missing from adapter
|
687
|
+
# @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
|
688
|
+
elsif @config[:retries]
|
689
|
+
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
690
|
+
raw_connection.busy_handler do |count|
|
691
|
+
count <= retries
|
692
|
+
end
|
693
|
+
end
|
607
694
|
|
608
|
-
|
695
|
+
# Enforce foreign key constraints
|
696
|
+
# https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
697
|
+
# https://www.sqlite.org/foreignkeys.html
|
698
|
+
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
699
|
+
unless @memory_database
|
700
|
+
# Journal mode WAL allows for greater concurrency (many readers + one writer)
|
701
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_mode
|
702
|
+
raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
|
703
|
+
# Set more relaxed level of database durability
|
704
|
+
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
|
705
|
+
# https://www.sqlite.org/pragma.html#pragma_synchronous
|
706
|
+
raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
|
707
|
+
# Set the global memory map so all processes can share some data
|
708
|
+
# https://www.sqlite.org/pragma.html#pragma_mmap_size
|
709
|
+
# https://www.sqlite.org/mmap.html
|
710
|
+
raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
|
711
|
+
end
|
712
|
+
# Impose a limit on the WAL file to prevent unlimited growth
|
713
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
|
714
|
+
raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
|
715
|
+
# Set the local connection cache to 2000 pages
|
716
|
+
# https://www.sqlite.org/pragma.html#pragma_cache_size
|
717
|
+
raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
|
609
718
|
end
|
610
|
-
|
611
719
|
end
|
612
720
|
# DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
|
613
721
|
end
|
614
722
|
|
615
723
|
module ActiveRecord::ConnectionAdapters
|
616
|
-
class SQLite3Column < JdbcColumn
|
617
|
-
def initialize(name, *args)
|
618
|
-
if Hash === name
|
619
|
-
super
|
620
|
-
else
|
621
|
-
super(nil, name, *args)
|
622
|
-
end
|
623
|
-
end
|
624
|
-
|
625
|
-
def self.string_to_binary(value)
|
626
|
-
value
|
627
|
-
end
|
628
|
-
|
629
|
-
def self.binary_to_string(value)
|
630
|
-
if value.respond_to?(:encoding) && value.encoding != Encoding::ASCII_8BIT
|
631
|
-
value = value.force_encoding(Encoding::ASCII_8BIT)
|
632
|
-
end
|
633
|
-
value
|
634
|
-
end
|
635
|
-
|
636
|
-
# @override {ActiveRecord::ConnectionAdapters::JdbcColumn#init_column}
|
637
|
-
def init_column(name, default, *args)
|
638
|
-
if default =~ /NULL/
|
639
|
-
@default = nil
|
640
|
-
else
|
641
|
-
super
|
642
|
-
end
|
643
|
-
end
|
644
|
-
|
645
|
-
# @override {ActiveRecord::ConnectionAdapters::JdbcColumn#default_value}
|
646
|
-
def default_value(value)
|
647
|
-
# JDBC returns column default strings with actual single quotes :
|
648
|
-
return $1 if value =~ /^'(.*)'$/
|
649
|
-
|
650
|
-
value
|
651
|
-
end
|
652
|
-
|
653
|
-
# @override {ActiveRecord::ConnectionAdapters::Column#type_cast}
|
654
|
-
def type_cast(value)
|
655
|
-
return nil if value.nil?
|
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
|
664
|
-
end
|
665
|
-
end
|
666
|
-
|
667
|
-
private
|
668
|
-
|
669
|
-
# @override {ActiveRecord::ConnectionAdapters::Column#extract_limit}
|
670
|
-
def extract_limit(sql_type)
|
671
|
-
return nil if sql_type =~ /^(real)\(\d+/i
|
672
|
-
super
|
673
|
-
end
|
674
|
-
|
675
|
-
def extract_precision(sql_type)
|
676
|
-
case sql_type
|
677
|
-
when /^(real)\((\d+)(,\d+)?\)/i then $2.to_i
|
678
|
-
else super
|
679
|
-
end
|
680
|
-
end
|
681
|
-
|
682
|
-
def extract_scale(sql_type)
|
683
|
-
case sql_type
|
684
|
-
when /^(real)\((\d+)\)/i then 0
|
685
|
-
when /^(real)\((\d+)(,(\d+))\)/i then $4.to_i
|
686
|
-
else super
|
687
|
-
end
|
688
|
-
end
|
689
|
-
end
|
690
724
|
|
691
725
|
remove_const(:SQLite3Adapter) if const_defined?(:SQLite3Adapter)
|
692
726
|
|
@@ -702,6 +736,30 @@ module ActiveRecord::ConnectionAdapters
|
|
702
736
|
include ArJdbc::Abstract::StatementCache
|
703
737
|
include ArJdbc::Abstract::TransactionSupport
|
704
738
|
|
739
|
+
##
|
740
|
+
# :singleton-method:
|
741
|
+
# Configure the SQLite3Adapter to be used in a strict strings mode.
|
742
|
+
# This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
|
743
|
+
# For example, it is possible to create an index for a non existing column.
|
744
|
+
# If you wish to enable this mode you can add the following line to your application.rb file:
|
745
|
+
#
|
746
|
+
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
747
|
+
class_attribute :strict_strings_by_default, default: false # Does not actually do anything right now
|
748
|
+
|
749
|
+
def initialize(...)
|
750
|
+
super
|
751
|
+
|
752
|
+
conn_params = @config.compact
|
753
|
+
|
754
|
+
# NOTE: strict strings is not supported by the jdbc driver yet,
|
755
|
+
# hope it will supported soon, I open a issue in their repository.
|
756
|
+
# https://github.com/xerial/sqlite-jdbc/issues/1153
|
757
|
+
#
|
758
|
+
# @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
759
|
+
|
760
|
+
@connection_parameters = conn_params
|
761
|
+
end
|
762
|
+
|
705
763
|
def self.represent_boolean_as_integer=(value) # :nodoc:
|
706
764
|
if value == false
|
707
765
|
raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
|
@@ -736,23 +794,15 @@ module ActiveRecord::ConnectionAdapters
|
|
736
794
|
# SQLite driver doesn't support all types of insert statements with executeUpdate so
|
737
795
|
# make it act like a regular query and the ids will be returned from #last_inserted_id
|
738
796
|
# example: INSERT INTO "aircraft" DEFAULT VALUES
|
739
|
-
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
740
|
-
|
797
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
798
|
+
sql, binds = sql_for_insert(sql, pk, binds, returning)
|
799
|
+
internal_exec_query(sql, name, binds)
|
741
800
|
end
|
742
801
|
|
743
802
|
def jdbc_column_class
|
744
803
|
::ActiveRecord::ConnectionAdapters::SQLite3Column
|
745
804
|
end
|
746
805
|
|
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
806
|
# 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
807
|
# ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
758
808
|
|
@@ -770,6 +820,24 @@ module ActiveRecord::ConnectionAdapters
|
|
770
820
|
::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
|
771
821
|
|
772
822
|
class << self
|
823
|
+
def jdbc_connection_class
|
824
|
+
::ActiveRecord::ConnectionAdapters::SQLite3JdbcConnection
|
825
|
+
end
|
826
|
+
|
827
|
+
def new_client(conn_params, adapter_instance)
|
828
|
+
jdbc_connection_class.new(conn_params, adapter_instance)
|
829
|
+
end
|
830
|
+
|
831
|
+
def dbconsole(config, options = {})
|
832
|
+
args = []
|
833
|
+
|
834
|
+
args << "-#{options[:mode]}" if options[:mode]
|
835
|
+
args << "-header" if options[:header]
|
836
|
+
args << File.expand_path(config.database, const_defined?(:Rails) && Rails.respond_to?(:root) ? Rails.root : nil)
|
837
|
+
|
838
|
+
find_cmd_and_exec("sqlite3", *args)
|
839
|
+
end
|
840
|
+
|
773
841
|
private
|
774
842
|
def initialize_type_map(m)
|
775
843
|
super
|