activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
  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 +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,59 +1,612 @@
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 'arel/visitors/bind_visitor'
2
4
 
3
- gem 'sqlite3', '~> 1.3.4'
5
+ gem 'sqlite3', '~> 1.3.6'
4
6
  require 'sqlite3'
5
7
 
6
8
  module ActiveRecord
7
- class Base
9
+ module ConnectionHandling # :nodoc:
8
10
  # sqlite3 adapter reuses sqlite_connection.
9
- def self.sqlite3_connection(config) # :nodoc:
11
+ def sqlite3_connection(config)
10
12
  # Require database.
11
13
  unless config[:database]
12
14
  raise ArgumentError, "No database file specified. Missing argument: database"
13
15
  end
14
16
 
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"'
17
+ # Allow database path relative to Rails.root, but only if the database
18
+ # path is not the special path that tells sqlite to build a database only
19
+ # in memory.
20
+ if ':memory:' != config[:database]
21
+ config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
22
+ dirname = File.dirname(config[:database])
23
+ Dir.mkdir(dirname) unless File.directory?(dirname)
24
24
  end
25
25
 
26
26
  db = SQLite3::Database.new(
27
- config[:database],
27
+ config[:database].to_s,
28
28
  :results_as_hash => true
29
29
  )
30
30
 
31
- db.busy_timeout(config[:timeout]) if config[:timeout]
31
+ db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
32
32
 
33
- ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
33
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
34
+ rescue Errno::ENOENT => error
35
+ if error.message.include?("No such file or directory")
36
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
37
+ else
38
+ raise
39
+ end
34
40
  end
35
41
  end
36
42
 
37
43
  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
+ 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
+ # The SQLite3 adapter works SQLite 3.6.16 or newer
54
+ # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
55
+ #
56
+ # Options:
57
+ #
58
+ # * <tt>:database</tt> - Path to the database file.
59
+ class SQLite3Adapter < AbstractAdapter
60
+ ADAPTER_NAME = 'SQLite'.freeze
61
+ include Savepoints
62
+
63
+ NATIVE_DATABASE_TYPES = {
64
+ primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
65
+ string: { name: "varchar" },
66
+ text: { name: "text" },
67
+ integer: { name: "integer" },
68
+ float: { name: "float" },
69
+ decimal: { name: "decimal" },
70
+ datetime: { name: "datetime" },
71
+ time: { name: "time" },
72
+ date: { name: "date" },
73
+ binary: { name: "blob" },
74
+ boolean: { name: "boolean" }
75
+ }
76
+
77
+ class StatementPool < ConnectionAdapters::StatementPool
78
+ def initialize(connection, max)
44
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
+ private
103
+ def cache
104
+ @cache[$$]
105
+ end
106
+
107
+ def dealloc(stmt)
108
+ stmt.close unless stmt.closed?
109
+ end
110
+ end
111
+
112
+ def initialize(connection, logger, connection_options, config)
113
+ super(connection, logger)
114
+
115
+ @active = nil
116
+ @statements = StatementPool.new(@connection,
117
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
118
+ @config = config
119
+
120
+ @visitor = Arel::Visitors::SQLite.new self
121
+
122
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
123
+ @prepared_statements = true
124
+ else
125
+ @prepared_statements = false
45
126
  end
46
127
  end
47
128
 
129
+ def supports_ddl_transactions?
130
+ true
131
+ end
132
+
133
+ def supports_savepoints?
134
+ true
135
+ end
136
+
137
+ def supports_partial_index?
138
+ sqlite_version >= '3.8.0'
139
+ end
140
+
141
+ # Returns true, since this connection adapter supports prepared statement
142
+ # caching.
143
+ def supports_statement_cache?
144
+ true
145
+ end
146
+
147
+ # Returns true, since this connection adapter supports migrations.
148
+ def supports_migrations? #:nodoc:
149
+ true
150
+ end
151
+
152
+ def supports_primary_key? #:nodoc:
153
+ true
154
+ end
155
+
156
+ def requires_reloading?
157
+ true
158
+ end
159
+
160
+ def supports_views?
161
+ true
162
+ end
163
+
164
+ def active?
165
+ @active != false
166
+ end
167
+
168
+ # Disconnects from the database if already connected. Otherwise, this
169
+ # method does nothing.
170
+ def disconnect!
171
+ super
172
+ @active = false
173
+ @connection.close rescue nil
174
+ end
175
+
176
+ # Clears the prepared statements cache.
177
+ def clear_cache!
178
+ @statements.clear
179
+ end
180
+
181
+ def supports_index_sort_order?
182
+ true
183
+ end
184
+
185
+ # Returns 62. SQLite supports index names up to 64
186
+ # characters. The rest is used by rails internally to perform
187
+ # temporary rename operations
188
+ def allowed_index_name_length
189
+ index_name_length - 2
190
+ end
191
+
192
+ def native_database_types #:nodoc:
193
+ NATIVE_DATABASE_TYPES
194
+ end
195
+
48
196
  # Returns the current database encoding format as a string, eg: 'UTF-8'
49
197
  def encoding
50
- if @connection.respond_to?(:encoding)
51
- @connection.encoding.to_s
198
+ @connection.encoding.to_s
199
+ end
200
+
201
+ def supports_explain?
202
+ true
203
+ end
204
+
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
+ #--
254
+ # DATABASE STATEMENTS ======================================
255
+ #++
256
+
257
+ def explain(arel, binds = [])
258
+ 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
274
+ end
275
+
276
+ def exec_query(sql, name = nil, binds = [])
277
+ type_casted_binds = binds.map { |col, val|
278
+ [col, type_cast(val, col)]
279
+ }
280
+
281
+ log(sql, name, type_casted_binds) do
282
+ # Don't cache statements if they are not prepared
283
+ if without_prepared_statement?(binds)
284
+ stmt = @connection.prepare(sql)
285
+ begin
286
+ cols = stmt.columns
287
+ records = stmt.to_a
288
+ ensure
289
+ stmt.close
290
+ end
291
+ stmt = records
292
+ else
293
+ cache = @statements[sql] ||= {
294
+ :stmt => @connection.prepare(sql)
295
+ }
296
+ stmt = cache[:stmt]
297
+ cols = cache[:cols] ||= stmt.columns
298
+ stmt.reset!
299
+ stmt.bind_params type_casted_binds.map { |_, val| val }
300
+ end
301
+
302
+ ActiveRecord::Result.new(cols, stmt.to_a)
303
+ end
304
+ end
305
+
306
+ def exec_delete(sql, name = 'SQL', binds = [])
307
+ exec_query(sql, name, binds)
308
+ @connection.changes
309
+ end
310
+ alias :exec_update :exec_delete
311
+
312
+ def last_inserted_id(result)
313
+ @connection.last_insert_row_id
314
+ end
315
+
316
+ def execute(sql, name = nil) #:nodoc:
317
+ log(sql, name) { @connection.execute(sql) }
318
+ end
319
+
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
+ def begin_db_transaction #:nodoc:
341
+ log('begin transaction',nil) { @connection.transaction }
342
+ end
343
+
344
+ def commit_db_transaction #:nodoc:
345
+ log('commit transaction',nil) { @connection.commit }
346
+ end
347
+
348
+ def exec_rollback_db_transaction #:nodoc:
349
+ log('rollback transaction',nil) { @connection.rollback }
350
+ end
351
+
352
+ # SCHEMA STATEMENTS ========================================
353
+
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']
364
+ end
365
+ end
366
+ alias data_sources tables
367
+
368
+ def table_exists?(table_name)
369
+ table_name && tables(nil, table_name).any?
370
+ end
371
+ alias data_source_exists? table_exists?
372
+
373
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
374
+ def columns(table_name) #:nodoc:
375
+ table_structure(table_name).map do |field|
376
+ case field["dflt_value"]
377
+ when /^null$/i
378
+ field["dflt_value"] = nil
379
+ when /^'(.*)'$/m
380
+ field["dflt_value"] = $1.gsub("''", "'")
381
+ when /^"(.*)"$/m
382
+ field["dflt_value"] = $1.gsub('""', '"')
383
+ end
384
+
385
+ 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)
388
+ end
389
+ end
390
+
391
+ # Returns an array of indexes for the given table.
392
+ def indexes(table_name, name = nil) #:nodoc:
393
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
394
+ sql = <<-SQL
395
+ SELECT sql
396
+ FROM sqlite_master
397
+ WHERE name=#{quote(row['name'])} AND type='index'
398
+ UNION ALL
399
+ SELECT sql
400
+ FROM sqlite_temp_master
401
+ WHERE name=#{quote(row['name'])} AND type='index'
402
+ SQL
403
+ index_sql = exec_query(sql).first['sql']
404
+ match = /\sWHERE\s+(.+)$/i.match(index_sql)
405
+ where = match[1] if match
406
+ IndexDefinition.new(
407
+ table_name,
408
+ row['name'],
409
+ row['unique'] != 0,
410
+ exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
411
+ col['name']
412
+ }, nil, nil, where)
413
+ end
414
+ end
415
+
416
+ def primary_key(table_name) #:nodoc:
417
+ pks = table_structure(table_name).select { |f| f['pk'] > 0 }
418
+ return nil unless pks.count == 1
419
+ pks[0]['name']
420
+ end
421
+
422
+ def remove_index!(table_name, index_name) #:nodoc:
423
+ exec_query "DROP INDEX #{quote_column_name(index_name)}"
424
+ end
425
+
426
+ # Renames a table.
427
+ #
428
+ # Example:
429
+ # rename_table('octopuses', 'octopi')
430
+ def rename_table(table_name, new_name)
431
+ exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
432
+ rename_table_indexes(table_name, new_name)
433
+ end
434
+
435
+ # See: http://www.sqlite.org/lang_altertable.html
436
+ # SQLite has an additional restriction on the ALTER TABLE statement
437
+ def valid_alter_table_type?(type)
438
+ type.to_sym != :primary_key
439
+ end
440
+
441
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
442
+ if valid_alter_table_type?(type)
443
+ super(table_name, column_name, type, options)
52
444
  else
53
- @connection.execute('PRAGMA encoding')[0]['encoding']
445
+ alter_table(table_name) do |definition|
446
+ definition.column(column_name, type, options)
447
+ end
448
+ end
449
+ end
450
+
451
+ def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
452
+ alter_table(table_name) do |definition|
453
+ definition.remove_column column_name
454
+ end
455
+ end
456
+
457
+ def change_column_default(table_name, column_name, default) #:nodoc:
458
+ alter_table(table_name) do |definition|
459
+ definition[column_name].default = default
460
+ end
461
+ end
462
+
463
+ def change_column_null(table_name, column_name, null, default = nil)
464
+ unless null || default.nil?
465
+ 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
+ end
467
+ alter_table(table_name) do |definition|
468
+ definition[column_name].null = null
54
469
  end
55
470
  end
56
471
 
472
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
473
+ alter_table(table_name) do |definition|
474
+ include_default = options_include_default?(options)
475
+ definition[column_name].instance_eval do
476
+ self.type = type
477
+ self.limit = options[:limit] if options.include?(:limit)
478
+ self.default = options[:default] if include_default
479
+ self.null = options[:null] if options.include?(:null)
480
+ self.precision = options[:precision] if options.include?(:precision)
481
+ self.scale = options[:scale] if options.include?(:scale)
482
+ end
483
+ end
484
+ end
485
+
486
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
487
+ column = column_for(table_name, column_name)
488
+ alter_table(table_name, rename: {column.name => new_column_name.to_s})
489
+ rename_column_indexes(table_name, column.name, new_column_name)
490
+ end
491
+
492
+ protected
493
+
494
+ def initialize_type_map(m)
495
+ super
496
+ m.register_type(/binary/i, SQLite3Binary.new)
497
+ end
498
+
499
+ def table_structure(table_name)
500
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
501
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
502
+ structure
503
+ end
504
+
505
+ def alter_table(table_name, options = {}) #:nodoc:
506
+ altered_table_name = "a#{table_name}"
507
+ caller = lambda {|definition| yield definition if block_given?}
508
+
509
+ transaction do
510
+ move_table(table_name, altered_table_name,
511
+ options.merge(:temporary => true))
512
+ move_table(altered_table_name, table_name, &caller)
513
+ end
514
+ end
515
+
516
+ def move_table(from, to, options = {}, &block) #:nodoc:
517
+ copy_table(from, to, options, &block)
518
+ drop_table(from)
519
+ end
520
+
521
+ def copy_table(from, to, options = {}) #:nodoc:
522
+ from_primary_key = primary_key(from)
523
+ options[:id] = false
524
+ create_table(to, options) do |definition|
525
+ @definition = definition
526
+ @definition.primary_key(from_primary_key) if from_primary_key.present?
527
+ columns(from).each do |column|
528
+ column_name = options[:rename] ?
529
+ (options[:rename][column.name] ||
530
+ options[:rename][column.name.to_sym] ||
531
+ column.name) : column.name
532
+ next if column_name == from_primary_key
533
+
534
+ @definition.column(column_name, column.type,
535
+ :limit => column.limit, :default => column.default,
536
+ :precision => column.precision, :scale => column.scale,
537
+ :null => column.null)
538
+ end
539
+ yield @definition if block_given?
540
+ end
541
+ copy_table_indexes(from, to, options[:rename] || {})
542
+ copy_table_contents(from, to,
543
+ @definition.columns.map {|column| column.name},
544
+ options[:rename] || {})
545
+ end
546
+
547
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
548
+ indexes(from).each do |index|
549
+ name = index.name
550
+ if to == "a#{from}"
551
+ name = "t#{name}"
552
+ elsif from == "a#{to}"
553
+ name = name[1..-1]
554
+ end
555
+
556
+ to_column_names = columns(to).map { |c| c.name }
557
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
558
+ to_column_names.include?(column)
559
+ end
560
+
561
+ unless columns.empty?
562
+ # index name can't be the same
563
+ opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
564
+ opts[:unique] = true if index.unique
565
+ add_index(to, columns, opts)
566
+ end
567
+ end
568
+ end
569
+
570
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
571
+ column_mappings = Hash[columns.map {|name| [name, name]}]
572
+ rename.each { |a| column_mappings[a.last] = a.first }
573
+ from_columns = columns(from).collect {|col| col.name}
574
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
575
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
576
+
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
592
+ end
593
+
594
+ def sqlite_version
595
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
596
+ end
597
+
598
+ def translate_exception(exception, message)
599
+ case exception.message
600
+ # SQLite 3.8.2 returns a newly formatted error message:
601
+ # UNIQUE constraint failed: *table_name*.*column_name*
602
+ # Older versions of SQLite return:
603
+ # column *column_name* is not unique
604
+ when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
605
+ RecordNotUnique.new(message, exception)
606
+ else
607
+ super
608
+ end
609
+ end
57
610
  end
58
611
  end
59
612
  end