activerecord-jdbc-adapter 70.2-java → 71.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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/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 +11 -10
@@ -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
|