activerecord 4.2.11.3 → 5.0.0.beta1

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 (229) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1029 -1349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record.rb +7 -3
  7. data/lib/active_record/aggregations.rb +35 -25
  8. data/lib/active_record/association_relation.rb +2 -2
  9. data/lib/active_record/associations.rb +305 -204
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +10 -8
  12. data/lib/active_record/associations/association_scope.rb +73 -102
  13. data/lib/active_record/associations/belongs_to_association.rb +20 -32
  14. data/lib/active_record/associations/builder/association.rb +28 -34
  15. data/lib/active_record/associations/builder/belongs_to.rb +41 -18
  16. data/lib/active_record/associations/builder/collection_association.rb +8 -24
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
  18. data/lib/active_record/associations/builder/has_many.rb +4 -4
  19. data/lib/active_record/associations/builder/has_one.rb +10 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -9
  21. data/lib/active_record/associations/collection_association.rb +40 -43
  22. data/lib/active_record/associations/collection_proxy.rb +55 -29
  23. data/lib/active_record/associations/foreign_association.rb +1 -1
  24. data/lib/active_record/associations/has_many_association.rb +20 -71
  25. data/lib/active_record/associations/has_many_through_association.rb +8 -52
  26. data/lib/active_record/associations/has_one_association.rb +12 -5
  27. data/lib/active_record/associations/join_dependency.rb +28 -18
  28. data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
  29. data/lib/active_record/associations/preloader.rb +13 -4
  30. data/lib/active_record/associations/preloader/association.rb +45 -51
  31. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +5 -4
  35. data/lib/active_record/associations/singular_association.rb +6 -0
  36. data/lib/active_record/associations/through_association.rb +11 -3
  37. data/lib/active_record/attribute.rb +61 -17
  38. data/lib/active_record/attribute/user_provided_default.rb +23 -0
  39. data/lib/active_record/attribute_assignment.rb +27 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods.rb +79 -26
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  44. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  45. data/lib/active_record/attribute_methods/query.rb +2 -2
  46. data/lib/active_record/attribute_methods/read.rb +26 -42
  47. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
  49. data/lib/active_record/attribute_methods/write.rb +13 -24
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set.rb +30 -3
  52. data/lib/active_record/attribute_set/builder.rb +6 -4
  53. data/lib/active_record/attributes.rb +194 -81
  54. data/lib/active_record/autosave_association.rb +33 -15
  55. data/lib/active_record/base.rb +30 -18
  56. data/lib/active_record/callbacks.rb +36 -40
  57. data/lib/active_record/coders/yaml_column.rb +20 -8
  58. data/lib/active_record/collection_cache_key.rb +31 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
  70. data/lib/active_record/connection_adapters/column.rb +27 -41
  71. data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  85. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  87. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
  103. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  104. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
  107. data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
  108. data/lib/active_record/connection_handling.rb +5 -5
  109. data/lib/active_record/core.rb +72 -104
  110. data/lib/active_record/counter_cache.rb +9 -20
  111. data/lib/active_record/dynamic_matchers.rb +1 -20
  112. data/lib/active_record/enum.rb +110 -76
  113. data/lib/active_record/errors.rb +72 -47
  114. data/lib/active_record/explain_registry.rb +1 -1
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +19 -4
  117. data/lib/active_record/fixtures.rb +76 -40
  118. data/lib/active_record/gem_version.rb +4 -4
  119. data/lib/active_record/inheritance.rb +27 -40
  120. data/lib/active_record/integration.rb +4 -4
  121. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  122. data/lib/active_record/locale/en.yml +3 -2
  123. data/lib/active_record/locking/optimistic.rb +10 -14
  124. data/lib/active_record/locking/pessimistic.rb +1 -1
  125. data/lib/active_record/log_subscriber.rb +40 -22
  126. data/lib/active_record/migration.rb +304 -133
  127. data/lib/active_record/migration/command_recorder.rb +59 -18
  128. data/lib/active_record/migration/compatibility.rb +90 -0
  129. data/lib/active_record/model_schema.rb +92 -40
  130. data/lib/active_record/nested_attributes.rb +45 -34
  131. data/lib/active_record/null_relation.rb +15 -7
  132. data/lib/active_record/persistence.rb +112 -72
  133. data/lib/active_record/querying.rb +6 -5
  134. data/lib/active_record/railtie.rb +20 -13
  135. data/lib/active_record/railties/controller_runtime.rb +1 -1
  136. data/lib/active_record/railties/databases.rake +47 -38
  137. data/lib/active_record/readonly_attributes.rb +1 -1
  138. data/lib/active_record/reflection.rb +182 -57
  139. data/lib/active_record/relation.rb +152 -100
  140. data/lib/active_record/relation/batches.rb +133 -33
  141. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  142. data/lib/active_record/relation/calculations.rb +80 -101
  143. data/lib/active_record/relation/delegation.rb +6 -19
  144. data/lib/active_record/relation/finder_methods.rb +58 -46
  145. data/lib/active_record/relation/from_clause.rb +32 -0
  146. data/lib/active_record/relation/merger.rb +13 -42
  147. data/lib/active_record/relation/predicate_builder.rb +99 -105
  148. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  149. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
  150. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  151. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  152. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  153. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
  154. data/lib/active_record/relation/query_attribute.rb +19 -0
  155. data/lib/active_record/relation/query_methods.rb +274 -238
  156. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  157. data/lib/active_record/relation/spawn_methods.rb +3 -6
  158. data/lib/active_record/relation/where_clause.rb +173 -0
  159. data/lib/active_record/relation/where_clause_factory.rb +37 -0
  160. data/lib/active_record/result.rb +4 -3
  161. data/lib/active_record/runtime_registry.rb +1 -1
  162. data/lib/active_record/sanitization.rb +94 -65
  163. data/lib/active_record/schema.rb +23 -22
  164. data/lib/active_record/schema_dumper.rb +33 -22
  165. data/lib/active_record/schema_migration.rb +10 -4
  166. data/lib/active_record/scoping.rb +17 -6
  167. data/lib/active_record/scoping/default.rb +19 -6
  168. data/lib/active_record/scoping/named.rb +39 -28
  169. data/lib/active_record/secure_token.rb +38 -0
  170. data/lib/active_record/serialization.rb +2 -4
  171. data/lib/active_record/statement_cache.rb +15 -13
  172. data/lib/active_record/store.rb +8 -3
  173. data/lib/active_record/suppressor.rb +54 -0
  174. data/lib/active_record/table_metadata.rb +64 -0
  175. data/lib/active_record/tasks/database_tasks.rb +30 -40
  176. data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
  177. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  178. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  179. data/lib/active_record/timestamp.rb +16 -9
  180. data/lib/active_record/touch_later.rb +58 -0
  181. data/lib/active_record/transactions.rb +138 -56
  182. data/lib/active_record/type.rb +66 -17
  183. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  184. data/lib/active_record/type/date.rb +2 -45
  185. data/lib/active_record/type/date_time.rb +2 -49
  186. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  187. data/lib/active_record/type/internal/timezone.rb +15 -0
  188. data/lib/active_record/type/serialized.rb +9 -14
  189. data/lib/active_record/type/time.rb +3 -21
  190. data/lib/active_record/type/type_map.rb +4 -4
  191. data/lib/active_record/type_caster.rb +7 -0
  192. data/lib/active_record/type_caster/connection.rb +29 -0
  193. data/lib/active_record/type_caster/map.rb +19 -0
  194. data/lib/active_record/validations.rb +33 -32
  195. data/lib/active_record/validations/absence.rb +24 -0
  196. data/lib/active_record/validations/associated.rb +10 -3
  197. data/lib/active_record/validations/length.rb +36 -0
  198. data/lib/active_record/validations/presence.rb +12 -12
  199. data/lib/active_record/validations/uniqueness.rb +24 -21
  200. data/lib/rails/generators/active_record/migration.rb +7 -0
  201. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  202. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  203. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
  204. data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
  205. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  206. metadata +50 -35
  207. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  208. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  209. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  210. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  211. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  212. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  213. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  214. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  215. data/lib/active_record/type/big_integer.rb +0 -13
  216. data/lib/active_record/type/binary.rb +0 -50
  217. data/lib/active_record/type/boolean.rb +0 -31
  218. data/lib/active_record/type/decimal.rb +0 -64
  219. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  220. data/lib/active_record/type/decorator.rb +0 -14
  221. data/lib/active_record/type/float.rb +0 -19
  222. data/lib/active_record/type/integer.rb +0 -59
  223. data/lib/active_record/type/mutable.rb +0 -16
  224. data/lib/active_record/type/numeric.rb +0 -36
  225. data/lib/active_record/type/string.rb +0 -40
  226. data/lib/active_record/type/text.rb +0 -11
  227. data/lib/active_record/type/time_value.rb +0 -38
  228. data/lib/active_record/type/unsigned_integer.rb +0 -15
  229. data/lib/active_record/type/value.rb +0 -110
@@ -10,33 +10,46 @@ module ActiveRecord
10
10
  @columns = {}
11
11
  @columns_hash = {}
12
12
  @primary_keys = {}
13
- @tables = {}
13
+ @data_sources = {}
14
+ end
15
+
16
+ def initialize_dup(other)
17
+ super
18
+ @columns = @columns.dup
19
+ @columns_hash = @columns_hash.dup
20
+ @primary_keys = @primary_keys.dup
21
+ @data_sources = @data_sources.dup
14
22
  end
15
23
 
16
24
  def primary_keys(table_name)
17
- @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
25
+ @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
18
26
  end
19
27
 
20
28
  # A cached lookup for table existence.
21
- def table_exists?(name)
22
- prepare_tables if @tables.empty?
23
- return @tables[name] if @tables.key? name
29
+ def data_source_exists?(name)
30
+ prepare_data_sources if @data_sources.empty?
31
+ return @data_sources[name] if @data_sources.key? name
24
32
 
25
- @tables[name] = connection.table_exists?(name)
33
+ @data_sources[name] = connection.data_source_exists?(name)
26
34
  end
35
+ alias table_exists? data_source_exists?
36
+ deprecate :table_exists? => "use #data_source_exists? instead"
37
+
27
38
 
28
39
  # Add internal cache for table with +table_name+.
29
40
  def add(table_name)
30
- if table_exists?(table_name)
41
+ if data_source_exists?(table_name)
31
42
  primary_keys(table_name)
32
43
  columns(table_name)
33
44
  columns_hash(table_name)
34
45
  end
35
46
  end
36
47
 
37
- def tables(name)
38
- @tables[name]
48
+ def data_sources(name)
49
+ @data_sources[name]
39
50
  end
51
+ alias tables data_sources
52
+ deprecate :tables => "use #data_sources instead"
40
53
 
41
54
  # Get the columns for a table
42
55
  def columns(table_name)
@@ -56,38 +69,38 @@ module ActiveRecord
56
69
  @columns.clear
57
70
  @columns_hash.clear
58
71
  @primary_keys.clear
59
- @tables.clear
72
+ @data_sources.clear
60
73
  @version = nil
61
74
  end
62
75
 
63
76
  def size
64
- [@columns, @columns_hash, @primary_keys, @tables].map { |x|
65
- x.size
66
- }.inject :+
77
+ [@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
67
78
  end
68
79
 
69
- # Clear out internal caches for table with +table_name+.
70
- def clear_table_cache!(table_name)
71
- @columns.delete table_name
72
- @columns_hash.delete table_name
73
- @primary_keys.delete table_name
74
- @tables.delete table_name
80
+ # Clear out internal caches for the data source +name+.
81
+ def clear_data_source_cache!(name)
82
+ @columns.delete name
83
+ @columns_hash.delete name
84
+ @primary_keys.delete name
85
+ @data_sources.delete name
75
86
  end
87
+ alias clear_table_cache! clear_data_source_cache!
88
+ deprecate :clear_table_cache! => "use #clear_data_source_cache! instead"
76
89
 
77
90
  def marshal_dump
78
91
  # if we get current version during initialization, it happens stack over flow.
79
92
  @version = ActiveRecord::Migrator.current_version
80
- [@version, @columns, @columns_hash, @primary_keys, @tables]
93
+ [@version, @columns, @columns_hash, @primary_keys, @data_sources]
81
94
  end
82
95
 
83
96
  def marshal_load(array)
84
- @version, @columns, @columns_hash, @primary_keys, @tables = array
97
+ @version, @columns, @columns_hash, @primary_keys, @data_sources = array
85
98
  end
86
99
 
87
100
  private
88
101
 
89
- def prepare_tables
90
- connection.tables.each { |table| @tables[table] = true }
102
+ def prepare_data_sources
103
+ connection.data_sources.each { |source| @data_sources[source] = true }
91
104
  end
92
105
  end
93
106
  end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ # :stopdoc:
3
+ module ConnectionAdapters
4
+ class SqlTypeMetadata
5
+ attr_reader :sql_type, :type, :limit, :precision, :scale
6
+
7
+ def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
8
+ @sql_type = sql_type
9
+ @type = type
10
+ @limit = limit
11
+ @precision = precision
12
+ @scale = scale
13
+ end
14
+
15
+ def ==(other)
16
+ other.is_a?(SqlTypeMetadata) &&
17
+ attributes_for_hash == other.attributes_for_hash
18
+ end
19
+ alias eql? ==
20
+
21
+ def hash
22
+ attributes_for_hash.hash
23
+ end
24
+
25
+ protected
26
+
27
+ def attributes_for_hash
28
+ [self.class, sql_type, type, limit, precision, scale]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLite3
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+ private
6
+ def add_column_options!(sql, options)
7
+ if options[:collation]
8
+ sql << " COLLATE \"#{options[:collation]}\""
9
+ end
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,6 +1,6 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'active_record/connection_adapters/statement_pool'
3
- require 'arel/visitors/bind_visitor'
3
+ require 'active_record/connection_adapters/sqlite3/schema_creation'
4
4
 
5
5
  gem 'sqlite3', '~> 1.3.6'
6
6
  require 'sqlite3'
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
34
34
  rescue Errno::ENOENT => error
35
35
  if error.message.include?("No such file or directory")
36
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
36
+ raise ActiveRecord::NoDatabaseError
37
37
  else
38
38
  raise
39
39
  end
@@ -41,15 +41,6 @@ module ActiveRecord
41
41
  end
42
42
 
43
43
  module ConnectionAdapters #:nodoc:
44
- class SQLite3Binary < Type::Binary # :nodoc:
45
- def cast_value(value)
46
- if value.encoding != Encoding::ASCII_8BIT
47
- value = value.force_encoding(Encoding::ASCII_8BIT)
48
- end
49
- value
50
- end
51
- end
52
-
53
44
  # The SQLite3 adapter works SQLite 3.6.16 or newer
54
45
  # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
55
46
  #
@@ -75,52 +66,29 @@ module ActiveRecord
75
66
  }
76
67
 
77
68
  class StatementPool < ConnectionAdapters::StatementPool
78
- def initialize(connection, max)
79
- super
80
- @cache = Hash.new { |h,pid| h[pid] = {} }
81
- end
82
-
83
- def each(&block); cache.each(&block); end
84
- def key?(key); cache.key?(key); end
85
- def [](key); cache[key]; end
86
- def length; cache.length; end
87
-
88
- def []=(sql, key)
89
- while @max <= cache.size
90
- dealloc(cache.shift.last[:stmt])
91
- end
92
- cache[sql] = key
93
- end
94
-
95
- def clear
96
- cache.each_value do |hash|
97
- dealloc hash[:stmt]
98
- end
99
- cache.clear
100
- end
101
-
102
69
  private
103
- def cache
104
- @cache[$$]
105
- end
106
70
 
107
71
  def dealloc(stmt)
108
- stmt.close unless stmt.closed?
72
+ stmt[:stmt].close unless stmt[:stmt].closed?
109
73
  end
110
74
  end
111
75
 
76
+ def schema_creation # :nodoc:
77
+ SQLite3::SchemaCreation.new self
78
+ end
79
+
112
80
  def initialize(connection, logger, connection_options, config)
113
- super(connection, logger)
81
+ super(connection, logger, config)
114
82
 
115
83
  @active = nil
116
- @statements = StatementPool.new(@connection,
117
- self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
118
- @config = config
84
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
119
85
 
120
86
  @visitor = Arel::Visitors::SQLite.new self
87
+ @quoted_column_names = {}
121
88
 
122
89
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
123
90
  @prepared_statements = true
91
+ @visitor.extend(DetermineIfPreparableVisitor)
124
92
  else
125
93
  @prepared_statements = false
126
94
  end
@@ -161,6 +129,10 @@ module ActiveRecord
161
129
  true
162
130
  end
163
131
 
132
+ def supports_datetime_with_precision?
133
+ true
134
+ end
135
+
164
136
  def active?
165
137
  @active != false
166
138
  end
@@ -237,17 +209,7 @@ module ActiveRecord
237
209
  end
238
210
 
239
211
  def quote_column_name(name) #:nodoc:
240
- %Q("#{name.to_s.gsub('"', '""')}")
241
- end
242
-
243
- # Quote date/time values for use in SQL input. Includes microseconds
244
- # if the value is a Time responding to usec.
245
- def quoted_date(value) #:nodoc:
246
- if value.respond_to?(:usec)
247
- "#{super}.#{sprintf("%06d", value.usec)}"
248
- else
249
- super
250
- end
212
+ @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}")
251
213
  end
252
214
 
253
215
  #--
@@ -260,7 +222,7 @@ module ActiveRecord
260
222
  end
261
223
 
262
224
  class ExplainPrettyPrinter
263
- # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
225
+ # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
264
226
  # the output of the SQLite shell:
265
227
  #
266
228
  # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
@@ -273,17 +235,18 @@ module ActiveRecord
273
235
  end
274
236
  end
275
237
 
276
- def exec_query(sql, name = nil, binds = [])
277
- type_casted_binds = binds.map { |col, val|
278
- [col, type_cast(val, col)]
279
- }
238
+ def exec_query(sql, name = nil, binds = [], prepare: false)
239
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
280
240
 
281
- log(sql, name, type_casted_binds) do
241
+ log(sql, name, binds) do
282
242
  # Don't cache statements if they are not prepared
283
- if without_prepared_statement?(binds)
243
+ unless prepare
284
244
  stmt = @connection.prepare(sql)
285
245
  begin
286
246
  cols = stmt.columns
247
+ unless without_prepared_statement?(binds)
248
+ stmt.bind_params(type_casted_binds)
249
+ end
287
250
  records = stmt.to_a
288
251
  ensure
289
252
  stmt.close
@@ -296,7 +259,7 @@ module ActiveRecord
296
259
  stmt = cache[:stmt]
297
260
  cols = cache[:cols] ||= stmt.columns
298
261
  stmt.reset!
299
- stmt.bind_params type_casted_binds.map { |_, val| val }
262
+ stmt.bind_params(type_casted_binds)
300
263
  end
301
264
 
302
265
  ActiveRecord::Result.new(cols, stmt.to_a)
@@ -351,24 +314,57 @@ module ActiveRecord
351
314
 
352
315
  # SCHEMA STATEMENTS ========================================
353
316
 
354
- def tables(name = nil, table_name = nil) #:nodoc:
355
- sql = <<-SQL
356
- SELECT name
357
- FROM sqlite_master
358
- WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence'
359
- SQL
360
- sql << " AND name = #{quote_table_name(table_name)}" if table_name
361
-
362
- exec_query(sql, 'SCHEMA').map do |row|
363
- row['name']
317
+ def tables(name = nil) # :nodoc:
318
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
319
+ #tables currently returns both tables and views.
320
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
321
+ Use #data_sources instead.
322
+ MSG
323
+
324
+ if name
325
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
326
+ Passing arguments to #tables is deprecated without replacement.
327
+ MSG
364
328
  end
329
+
330
+ data_sources
331
+ end
332
+
333
+ def data_sources
334
+ select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA')
365
335
  end
366
- alias data_sources tables
367
336
 
368
337
  def table_exists?(table_name)
369
- table_name && tables(nil, table_name).any?
338
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
339
+ #table_exists? currently checks both tables and views.
340
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
341
+ Use #data_source_exists? instead.
342
+ MSG
343
+
344
+ data_source_exists?(table_name)
345
+ end
346
+
347
+ def data_source_exists?(table_name)
348
+ return false unless table_name.present?
349
+
350
+ sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
351
+ sql << " AND name = #{quote(table_name)}"
352
+
353
+ select_values(sql, 'SCHEMA').any?
354
+ end
355
+
356
+ def views # :nodoc:
357
+ select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
358
+ end
359
+
360
+ def view_exists?(view_name) # :nodoc:
361
+ return false unless view_name.present?
362
+
363
+ sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
364
+ sql << " AND name = #{quote(view_name)}"
365
+
366
+ select_values(sql, 'SCHEMA').any?
370
367
  end
371
- alias data_source_exists? table_exists?
372
368
 
373
369
  # Returns an array of +Column+ objects for the table specified by +table_name+.
374
370
  def columns(table_name) #:nodoc:
@@ -382,9 +378,10 @@ module ActiveRecord
382
378
  field["dflt_value"] = $1.gsub('""', '"')
383
379
  end
384
380
 
381
+ collation = field['collation']
385
382
  sql_type = field['type']
386
- cast_type = lookup_cast_type(sql_type)
387
- new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
383
+ type_metadata = fetch_type_metadata(sql_type)
384
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation)
388
385
  end
389
386
  end
390
387
 
@@ -413,13 +410,13 @@ module ActiveRecord
413
410
  end
414
411
  end
415
412
 
416
- def primary_key(table_name) #:nodoc:
413
+ def primary_keys(table_name) # :nodoc:
417
414
  pks = table_structure(table_name).select { |f| f['pk'] > 0 }
418
- return nil unless pks.count == 1
419
- pks[0]['name']
415
+ pks.sort_by { |f| f['pk'] }.map { |f| f['name'] }
420
416
  end
421
417
 
422
- def remove_index!(table_name, index_name) #:nodoc:
418
+ def remove_index(table_name, options = {}) #:nodoc:
419
+ index_name = index_name_for_remove(table_name, options)
423
420
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
424
421
  end
425
422
 
@@ -454,13 +451,15 @@ module ActiveRecord
454
451
  end
455
452
  end
456
453
 
457
- def change_column_default(table_name, column_name, default) #:nodoc:
454
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
455
+ default = extract_new_default_value(default_or_changes)
456
+
458
457
  alter_table(table_name) do |definition|
459
458
  definition[column_name].default = default
460
459
  end
461
460
  end
462
461
 
463
- def change_column_null(table_name, column_name, null, default = nil)
462
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
464
463
  unless null || default.nil?
465
464
  exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
466
465
  end
@@ -479,6 +478,7 @@ module ActiveRecord
479
478
  self.null = options[:null] if options.include?(:null)
480
479
  self.precision = options[:precision] if options.include?(:precision)
481
480
  self.scale = options[:scale] if options.include?(:scale)
481
+ self.collation = options[:collation] if options.include?(:collation)
482
482
  end
483
483
  end
484
484
  end
@@ -491,15 +491,10 @@ module ActiveRecord
491
491
 
492
492
  protected
493
493
 
494
- def initialize_type_map(m)
495
- super
496
- m.register_type(/binary/i, SQLite3Binary.new)
497
- end
498
-
499
494
  def table_structure(table_name)
500
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
495
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA')
501
496
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
502
- structure
497
+ table_structure_with_collation(table_name, structure)
503
498
  end
504
499
 
505
500
  def alter_table(table_name, options = {}) #:nodoc:
@@ -534,13 +529,13 @@ module ActiveRecord
534
529
  @definition.column(column_name, column.type,
535
530
  :limit => column.limit, :default => column.default,
536
531
  :precision => column.precision, :scale => column.scale,
537
- :null => column.null)
532
+ :null => column.null, collation: column.collation)
538
533
  end
539
534
  yield @definition if block_given?
540
535
  end
541
536
  copy_table_indexes(from, to, options[:rename] || {})
542
537
  copy_table_contents(from, to,
543
- @definition.columns.map {|column| column.name},
538
+ @definition.columns.map(&:name),
544
539
  options[:rename] || {})
545
540
  end
546
541
 
@@ -553,7 +548,7 @@ module ActiveRecord
553
548
  name = name[1..-1]
554
549
  end
555
550
 
556
- to_column_names = columns(to).map { |c| c.name }
551
+ to_column_names = columns(to).map(&:name)
557
552
  columns = index.columns.map {|c| rename[c] || c }.select do |column|
558
553
  to_column_names.include?(column)
559
554
  end
@@ -570,25 +565,14 @@ module ActiveRecord
570
565
  def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
571
566
  column_mappings = Hash[columns.map {|name| [name, name]}]
572
567
  rename.each { |a| column_mappings[a.last] = a.first }
573
- from_columns = columns(from).collect {|col| col.name}
568
+ from_columns = columns(from).collect(&:name)
574
569
  columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
570
+ from_columns_to_copy = columns.map { |col| column_mappings[col] }
575
571
  quoted_columns = columns.map { |col| quote_column_name(col) } * ','
572
+ quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
576
573
 
577
- quoted_to = quote_table_name(to)
578
-
579
- raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
580
-
581
- exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
582
- sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
583
-
584
- column_values = columns.map do |col|
585
- quote(row[column_mappings[col]], raw_column_mappings[col])
586
- end
587
-
588
- sql << column_values * ', '
589
- sql << ')'
590
- exec_query sql
591
- end
574
+ exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
575
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
592
576
  end
593
577
 
594
578
  def sqlite_version
@@ -602,11 +586,51 @@ module ActiveRecord
602
586
  # Older versions of SQLite return:
603
587
  # column *column_name* is not unique
604
588
  when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
605
- RecordNotUnique.new(message, exception)
589
+ RecordNotUnique.new(message)
606
590
  else
607
591
  super
608
592
  end
609
593
  end
594
+
595
+ private
596
+ COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
597
+
598
+ def table_structure_with_collation(table_name, basic_structure)
599
+ collation_hash = {}
600
+ sql = "SELECT sql FROM
601
+ (SELECT * FROM sqlite_master UNION ALL
602
+ SELECT * FROM sqlite_temp_master)
603
+ WHERE type='table' and name='#{ table_name }' \;"
604
+
605
+ # Result will have following sample string
606
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
607
+ # "password_digest" varchar COLLATE "NOCASE");
608
+ result = exec_query(sql, 'SCHEMA').first
609
+
610
+ if result
611
+ # Splitting with left parantheses and picking up last will return all
612
+ # columns separated with comma(,).
613
+ columns_string = result["sql"].split('(').last
614
+
615
+ columns_string.split(',').each do |column_string|
616
+ # This regex will match the column name and collation type and will save
617
+ # the value in $1 and $2 respectively.
618
+ collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
619
+ end
620
+
621
+ basic_structure.map! do |column|
622
+ column_name = column['name']
623
+
624
+ if collation_hash.has_key? column_name
625
+ column['collation'] = collation_hash[column_name]
626
+ end
627
+
628
+ column
629
+ end
630
+ else
631
+ basic_structure.to_hash
632
+ end
633
+ end
610
634
  end
611
635
  end
612
636
  end