activerecord 3.2.19 → 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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,55 +1,569 @@
1
- require 'active_record/connection_adapters/sqlite_adapter'
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
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'
2
6
 
3
- gem 'sqlite3', '~> 1.3.5'
7
+ gem 'sqlite3', '~> 1.3.6'
4
8
  require 'sqlite3'
5
9
 
6
10
  module ActiveRecord
7
- class Base
8
- # sqlite3 adapter reuses sqlite_connection.
9
- def self.sqlite3_connection(config) # :nodoc:
11
+ module ConnectionHandling # :nodoc:
12
+ def sqlite3_connection(config)
10
13
  # Require database.
11
14
  unless config[:database]
12
15
  raise ArgumentError, "No database file specified. Missing argument: database"
13
16
  end
14
17
 
15
- # Allow database path relative to Rails.root, but only if
16
- # the database path is not the special path that tells
17
- # Sqlite to build a database only in memory.
18
- if defined?(Rails.root) && ':memory:' != config[:database]
19
- config[:database] = File.expand_path(config[:database], Rails.root)
20
- end
21
-
22
- unless 'sqlite3' == config[:adapter]
23
- raise ArgumentError, 'adapter name should be "sqlite3"'
18
+ # Allow database path relative to Rails.root, but only if the database
19
+ # path is not the special path that tells sqlite to build a database only
20
+ # in memory.
21
+ if ':memory:' != config[:database]
22
+ config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
23
+ dirname = File.dirname(config[:database])
24
+ Dir.mkdir(dirname) unless File.directory?(dirname)
24
25
  end
25
26
 
26
27
  db = SQLite3::Database.new(
27
- config[:database],
28
+ config[:database].to_s,
28
29
  :results_as_hash => true
29
30
  )
30
31
 
31
- db.busy_timeout(config[:timeout]) if config[:timeout]
32
+ db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
32
33
 
33
- ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
34
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
35
+ rescue Errno::ENOENT => error
36
+ if error.message.include?("No such file or directory")
37
+ raise ActiveRecord::NoDatabaseError
38
+ else
39
+ raise
40
+ end
34
41
  end
35
42
  end
36
43
 
37
44
  module ConnectionAdapters #:nodoc:
38
- class SQLite3Adapter < SQLiteAdapter # :nodoc:
39
- def quote(value, column = nil)
40
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
41
- s = column.class.string_to_binary(value).unpack("H*")[0]
42
- "x'#{s}'"
43
- else
44
- super
45
+ # The SQLite3 adapter works SQLite 3.6.16 or newer
46
+ # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
47
+ #
48
+ # Options:
49
+ #
50
+ # * <tt>:database</tt> - Path to the database file.
51
+ class SQLite3Adapter < AbstractAdapter
52
+ ADAPTER_NAME = 'SQLite'.freeze
53
+
54
+ include SQLite3::Quoting
55
+
56
+ NATIVE_DATABASE_TYPES = {
57
+ primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
58
+ string: { name: "varchar" },
59
+ text: { name: "text" },
60
+ integer: { name: "integer" },
61
+ float: { name: "float" },
62
+ decimal: { name: "decimal" },
63
+ datetime: { name: "datetime" },
64
+ time: { name: "time" },
65
+ date: { name: "date" },
66
+ binary: { name: "blob" },
67
+ boolean: { name: "boolean" }
68
+ }
69
+
70
+ class StatementPool < ConnectionAdapters::StatementPool
71
+ private
72
+
73
+ def dealloc(stmt)
74
+ stmt[:stmt].close unless stmt[:stmt].closed?
45
75
  end
46
76
  end
47
77
 
78
+ def schema_creation # :nodoc:
79
+ SQLite3::SchemaCreation.new self
80
+ end
81
+
82
+ def arel_visitor # :nodoc:
83
+ Arel::Visitors::SQLite.new(self)
84
+ end
85
+
86
+ def initialize(connection, logger, connection_options, config)
87
+ super(connection, logger, config)
88
+
89
+ @active = nil
90
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
91
+ end
92
+
93
+ def supports_ddl_transactions?
94
+ true
95
+ end
96
+
97
+ def supports_savepoints?
98
+ true
99
+ end
100
+
101
+ def supports_partial_index?
102
+ sqlite_version >= '3.8.0'
103
+ end
104
+
105
+ # Returns true, since this connection adapter supports prepared statement
106
+ # caching.
107
+ def supports_statement_cache?
108
+ true
109
+ end
110
+
111
+ # Returns true, since this connection adapter supports migrations.
112
+ def supports_migrations? #:nodoc:
113
+ true
114
+ end
115
+
116
+ def supports_primary_key? #:nodoc:
117
+ true
118
+ end
119
+
120
+ def requires_reloading?
121
+ true
122
+ end
123
+
124
+ def supports_views?
125
+ true
126
+ end
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
+
136
+ def active?
137
+ @active != false
138
+ end
139
+
140
+ # Disconnects from the database if already connected. Otherwise, this
141
+ # method does nothing.
142
+ def disconnect!
143
+ super
144
+ @active = false
145
+ @connection.close rescue nil
146
+ end
147
+
148
+ # Clears the prepared statements cache.
149
+ def clear_cache!
150
+ @statements.clear
151
+ end
152
+
153
+ def supports_index_sort_order?
154
+ true
155
+ end
156
+
157
+ def valid_type?(type)
158
+ true
159
+ end
160
+
161
+ # Returns 62. SQLite supports index names up to 64
162
+ # characters. The rest is used by rails internally to perform
163
+ # temporary rename operations
164
+ def allowed_index_name_length
165
+ index_name_length - 2
166
+ end
167
+
168
+ def native_database_types #:nodoc:
169
+ NATIVE_DATABASE_TYPES
170
+ end
171
+
48
172
  # Returns the current database encoding format as a string, eg: 'UTF-8'
49
173
  def encoding
50
174
  @connection.encoding.to_s
51
175
  end
52
176
 
177
+ def supports_explain?
178
+ true
179
+ end
180
+
181
+ #--
182
+ # DATABASE STATEMENTS ======================================
183
+ #++
184
+
185
+ def explain(arel, binds = [])
186
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
187
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
188
+ end
189
+
190
+ def exec_query(sql, name = nil, binds = [], prepare: false)
191
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
192
+
193
+ log(sql, name, binds) do
194
+ # Don't cache statements if they are not prepared
195
+ unless prepare
196
+ stmt = @connection.prepare(sql)
197
+ begin
198
+ cols = stmt.columns
199
+ unless without_prepared_statement?(binds)
200
+ stmt.bind_params(type_casted_binds)
201
+ end
202
+ records = stmt.to_a
203
+ ensure
204
+ stmt.close
205
+ end
206
+ stmt = records
207
+ else
208
+ cache = @statements[sql] ||= {
209
+ :stmt => @connection.prepare(sql)
210
+ }
211
+ stmt = cache[:stmt]
212
+ cols = cache[:cols] ||= stmt.columns
213
+ stmt.reset!
214
+ stmt.bind_params(type_casted_binds)
215
+ end
216
+
217
+ ActiveRecord::Result.new(cols, stmt.to_a)
218
+ end
219
+ end
220
+
221
+ def exec_delete(sql, name = 'SQL', binds = [])
222
+ exec_query(sql, name, binds)
223
+ @connection.changes
224
+ end
225
+ alias :exec_update :exec_delete
226
+
227
+ def last_inserted_id(result)
228
+ @connection.last_insert_row_id
229
+ end
230
+
231
+ def execute(sql, name = nil) #:nodoc:
232
+ log(sql, name) { @connection.execute(sql) }
233
+ end
234
+
235
+ def begin_db_transaction #:nodoc:
236
+ log('begin transaction',nil) { @connection.transaction }
237
+ end
238
+
239
+ def commit_db_transaction #:nodoc:
240
+ log('commit transaction',nil) { @connection.commit }
241
+ end
242
+
243
+ def exec_rollback_db_transaction #:nodoc:
244
+ log('rollback transaction',nil) { @connection.rollback }
245
+ end
246
+
247
+ # SCHEMA STATEMENTS ========================================
248
+
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
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')
267
+ end
268
+
269
+ def table_exists?(table_name)
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?
299
+ end
300
+
301
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
302
+ def columns(table_name) # :nodoc:
303
+ table_name = table_name.to_s
304
+ table_structure(table_name).map do |field|
305
+ case field["dflt_value"]
306
+ when /^null$/i
307
+ field["dflt_value"] = nil
308
+ when /^'(.*)'$/m
309
+ field["dflt_value"] = $1.gsub("''", "'")
310
+ when /^"(.*)"$/m
311
+ field["dflt_value"] = $1.gsub('""', '"')
312
+ end
313
+
314
+ collation = field['collation']
315
+ sql_type = field['type']
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)
318
+ end
319
+ end
320
+
321
+ # Returns an array of indexes for the given table.
322
+ def indexes(table_name, name = nil) #:nodoc:
323
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
324
+ sql = <<-SQL
325
+ SELECT sql
326
+ FROM sqlite_master
327
+ WHERE name=#{quote(row['name'])} AND type='index'
328
+ UNION ALL
329
+ SELECT sql
330
+ FROM sqlite_temp_master
331
+ WHERE name=#{quote(row['name'])} AND type='index'
332
+ SQL
333
+ index_sql = exec_query(sql).first['sql']
334
+ match = /\sWHERE\s+(.+)$/i.match(index_sql)
335
+ where = match[1] if match
336
+ IndexDefinition.new(
337
+ table_name,
338
+ row['name'],
339
+ row['unique'] != 0,
340
+ exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
341
+ col['name']
342
+ }, nil, nil, where)
343
+ end
344
+ end
345
+
346
+ def primary_keys(table_name) # :nodoc:
347
+ pks = table_structure(table_name).select { |f| f['pk'] > 0 }
348
+ pks.sort_by { |f| f['pk'] }.map { |f| f['name'] }
349
+ end
350
+
351
+ def remove_index(table_name, options = {}) #:nodoc:
352
+ index_name = index_name_for_remove(table_name, options)
353
+ exec_query "DROP INDEX #{quote_column_name(index_name)}"
354
+ end
355
+
356
+ # Renames a table.
357
+ #
358
+ # Example:
359
+ # rename_table('octopuses', 'octopi')
360
+ def rename_table(table_name, new_name)
361
+ exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
362
+ rename_table_indexes(table_name, new_name)
363
+ end
364
+
365
+ # See: http://www.sqlite.org/lang_altertable.html
366
+ # SQLite has an additional restriction on the ALTER TABLE statement
367
+ def valid_alter_table_type?(type)
368
+ type.to_sym != :primary_key
369
+ end
370
+
371
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
372
+ if valid_alter_table_type?(type)
373
+ super(table_name, column_name, type, options)
374
+ else
375
+ alter_table(table_name) do |definition|
376
+ definition.column(column_name, type, options)
377
+ end
378
+ end
379
+ end
380
+
381
+ def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
382
+ alter_table(table_name) do |definition|
383
+ definition.remove_column column_name
384
+ end
385
+ end
386
+
387
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
388
+ default = extract_new_default_value(default_or_changes)
389
+
390
+ alter_table(table_name) do |definition|
391
+ definition[column_name].default = default
392
+ end
393
+ end
394
+
395
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
396
+ unless null || default.nil?
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")
398
+ end
399
+ alter_table(table_name) do |definition|
400
+ definition[column_name].null = null
401
+ end
402
+ end
403
+
404
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
405
+ alter_table(table_name) do |definition|
406
+ include_default = options_include_default?(options)
407
+ definition[column_name].instance_eval do
408
+ self.type = type
409
+ self.limit = options[:limit] if options.include?(:limit)
410
+ self.default = options[:default] if include_default
411
+ self.null = options[:null] if options.include?(:null)
412
+ self.precision = options[:precision] if options.include?(:precision)
413
+ self.scale = options[:scale] if options.include?(:scale)
414
+ self.collation = options[:collation] if options.include?(:collation)
415
+ end
416
+ end
417
+ end
418
+
419
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
420
+ column = column_for(table_name, column_name)
421
+ alter_table(table_name, rename: {column.name => new_column_name.to_s})
422
+ rename_column_indexes(table_name, column.name, new_column_name)
423
+ end
424
+
425
+ protected
426
+
427
+ def table_structure(table_name)
428
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA')
429
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
430
+ table_structure_with_collation(table_name, structure)
431
+ end
432
+
433
+ def alter_table(table_name, options = {}) #:nodoc:
434
+ altered_table_name = "a#{table_name}"
435
+ caller = lambda {|definition| yield definition if block_given?}
436
+
437
+ transaction do
438
+ move_table(table_name, altered_table_name,
439
+ options.merge(:temporary => true))
440
+ move_table(altered_table_name, table_name, &caller)
441
+ end
442
+ end
443
+
444
+ def move_table(from, to, options = {}, &block) #:nodoc:
445
+ copy_table(from, to, options, &block)
446
+ drop_table(from)
447
+ end
448
+
449
+ def copy_table(from, to, options = {}) #:nodoc:
450
+ from_primary_key = primary_key(from)
451
+ options[:id] = false
452
+ create_table(to, options) do |definition|
453
+ @definition = definition
454
+ @definition.primary_key(from_primary_key) if from_primary_key.present?
455
+ columns(from).each do |column|
456
+ column_name = options[:rename] ?
457
+ (options[:rename][column.name] ||
458
+ options[:rename][column.name.to_sym] ||
459
+ column.name) : column.name
460
+ next if column_name == from_primary_key
461
+
462
+ @definition.column(column_name, column.type,
463
+ :limit => column.limit, :default => column.default,
464
+ :precision => column.precision, :scale => column.scale,
465
+ :null => column.null, collation: column.collation)
466
+ end
467
+ yield @definition if block_given?
468
+ end
469
+ copy_table_indexes(from, to, options[:rename] || {})
470
+ copy_table_contents(from, to,
471
+ @definition.columns.map(&:name),
472
+ options[:rename] || {})
473
+ end
474
+
475
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
476
+ indexes(from).each do |index|
477
+ name = index.name
478
+ if to == "a#{from}"
479
+ name = "t#{name}"
480
+ elsif from == "a#{to}"
481
+ name = name[1..-1]
482
+ end
483
+
484
+ to_column_names = columns(to).map(&:name)
485
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
486
+ to_column_names.include?(column)
487
+ end
488
+
489
+ unless columns.empty?
490
+ # index name can't be the same
491
+ opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
492
+ opts[:unique] = true if index.unique
493
+ add_index(to, columns, opts)
494
+ end
495
+ end
496
+ end
497
+
498
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
499
+ column_mappings = Hash[columns.map {|name| [name, name]}]
500
+ rename.each { |a| column_mappings[a.last] = a.first }
501
+ from_columns = columns(from).collect(&:name)
502
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
503
+ from_columns_to_copy = columns.map { |col| column_mappings[col] }
504
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
505
+ quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
506
+
507
+ exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
508
+ SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
509
+ end
510
+
511
+ def sqlite_version
512
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
513
+ end
514
+
515
+ def translate_exception(exception, message)
516
+ case exception.message
517
+ # SQLite 3.8.2 returns a newly formatted error message:
518
+ # UNIQUE constraint failed: *table_name*.*column_name*
519
+ # Older versions of SQLite return:
520
+ # column *column_name* is not unique
521
+ when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
522
+ RecordNotUnique.new(message)
523
+ else
524
+ super
525
+ end
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
53
567
  end
54
568
  end
55
569
  end
@@ -1,38 +1,57 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class StatementPool
3
+ class StatementPool # :nodoc:
4
4
  include Enumerable
5
5
 
6
- def initialize(connection, max = 1000)
7
- @connection = connection
8
- @max = max
6
+ DEFAULT_STATEMENT_LIMIT = 1000
7
+
8
+ def initialize(statement_limit = nil)
9
+ @cache = Hash.new { |h,pid| h[pid] = {} }
10
+ @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
9
11
  end
10
12
 
11
- def each
12
- raise NotImplementedError
13
+ def each(&block)
14
+ cache.each(&block)
13
15
  end
14
16
 
15
17
  def key?(key)
16
- raise NotImplementedError
18
+ cache.key?(key)
17
19
  end
18
20
 
19
21
  def [](key)
20
- raise NotImplementedError
22
+ cache[key]
21
23
  end
22
24
 
23
25
  def length
24
- raise NotImplementedError
26
+ cache.length
25
27
  end
26
28
 
27
- def []=(sql, key)
28
- raise NotImplementedError
29
+ def []=(sql, stmt)
30
+ while @statement_limit <= cache.size
31
+ dealloc(cache.shift.last)
32
+ end
33
+ cache[sql] = stmt
29
34
  end
30
35
 
31
36
  def clear
32
- raise NotImplementedError
37
+ cache.each_value do |stmt|
38
+ dealloc stmt
39
+ end
40
+ cache.clear
33
41
  end
34
42
 
35
43
  def delete(key)
44
+ dealloc cache[key]
45
+ cache.delete(key)
46
+ end
47
+
48
+ private
49
+
50
+ def cache
51
+ @cache[Process.pid]
52
+ end
53
+
54
+ def dealloc(stmt)
36
55
  raise NotImplementedError
37
56
  end
38
57
  end