activerecord-jdbc-alt-adapter 71.0.0-java → 72.0.0.rc1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/activerecord-jdbc-adapter.gemspec +1 -1
  4. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  5. data/lib/arel/visitors/sqlserver.rb +13 -9
  6. data/lib/arjdbc/abstract/core.rb +10 -1
  7. data/lib/arjdbc/abstract/database_statements.rb +1 -1
  8. data/lib/arjdbc/abstract/transaction_support.rb +3 -1
  9. data/lib/arjdbc/jdbc/adapter.rb +0 -1
  10. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  11. data/lib/arjdbc/mssql/adapter.rb +18 -15
  12. data/lib/arjdbc/mssql/adapter_hash_config.rb +53 -0
  13. data/lib/arjdbc/mssql/column.rb +16 -7
  14. data/lib/arjdbc/mssql/database_statements.rb +7 -2
  15. data/lib/arjdbc/mssql/quoting.rb +56 -36
  16. data/lib/arjdbc/mssql/schema_creation.rb +16 -0
  17. data/lib/arjdbc/mssql/schema_statements.rb +44 -19
  18. data/lib/arjdbc/mssql.rb +1 -1
  19. data/lib/arjdbc/mysql/adapter.rb +8 -11
  20. data/lib/arjdbc/mysql/adapter_hash_config.rb +159 -0
  21. data/lib/arjdbc/mysql.rb +1 -1
  22. data/lib/arjdbc/postgresql/adapter.rb +17 -11
  23. data/lib/arjdbc/postgresql/adapter_hash_config.rb +98 -0
  24. data/lib/arjdbc/postgresql/base/array_encoder.rb +3 -1
  25. data/lib/arjdbc/postgresql/oid_types.rb +2 -2
  26. data/lib/arjdbc/postgresql.rb +1 -1
  27. data/lib/arjdbc/sqlite3/adapter.rb +193 -90
  28. data/lib/arjdbc/sqlite3/adapter_hash_config.rb +91 -0
  29. data/lib/arjdbc/sqlite3/column.rb +17 -3
  30. data/lib/arjdbc/sqlite3/pragmas.rb +105 -0
  31. data/lib/arjdbc/sqlite3.rb +1 -1
  32. data/lib/arjdbc/version.rb +1 -1
  33. data/lib/arjdbc.rb +16 -1
  34. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +5 -0
  35. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +12 -2
  36. metadata +12 -8
  37. data/lib/arjdbc/jdbc/base_ext.rb +0 -17
@@ -17,6 +17,8 @@ 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
19
  require "arjdbc/sqlite3/column"
20
+ require "arjdbc/sqlite3/adapter_hash_config"
21
+ require "arjdbc/sqlite3/pragmas"
20
22
 
21
23
  require "arjdbc/abstract/relation_query_attribute_monkey_patch"
22
24
 
@@ -58,18 +60,12 @@ module ArJdbc
58
60
  # DIFFERENCE: Some common constant names to reduce differences in rest of this module from AR5 version
59
61
  ConnectionAdapters = ::ActiveRecord::ConnectionAdapters
60
62
  IndexDefinition = ::ActiveRecord::ConnectionAdapters::IndexDefinition
63
+ ForeignKeyDefinition = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
61
64
  Quoting = ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
62
65
  RecordNotUnique = ::ActiveRecord::RecordNotUnique
63
66
  SchemaCreation = ConnectionAdapters::SQLite3::SchemaCreation
64
67
  SQLite3Adapter = ConnectionAdapters::AbstractAdapter
65
68
 
66
- ADAPTER_NAME = 'SQLite'
67
-
68
- # DIFFERENCE: FQN
69
- include ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
70
- include ::ActiveRecord::ConnectionAdapters::SQLite3::SchemaStatements
71
- include ::ActiveRecord::ConnectionAdapters::SQLite3::DatabaseStatements
72
-
73
69
  NATIVE_DATABASE_TYPES = {
74
70
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
75
71
  string: { name: "varchar" },
@@ -84,7 +80,16 @@ module ArJdbc
84
80
  boolean: { name: "boolean" },
85
81
  json: { name: "json" },
86
82
  }
87
-
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
+
88
93
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
89
94
  private
90
95
  def dealloc(stmt)
@@ -160,8 +165,23 @@ module ArJdbc
160
165
  !@memory_database
161
166
  end
162
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
+
163
176
  def active?
164
- @raw_connection && !@raw_connection.closed?
177
+ if connected?
178
+ @lock.synchronize do
179
+ if @raw_connection&.active?
180
+ verified!
181
+ true
182
+ end
183
+ end
184
+ end || false
165
185
  end
166
186
 
167
187
  def return_value_after_insert?(column) # :nodoc:
@@ -173,10 +193,11 @@ module ArJdbc
173
193
  # Disconnects from the database if already connected. Otherwise, this
174
194
  # method does nothing.
175
195
  def disconnect!
176
- super
177
-
178
- @raw_connection&.close rescue nil
179
- @raw_connection = nil
196
+ @lock.synchronize do
197
+ super
198
+ @raw_connection&.close rescue nil
199
+ @raw_connection = nil
200
+ end
180
201
  end
181
202
 
182
203
  def supports_index_sort_order?
@@ -241,7 +262,6 @@ module ArJdbc
241
262
  internal_exec_query "DROP INDEX #{quote_column_name(index_name)}"
242
263
  end
243
264
 
244
-
245
265
  # Renames a table.
246
266
  #
247
267
  # Example:
@@ -330,15 +350,31 @@ module ArJdbc
330
350
  end
331
351
  alias :add_belongs_to :add_reference
332
352
 
353
+ FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
354
+ DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
333
355
  def foreign_keys(table_name)
334
356
  # SQLite returns 1 row for each column of composite foreign keys.
335
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
+
336
371
  grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
337
372
  grouped_fk.map do |group|
338
373
  row = group.first
339
374
  options = {
340
375
  on_delete: extract_foreign_key_action(row["on_delete"]),
341
- 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"]]]
342
378
  }
343
379
 
344
380
  if group.one?
@@ -348,8 +384,7 @@ module ArJdbc
348
384
  options[:column] = group.map { |row| row["from"] }
349
385
  options[:primary_key] = group.map { |row| row["to"] }
350
386
  end
351
- # DIFFERENCE: FQN
352
- ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row["table"], options)
387
+ ForeignKeyDefinition.new(table_name, row["table"], options)
353
388
  end
354
389
  end
355
390
 
@@ -396,7 +431,14 @@ module ArJdbc
396
431
 
397
432
  type_metadata = fetch_type_metadata(field["type"])
398
433
  default_value = extract_value_from_default(default)
399
- default_function = extract_default_function(default_value, 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
+
400
442
  rowid = is_column_the_rowid?(field, definitions)
401
443
 
402
444
  ActiveRecord::ConnectionAdapters::SQLite3Column.new(
@@ -407,7 +449,8 @@ module ArJdbc
407
449
  default_function,
408
450
  collation: field["collation"],
409
451
  auto_increment: field["auto_increment"],
410
- rowid: rowid
452
+ rowid: rowid,
453
+ generated_type: generated_type
411
454
  )
412
455
  end
413
456
 
@@ -419,7 +462,12 @@ module ArJdbc
419
462
  end
420
463
 
421
464
  def table_structure(table_name)
422
- structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
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
+
423
471
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
424
472
  table_structure_with_collation(table_name, structure)
425
473
  end
@@ -459,8 +507,9 @@ module ArJdbc
459
507
  # See: https://www.sqlite.org/lang_altertable.html
460
508
  # SQLite has an additional restriction on the ALTER TABLE statement
461
509
  def invalid_alter_table_type?(type, options)
462
- type.to_sym == :primary_key || options[:primary_key] ||
463
- 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])
464
513
  end
465
514
 
466
515
  def alter_table(
@@ -516,12 +565,6 @@ module ArJdbc
516
565
  options[:rename][column.name.to_sym] ||
517
566
  column.name) : column.name
518
567
 
519
- if column.has_default?
520
- type = lookup_cast_type_from_column(column)
521
- default = type.deserialize(column.default)
522
- default = -> { column.default_function } if default.nil?
523
- end
524
-
525
568
  column_options = {
526
569
  limit: column.limit,
527
570
  precision: column.precision,
@@ -531,19 +574,31 @@ module ArJdbc
531
574
  primary_key: column_name == from_primary_key
532
575
  }
533
576
 
534
- unless column.auto_increment?
535
- column_options[:default] = default
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?
582
+ type = lookup_cast_type_from_column(column)
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
536
589
  end
537
590
 
538
- column_type = column.bigint? ? :bigint : column.type
591
+ column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
539
592
  @definition.column(column_name, column_type, **column_options)
540
593
  end
541
594
 
542
595
  yield @definition if block_given?
543
596
  end
544
597
  copy_table_indexes(from, to, options[:rename] || {})
598
+
599
+ columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
545
600
  copy_table_contents(from, to,
546
- @definition.columns.map(&:name),
601
+ columns_to_copy,
547
602
  options[:rename] || {})
548
603
  end
549
604
 
@@ -617,32 +672,22 @@ module ArJdbc
617
672
 
618
673
  COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
619
674
  PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*\"(\w+)\".+PRIMARY KEY AUTOINCREMENT/i
675
+ GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
620
676
 
621
677
  def table_structure_with_collation(table_name, basic_structure)
622
678
  collation_hash = {}
623
679
  auto_increments = {}
624
- sql = <<~SQL
625
- SELECT sql FROM
626
- (SELECT * FROM sqlite_master UNION ALL
627
- SELECT * FROM sqlite_temp_master)
628
- WHERE type = 'table' AND name = #{quote(table_name)}
629
- SQL
680
+ generated_columns = {}
630
681
 
631
- # Result will have following sample string
632
- # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
633
- # "password_digest" varchar COLLATE "NOCASE");
634
- result = query_value(sql, "SCHEMA")
682
+ column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
635
683
 
636
- if result
637
- # Splitting with left parentheses and discarding the first part will return all
638
- # columns separated with comma(,).
639
- columns_string = result.split("(", 2).last
640
-
641
- columns_string.split(",").each do |column_string|
684
+ if column_strings.any?
685
+ column_strings.each do |column_string|
642
686
  # This regex will match the column name and collation type and will save
643
687
  # the value in $1 and $2 respectively.
644
688
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
645
689
  auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
690
+ generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
646
691
  end
647
692
 
648
693
  basic_structure.map do |column|
@@ -656,6 +701,10 @@ module ArJdbc
656
701
  column["auto_increment"] = true
657
702
  end
658
703
 
704
+ if generated_columns.has_key?(column_name)
705
+ column["dflt_value"] = generated_columns[column_name]
706
+ end
707
+
659
708
  column
660
709
  end
661
710
  else
@@ -663,6 +712,50 @@ module ArJdbc
663
712
  end
664
713
  end
665
714
 
715
+ UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
716
+ FINAL_CLOSE_PARENS_REGEX = /\);*\z/
717
+
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
723
+
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
730
+
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")
737
+
738
+ return [] unless result
739
+
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
750
+
751
+ def table_info(table_name)
752
+ if supports_virtual_columns?
753
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
754
+ else
755
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
756
+ end
757
+ end
758
+
666
759
  def arel_visitor
667
760
  Arel::Visitors::SQLite.new(self)
668
761
  end
@@ -692,29 +785,17 @@ module ArJdbc
692
785
  end
693
786
  end
694
787
 
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")
788
+ super
789
+
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
798
+ end
718
799
  end
719
800
  end
720
801
  # DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
@@ -729,13 +810,44 @@ module ActiveRecord::ConnectionAdapters
729
810
  # ActiveRecord::ConnectionAdapters::SQLite3Adapter. Once we can do that we can remove the
730
811
  # module SQLite3 above and remove a majority of this file.
731
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
+
732
841
  include ArJdbc::Abstract::Core
733
842
  include ArJdbc::SQLite3
843
+ include ArJdbc::SQLite3Config
844
+
734
845
  include ArJdbc::Abstract::ConnectionManagement
735
846
  include ArJdbc::Abstract::DatabaseStatements
736
847
  include ArJdbc::Abstract::StatementCache
737
848
  include ArJdbc::Abstract::TransactionSupport
738
849
 
850
+
739
851
  ##
740
852
  # :singleton-method:
741
853
  # Configure the SQLite3Adapter to be used in a strict strings mode.
@@ -749,7 +861,16 @@ module ActiveRecord::ConnectionAdapters
749
861
  def initialize(...)
750
862
  super
751
863
 
752
- conn_params = @config.compact
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)
753
874
 
754
875
  # NOTE: strict strings is not supported by the jdbc driver yet,
755
876
  # hope it will supported soon, I open a issue in their repository.
@@ -820,24 +941,6 @@ module ActiveRecord::ConnectionAdapters
820
941
  ::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
821
942
 
822
943
  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
-
841
944
  private
842
945
  def initialize_type_map(m)
843
946
  super
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArJdbc
4
+ module SQLite3Config
5
+ def build_connection_config(config)
6
+ config = config.deep_dup
7
+
8
+ load_jdbc_driver
9
+
10
+ config[:driver] ||= "org.sqlite.JDBC"
11
+
12
+ parse_sqlite3_config!(config)
13
+
14
+ database = config[:database]
15
+
16
+ # NOTE: "jdbc:sqlite::memory:" syntax is supported
17
+ config[:url] ||= "jdbc:sqlite:#{database == ':memory:' ? '' : database}"
18
+ config[:connection_alive_sql] ||= "SELECT 1"
19
+
20
+ config[:properties] = build_properties(config)
21
+
22
+ config
23
+ end
24
+
25
+ private
26
+
27
+ def load_jdbc_driver
28
+ require "jdbc/sqlite3"
29
+
30
+ ::Jdbc::SQLite3.load_driver(:require) if defined?(::Jdbc::SQLite3.load_driver)
31
+ rescue LoadError
32
+ # assuming driver.jar is on the class-path
33
+ end
34
+
35
+ def build_properties(config)
36
+ properties = config[:properties] || {}
37
+
38
+ if config[:readonly]
39
+ # See
40
+ # * http://sqlite.org/c3ref/open.html
41
+ # * http://sqlite.org/c3ref/c_open_autoproxy.html
42
+ # => 0x01 = readonly, 0x40 = uri (default in JDBC)
43
+ properties[:open_mode] =
44
+ ::SQLite3::Constants::Open::READONLY | ::SQLite3::Constants::Open::URI
45
+ end
46
+
47
+ if config[:flags]
48
+ properties[:open_mode] ||= 0
49
+ properties[:open_mode] |= config[:flags]
50
+
51
+ # JDBC driver has an extra flag for it
52
+ if config[:flags] & ::SQLite3::Constants::Open::SHAREDCACHE != 0
53
+ properties[:shared_cache] = true
54
+ end
55
+ end
56
+
57
+ timeout = config[:timeout]
58
+ if timeout && timeout.to_s !~ /\A\d+\Z/
59
+ raise ActiveRecord::StatementInvalid.new(
60
+ "TypeError: Timeout must be nil or a number (got: #{timeout}).",
61
+ connection_pool: ActiveRecord::ConnectionAdapters::NullPool.new
62
+ )
63
+ end
64
+
65
+ properties["busy_timeout"] ||= timeout unless timeout.nil?
66
+
67
+ properties
68
+ end
69
+
70
+ def parse_sqlite3_config!(config)
71
+ database = (config[:database] ||= config[:dbfile])
72
+
73
+ if database != ":memory:"
74
+ # make sure to have an absolute path. Ruby and Java don't agree
75
+ # on working directory
76
+ base_dir = defined?(Rails.root) ? Rails.root : nil
77
+ config[:database] = File.expand_path(database, base_dir)
78
+ dirname = File.dirname(config[:database])
79
+ Dir.mkdir(dirname) unless File.directory?(dirname)
80
+ end
81
+ rescue Errno::ENOENT => e
82
+ if e.message.include?("No such file or directory")
83
+ raise ActiveRecord::NoDatabaseError.new(
84
+ connection_pool: ActiveRecord::ConnectionAdapters::NullPool.new
85
+ )
86
+ end
87
+
88
+ raise
89
+ end
90
+ end
91
+ end
@@ -4,12 +4,14 @@ module ActiveRecord::ConnectionAdapters
4
4
  class SQLite3Column < JdbcColumn
5
5
 
6
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, **)
7
+
8
+ def initialize(*, auto_increment: nil, rowid: false, generated_type: nil, **)
9
9
  super
10
+
10
11
  @auto_increment = auto_increment
11
12
  @default = nil if default =~ /NULL/
12
13
  @rowid = rowid
14
+ @generated_type = generated_type
13
15
  end
14
16
 
15
17
  def self.string_to_binary(value)
@@ -39,6 +41,18 @@ module ActiveRecord::ConnectionAdapters
39
41
  auto_increment? || rowid
40
42
  end
41
43
 
44
+ def virtual?
45
+ !@generated_type.nil?
46
+ end
47
+
48
+ def virtual_stored?
49
+ virtual? && @generated_type == :stored
50
+ end
51
+
52
+ def has_default?
53
+ super && !virtual?
54
+ end
55
+
42
56
  def init_with(coder)
43
57
  @auto_increment = coder["auto_increment"]
44
58
  super
@@ -100,4 +114,4 @@ module ActiveRecord::ConnectionAdapters
100
114
  end
101
115
  end
102
116
  end
103
- end
117
+ end