activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/per_thread_registry"
4
+
5
+ module ActiveRecord
6
+ # This is a thread locals registry for Active Record. For example:
7
+ #
8
+ # ActiveRecord::RuntimeRegistry.connection_handler
9
+ #
10
+ # returns the connection handler local to the current thread.
11
+ #
12
+ # See the documentation of ActiveSupport::PerThreadRegistry
13
+ # for further details.
14
+ class RuntimeRegistry # :nodoc:
15
+ extend ActiveSupport::PerThreadRegistry
16
+
17
+ attr_accessor :connection_handler, :sql_runtime
18
+
19
+ [:connection_handler, :sql_runtime].each do |val|
20
+ class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
21
+ class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Sanitization
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Accepts an array or string of SQL conditions and sanitizes
9
+ # them into a valid SQL fragment for a WHERE clause.
10
+ #
11
+ # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
12
+ # # => "name='foo''bar' and group_id=4"
13
+ #
14
+ # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
15
+ # # => "name='foo''bar' and group_id='4'"
16
+ #
17
+ # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
+ # # => "name='foo''bar' and group_id='4'"
19
+ #
20
+ # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
21
+ # # => "name='foo''bar' and group_id='4'"
22
+ def sanitize_sql_for_conditions(condition)
23
+ return nil if condition.blank?
24
+
25
+ case condition
26
+ when Array; sanitize_sql_array(condition)
27
+ else condition
28
+ end
29
+ end
30
+ alias :sanitize_sql :sanitize_sql_for_conditions
31
+
32
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
33
+ # them into a valid SQL fragment for a SET clause.
34
+ #
35
+ # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
36
+ # # => "name=NULL and group_id=4"
37
+ #
38
+ # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
39
+ # # => "name=NULL and group_id=4"
40
+ #
41
+ # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
42
+ # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
43
+ #
44
+ # sanitize_sql_for_assignment("name=NULL and group_id='4'")
45
+ # # => "name=NULL and group_id='4'"
46
+ def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
47
+ case assignments
48
+ when Array; sanitize_sql_array(assignments)
49
+ when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
50
+ else assignments
51
+ end
52
+ end
53
+
54
+ # Accepts an array, or string of SQL conditions and sanitizes
55
+ # them into a valid SQL fragment for an ORDER clause.
56
+ #
57
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
58
+ # # => "field(id, 1,3,2)"
59
+ #
60
+ # sanitize_sql_for_order("id ASC")
61
+ # # => "id ASC"
62
+ def sanitize_sql_for_order(condition)
63
+ if condition.is_a?(Array) && condition.first.to_s.include?("?")
64
+ enforce_raw_sql_whitelist([condition.first],
65
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
66
+ )
67
+
68
+ # Ensure we aren't dealing with a subclass of String that might
69
+ # override methods we use (eg. Arel::Nodes::SqlLiteral).
70
+ if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
71
+ condition = [String.new(condition.first), *condition[1..-1]]
72
+ end
73
+
74
+ Arel.sql(sanitize_sql_array(condition))
75
+ else
76
+ condition
77
+ end
78
+ end
79
+
80
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
81
+ #
82
+ # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
83
+ # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
84
+ def sanitize_sql_hash_for_assignment(attrs, table)
85
+ c = connection
86
+ attrs.map do |attr, value|
87
+ type = type_for_attribute(attr)
88
+ value = type.serialize(type.cast(value))
89
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
90
+ end.join(", ")
91
+ end
92
+
93
+ # Sanitizes a +string+ so that it is safe to use within an SQL
94
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
95
+ #
96
+ # sanitize_sql_like("100%")
97
+ # # => "100\\%"
98
+ #
99
+ # sanitize_sql_like("snake_cased_string")
100
+ # # => "snake\\_cased\\_string"
101
+ #
102
+ # sanitize_sql_like("100%", "!")
103
+ # # => "100!%"
104
+ #
105
+ # sanitize_sql_like("snake_cased_string", "!")
106
+ # # => "snake!_cased!_string"
107
+ def sanitize_sql_like(string, escape_character = "\\")
108
+ pattern = Regexp.union(escape_character, "%", "_")
109
+ string.gsub(pattern) { |x| [escape_character, x].join }
110
+ end
111
+
112
+ # Accepts an array of conditions. The array has each value
113
+ # sanitized and interpolated into the SQL statement.
114
+ #
115
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
116
+ # # => "name='foo''bar' and group_id=4"
117
+ #
118
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
119
+ # # => "name='foo''bar' and group_id=4"
120
+ #
121
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
122
+ # # => "name='foo''bar' and group_id='4'"
123
+ def sanitize_sql_array(ary)
124
+ statement, *values = ary
125
+ if values.first.is_a?(Hash) && /:\w+/.match?(statement)
126
+ replace_named_bind_variables(statement, values.first)
127
+ elsif statement.include?("?")
128
+ replace_bind_variables(statement, values)
129
+ elsif statement.blank?
130
+ statement
131
+ else
132
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
133
+ end
134
+ end
135
+
136
+ private
137
+ # Accepts a hash of SQL conditions and replaces those attributes
138
+ # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
139
+ # relationship with their expanded aggregate attribute values.
140
+ #
141
+ # Given:
142
+ #
143
+ # class Person < ActiveRecord::Base
144
+ # composed_of :address, class_name: "Address",
145
+ # mapping: [%w(address_street street), %w(address_city city)]
146
+ # end
147
+ #
148
+ # Then:
149
+ #
150
+ # { address: Address.new("813 abc st.", "chicago") }
151
+ # # => { address_street: "813 abc st.", address_city: "chicago" }
152
+ def expand_hash_conditions_for_aggregates(attrs) # :doc:
153
+ expanded_attrs = {}
154
+ attrs.each do |attr, value|
155
+ if aggregation = reflect_on_aggregation(attr.to_sym)
156
+ mapping = aggregation.mapping
157
+ mapping.each do |field_attr, aggregate_attr|
158
+ expanded_attrs[field_attr] = if value.is_a?(Array)
159
+ value.map { |it| it.send(aggregate_attr) }
160
+ elsif mapping.size == 1 && !value.respond_to?(aggregate_attr)
161
+ value
162
+ else
163
+ value.send(aggregate_attr)
164
+ end
165
+ end
166
+ else
167
+ expanded_attrs[attr] = value
168
+ end
169
+ end
170
+ expanded_attrs
171
+ end
172
+ deprecate :expand_hash_conditions_for_aggregates
173
+
174
+ def replace_bind_variables(statement, values)
175
+ raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
176
+ bound = values.dup
177
+ c = connection
178
+ statement.gsub(/\?/) do
179
+ replace_bind_variable(bound.shift, c)
180
+ end
181
+ end
182
+
183
+ def replace_bind_variable(value, c = connection)
184
+ if ActiveRecord::Relation === value
185
+ value.to_sql
186
+ else
187
+ quote_bound_value(value, c)
188
+ end
189
+ end
190
+
191
+ def replace_named_bind_variables(statement, bind_vars)
192
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
193
+ if $1 == ":" # skip postgresql casts
194
+ match # return the whole match
195
+ elsif bind_vars.include?(match = $2.to_sym)
196
+ replace_bind_variable(bind_vars[match])
197
+ else
198
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
199
+ end
200
+ end
201
+ end
202
+
203
+ def quote_bound_value(value, c = connection)
204
+ if value.respond_to?(:map) && !value.acts_like?(:string)
205
+ if value.respond_to?(:empty?) && value.empty?
206
+ c.quote(nil)
207
+ else
208
+ value.map { |v| c.quote(v) }.join(",")
209
+ end
210
+ else
211
+ c.quote(value)
212
+ end
213
+ end
214
+
215
+ def raise_if_bind_arity_mismatch(statement, expected, provided)
216
+ unless expected == provided
217
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # = Active Record \Schema
5
+ #
6
+ # Allows programmers to programmatically define a schema in a portable
7
+ # DSL. This means you can define tables, indexes, etc. without using SQL
8
+ # directly, so your applications can more easily support multiple
9
+ # databases.
10
+ #
11
+ # Usage:
12
+ #
13
+ # ActiveRecord::Schema.define do
14
+ # create_table :authors do |t|
15
+ # t.string :name, null: false
16
+ # end
17
+ #
18
+ # add_index :authors, :name, :unique
19
+ #
20
+ # create_table :posts do |t|
21
+ # t.integer :author_id, null: false
22
+ # t.string :subject
23
+ # t.text :body
24
+ # t.boolean :private, default: false
25
+ # end
26
+ #
27
+ # add_index :posts, :author_id
28
+ # end
29
+ #
30
+ # ActiveRecord::Schema is only supported by database adapters that also
31
+ # support migrations, the two features being very similar.
32
+ class Schema < Migration::Current
33
+ # Eval the given block. All methods available to the current connection
34
+ # adapter are available within the block, so you can easily use the
35
+ # database definition DSL to build up your schema (
36
+ # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
37
+ # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
38
+ #
39
+ # The +info+ hash is optional, and if given is used to define metadata
40
+ # about the current schema (currently, only the schema's version):
41
+ #
42
+ # ActiveRecord::Schema.define(version: 2038_01_19_000001) do
43
+ # ...
44
+ # end
45
+ def self.define(info = {}, &block)
46
+ new.define(info, &block)
47
+ end
48
+
49
+ def define(info, &block) # :nodoc:
50
+ instance_eval(&block)
51
+
52
+ if info[:version].present?
53
+ ActiveRecord::SchemaMigration.create_table
54
+ connection.assume_migrated_upto_version(info[:version], migrations_paths)
55
+ end
56
+
57
+ ActiveRecord::InternalMetadata.create_table
58
+ ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
59
+ end
60
+
61
+ private
62
+ # Returns the migrations paths.
63
+ #
64
+ # ActiveRecord::Schema.new.migrations_paths
65
+ # # => ["db/migrate"] # Rails migration path by default.
66
+ def migrations_paths
67
+ ActiveRecord::Migrator.migrations_paths
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module ActiveRecord
6
+ # = Active Record Schema Dumper
7
+ #
8
+ # This class is used to dump the database schema for some connection to some
9
+ # output format (i.e., ActiveRecord::Schema).
10
+ class SchemaDumper #:nodoc:
11
+ private_class_method :new
12
+
13
+ ##
14
+ # :singleton-method:
15
+ # A list of tables which should not be dumped to the schema.
16
+ # Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby.
17
+ # Only strings are accepted if ActiveRecord::Base.schema_format == :sql.
18
+ cattr_accessor :ignore_tables, default: []
19
+
20
+ class << self
21
+ def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
22
+ connection.create_schema_dumper(generate_options(config)).dump(stream)
23
+ stream
24
+ end
25
+
26
+ private
27
+ def generate_options(config)
28
+ {
29
+ table_name_prefix: config.table_name_prefix,
30
+ table_name_suffix: config.table_name_suffix
31
+ }
32
+ end
33
+ end
34
+
35
+ def dump(stream)
36
+ header(stream)
37
+ extensions(stream)
38
+ tables(stream)
39
+ trailer(stream)
40
+ stream
41
+ end
42
+
43
+ private
44
+
45
+ def initialize(connection, options = {})
46
+ @connection = connection
47
+ @version = connection.migration_context.current_version rescue nil
48
+ @options = options
49
+ end
50
+
51
+ # turns 20170404131909 into "2017_04_04_131909"
52
+ def formatted_version
53
+ stringified = @version.to_s
54
+ return stringified unless stringified.length == 14
55
+ stringified.insert(4, "_").insert(7, "_").insert(10, "_")
56
+ end
57
+
58
+ def define_params
59
+ @version ? "version: #{formatted_version}" : ""
60
+ end
61
+
62
+ def header(stream)
63
+ stream.puts <<HEADER
64
+ # This file is auto-generated from the current state of the database. Instead
65
+ # of editing this file, please use the migrations feature of Active Record to
66
+ # incrementally modify your database, and then regenerate this schema definition.
67
+ #
68
+ # Note that this schema.rb definition is the authoritative source for your
69
+ # database schema. If you need to create the application database on another
70
+ # system, you should be using db:schema:load, not running all the migrations
71
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
72
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
73
+ #
74
+ # It's strongly recommended that you check this file into your version control system.
75
+
76
+ ActiveRecord::Schema.define(#{define_params}) do
77
+
78
+ HEADER
79
+ end
80
+
81
+ def trailer(stream)
82
+ stream.puts "end"
83
+ end
84
+
85
+ # extensions are only supported by PostgreSQL
86
+ def extensions(stream)
87
+ end
88
+
89
+ def tables(stream)
90
+ sorted_tables = @connection.tables.sort
91
+
92
+ sorted_tables.each do |table_name|
93
+ table(table_name, stream) unless ignored?(table_name)
94
+ end
95
+
96
+ # dump foreign keys at the end to make sure all dependent tables exist.
97
+ if @connection.supports_foreign_keys?
98
+ sorted_tables.each do |tbl|
99
+ foreign_keys(tbl, stream) unless ignored?(tbl)
100
+ end
101
+ end
102
+ end
103
+
104
+ def table(table, stream)
105
+ columns = @connection.columns(table)
106
+ begin
107
+ tbl = StringIO.new
108
+
109
+ # first dump primary key column
110
+ pk = @connection.primary_key(table)
111
+
112
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
113
+
114
+ case pk
115
+ when String
116
+ tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
117
+ pkcol = columns.detect { |c| c.name == pk }
118
+ pkcolspec = column_spec_for_primary_key(pkcol)
119
+ if pkcolspec.present?
120
+ tbl.print ", #{format_colspec(pkcolspec)}"
121
+ end
122
+ when Array
123
+ tbl.print ", primary_key: #{pk.inspect}"
124
+ else
125
+ tbl.print ", id: false"
126
+ end
127
+
128
+ table_options = @connection.table_options(table)
129
+ if table_options.present?
130
+ tbl.print ", #{format_options(table_options)}"
131
+ end
132
+
133
+ tbl.puts ", force: :cascade do |t|"
134
+
135
+ # then dump all non-primary key columns
136
+ columns.each do |column|
137
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
138
+ next if column.name == pk
139
+ type, colspec = column_spec(column)
140
+ tbl.print " t.#{type} #{column.name.inspect}"
141
+ tbl.print ", #{format_colspec(colspec)}" if colspec.present?
142
+ tbl.puts
143
+ end
144
+
145
+ indexes_in_create(table, tbl)
146
+
147
+ tbl.puts " end"
148
+ tbl.puts
149
+
150
+ tbl.rewind
151
+ stream.print tbl.read
152
+ rescue => e
153
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
154
+ stream.puts "# #{e.message}"
155
+ stream.puts
156
+ end
157
+ end
158
+
159
+ # Keep it for indexing materialized views
160
+ def indexes(table, stream)
161
+ if (indexes = @connection.indexes(table)).any?
162
+ add_index_statements = indexes.map do |index|
163
+ table_name = remove_prefix_and_suffix(index.table).inspect
164
+ " add_index #{([table_name] + index_parts(index)).join(', ')}"
165
+ end
166
+
167
+ stream.puts add_index_statements.sort.join("\n")
168
+ stream.puts
169
+ end
170
+ end
171
+
172
+ def indexes_in_create(table, stream)
173
+ if (indexes = @connection.indexes(table)).any?
174
+ index_statements = indexes.map do |index|
175
+ " t.index #{index_parts(index).join(', ')}"
176
+ end
177
+ stream.puts index_statements.sort.join("\n")
178
+ end
179
+ end
180
+
181
+ def index_parts(index)
182
+ index_parts = [
183
+ index.columns.inspect,
184
+ "name: #{index.name.inspect}",
185
+ ]
186
+ index_parts << "unique: true" if index.unique
187
+ index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present?
188
+ index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present?
189
+ index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
190
+ index_parts << "where: #{index.where.inspect}" if index.where
191
+ index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
192
+ index_parts << "type: #{index.type.inspect}" if index.type
193
+ index_parts << "comment: #{index.comment.inspect}" if index.comment
194
+ index_parts
195
+ end
196
+
197
+ def foreign_keys(table, stream)
198
+ if (foreign_keys = @connection.foreign_keys(table)).any?
199
+ add_foreign_key_statements = foreign_keys.map do |foreign_key|
200
+ parts = [
201
+ "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
202
+ remove_prefix_and_suffix(foreign_key.to_table).inspect,
203
+ ]
204
+
205
+ if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
206
+ parts << "column: #{foreign_key.column.inspect}"
207
+ end
208
+
209
+ if foreign_key.custom_primary_key?
210
+ parts << "primary_key: #{foreign_key.primary_key.inspect}"
211
+ end
212
+
213
+ if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/
214
+ parts << "name: #{foreign_key.name.inspect}"
215
+ end
216
+
217
+ parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
218
+ parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
219
+
220
+ " #{parts.join(', ')}"
221
+ end
222
+
223
+ stream.puts add_foreign_key_statements.sort.join("\n")
224
+ end
225
+ end
226
+
227
+ def format_colspec(colspec)
228
+ colspec.map { |key, value| "#{key}: #{value}" }.join(", ")
229
+ end
230
+
231
+ def format_options(options)
232
+ options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
233
+ end
234
+
235
+ def format_index_parts(options)
236
+ if options.is_a?(Hash)
237
+ "{ #{format_options(options)} }"
238
+ else
239
+ options.inspect
240
+ end
241
+ end
242
+
243
+ def remove_prefix_and_suffix(table)
244
+ prefix = Regexp.escape(@options[:table_name_prefix].to_s)
245
+ suffix = Regexp.escape(@options[:table_name_suffix].to_s)
246
+ table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1")
247
+ end
248
+
249
+ def ignored?(table_name)
250
+ [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored|
251
+ ignored === remove_prefix_and_suffix(table_name)
252
+ end
253
+ end
254
+ end
255
+ end