activerecord 6.0.0.beta2 → 6.0.2.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +471 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +10 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +6 -2
  16. data/lib/active_record/associations/collection_proxy.rb +2 -2
  17. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  18. data/lib/active_record/associations/join_dependency.rb +14 -9
  19. data/lib/active_record/associations/join_dependency/join_association.rb +12 -3
  20. data/lib/active_record/associations/preloader.rb +13 -8
  21. data/lib/active_record/associations/preloader/association.rb +34 -30
  22. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  23. data/lib/active_record/attribute_methods.rb +3 -53
  24. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  25. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  26. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  27. data/lib/active_record/attribute_methods/query.rb +2 -3
  28. data/lib/active_record/attribute_methods/read.rb +3 -9
  29. data/lib/active_record/attribute_methods/write.rb +6 -12
  30. data/lib/active_record/attributes.rb +13 -0
  31. data/lib/active_record/autosave_association.rb +21 -7
  32. data/lib/active_record/base.rb +0 -1
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -11
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +88 -61
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -4
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  39. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  40. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +79 -22
  41. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
  42. data/lib/active_record/connection_adapters/abstract_adapter.rb +114 -34
  43. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +78 -73
  44. data/lib/active_record/connection_adapters/column.rb +17 -13
  45. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  46. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  49. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +10 -7
  51. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  53. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  54. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  55. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  56. data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
  57. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  58. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  59. data/lib/active_record/connection_adapters/postgresql_adapter.rb +68 -27
  60. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  61. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  62. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  63. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  64. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -114
  66. data/lib/active_record/connection_handling.rb +31 -13
  67. data/lib/active_record/core.rb +23 -24
  68. data/lib/active_record/database_configurations.rb +73 -44
  69. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  70. data/lib/active_record/database_configurations/url_config.rb +12 -12
  71. data/lib/active_record/dynamic_matchers.rb +1 -1
  72. data/lib/active_record/enum.rb +15 -0
  73. data/lib/active_record/errors.rb +1 -1
  74. data/lib/active_record/fixtures.rb +11 -6
  75. data/lib/active_record/gem_version.rb +2 -2
  76. data/lib/active_record/insert_all.rb +179 -0
  77. data/lib/active_record/integration.rb +13 -1
  78. data/lib/active_record/internal_metadata.rb +5 -1
  79. data/lib/active_record/locking/optimistic.rb +3 -4
  80. data/lib/active_record/log_subscriber.rb +1 -1
  81. data/lib/active_record/middleware/database_selector.rb +3 -3
  82. data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
  83. data/lib/active_record/migration.rb +62 -44
  84. data/lib/active_record/migration/command_recorder.rb +28 -14
  85. data/lib/active_record/migration/compatibility.rb +10 -0
  86. data/lib/active_record/model_schema.rb +3 -0
  87. data/lib/active_record/persistence.rb +206 -13
  88. data/lib/active_record/querying.rb +17 -12
  89. data/lib/active_record/railtie.rb +0 -1
  90. data/lib/active_record/railties/databases.rake +127 -25
  91. data/lib/active_record/reflection.rb +3 -3
  92. data/lib/active_record/relation.rb +99 -20
  93. data/lib/active_record/relation/calculations.rb +38 -40
  94. data/lib/active_record/relation/delegation.rb +22 -30
  95. data/lib/active_record/relation/finder_methods.rb +17 -12
  96. data/lib/active_record/relation/merger.rb +11 -16
  97. data/lib/active_record/relation/query_methods.rb +228 -76
  98. data/lib/active_record/relation/where_clause.rb +9 -5
  99. data/lib/active_record/sanitization.rb +33 -4
  100. data/lib/active_record/schema.rb +1 -1
  101. data/lib/active_record/schema_dumper.rb +10 -1
  102. data/lib/active_record/schema_migration.rb +1 -1
  103. data/lib/active_record/scoping/default.rb +6 -7
  104. data/lib/active_record/scoping/named.rb +3 -2
  105. data/lib/active_record/statement_cache.rb +2 -2
  106. data/lib/active_record/store.rb +48 -0
  107. data/lib/active_record/table_metadata.rb +9 -13
  108. data/lib/active_record/tasks/database_tasks.rb +109 -6
  109. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  110. data/lib/active_record/test_databases.rb +1 -16
  111. data/lib/active_record/test_fixtures.rb +1 -0
  112. data/lib/active_record/timestamp.rb +26 -16
  113. data/lib/active_record/touch_later.rb +4 -2
  114. data/lib/active_record/transactions.rb +56 -46
  115. data/lib/active_record/type_caster/connection.rb +16 -10
  116. data/lib/active_record/validations.rb +1 -0
  117. data/lib/active_record/validations/uniqueness.rb +3 -5
  118. data/lib/arel.rb +12 -5
  119. data/lib/arel/insert_manager.rb +3 -3
  120. data/lib/arel/nodes.rb +2 -1
  121. data/lib/arel/nodes/comment.rb +29 -0
  122. data/lib/arel/nodes/select_core.rb +16 -12
  123. data/lib/arel/nodes/unary.rb +1 -0
  124. data/lib/arel/nodes/values_list.rb +2 -17
  125. data/lib/arel/select_manager.rb +10 -10
  126. data/lib/arel/visitors/depth_first.rb +7 -2
  127. data/lib/arel/visitors/dot.rb +7 -2
  128. data/lib/arel/visitors/ibm_db.rb +13 -0
  129. data/lib/arel/visitors/informix.rb +6 -0
  130. data/lib/arel/visitors/mssql.rb +15 -1
  131. data/lib/arel/visitors/oracle12.rb +4 -5
  132. data/lib/arel/visitors/postgresql.rb +4 -10
  133. data/lib/arel/visitors/to_sql.rb +107 -131
  134. data/lib/arel/visitors/visitor.rb +9 -5
  135. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  136. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  137. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  138. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  139. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  140. metadata +16 -12
  141. data/lib/active_record/collection_cache_key.rb +0 -53
  142. data/lib/arel/nodes/values.rb +0 -16
@@ -13,6 +13,7 @@ module ActiveRecord
13
13
  @columns_hash = {}
14
14
  @primary_keys = {}
15
15
  @data_sources = {}
16
+ @indexes = {}
16
17
  end
17
18
 
18
19
  def initialize_dup(other)
@@ -21,22 +22,27 @@ module ActiveRecord
21
22
  @columns_hash = @columns_hash.dup
22
23
  @primary_keys = @primary_keys.dup
23
24
  @data_sources = @data_sources.dup
25
+ @indexes = @indexes.dup
24
26
  end
25
27
 
26
28
  def encode_with(coder)
27
- coder["columns"] = @columns
28
- coder["columns_hash"] = @columns_hash
29
- coder["primary_keys"] = @primary_keys
30
- coder["data_sources"] = @data_sources
31
- coder["version"] = connection.migration_context.current_version
29
+ coder["columns"] = @columns
30
+ coder["columns_hash"] = @columns_hash
31
+ coder["primary_keys"] = @primary_keys
32
+ coder["data_sources"] = @data_sources
33
+ coder["indexes"] = @indexes
34
+ coder["version"] = connection.migration_context.current_version
35
+ coder["database_version"] = database_version
32
36
  end
33
37
 
34
38
  def init_with(coder)
35
- @columns = coder["columns"]
36
- @columns_hash = coder["columns_hash"]
37
- @primary_keys = coder["primary_keys"]
38
- @data_sources = coder["data_sources"]
39
- @version = coder["version"]
39
+ @columns = coder["columns"]
40
+ @columns_hash = coder["columns_hash"]
41
+ @primary_keys = coder["primary_keys"]
42
+ @data_sources = coder["data_sources"]
43
+ @indexes = coder["indexes"] || {}
44
+ @version = coder["version"]
45
+ @database_version = coder["database_version"]
40
46
  end
41
47
 
42
48
  def primary_keys(table_name)
@@ -57,6 +63,7 @@ module ActiveRecord
57
63
  primary_keys(table_name)
58
64
  columns(table_name)
59
65
  columns_hash(table_name)
66
+ indexes(table_name)
60
67
  end
61
68
  end
62
69
 
@@ -82,17 +89,27 @@ module ActiveRecord
82
89
  @columns_hash.key?(table_name)
83
90
  end
84
91
 
92
+ def indexes(table_name)
93
+ @indexes[table_name] ||= connection.indexes(table_name)
94
+ end
95
+
96
+ def database_version # :nodoc:
97
+ @database_version ||= connection.get_database_version
98
+ end
99
+
85
100
  # Clears out internal caches
86
101
  def clear!
87
102
  @columns.clear
88
103
  @columns_hash.clear
89
104
  @primary_keys.clear
90
105
  @data_sources.clear
106
+ @indexes.clear
91
107
  @version = nil
108
+ @database_version = nil
92
109
  end
93
110
 
94
111
  def size
95
- [@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
112
+ [@columns, @columns_hash, @primary_keys, @data_sources].sum(&:size)
96
113
  end
97
114
 
98
115
  # Clear out internal caches for the data source +name+.
@@ -101,20 +118,21 @@ module ActiveRecord
101
118
  @columns_hash.delete name
102
119
  @primary_keys.delete name
103
120
  @data_sources.delete name
121
+ @indexes.delete name
104
122
  end
105
123
 
106
124
  def marshal_dump
107
125
  # if we get current version during initialization, it happens stack over flow.
108
126
  @version = connection.migration_context.current_version
109
- [@version, @columns, @columns_hash, @primary_keys, @data_sources]
127
+ [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version]
110
128
  end
111
129
 
112
130
  def marshal_load(array)
113
- @version, @columns, @columns_hash, @primary_keys, @data_sources = array
131
+ @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
132
+ @indexes = @indexes || {}
114
133
  end
115
134
 
116
135
  private
117
-
118
136
  def prepare_data_sources
119
137
  connection.data_sources.each { |source| @data_sources[source] = true }
120
138
  end
@@ -16,19 +16,22 @@ module ActiveRecord
16
16
 
17
17
  def ==(other)
18
18
  other.is_a?(SqlTypeMetadata) &&
19
- attributes_for_hash == other.attributes_for_hash
19
+ sql_type == other.sql_type &&
20
+ type == other.type &&
21
+ limit == other.limit &&
22
+ precision == other.precision &&
23
+ scale == other.scale
20
24
  end
21
25
  alias eql? ==
22
26
 
23
27
  def hash
24
- attributes_for_hash.hash
28
+ SqlTypeMetadata.hash ^
29
+ sql_type.hash ^
30
+ type.hash ^
31
+ limit.hash ^
32
+ precision.hash >> 1 ^
33
+ scale.hash >> 2
25
34
  end
26
-
27
- protected
28
-
29
- def attributes_for_hash
30
- [self.class, sql_type, type, limit, precision, scale]
31
- end
32
35
  end
33
36
  end
34
37
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module SQLite3
6
+ module DatabaseStatements
7
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
8
+ :begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with
9
+ ) # :nodoc:
10
+ private_constant :READ_QUERY
11
+
12
+ def write_query?(sql) # :nodoc:
13
+ !READ_QUERY.match?(sql)
14
+ end
15
+
16
+ def execute(sql, name = nil) #:nodoc:
17
+ if preventing_writes? && write_query?(sql)
18
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
19
+ end
20
+
21
+ materialize_transactions
22
+
23
+ log(sql, name) do
24
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
25
+ @connection.execute(sql)
26
+ end
27
+ end
28
+ end
29
+
30
+ def exec_query(sql, name = nil, binds = [], prepare: false)
31
+ if preventing_writes? && write_query?(sql)
32
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
33
+ end
34
+
35
+ materialize_transactions
36
+
37
+ type_casted_binds = type_casted_binds(binds)
38
+
39
+ log(sql, name, binds, type_casted_binds) do
40
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
41
+ # Don't cache statements if they are not prepared
42
+ unless prepare
43
+ stmt = @connection.prepare(sql)
44
+ begin
45
+ cols = stmt.columns
46
+ unless without_prepared_statement?(binds)
47
+ stmt.bind_params(type_casted_binds)
48
+ end
49
+ records = stmt.to_a
50
+ ensure
51
+ stmt.close
52
+ end
53
+ else
54
+ stmt = @statements[sql] ||= @connection.prepare(sql)
55
+ cols = stmt.columns
56
+ stmt.reset!
57
+ stmt.bind_params(type_casted_binds)
58
+ records = stmt.to_a
59
+ end
60
+
61
+ ActiveRecord::Result.new(cols, records)
62
+ end
63
+ end
64
+ end
65
+
66
+ def exec_delete(sql, name = "SQL", binds = [])
67
+ exec_query(sql, name, binds)
68
+ @connection.changes
69
+ end
70
+ alias :exec_update :exec_delete
71
+
72
+ def begin_db_transaction #:nodoc:
73
+ log("begin transaction", nil) { @connection.transaction }
74
+ end
75
+
76
+ def commit_db_transaction #:nodoc:
77
+ log("commit transaction", nil) { @connection.commit }
78
+ end
79
+
80
+ def exec_rollback_db_transaction #:nodoc:
81
+ log("rollback transaction", nil) { @connection.rollback }
82
+ end
83
+
84
+
85
+ private
86
+ def execute_batch(sql, name = nil)
87
+ if preventing_writes? && write_query?(sql)
88
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
89
+ end
90
+
91
+ materialize_transactions
92
+
93
+ log(sql, name) do
94
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
95
+ @connection.execute_batch2(sql)
96
+ end
97
+ end
98
+ end
99
+
100
+ def last_inserted_id(result)
101
+ @connection.last_insert_row_id
102
+ end
103
+
104
+ def build_fixture_statements(fixture_set)
105
+ fixture_set.flat_map do |table_name, fixtures|
106
+ next if fixtures.empty?
107
+ fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
108
+ end.compact
109
+ end
110
+
111
+ def build_truncate_statements(*table_names)
112
+ truncate_tables = table_names.map do |table_name|
113
+ "DELETE FROM #{quote_table_name(table_name)}"
114
+ end
115
+ combine_multi_statements(truncate_tables)
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -13,11 +13,11 @@ module ActiveRecord
13
13
  end
14
14
 
15
15
  def quote_table_name(name)
16
- @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
16
+ self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
17
17
  end
18
18
 
19
19
  def quote_column_name(name)
20
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
20
+ self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
21
21
  end
22
22
 
23
23
  def quoted_time(value)
@@ -45,6 +45,42 @@ module ActiveRecord
45
45
  0
46
46
  end
47
47
 
48
+ def column_name_matcher
49
+ COLUMN_NAME
50
+ end
51
+
52
+ def column_name_with_order_matcher
53
+ COLUMN_NAME_WITH_ORDER
54
+ end
55
+
56
+ COLUMN_NAME = /
57
+ \A
58
+ (
59
+ (?:
60
+ # "table_name"."column_name" | function(one or no argument)
61
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
62
+ )
63
+ (?:\s+AS\s+(?:\w+|"\w+"))?
64
+ )
65
+ (?:\s*,\s*\g<1>)*
66
+ \z
67
+ /ix
68
+
69
+ COLUMN_NAME_WITH_ORDER = /
70
+ \A
71
+ (
72
+ (?:
73
+ # "table_name"."column_name" | function(one or no argument)
74
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
75
+ )
76
+ (?:\s+ASC|\s+DESC)?
77
+ )
78
+ (?:\s*,\s*\g<1>)*
79
+ \z
80
+ /ix
81
+
82
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
83
+
48
84
  private
49
85
 
50
86
  def _type_cast(value)
@@ -72,7 +72,7 @@ module ActiveRecord
72
72
  table = strip_table_name_prefix_and_suffix(table)
73
73
  fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
74
74
  fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
75
- end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table}")
75
+ end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
76
76
 
77
77
  foreign_keys.delete(fkey)
78
78
  alter_table(from_table, foreign_keys)
@@ -105,7 +105,7 @@ module ActiveRecord
105
105
  end
106
106
 
107
107
  type_metadata = fetch_type_metadata(field["type"])
108
- Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"])
108
+ Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
109
109
  end
110
110
 
111
111
  def data_source_sql(name = nil, type: nil)
@@ -4,12 +4,13 @@ require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
5
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
6
6
  require "active_record/connection_adapters/sqlite3/quoting"
7
+ require "active_record/connection_adapters/sqlite3/database_statements"
7
8
  require "active_record/connection_adapters/sqlite3/schema_creation"
8
9
  require "active_record/connection_adapters/sqlite3/schema_definitions"
9
10
  require "active_record/connection_adapters/sqlite3/schema_dumper"
10
11
  require "active_record/connection_adapters/sqlite3/schema_statements"
11
12
 
12
- gem "sqlite3", "~> 1.3", ">= 1.3.6"
13
+ gem "sqlite3", "~> 1.4"
13
14
  require "sqlite3"
14
15
 
15
16
  module ActiveRecord
@@ -36,8 +37,6 @@ module ActiveRecord
36
37
  config.merge(results_as_hash: true)
37
38
  )
38
39
 
39
- db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
40
-
41
40
  ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
42
41
  rescue Errno::ENOENT => error
43
42
  if error.message.include?("No such file or directory")
@@ -60,6 +59,7 @@ module ActiveRecord
60
59
 
61
60
  include SQLite3::Quoting
62
61
  include SQLite3::SchemaStatements
62
+ include SQLite3::DatabaseStatements
63
63
 
64
64
  NATIVE_DATABASE_TYPES = {
65
65
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -95,12 +95,19 @@ module ActiveRecord
95
95
 
96
96
  def initialize(connection, logger, connection_options, config)
97
97
  super(connection, logger, config)
98
-
99
- @active = true
100
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
101
98
  configure_connection
102
99
  end
103
100
 
101
+ def self.database_exists?(config)
102
+ config = config.symbolize_keys
103
+ if config[:database] == ":memory:"
104
+ return true
105
+ else
106
+ database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
107
+ File.exist?(database_file)
108
+ end
109
+ end
110
+
104
111
  def supports_ddl_transactions?
105
112
  true
106
113
  end
@@ -114,7 +121,7 @@ module ActiveRecord
114
121
  end
115
122
 
116
123
  def supports_expression_index?
117
- sqlite_version >= "3.9.0"
124
+ database_version >= "3.9.0"
118
125
  end
119
126
 
120
127
  def requires_reloading?
@@ -137,23 +144,33 @@ module ActiveRecord
137
144
  true
138
145
  end
139
146
 
147
+ def supports_common_table_expressions?
148
+ database_version >= "3.8.3"
149
+ end
150
+
151
+ def supports_insert_on_conflict?
152
+ database_version >= "3.24.0"
153
+ end
154
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
155
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
156
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
157
+
140
158
  def active?
141
- @active
159
+ !@connection.closed?
160
+ end
161
+
162
+ def reconnect!
163
+ super
164
+ connect if @connection.closed?
142
165
  end
143
166
 
144
167
  # Disconnects from the database if already connected. Otherwise, this
145
168
  # method does nothing.
146
169
  def disconnect!
147
170
  super
148
- @active = false
149
171
  @connection.close rescue nil
150
172
  end
151
173
 
152
- # Clears the prepared statements cache.
153
- def clear_cache!
154
- @statements.clear
155
- end
156
-
157
174
  def supports_index_sort_order?
158
175
  true
159
176
  end
@@ -201,91 +218,11 @@ module ActiveRecord
201
218
  #--
202
219
  # DATABASE STATEMENTS ======================================
203
220
  #++
204
-
205
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc:
206
- private_constant :READ_QUERY
207
-
208
- def write_query?(sql) # :nodoc:
209
- !READ_QUERY.match?(sql)
210
- end
211
-
212
221
  def explain(arel, binds = [])
213
222
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
214
223
  SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
215
224
  end
216
225
 
217
- def exec_query(sql, name = nil, binds = [], prepare: false)
218
- if preventing_writes? && write_query?(sql)
219
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
220
- end
221
-
222
- materialize_transactions
223
-
224
- type_casted_binds = type_casted_binds(binds)
225
-
226
- log(sql, name, binds, type_casted_binds) do
227
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
228
- # Don't cache statements if they are not prepared
229
- unless prepare
230
- stmt = @connection.prepare(sql)
231
- begin
232
- cols = stmt.columns
233
- unless without_prepared_statement?(binds)
234
- stmt.bind_params(type_casted_binds)
235
- end
236
- records = stmt.to_a
237
- ensure
238
- stmt.close
239
- end
240
- else
241
- stmt = @statements[sql] ||= @connection.prepare(sql)
242
- cols = stmt.columns
243
- stmt.reset!
244
- stmt.bind_params(type_casted_binds)
245
- records = stmt.to_a
246
- end
247
-
248
- ActiveRecord::Result.new(cols, records)
249
- end
250
- end
251
- end
252
-
253
- def exec_delete(sql, name = "SQL", binds = [])
254
- exec_query(sql, name, binds)
255
- @connection.changes
256
- end
257
- alias :exec_update :exec_delete
258
-
259
- def last_inserted_id(result)
260
- @connection.last_insert_row_id
261
- end
262
-
263
- def execute(sql, name = nil) #:nodoc:
264
- if preventing_writes? && write_query?(sql)
265
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
266
- end
267
-
268
- materialize_transactions
269
-
270
- log(sql, name) do
271
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
272
- @connection.execute(sql)
273
- end
274
- end
275
- end
276
-
277
- def begin_db_transaction #:nodoc:
278
- log("begin transaction", nil) { @connection.transaction }
279
- end
280
-
281
- def commit_db_transaction #:nodoc:
282
- log("commit transaction", nil) { @connection.commit }
283
- end
284
-
285
- def exec_rollback_db_transaction #:nodoc:
286
- log("rollback transaction", nil) { @connection.rollback }
287
- end
288
-
289
226
  # SCHEMA STATEMENTS ========================================
290
227
 
291
228
  def primary_keys(table_name) # :nodoc:
@@ -381,15 +318,26 @@ module ActiveRecord
381
318
  end
382
319
  end
383
320
 
384
- def insert_fixtures_set(fixture_set, tables_to_delete = [])
385
- disable_referential_integrity do
386
- transaction(requires_new: true) do
387
- tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
321
+ def build_insert_sql(insert) # :nodoc:
322
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
388
323
 
389
- fixture_set.each do |table_name, rows|
390
- rows.each { |row| insert_fixture(row, table_name) }
391
- end
392
- end
324
+ if insert.skip_duplicates?
325
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
326
+ elsif insert.update_duplicates?
327
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
328
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
329
+ end
330
+
331
+ sql
332
+ end
333
+
334
+ def get_database_version # :nodoc:
335
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
336
+ end
337
+
338
+ def check_version # :nodoc:
339
+ if database_version < "3.8.0"
340
+ raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
393
341
  end
394
342
  end
395
343
 
@@ -400,12 +348,6 @@ module ActiveRecord
400
348
  999
401
349
  end
402
350
 
403
- def check_version
404
- if sqlite_version < "3.8.0"
405
- raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
406
- end
407
- end
408
-
409
351
  def initialize_type_map(m = type_map)
410
352
  super
411
353
  register_class_with_limit m, %r(int)i, SQLite3Integer
@@ -523,10 +465,6 @@ module ActiveRecord
523
465
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
524
466
  end
525
467
 
526
- def sqlite_version
527
- @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
528
- end
529
-
530
468
  def translate_exception(exception, message:, sql:, binds:)
531
469
  case exception.message
532
470
  # SQLite 3.8.2 returns a newly formatted error message:
@@ -561,9 +499,9 @@ module ActiveRecord
561
499
  result = exec_query(sql, "SCHEMA").first
562
500
 
563
501
  if result
564
- # Splitting with left parentheses and picking up last will return all
502
+ # Splitting with left parentheses and discarding the first part will return all
565
503
  # columns separated with comma(,).
566
- columns_string = result["sql"].split("(").last
504
+ columns_string = result["sql"].split("(", 2).last
567
505
 
568
506
  columns_string.split(",").each do |column_string|
569
507
  # This regex will match the column name and collation type and will save
@@ -589,7 +527,21 @@ module ActiveRecord
589
527
  Arel::Visitors::SQLite.new(self)
590
528
  end
591
529
 
530
+ def build_statement_pool
531
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
532
+ end
533
+
534
+ def connect
535
+ @connection = ::SQLite3::Database.new(
536
+ @config[:database].to_s,
537
+ @config.merge(results_as_hash: true)
538
+ )
539
+ configure_connection
540
+ end
541
+
592
542
  def configure_connection
543
+ @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
544
+
593
545
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
594
546
  end
595
547