activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha1-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/main.yml +135 -21
- data/.github/workflows/ruby.yml +10 -10
- data/.gitignore +1 -0
- data/.solargraph.yml +15 -0
- data/Gemfile +17 -4
- data/README.md +7 -3
- data/RUNNING_TESTS.md +36 -0
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/activerecord-jdbc-alt-adapter.gemspec +1 -1
- data/lib/arjdbc/abstract/connection_management.rb +23 -10
- data/lib/arjdbc/abstract/core.rb +5 -6
- data/lib/arjdbc/abstract/database_statements.rb +35 -25
- data/lib/arjdbc/abstract/statement_cache.rb +1 -6
- data/lib/arjdbc/abstract/transaction_support.rb +37 -9
- 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/mssql/adapter.rb +93 -80
- data/lib/arjdbc/mssql/column.rb +1 -0
- data/lib/arjdbc/mssql/connection_methods.rb +7 -55
- data/lib/arjdbc/mssql/database_statements.rb +182 -71
- data/lib/arjdbc/mssql/explain_support.rb +8 -5
- data/lib/arjdbc/mssql/schema_creation.rb +1 -1
- data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
- data/lib/arjdbc/mssql/schema_statements.rb +19 -11
- data/lib/arjdbc/mssql/server_version.rb +56 -0
- data/lib/arjdbc/mssql/utils.rb +23 -9
- data/lib/arjdbc/mysql/adapter.rb +64 -22
- data/lib/arjdbc/sqlite3/adapter.rb +218 -135
- data/lib/arjdbc/sqlite3/column.rb +103 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
- 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/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +9 -12
- data/lib/arel/visitors/sql_server/ng42.rb +0 -294
- data/lib/arel/visitors/sql_server.rb +0 -124
- data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
- data/lib/arjdbc/mssql/lock_methods.rb +0 -77
- data/lib/arjdbc/mssql/old_adapter.rb +0 -804
- data/lib/arjdbc/mssql/old_column.rb +0 -200
@@ -16,6 +16,7 @@ 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"
|
19
20
|
|
20
21
|
module SQLite3
|
21
22
|
module Constants
|
@@ -89,20 +90,8 @@ module ArJdbc
|
|
89
90
|
end
|
90
91
|
end
|
91
92
|
|
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
93
|
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
|
94
|
+
@config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
|
106
95
|
end
|
107
96
|
|
108
97
|
def supports_ddl_transactions?
|
@@ -153,6 +142,10 @@ module ArJdbc
|
|
153
142
|
database_version >= "3.8.3"
|
154
143
|
end
|
155
144
|
|
145
|
+
def supports_insert_returning?
|
146
|
+
database_version >= "3.35.0"
|
147
|
+
end
|
148
|
+
|
156
149
|
def supports_insert_on_conflict?
|
157
150
|
database_version >= "3.24.0"
|
158
151
|
end
|
@@ -166,19 +159,22 @@ module ArJdbc
|
|
166
159
|
end
|
167
160
|
|
168
161
|
def active?
|
169
|
-
!@raw_connection.closed?
|
162
|
+
@raw_connection && !@raw_connection.closed?
|
170
163
|
end
|
171
164
|
|
172
|
-
def
|
173
|
-
|
174
|
-
connect if @connection.closed?
|
165
|
+
def return_value_after_insert?(column) # :nodoc:
|
166
|
+
column.auto_populated?
|
175
167
|
end
|
176
168
|
|
169
|
+
# MISSING: alias :reset! :reconnect!
|
170
|
+
|
177
171
|
# Disconnects from the database if already connected. Otherwise, this
|
178
172
|
# method does nothing.
|
179
173
|
def disconnect!
|
180
174
|
super
|
181
|
-
|
175
|
+
|
176
|
+
@raw_connection&.close rescue nil
|
177
|
+
@raw_connection = nil
|
182
178
|
end
|
183
179
|
|
184
180
|
def supports_index_sort_order?
|
@@ -191,7 +187,7 @@ module ArJdbc
|
|
191
187
|
|
192
188
|
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
193
189
|
def encoding
|
194
|
-
|
190
|
+
any_raw_connection.encoding.to_s
|
195
191
|
end
|
196
192
|
|
197
193
|
def supports_explain?
|
@@ -218,8 +214,14 @@ module ArJdbc
|
|
218
214
|
end
|
219
215
|
end
|
220
216
|
|
221
|
-
def
|
222
|
-
|
217
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
218
|
+
sql = "PRAGMA foreign_key_check"
|
219
|
+
result = execute(sql)
|
220
|
+
|
221
|
+
unless result.blank?
|
222
|
+
tables = result.map { |row| row["table"] }
|
223
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
224
|
+
end
|
223
225
|
end
|
224
226
|
|
225
227
|
# SCHEMA STATEMENTS ========================================
|
@@ -234,7 +236,7 @@ module ArJdbc
|
|
234
236
|
|
235
237
|
index_name = index_name_for_remove(table_name, column_name, options)
|
236
238
|
|
237
|
-
|
239
|
+
internal_exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
238
240
|
end
|
239
241
|
|
240
242
|
|
@@ -242,10 +244,11 @@ module ArJdbc
|
|
242
244
|
#
|
243
245
|
# Example:
|
244
246
|
# rename_table('octopuses', 'octopi')
|
245
|
-
def rename_table(table_name, new_name)
|
247
|
+
def rename_table(table_name, new_name, **options)
|
248
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
246
249
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
247
250
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
248
|
-
|
251
|
+
internal_exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
249
252
|
rename_table_indexes(table_name, new_name)
|
250
253
|
end
|
251
254
|
|
@@ -285,8 +288,10 @@ module ArJdbc
|
|
285
288
|
end
|
286
289
|
|
287
290
|
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
291
|
+
validate_change_column_null_argument!(null)
|
292
|
+
|
288
293
|
unless null || default.nil?
|
289
|
-
|
294
|
+
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
295
|
end
|
291
296
|
alter_table(table_name) do |definition|
|
292
297
|
definition[column_name].null = null
|
@@ -295,10 +300,7 @@ module ArJdbc
|
|
295
300
|
|
296
301
|
def change_column(table_name, column_name, type, **options) #:nodoc:
|
297
302
|
alter_table(table_name) do |definition|
|
298
|
-
definition
|
299
|
-
self.type = aliased_types(type.to_s, type)
|
300
|
-
self.options.merge!(options)
|
301
|
-
end
|
303
|
+
definition.change_column(column_name, type, **options)
|
302
304
|
end
|
303
305
|
end
|
304
306
|
|
@@ -308,20 +310,42 @@ module ArJdbc
|
|
308
310
|
rename_column_indexes(table_name, column.name, new_column_name)
|
309
311
|
end
|
310
312
|
|
313
|
+
def add_timestamps(table_name, **options)
|
314
|
+
options[:null] = false if options[:null].nil?
|
315
|
+
|
316
|
+
if !options.key?(:precision)
|
317
|
+
options[:precision] = 6
|
318
|
+
end
|
319
|
+
|
320
|
+
alter_table(table_name) do |definition|
|
321
|
+
definition.column :created_at, :datetime, **options
|
322
|
+
definition.column :updated_at, :datetime, **options
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
311
326
|
def add_reference(table_name, ref_name, **options) # :nodoc:
|
312
327
|
super(table_name, ref_name, type: :integer, **options)
|
313
328
|
end
|
314
329
|
alias :add_belongs_to :add_reference
|
315
330
|
|
316
331
|
def foreign_keys(table_name)
|
317
|
-
|
318
|
-
fk_info
|
332
|
+
# SQLite returns 1 row for each column of composite foreign keys.
|
333
|
+
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
334
|
+
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
335
|
+
grouped_fk.map do |group|
|
336
|
+
row = group.first
|
319
337
|
options = {
|
320
|
-
column: row["from"],
|
321
|
-
primary_key: row["to"],
|
322
338
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
323
339
|
on_update: extract_foreign_key_action(row["on_update"])
|
324
340
|
}
|
341
|
+
|
342
|
+
if group.one?
|
343
|
+
options[:column] = row["from"]
|
344
|
+
options[:primary_key] = row["to"]
|
345
|
+
else
|
346
|
+
options[:column] = group.map { |row| row["from"] }
|
347
|
+
options[:primary_key] = group.map { |row| row["to"] }
|
348
|
+
end
|
325
349
|
# DIFFERENCE: FQN
|
326
350
|
::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
|
327
351
|
end
|
@@ -342,6 +366,7 @@ module ArJdbc
|
|
342
366
|
end
|
343
367
|
end
|
344
368
|
|
369
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
345
370
|
sql
|
346
371
|
end
|
347
372
|
|
@@ -349,6 +374,10 @@ module ArJdbc
|
|
349
374
|
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
350
375
|
end
|
351
376
|
|
377
|
+
def use_insert_returning?
|
378
|
+
@use_insert_returning
|
379
|
+
end
|
380
|
+
|
352
381
|
def get_database_version # :nodoc:
|
353
382
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
354
383
|
end
|
@@ -359,6 +388,27 @@ module ArJdbc
|
|
359
388
|
end
|
360
389
|
end
|
361
390
|
|
391
|
+
# DIFFERENCE: here to
|
392
|
+
def new_column_from_field(table_name, field, definitions)
|
393
|
+
default = field["dflt_value"]
|
394
|
+
|
395
|
+
type_metadata = fetch_type_metadata(field["type"])
|
396
|
+
default_value = extract_value_from_default(default)
|
397
|
+
default_function = extract_default_function(default_value, default)
|
398
|
+
rowid = is_column_the_rowid?(field, definitions)
|
399
|
+
|
400
|
+
ActiveRecord::ConnectionAdapters::SQLite3Column.new(
|
401
|
+
field["name"],
|
402
|
+
default_value,
|
403
|
+
type_metadata,
|
404
|
+
field["notnull"].to_i == 0,
|
405
|
+
default_function,
|
406
|
+
collation: field["collation"],
|
407
|
+
auto_increment: field["auto_increment"],
|
408
|
+
rowid: rowid
|
409
|
+
)
|
410
|
+
end
|
411
|
+
|
362
412
|
private
|
363
413
|
# See https://www.sqlite.org/limits.html,
|
364
414
|
# the default value is 999 when not configured.
|
@@ -367,7 +417,7 @@ module ArJdbc
|
|
367
417
|
end
|
368
418
|
|
369
419
|
def table_structure(table_name)
|
370
|
-
structure =
|
420
|
+
structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
371
421
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
372
422
|
table_structure_with_collation(table_name, structure)
|
373
423
|
end
|
@@ -377,15 +427,18 @@ module ArJdbc
|
|
377
427
|
case default
|
378
428
|
when /^null$/i
|
379
429
|
nil
|
380
|
-
|
381
|
-
when /^'(
|
430
|
+
# Quoted types
|
431
|
+
when /^'([^|]*)'$/m
|
382
432
|
$1.gsub("''", "'")
|
383
|
-
|
384
|
-
when /^"(
|
433
|
+
# Quoted types
|
434
|
+
when /^"([^|]*)"$/m
|
385
435
|
$1.gsub('""', '"')
|
386
|
-
|
436
|
+
# Numeric types
|
387
437
|
when /\A-?\d+(\.\d*)?\z/
|
388
438
|
$&
|
439
|
+
# Binary columns
|
440
|
+
when /x'(.*)'/
|
441
|
+
[ $1 ].pack("H*")
|
389
442
|
else
|
390
443
|
# Anything else is blank or some function
|
391
444
|
# and we can't know the value of that, so return nil.
|
@@ -398,7 +451,7 @@ module ArJdbc
|
|
398
451
|
end
|
399
452
|
|
400
453
|
def has_default_function?(default_value, default)
|
401
|
-
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
|
454
|
+
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
|
402
455
|
end
|
403
456
|
|
404
457
|
# See: https://www.sqlite.org/lang_altertable.html
|
@@ -464,14 +517,24 @@ module ArJdbc
|
|
464
517
|
if column.has_default?
|
465
518
|
type = lookup_cast_type_from_column(column)
|
466
519
|
default = type.deserialize(column.default)
|
520
|
+
default = -> { column.default_function } if default.nil?
|
467
521
|
end
|
468
522
|
|
469
|
-
|
470
|
-
limit: column.limit,
|
471
|
-
precision: column.precision,
|
472
|
-
|
523
|
+
column_options = {
|
524
|
+
limit: column.limit,
|
525
|
+
precision: column.precision,
|
526
|
+
scale: column.scale,
|
527
|
+
null: column.null,
|
528
|
+
collation: column.collation,
|
473
529
|
primary_key: column_name == from_primary_key
|
474
|
-
|
530
|
+
}
|
531
|
+
|
532
|
+
unless column.auto_increment?
|
533
|
+
column_options[:default] = default
|
534
|
+
end
|
535
|
+
|
536
|
+
column_type = column.bigint? ? :bigint : column.type
|
537
|
+
@definition.column(column_name, column_type, **column_options)
|
475
538
|
end
|
476
539
|
|
477
540
|
yield @definition if block_given?
|
@@ -519,8 +582,8 @@ module ArJdbc
|
|
519
582
|
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
520
583
|
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
521
584
|
|
522
|
-
|
523
|
-
|
585
|
+
internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
586
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
524
587
|
end
|
525
588
|
|
526
589
|
def translate_exception(exception, message:, sql:, binds:)
|
@@ -530,25 +593,32 @@ module ArJdbc
|
|
530
593
|
# column *column_name* is not unique
|
531
594
|
if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
|
532
595
|
# DIFFERENCE: FQN
|
533
|
-
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
|
596
|
+
::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
534
597
|
elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
|
535
598
|
# DIFFERENCE: FQN
|
536
|
-
::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
|
599
|
+
::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
537
600
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
538
601
|
# DIFFERENCE: FQN
|
539
|
-
::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
|
602
|
+
::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
540
603
|
elsif exception.message.match?(/called on a closed database/i)
|
541
604
|
# DIFFERENCE: FQN
|
542
|
-
::ActiveRecord::ConnectionNotEstablished.new(exception)
|
605
|
+
::ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
606
|
+
elsif exception.message.match?(/sql error/i)
|
607
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
608
|
+
elsif exception.message.match?(/write a readonly database/i)
|
609
|
+
message = message.sub('org.sqlite.SQLiteException', 'SQLite3::ReadOnlyException')
|
610
|
+
::ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
543
611
|
else
|
544
612
|
super
|
545
613
|
end
|
546
614
|
end
|
547
615
|
|
548
616
|
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
|
617
|
+
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*\"(\w+)\".+PRIMARY KEY AUTOINCREMENT/i
|
549
618
|
|
550
619
|
def table_structure_with_collation(table_name, basic_structure)
|
551
620
|
collation_hash = {}
|
621
|
+
auto_increments = {}
|
552
622
|
sql = <<~SQL
|
553
623
|
SELECT sql FROM
|
554
624
|
(SELECT * FROM sqlite_master UNION ALL
|
@@ -570,6 +640,7 @@ module ArJdbc
|
|
570
640
|
# This regex will match the column name and collation type and will save
|
571
641
|
# the value in $1 and $2 respectively.
|
572
642
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
643
|
+
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
573
644
|
end
|
574
645
|
|
575
646
|
basic_structure.map do |column|
|
@@ -579,6 +650,10 @@ module ArJdbc
|
|
579
650
|
column["collation"] = collation_hash[column_name]
|
580
651
|
end
|
581
652
|
|
653
|
+
if auto_increments.has_key?(column_name)
|
654
|
+
column["auto_increment"] = true
|
655
|
+
end
|
656
|
+
|
582
657
|
column
|
583
658
|
end
|
584
659
|
else
|
@@ -594,99 +669,86 @@ module ArJdbc
|
|
594
669
|
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
595
670
|
end
|
596
671
|
|
597
|
-
def connect
|
598
|
-
@connection = ::SQLite3::Database.new(
|
599
|
-
@config[:database].to_s,
|
600
|
-
@config.merge(results_as_hash: true)
|
601
|
-
)
|
602
|
-
end
|
603
|
-
|
604
672
|
def configure_connection
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
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)
|
673
|
+
if @config[:timeout] && @config[:retries]
|
674
|
+
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
675
|
+
elsif @config[:timeout]
|
676
|
+
# FIXME: missing from adapter
|
677
|
+
# @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
|
678
|
+
elsif @config[:retries]
|
679
|
+
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
680
|
+
raw_connection.busy_handler do |count|
|
681
|
+
count <= retries
|
682
|
+
end
|
632
683
|
end
|
633
|
-
value
|
634
|
-
end
|
635
684
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
685
|
+
# Enforce foreign key constraints
|
686
|
+
# https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
687
|
+
# https://www.sqlite.org/foreignkeys.html
|
688
|
+
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
689
|
+
unless @memory_database
|
690
|
+
# Journal mode WAL allows for greater concurrency (many readers + one writer)
|
691
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_mode
|
692
|
+
raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
|
693
|
+
# Set more relaxed level of database durability
|
694
|
+
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
|
695
|
+
# https://www.sqlite.org/pragma.html#pragma_synchronous
|
696
|
+
raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
|
697
|
+
# Set the global memory map so all processes can share some data
|
698
|
+
# https://www.sqlite.org/pragma.html#pragma_mmap_size
|
699
|
+
# https://www.sqlite.org/mmap.html
|
700
|
+
raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
|
642
701
|
end
|
702
|
+
# Impose a limit on the WAL file to prevent unlimited growth
|
703
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
|
704
|
+
raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
|
705
|
+
# Set the local connection cache to 2000 pages
|
706
|
+
# https://www.sqlite.org/pragma.html#pragma_cache_size
|
707
|
+
raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
|
643
708
|
end
|
644
709
|
|
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
|
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
|
710
|
+
def configure_connection
|
711
|
+
if @config[:timeout] && @config[:retries]
|
712
|
+
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
713
|
+
elsif @config[:timeout]
|
714
|
+
# FIXME:
|
715
|
+
# @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
|
716
|
+
elsif @config[:retries]
|
717
|
+
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
718
|
+
raw_connection.busy_handler do |count|
|
719
|
+
count <= retries
|
720
|
+
end
|
679
721
|
end
|
680
|
-
end
|
681
722
|
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
723
|
+
# Enforce foreign key constraints
|
724
|
+
# https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
725
|
+
# https://www.sqlite.org/foreignkeys.html
|
726
|
+
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
727
|
+
unless @memory_database
|
728
|
+
# Journal mode WAL allows for greater concurrency (many readers + one writer)
|
729
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_mode
|
730
|
+
raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
|
731
|
+
# Set more relaxed level of database durability
|
732
|
+
# 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
|
733
|
+
# https://www.sqlite.org/pragma.html#pragma_synchronous
|
734
|
+
raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
|
735
|
+
# Set the global memory map so all processes can share some data
|
736
|
+
# https://www.sqlite.org/pragma.html#pragma_mmap_size
|
737
|
+
# https://www.sqlite.org/mmap.html
|
738
|
+
raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
|
687
739
|
end
|
740
|
+
# Impose a limit on the WAL file to prevent unlimited growth
|
741
|
+
# https://www.sqlite.org/pragma.html#pragma_journal_size_limit
|
742
|
+
raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
|
743
|
+
# Set the local connection cache to 2000 pages
|
744
|
+
# https://www.sqlite.org/pragma.html#pragma_cache_size
|
745
|
+
raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
|
688
746
|
end
|
689
747
|
end
|
748
|
+
# DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
|
749
|
+
end
|
750
|
+
|
751
|
+
module ActiveRecord::ConnectionAdapters
|
690
752
|
|
691
753
|
remove_const(:SQLite3Adapter) if const_defined?(:SQLite3Adapter)
|
692
754
|
|
@@ -702,6 +764,16 @@ module ActiveRecord::ConnectionAdapters
|
|
702
764
|
include ArJdbc::Abstract::StatementCache
|
703
765
|
include ArJdbc::Abstract::TransactionSupport
|
704
766
|
|
767
|
+
##
|
768
|
+
# :singleton-method:
|
769
|
+
# Configure the SQLite3Adapter to be used in a strict strings mode.
|
770
|
+
# This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
|
771
|
+
# For example, it is possible to create an index for a non existing column.
|
772
|
+
# If you wish to enable this mode you can add the following line to your application.rb file:
|
773
|
+
#
|
774
|
+
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
775
|
+
class_attribute :strict_strings_by_default, default: false # Does not actually do anything right now
|
776
|
+
|
705
777
|
def self.represent_boolean_as_integer=(value) # :nodoc:
|
706
778
|
if value == false
|
707
779
|
raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
|
@@ -736,8 +808,9 @@ module ActiveRecord::ConnectionAdapters
|
|
736
808
|
# SQLite driver doesn't support all types of insert statements with executeUpdate so
|
737
809
|
# make it act like a regular query and the ids will be returned from #last_inserted_id
|
738
810
|
# example: INSERT INTO "aircraft" DEFAULT VALUES
|
739
|
-
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
740
|
-
|
811
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
812
|
+
sql, binds = sql_for_insert(sql, pk, binds, returning)
|
813
|
+
internal_exec_query(sql, name, binds)
|
741
814
|
end
|
742
815
|
|
743
816
|
def jdbc_column_class
|
@@ -770,6 +843,16 @@ module ActiveRecord::ConnectionAdapters
|
|
770
843
|
::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
|
771
844
|
|
772
845
|
class << self
|
846
|
+
def dbconsole(config, options = {})
|
847
|
+
args = []
|
848
|
+
|
849
|
+
args << "-#{options[:mode]}" if options[:mode]
|
850
|
+
args << "-header" if options[:header]
|
851
|
+
args << File.expand_path(config.database, const_defined?(:Rails) && Rails.respond_to?(:root) ? Rails.root : nil)
|
852
|
+
|
853
|
+
find_cmd_and_exec("sqlite3", *args)
|
854
|
+
end
|
855
|
+
|
773
856
|
private
|
774
857
|
def initialize_type_map(m)
|
775
858
|
super
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord::ConnectionAdapters
|
4
|
+
class SQLite3Column < JdbcColumn
|
5
|
+
|
6
|
+
attr_reader :rowid
|
7
|
+
|
8
|
+
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, auto_increment: nil, rowid: false, **)
|
9
|
+
super
|
10
|
+
@auto_increment = auto_increment
|
11
|
+
@default = nil if default =~ /NULL/
|
12
|
+
@rowid = rowid
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.string_to_binary(value)
|
16
|
+
value
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.binary_to_string(value)
|
20
|
+
if value.respond_to?(:encoding) && value.encoding != Encoding::ASCII_8BIT
|
21
|
+
value = value.force_encoding(Encoding::ASCII_8BIT)
|
22
|
+
end
|
23
|
+
value
|
24
|
+
end
|
25
|
+
|
26
|
+
# @override {ActiveRecord::ConnectionAdapters::JdbcColumn#default_value}
|
27
|
+
def default_value(value)
|
28
|
+
# JDBC returns column default strings with actual single quotes :
|
29
|
+
return $1 if value =~ /^'(.*)'$/
|
30
|
+
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
def auto_increment?
|
35
|
+
@auto_increment
|
36
|
+
end
|
37
|
+
|
38
|
+
def auto_incremented_by_db?
|
39
|
+
auto_increment? || rowid
|
40
|
+
end
|
41
|
+
|
42
|
+
def init_with(coder)
|
43
|
+
@auto_increment = coder["auto_increment"]
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def encode_with(coder)
|
48
|
+
coder["auto_increment"] = @auto_increment
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
other.is_a?(Column) &&
|
54
|
+
super &&
|
55
|
+
auto_increment? == other.auto_increment?
|
56
|
+
end
|
57
|
+
alias :eql? :==
|
58
|
+
|
59
|
+
def hash
|
60
|
+
Column.hash ^
|
61
|
+
super.hash ^
|
62
|
+
auto_increment?.hash ^
|
63
|
+
rowid.hash
|
64
|
+
end
|
65
|
+
|
66
|
+
# @override {ActiveRecord::ConnectionAdapters::Column#type_cast}
|
67
|
+
def type_cast(value)
|
68
|
+
return nil if value.nil?
|
69
|
+
case type
|
70
|
+
when :string then value
|
71
|
+
when :primary_key
|
72
|
+
value.respond_to?(:to_i) ? value.to_i : ( value ? 1 : 0 )
|
73
|
+
when :float then value.to_f
|
74
|
+
when :decimal then self.class.value_to_decimal(value)
|
75
|
+
when :boolean then self.class.value_to_boolean(value)
|
76
|
+
else super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# @override {ActiveRecord::ConnectionAdapters::Column#extract_limit}
|
83
|
+
def extract_limit(sql_type)
|
84
|
+
return nil if sql_type =~ /^(real)\(\d+/i
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
def extract_precision(sql_type)
|
89
|
+
case sql_type
|
90
|
+
when /^(real)\((\d+)(,\d+)?\)/i then $2.to_i
|
91
|
+
else super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_scale(sql_type)
|
96
|
+
case sql_type
|
97
|
+
when /^(real)\((\d+)\)/i then 0
|
98
|
+
when /^(real)\((\d+)(,(\d+))\)/i then $4.to_i
|
99
|
+
else super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|