activerecord 4.2.11.1 → 5.0.0

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 (246) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1282 -1195
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +8 -4
  8. data/lib/active_record/aggregations.rb +35 -24
  9. data/lib/active_record/association_relation.rb +3 -3
  10. data/lib/active_record/associations.rb +317 -209
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +11 -9
  13. data/lib/active_record/associations/association_scope.rb +73 -102
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  22. data/lib/active_record/associations/collection_association.rb +49 -41
  23. data/lib/active_record/associations/collection_proxy.rb +67 -27
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +20 -71
  26. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +29 -19
  29. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  30. data/lib/active_record/associations/preloader.rb +14 -4
  31. data/lib/active_record/associations/preloader/association.rb +46 -52
  32. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  33. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  35. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  36. data/lib/active_record/associations/singular_association.rb +7 -1
  37. data/lib/active_record/associations/through_association.rb +11 -3
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  40. data/lib/active_record/attribute_assignment.rb +19 -140
  41. data/lib/active_record/attribute_decorators.rb +6 -5
  42. data/lib/active_record/attribute_methods.rb +76 -47
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  44. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  45. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  46. data/lib/active_record/attribute_methods/query.rb +2 -2
  47. data/lib/active_record/attribute_methods/read.rb +31 -59
  48. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  50. data/lib/active_record/attribute_methods/write.rb +13 -37
  51. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attribute_set/builder.rb +6 -4
  54. data/lib/active_record/attributes.rb +199 -81
  55. data/lib/active_record/autosave_association.rb +49 -16
  56. data/lib/active_record/base.rb +32 -23
  57. data/lib/active_record/callbacks.rb +39 -43
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +20 -8
  60. data/lib/active_record/collection_cache_key.rb +40 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  62. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  63. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  64. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
  66. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  67. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  69. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  70. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
  71. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  72. data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
  73. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
  74. data/lib/active_record/connection_adapters/column.rb +28 -43
  75. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  76. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  77. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  78. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  79. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  80. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  84. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  85. data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  88. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
  91. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +37 -14
  121. data/lib/active_record/core.rb +89 -107
  122. data/lib/active_record/counter_cache.rb +13 -24
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +113 -76
  125. data/lib/active_record/errors.rb +87 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +76 -40
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +15 -15
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration.rb +363 -133
  140. data/lib/active_record/migration/command_recorder.rb +59 -18
  141. data/lib/active_record/migration/compatibility.rb +126 -0
  142. data/lib/active_record/model_schema.rb +129 -41
  143. data/lib/active_record/nested_attributes.rb +58 -29
  144. data/lib/active_record/null_relation.rb +16 -8
  145. data/lib/active_record/persistence.rb +121 -80
  146. data/lib/active_record/query_cache.rb +15 -18
  147. data/lib/active_record/querying.rb +10 -9
  148. data/lib/active_record/railtie.rb +23 -16
  149. data/lib/active_record/railties/controller_runtime.rb +1 -1
  150. data/lib/active_record/railties/databases.rake +69 -46
  151. data/lib/active_record/readonly_attributes.rb +1 -1
  152. data/lib/active_record/reflection.rb +282 -115
  153. data/lib/active_record/relation.rb +176 -116
  154. data/lib/active_record/relation/batches.rb +139 -34
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  156. data/lib/active_record/relation/calculations.rb +79 -108
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +163 -81
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +16 -42
  161. data/lib/active_record/relation/predicate_builder.rb +120 -107
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  163. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  164. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  166. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  167. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  168. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  169. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +308 -244
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +4 -7
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/result.rb +4 -3
  177. data/lib/active_record/runtime_registry.rb +1 -1
  178. data/lib/active_record/sanitization.rb +95 -66
  179. data/lib/active_record/schema.rb +26 -22
  180. data/lib/active_record/schema_dumper.rb +62 -38
  181. data/lib/active_record/schema_migration.rb +11 -14
  182. data/lib/active_record/scoping.rb +32 -15
  183. data/lib/active_record/scoping/default.rb +23 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/secure_token.rb +38 -0
  186. data/lib/active_record/serialization.rb +2 -4
  187. data/lib/active_record/statement_cache.rb +16 -14
  188. data/lib/active_record/store.rb +8 -3
  189. data/lib/active_record/suppressor.rb +58 -0
  190. data/lib/active_record/table_metadata.rb +68 -0
  191. data/lib/active_record/tasks/database_tasks.rb +57 -43
  192. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  193. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  194. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  195. data/lib/active_record/timestamp.rb +20 -9
  196. data/lib/active_record/touch_later.rb +58 -0
  197. data/lib/active_record/transactions.rb +138 -56
  198. data/lib/active_record/type.rb +66 -17
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -45
  201. data/lib/active_record/type/date_time.rb +2 -49
  202. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  203. data/lib/active_record/type/internal/timezone.rb +15 -0
  204. data/lib/active_record/type/serialized.rb +15 -14
  205. data/lib/active_record/type/time.rb +10 -16
  206. data/lib/active_record/type/type_map.rb +4 -4
  207. data/lib/active_record/type_caster.rb +7 -0
  208. data/lib/active_record/type_caster/connection.rb +29 -0
  209. data/lib/active_record/type_caster/map.rb +19 -0
  210. data/lib/active_record/validations.rb +33 -32
  211. data/lib/active_record/validations/absence.rb +23 -0
  212. data/lib/active_record/validations/associated.rb +10 -3
  213. data/lib/active_record/validations/length.rb +24 -0
  214. data/lib/active_record/validations/presence.rb +11 -12
  215. data/lib/active_record/validations/uniqueness.rb +30 -29
  216. data/lib/rails/generators/active_record/migration.rb +7 -0
  217. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  218. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  219. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  220. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  221. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  222. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  223. metadata +59 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  225. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  226. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  227. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  228. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  229. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  231. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  232. data/lib/active_record/type/big_integer.rb +0 -13
  233. data/lib/active_record/type/binary.rb +0 -50
  234. data/lib/active_record/type/boolean.rb +0 -31
  235. data/lib/active_record/type/decimal.rb +0 -64
  236. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  237. data/lib/active_record/type/decorator.rb +0 -14
  238. data/lib/active_record/type/float.rb +0 -19
  239. data/lib/active_record/type/integer.rb +0 -59
  240. data/lib/active_record/type/mutable.rb +0 -16
  241. data/lib/active_record/type/numeric.rb +0 -36
  242. data/lib/active_record/type/string.rb +0 -40
  243. data/lib/active_record/type/text.rb +0 -11
  244. data/lib/active_record/type/time_value.rb +0 -38
  245. data/lib/active_record/type/unsigned_integer.rb +0 -15
  246. 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,19 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLite3
4
+ class ExplainPrettyPrinter # :nodoc:
5
+ # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
6
+ # the output of the SQLite shell:
7
+ #
8
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
9
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
10
+ #
11
+ def pp(result)
12
+ result.rows.map do |row|
13
+ row.join('|')
14
+ end.join("\n") + "\n"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLite3
4
+ module Quoting # :nodoc:
5
+ def quote_string(s)
6
+ @connection.class.quote(s)
7
+ end
8
+
9
+ def quote_table_name_for_assignment(table, attr)
10
+ quote_column_name(attr)
11
+ end
12
+
13
+ def quote_column_name(name)
14
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
15
+ end
16
+
17
+ def quoted_time(value)
18
+ quoted_date(value)
19
+ end
20
+
21
+ private
22
+
23
+ def _quote(value)
24
+ if value.is_a?(Type::Binary::Data)
25
+ "x'#{value.hex}'"
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def _type_cast(value)
32
+ case value
33
+ when BigDecimal
34
+ value.to_f
35
+ when String
36
+ if value.encoding == Encoding::ASCII_8BIT
37
+ super(value.encode(Encoding::UTF_8))
38
+ else
39
+ super
40
+ end
41
+ else
42
+ super
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLite3
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+ private
6
+
7
+ def column_options(o)
8
+ options = super
9
+ options[:null] = false if o.primary_key
10
+ options
11
+ end
12
+
13
+ def add_column_options!(sql, options)
14
+ if options[:collation]
15
+ sql << " COLLATE \"#{options[:collation]}\""
16
+ end
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,13 +1,14 @@
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/explain_pretty_printer'
4
+ require 'active_record/connection_adapters/sqlite3/quoting'
5
+ require 'active_record/connection_adapters/sqlite3/schema_creation'
4
6
 
5
7
  gem 'sqlite3', '~> 1.3.6'
6
8
  require 'sqlite3'
7
9
 
8
10
  module ActiveRecord
9
11
  module ConnectionHandling # :nodoc:
10
- # sqlite3 adapter reuses sqlite_connection.
11
12
  def sqlite3_connection(config)
12
13
  # Require database.
13
14
  unless config[:database]
@@ -33,7 +34,7 @@ module ActiveRecord
33
34
  ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
34
35
  rescue Errno::ENOENT => error
35
36
  if error.message.include?("No such file or directory")
36
- raise ActiveRecord::NoDatabaseError.new(error.message, error)
37
+ raise ActiveRecord::NoDatabaseError
37
38
  else
38
39
  raise
39
40
  end
@@ -41,15 +42,6 @@ module ActiveRecord
41
42
  end
42
43
 
43
44
  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
45
  # The SQLite3 adapter works SQLite 3.6.16 or newer
54
46
  # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
55
47
  #
@@ -58,7 +50,8 @@ module ActiveRecord
58
50
  # * <tt>:database</tt> - Path to the database file.
59
51
  class SQLite3Adapter < AbstractAdapter
60
52
  ADAPTER_NAME = 'SQLite'.freeze
61
- include Savepoints
53
+
54
+ include SQLite3::Quoting
62
55
 
63
56
  NATIVE_DATABASE_TYPES = {
64
57
  primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
@@ -75,55 +68,26 @@ module ActiveRecord
75
68
  }
76
69
 
77
70
  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
71
  private
103
- def cache
104
- @cache[$$]
105
- end
106
72
 
107
73
  def dealloc(stmt)
108
- stmt.close unless stmt.closed?
74
+ stmt[:stmt].close unless stmt[:stmt].closed?
109
75
  end
110
76
  end
111
77
 
112
- def initialize(connection, logger, connection_options, config)
113
- super(connection, logger)
78
+ def schema_creation # :nodoc:
79
+ SQLite3::SchemaCreation.new self
80
+ end
114
81
 
115
- @active = nil
116
- @statements = StatementPool.new(@connection,
117
- self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
118
- @config = config
82
+ def arel_visitor # :nodoc:
83
+ Arel::Visitors::SQLite.new(self)
84
+ end
119
85
 
120
- @visitor = Arel::Visitors::SQLite.new self
86
+ def initialize(connection, logger, connection_options, config)
87
+ super(connection, logger, config)
121
88
 
122
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
123
- @prepared_statements = true
124
- else
125
- @prepared_statements = false
126
- end
89
+ @active = nil
90
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
127
91
  end
128
92
 
129
93
  def supports_ddl_transactions?
@@ -161,6 +125,14 @@ module ActiveRecord
161
125
  true
162
126
  end
163
127
 
128
+ def supports_datetime_with_precision?
129
+ true
130
+ end
131
+
132
+ def supports_multi_insert?
133
+ sqlite_version >= '3.7.11'
134
+ end
135
+
164
136
  def active?
165
137
  @active != false
166
138
  end
@@ -182,6 +154,10 @@ module ActiveRecord
182
154
  true
183
155
  end
184
156
 
157
+ def valid_type?(type)
158
+ true
159
+ end
160
+
185
161
  # Returns 62. SQLite supports index names up to 64
186
162
  # characters. The rest is used by rails internally to perform
187
163
  # temporary rename operations
@@ -202,88 +178,27 @@ module ActiveRecord
202
178
  true
203
179
  end
204
180
 
205
- # QUOTING ==================================================
206
-
207
- def _quote(value) # :nodoc:
208
- case value
209
- when Type::Binary::Data
210
- "x'#{value.hex}'"
211
- else
212
- super
213
- end
214
- end
215
-
216
- def _type_cast(value) # :nodoc:
217
- case value
218
- when BigDecimal
219
- value.to_f
220
- when String
221
- if value.encoding == Encoding::ASCII_8BIT
222
- super(value.encode(Encoding::UTF_8))
223
- else
224
- super
225
- end
226
- else
227
- super
228
- end
229
- end
230
-
231
- def quote_string(s) #:nodoc:
232
- @connection.class.quote(s)
233
- end
234
-
235
- def quote_table_name_for_assignment(table, attr)
236
- quote_column_name(attr)
237
- end
238
-
239
- 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
251
- end
252
-
253
181
  #--
254
182
  # DATABASE STATEMENTS ======================================
255
183
  #++
256
184
 
257
185
  def explain(arel, binds = [])
258
186
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
259
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
260
- end
261
-
262
- class ExplainPrettyPrinter
263
- # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
264
- # the output of the SQLite shell:
265
- #
266
- # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
267
- # 0|1|1|SCAN TABLE posts (~100000 rows)
268
- #
269
- def pp(result) # :nodoc:
270
- result.rows.map do |row|
271
- row.join('|')
272
- end.join("\n") + "\n"
273
- end
187
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
274
188
  end
275
189
 
276
- def exec_query(sql, name = nil, binds = [])
277
- type_casted_binds = binds.map { |col, val|
278
- [col, type_cast(val, col)]
279
- }
190
+ def exec_query(sql, name = nil, binds = [], prepare: false)
191
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
280
192
 
281
- log(sql, name, type_casted_binds) do
193
+ log(sql, name, binds) do
282
194
  # Don't cache statements if they are not prepared
283
- if without_prepared_statement?(binds)
195
+ unless prepare
284
196
  stmt = @connection.prepare(sql)
285
197
  begin
286
198
  cols = stmt.columns
199
+ unless without_prepared_statement?(binds)
200
+ stmt.bind_params(type_casted_binds)
201
+ end
287
202
  records = stmt.to_a
288
203
  ensure
289
204
  stmt.close
@@ -296,7 +211,7 @@ module ActiveRecord
296
211
  stmt = cache[:stmt]
297
212
  cols = cache[:cols] ||= stmt.columns
298
213
  stmt.reset!
299
- stmt.bind_params type_casted_binds.map { |_, val| val }
214
+ stmt.bind_params(type_casted_binds)
300
215
  end
301
216
 
302
217
  ActiveRecord::Result.new(cols, stmt.to_a)
@@ -317,26 +232,6 @@ module ActiveRecord
317
232
  log(sql, name) { @connection.execute(sql) }
318
233
  end
319
234
 
320
- def update_sql(sql, name = nil) #:nodoc:
321
- super
322
- @connection.changes
323
- end
324
-
325
- def delete_sql(sql, name = nil) #:nodoc:
326
- sql += " WHERE 1=1" unless sql =~ /WHERE/i
327
- super sql, name
328
- end
329
-
330
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
331
- super
332
- id_value || @connection.last_insert_row_id
333
- end
334
- alias :create :insert_sql
335
-
336
- def select_rows(sql, name = nil, binds = [])
337
- exec_query(sql, name, binds).rows
338
- end
339
-
340
235
  def begin_db_transaction #:nodoc:
341
236
  log('begin transaction',nil) { @connection.transaction }
342
237
  end
@@ -351,27 +246,61 @@ module ActiveRecord
351
246
 
352
247
  # SCHEMA STATEMENTS ========================================
353
248
 
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']
249
+ def tables(name = nil) # :nodoc:
250
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
251
+ #tables currently returns both tables and views.
252
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
253
+ Use #data_sources instead.
254
+ MSG
255
+
256
+ if name
257
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
258
+ Passing arguments to #tables is deprecated without replacement.
259
+ MSG
364
260
  end
261
+
262
+ data_sources
263
+ end
264
+
265
+ def data_sources
266
+ select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA')
365
267
  end
366
- alias data_sources tables
367
268
 
368
269
  def table_exists?(table_name)
369
- table_name && tables(nil, table_name).any?
270
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
271
+ #table_exists? currently checks both tables and views.
272
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
273
+ Use #data_source_exists? instead.
274
+ MSG
275
+
276
+ data_source_exists?(table_name)
277
+ end
278
+
279
+ def data_source_exists?(table_name)
280
+ return false unless table_name.present?
281
+
282
+ sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
283
+ sql << " AND name = #{quote(table_name)}"
284
+
285
+ select_values(sql, 'SCHEMA').any?
286
+ end
287
+
288
+ def views # :nodoc:
289
+ select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
290
+ end
291
+
292
+ def view_exists?(view_name) # :nodoc:
293
+ return false unless view_name.present?
294
+
295
+ sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
296
+ sql << " AND name = #{quote(view_name)}"
297
+
298
+ select_values(sql, 'SCHEMA').any?
370
299
  end
371
- alias data_source_exists? table_exists?
372
300
 
373
301
  # Returns an array of +Column+ objects for the table specified by +table_name+.
374
- def columns(table_name) #:nodoc:
302
+ def columns(table_name) # :nodoc:
303
+ table_name = table_name.to_s
375
304
  table_structure(table_name).map do |field|
376
305
  case field["dflt_value"]
377
306
  when /^null$/i
@@ -382,9 +311,10 @@ module ActiveRecord
382
311
  field["dflt_value"] = $1.gsub('""', '"')
383
312
  end
384
313
 
314
+ collation = field['collation']
385
315
  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)
316
+ type_metadata = fetch_type_metadata(sql_type)
317
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, table_name, nil, collation)
388
318
  end
389
319
  end
390
320
 
@@ -413,13 +343,13 @@ module ActiveRecord
413
343
  end
414
344
  end
415
345
 
416
- def primary_key(table_name) #:nodoc:
346
+ def primary_keys(table_name) # :nodoc:
417
347
  pks = table_structure(table_name).select { |f| f['pk'] > 0 }
418
- return nil unless pks.count == 1
419
- pks[0]['name']
348
+ pks.sort_by { |f| f['pk'] }.map { |f| f['name'] }
420
349
  end
421
350
 
422
- def remove_index!(table_name, index_name) #:nodoc:
351
+ def remove_index(table_name, options = {}) #:nodoc:
352
+ index_name = index_name_for_remove(table_name, options)
423
353
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
424
354
  end
425
355
 
@@ -454,13 +384,15 @@ module ActiveRecord
454
384
  end
455
385
  end
456
386
 
457
- def change_column_default(table_name, column_name, default) #:nodoc:
387
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
388
+ default = extract_new_default_value(default_or_changes)
389
+
458
390
  alter_table(table_name) do |definition|
459
391
  definition[column_name].default = default
460
392
  end
461
393
  end
462
394
 
463
- def change_column_null(table_name, column_name, null, default = nil)
395
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
464
396
  unless null || default.nil?
465
397
  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
398
  end
@@ -479,6 +411,7 @@ module ActiveRecord
479
411
  self.null = options[:null] if options.include?(:null)
480
412
  self.precision = options[:precision] if options.include?(:precision)
481
413
  self.scale = options[:scale] if options.include?(:scale)
414
+ self.collation = options[:collation] if options.include?(:collation)
482
415
  end
483
416
  end
484
417
  end
@@ -491,15 +424,10 @@ module ActiveRecord
491
424
 
492
425
  protected
493
426
 
494
- def initialize_type_map(m)
495
- super
496
- m.register_type(/binary/i, SQLite3Binary.new)
497
- end
498
-
499
427
  def table_structure(table_name)
500
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
428
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA')
501
429
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
502
- structure
430
+ table_structure_with_collation(table_name, structure)
503
431
  end
504
432
 
505
433
  def alter_table(table_name, options = {}) #:nodoc:
@@ -534,13 +462,13 @@ module ActiveRecord
534
462
  @definition.column(column_name, column.type,
535
463
  :limit => column.limit, :default => column.default,
536
464
  :precision => column.precision, :scale => column.scale,
537
- :null => column.null)
465
+ :null => column.null, collation: column.collation)
538
466
  end
539
467
  yield @definition if block_given?
540
468
  end
541
469
  copy_table_indexes(from, to, options[:rename] || {})
542
470
  copy_table_contents(from, to,
543
- @definition.columns.map {|column| column.name},
471
+ @definition.columns.map(&:name),
544
472
  options[:rename] || {})
545
473
  end
546
474
 
@@ -553,7 +481,7 @@ module ActiveRecord
553
481
  name = name[1..-1]
554
482
  end
555
483
 
556
- to_column_names = columns(to).map { |c| c.name }
484
+ to_column_names = columns(to).map(&:name)
557
485
  columns = index.columns.map {|c| rename[c] || c }.select do |column|
558
486
  to_column_names.include?(column)
559
487
  end
@@ -570,25 +498,14 @@ module ActiveRecord
570
498
  def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
571
499
  column_mappings = Hash[columns.map {|name| [name, name]}]
572
500
  rename.each { |a| column_mappings[a.last] = a.first }
573
- from_columns = columns(from).collect {|col| col.name}
501
+ from_columns = columns(from).collect(&:name)
574
502
  columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
503
+ from_columns_to_copy = columns.map { |col| column_mappings[col] }
575
504
  quoted_columns = columns.map { |col| quote_column_name(col) } * ','
505
+ quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
576
506
 
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
507
+ exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
508
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
592
509
  end
593
510
 
594
511
  def sqlite_version
@@ -602,11 +519,51 @@ module ActiveRecord
602
519
  # Older versions of SQLite return:
603
520
  # column *column_name* is not unique
604
521
  when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
605
- RecordNotUnique.new(message, exception)
522
+ RecordNotUnique.new(message)
606
523
  else
607
524
  super
608
525
  end
609
526
  end
527
+
528
+ private
529
+ COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
530
+
531
+ def table_structure_with_collation(table_name, basic_structure)
532
+ collation_hash = {}
533
+ sql = "SELECT sql FROM
534
+ (SELECT * FROM sqlite_master UNION ALL
535
+ SELECT * FROM sqlite_temp_master)
536
+ WHERE type='table' and name='#{ table_name }' \;"
537
+
538
+ # Result will have following sample string
539
+ # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
540
+ # "password_digest" varchar COLLATE "NOCASE");
541
+ result = exec_query(sql, 'SCHEMA').first
542
+
543
+ if result
544
+ # Splitting with left parentheses and picking up last will return all
545
+ # columns separated with comma(,).
546
+ columns_string = result["sql"].split('(').last
547
+
548
+ columns_string.split(',').each do |column_string|
549
+ # This regex will match the column name and collation type and will save
550
+ # the value in $1 and $2 respectively.
551
+ collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
552
+ end
553
+
554
+ basic_structure.map! do |column|
555
+ column_name = column['name']
556
+
557
+ if collation_hash.has_key? column_name
558
+ column['collation'] = collation_hash[column_name]
559
+ end
560
+
561
+ column
562
+ end
563
+ else
564
+ basic_structure.to_hash
565
+ end
566
+ end
610
567
  end
611
568
  end
612
569
  end