activerecord 5.2.4.4 → 6.0.3.4

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 (292) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +777 -552
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +5 -3
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +10 -2
  7. data/lib/active_record/advisory_lock_base.rb +18 -0
  8. data/lib/active_record/aggregations.rb +4 -3
  9. data/lib/active_record/association_relation.rb +10 -8
  10. data/lib/active_record/associations.rb +21 -16
  11. data/lib/active_record/associations/alias_tracker.rb +0 -1
  12. data/lib/active_record/associations/association.rb +56 -19
  13. data/lib/active_record/associations/association_scope.rb +4 -6
  14. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  16. data/lib/active_record/associations/builder/association.rb +14 -18
  17. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  18. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -40
  20. data/lib/active_record/associations/builder/has_many.rb +2 -0
  21. data/lib/active_record/associations/builder/has_one.rb +35 -1
  22. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  23. data/lib/active_record/associations/collection_association.rb +12 -23
  24. data/lib/active_record/associations/collection_proxy.rb +13 -17
  25. data/lib/active_record/associations/foreign_association.rb +7 -0
  26. data/lib/active_record/associations/has_many_association.rb +2 -11
  27. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  28. data/lib/active_record/associations/has_one_association.rb +28 -30
  29. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +37 -28
  31. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  32. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  33. data/lib/active_record/associations/preloader.rb +39 -32
  34. data/lib/active_record/associations/preloader/association.rb +38 -36
  35. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  36. data/lib/active_record/associations/singular_association.rb +2 -16
  37. data/lib/active_record/attribute_assignment.rb +7 -11
  38. data/lib/active_record/attribute_decorators.rb +0 -2
  39. data/lib/active_record/attribute_methods.rb +28 -100
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -2
  41. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  42. data/lib/active_record/attribute_methods/primary_key.rb +15 -24
  43. data/lib/active_record/attribute_methods/query.rb +2 -3
  44. data/lib/active_record/attribute_methods/read.rb +15 -54
  45. data/lib/active_record/attribute_methods/serialization.rb +1 -2
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -3
  47. data/lib/active_record/attribute_methods/write.rb +17 -25
  48. data/lib/active_record/attributes.rb +13 -1
  49. data/lib/active_record/autosave_association.rb +3 -5
  50. data/lib/active_record/base.rb +2 -3
  51. data/lib/active_record/callbacks.rb +6 -21
  52. data/lib/active_record/coders/yaml_column.rb +0 -1
  53. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +103 -18
  54. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  55. data/lib/active_record/connection_adapters/abstract/database_statements.rb +102 -124
  56. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -9
  57. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  58. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +20 -14
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +100 -72
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +175 -79
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -57
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +191 -43
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +142 -215
  65. data/lib/active_record/connection_adapters/column.rb +17 -13
  66. data/lib/active_record/connection_adapters/connection_specification.rb +54 -45
  67. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  68. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/database_statements.rb +70 -14
  70. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +0 -1
  71. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +4 -6
  73. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  74. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  75. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +132 -16
  76. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -10
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  82. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -3
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +63 -75
  98. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +168 -75
  101. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  102. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -12
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +135 -146
  107. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  108. data/lib/active_record/connection_handling.rb +139 -26
  109. data/lib/active_record/core.rb +103 -61
  110. data/lib/active_record/counter_cache.rb +8 -30
  111. data/lib/active_record/database_configurations.rb +233 -0
  112. data/lib/active_record/database_configurations/database_config.rb +37 -0
  113. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  114. data/lib/active_record/database_configurations/url_config.rb +78 -0
  115. data/lib/active_record/dynamic_matchers.rb +3 -4
  116. data/lib/active_record/enum.rb +37 -7
  117. data/lib/active_record/errors.rb +15 -7
  118. data/lib/active_record/explain.rb +1 -2
  119. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  120. data/lib/active_record/fixture_set/render_context.rb +17 -0
  121. data/lib/active_record/fixture_set/table_row.rb +152 -0
  122. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  123. data/lib/active_record/fixtures.rb +144 -474
  124. data/lib/active_record/gem_version.rb +3 -3
  125. data/lib/active_record/inheritance.rb +13 -6
  126. data/lib/active_record/insert_all.rb +179 -0
  127. data/lib/active_record/integration.rb +68 -16
  128. data/lib/active_record/internal_metadata.rb +11 -3
  129. data/lib/active_record/locking/optimistic.rb +5 -7
  130. data/lib/active_record/locking/pessimistic.rb +3 -3
  131. data/lib/active_record/log_subscriber.rb +8 -27
  132. data/lib/active_record/middleware/database_selector.rb +74 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
  134. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  135. data/lib/active_record/migration.rb +104 -85
  136. data/lib/active_record/migration/command_recorder.rb +54 -22
  137. data/lib/active_record/migration/compatibility.rb +79 -52
  138. data/lib/active_record/migration/join_table.rb +0 -1
  139. data/lib/active_record/model_schema.rb +33 -11
  140. data/lib/active_record/nested_attributes.rb +2 -4
  141. data/lib/active_record/no_touching.rb +9 -2
  142. data/lib/active_record/null_relation.rb +0 -1
  143. data/lib/active_record/persistence.rb +232 -29
  144. data/lib/active_record/query_cache.rb +11 -4
  145. data/lib/active_record/querying.rb +33 -21
  146. data/lib/active_record/railtie.rb +80 -43
  147. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  148. data/lib/active_record/railties/controller_runtime.rb +30 -35
  149. data/lib/active_record/railties/databases.rake +199 -46
  150. data/lib/active_record/reflection.rb +40 -38
  151. data/lib/active_record/relation.rb +322 -80
  152. data/lib/active_record/relation/batches.rb +13 -11
  153. data/lib/active_record/relation/calculations.rb +54 -48
  154. data/lib/active_record/relation/delegation.rb +33 -49
  155. data/lib/active_record/relation/finder_methods.rb +23 -28
  156. data/lib/active_record/relation/from_clause.rb +4 -0
  157. data/lib/active_record/relation/merger.rb +11 -21
  158. data/lib/active_record/relation/predicate_builder.rb +5 -11
  159. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  160. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  161. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  162. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  163. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  164. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  165. data/lib/active_record/relation/query_attribute.rb +13 -8
  166. data/lib/active_record/relation/query_methods.rb +221 -70
  167. data/lib/active_record/relation/spawn_methods.rb +1 -2
  168. data/lib/active_record/relation/where_clause.rb +14 -11
  169. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  170. data/lib/active_record/result.rb +30 -12
  171. data/lib/active_record/sanitization.rb +32 -40
  172. data/lib/active_record/schema.rb +2 -11
  173. data/lib/active_record/schema_dumper.rb +22 -7
  174. data/lib/active_record/schema_migration.rb +6 -2
  175. data/lib/active_record/scoping.rb +8 -9
  176. data/lib/active_record/scoping/default.rb +4 -6
  177. data/lib/active_record/scoping/named.rb +21 -17
  178. data/lib/active_record/statement_cache.rb +30 -3
  179. data/lib/active_record/store.rb +87 -8
  180. data/lib/active_record/suppressor.rb +2 -2
  181. data/lib/active_record/table_metadata.rb +23 -15
  182. data/lib/active_record/tasks/database_tasks.rb +194 -25
  183. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -6
  184. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -8
  185. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -9
  186. data/lib/active_record/test_databases.rb +23 -0
  187. data/lib/active_record/test_fixtures.rb +225 -0
  188. data/lib/active_record/timestamp.rb +39 -26
  189. data/lib/active_record/touch_later.rb +5 -4
  190. data/lib/active_record/transactions.rb +64 -73
  191. data/lib/active_record/translation.rb +1 -1
  192. data/lib/active_record/type.rb +3 -5
  193. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  194. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  195. data/lib/active_record/type/serialized.rb +0 -1
  196. data/lib/active_record/type/type_map.rb +0 -1
  197. data/lib/active_record/type/unsigned_integer.rb +0 -1
  198. data/lib/active_record/type_caster/connection.rb +15 -14
  199. data/lib/active_record/type_caster/map.rb +1 -4
  200. data/lib/active_record/validations.rb +3 -3
  201. data/lib/active_record/validations/associated.rb +1 -2
  202. data/lib/active_record/validations/uniqueness.rb +15 -27
  203. data/lib/arel.rb +62 -0
  204. data/lib/arel/alias_predication.rb +9 -0
  205. data/lib/arel/attributes.rb +22 -0
  206. data/lib/arel/attributes/attribute.rb +37 -0
  207. data/lib/arel/collectors/bind.rb +24 -0
  208. data/lib/arel/collectors/composite.rb +31 -0
  209. data/lib/arel/collectors/plain_string.rb +20 -0
  210. data/lib/arel/collectors/sql_string.rb +20 -0
  211. data/lib/arel/collectors/substitute_binds.rb +28 -0
  212. data/lib/arel/crud.rb +42 -0
  213. data/lib/arel/delete_manager.rb +18 -0
  214. data/lib/arel/errors.rb +9 -0
  215. data/lib/arel/expressions.rb +29 -0
  216. data/lib/arel/factory_methods.rb +49 -0
  217. data/lib/arel/insert_manager.rb +49 -0
  218. data/lib/arel/math.rb +45 -0
  219. data/lib/arel/nodes.rb +68 -0
  220. data/lib/arel/nodes/and.rb +32 -0
  221. data/lib/arel/nodes/ascending.rb +23 -0
  222. data/lib/arel/nodes/binary.rb +52 -0
  223. data/lib/arel/nodes/bind_param.rb +36 -0
  224. data/lib/arel/nodes/case.rb +55 -0
  225. data/lib/arel/nodes/casted.rb +50 -0
  226. data/lib/arel/nodes/comment.rb +29 -0
  227. data/lib/arel/nodes/count.rb +12 -0
  228. data/lib/arel/nodes/delete_statement.rb +45 -0
  229. data/lib/arel/nodes/descending.rb +23 -0
  230. data/lib/arel/nodes/equality.rb +18 -0
  231. data/lib/arel/nodes/extract.rb +24 -0
  232. data/lib/arel/nodes/false.rb +16 -0
  233. data/lib/arel/nodes/full_outer_join.rb +8 -0
  234. data/lib/arel/nodes/function.rb +44 -0
  235. data/lib/arel/nodes/grouping.rb +8 -0
  236. data/lib/arel/nodes/in.rb +8 -0
  237. data/lib/arel/nodes/infix_operation.rb +80 -0
  238. data/lib/arel/nodes/inner_join.rb +8 -0
  239. data/lib/arel/nodes/insert_statement.rb +37 -0
  240. data/lib/arel/nodes/join_source.rb +20 -0
  241. data/lib/arel/nodes/matches.rb +18 -0
  242. data/lib/arel/nodes/named_function.rb +23 -0
  243. data/lib/arel/nodes/node.rb +50 -0
  244. data/lib/arel/nodes/node_expression.rb +13 -0
  245. data/lib/arel/nodes/outer_join.rb +8 -0
  246. data/lib/arel/nodes/over.rb +15 -0
  247. data/lib/arel/nodes/regexp.rb +16 -0
  248. data/lib/arel/nodes/right_outer_join.rb +8 -0
  249. data/lib/arel/nodes/select_core.rb +67 -0
  250. data/lib/arel/nodes/select_statement.rb +41 -0
  251. data/lib/arel/nodes/sql_literal.rb +16 -0
  252. data/lib/arel/nodes/string_join.rb +11 -0
  253. data/lib/arel/nodes/table_alias.rb +27 -0
  254. data/lib/arel/nodes/terminal.rb +16 -0
  255. data/lib/arel/nodes/true.rb +16 -0
  256. data/lib/arel/nodes/unary.rb +45 -0
  257. data/lib/arel/nodes/unary_operation.rb +20 -0
  258. data/lib/arel/nodes/unqualified_column.rb +22 -0
  259. data/lib/arel/nodes/update_statement.rb +41 -0
  260. data/lib/arel/nodes/values_list.rb +9 -0
  261. data/lib/arel/nodes/window.rb +126 -0
  262. data/lib/arel/nodes/with.rb +11 -0
  263. data/lib/arel/order_predications.rb +13 -0
  264. data/lib/arel/predications.rb +256 -0
  265. data/lib/arel/select_manager.rb +271 -0
  266. data/lib/arel/table.rb +110 -0
  267. data/lib/arel/tree_manager.rb +72 -0
  268. data/lib/arel/update_manager.rb +34 -0
  269. data/lib/arel/visitors.rb +20 -0
  270. data/lib/arel/visitors/depth_first.rb +203 -0
  271. data/lib/arel/visitors/dot.rb +296 -0
  272. data/lib/arel/visitors/ibm_db.rb +34 -0
  273. data/lib/arel/visitors/informix.rb +62 -0
  274. data/lib/arel/visitors/mssql.rb +156 -0
  275. data/lib/arel/visitors/mysql.rb +83 -0
  276. data/lib/arel/visitors/oracle.rb +158 -0
  277. data/lib/arel/visitors/oracle12.rb +65 -0
  278. data/lib/arel/visitors/postgresql.rb +109 -0
  279. data/lib/arel/visitors/sqlite.rb +38 -0
  280. data/lib/arel/visitors/to_sql.rb +888 -0
  281. data/lib/arel/visitors/visitor.rb +45 -0
  282. data/lib/arel/visitors/where_sql.rb +22 -0
  283. data/lib/arel/window_predications.rb +9 -0
  284. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  285. data/lib/rails/generators/active_record/migration.rb +14 -2
  286. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  287. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  288. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  289. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  290. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  291. metadata +115 -29
  292. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class IBM_DB < Arel::Visitors::ToSql
6
+ private
7
+ def visit_Arel_Nodes_SelectCore(o, collector)
8
+ collector = super
9
+ maybe_visit o.optimizer_hints, collector
10
+ end
11
+
12
+ def visit_Arel_Nodes_OptimizerHints(o, collector)
13
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join
14
+ collector << "/* <OPTGUIDELINES>#{hints}</OPTGUIDELINES> */"
15
+ end
16
+
17
+ def visit_Arel_Nodes_Limit(o, collector)
18
+ collector << "FETCH FIRST "
19
+ collector = visit o.expr, collector
20
+ collector << " ROWS ONLY"
21
+ end
22
+
23
+ def is_distinct_from(o, collector)
24
+ collector << "DECODE("
25
+ collector = visit [o.left, o.right, 0, 1], collector
26
+ collector << ")"
27
+ end
28
+
29
+ def collect_optimizer_hints(o, collector)
30
+ collector
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class Informix < Arel::Visitors::ToSql
6
+ private
7
+ def visit_Arel_Nodes_SelectStatement(o, collector)
8
+ collector << "SELECT "
9
+ collector = maybe_visit o.offset, collector
10
+ collector = maybe_visit o.limit, collector
11
+ collector = o.cores.inject(collector) { |c, x|
12
+ visit_Arel_Nodes_SelectCore x, c
13
+ }
14
+ if o.orders.any?
15
+ collector << "ORDER BY "
16
+ collector = inject_join o.orders, collector, ", "
17
+ end
18
+ maybe_visit o.lock, collector
19
+ end
20
+
21
+ def visit_Arel_Nodes_SelectCore(o, collector)
22
+ collector = inject_join o.projections, collector, ", "
23
+ if o.source && !o.source.empty?
24
+ collector << " FROM "
25
+ collector = visit o.source, collector
26
+ end
27
+
28
+ if o.wheres.any?
29
+ collector << " WHERE "
30
+ collector = inject_join o.wheres, collector, " AND "
31
+ end
32
+
33
+ if o.groups.any?
34
+ collector << "GROUP BY "
35
+ collector = inject_join o.groups, collector, ", "
36
+ end
37
+
38
+ if o.havings.any?
39
+ collector << " HAVING "
40
+ collector = inject_join o.havings, collector, " AND "
41
+ end
42
+ collector
43
+ end
44
+
45
+ def visit_Arel_Nodes_OptimizerHints(o, collector)
46
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ")
47
+ collector << "/*+ #{hints} */"
48
+ end
49
+
50
+ def visit_Arel_Nodes_Offset(o, collector)
51
+ collector << "SKIP "
52
+ visit o.expr, collector
53
+ end
54
+
55
+ def visit_Arel_Nodes_Limit(o, collector)
56
+ collector << "FIRST "
57
+ visit o.expr, collector
58
+ collector << " "
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class MSSQL < Arel::Visitors::ToSql
6
+ RowNumber = Struct.new :children
7
+
8
+ def initialize(*)
9
+ @primary_keys = {}
10
+ super
11
+ end
12
+
13
+ private
14
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
15
+ right = o.right
16
+
17
+ if right.nil?
18
+ collector = visit o.left, collector
19
+ collector << " IS NULL"
20
+ else
21
+ collector << "EXISTS (VALUES ("
22
+ collector = visit o.left, collector
23
+ collector << ") INTERSECT VALUES ("
24
+ collector = visit right, collector
25
+ collector << "))"
26
+ end
27
+ end
28
+
29
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
30
+ if o.right.nil?
31
+ collector = visit o.left, collector
32
+ collector << " IS NOT NULL"
33
+ else
34
+ collector << "NOT "
35
+ visit_Arel_Nodes_IsNotDistinctFrom o, collector
36
+ end
37
+ end
38
+
39
+ def visit_Arel_Visitors_MSSQL_RowNumber(o, collector)
40
+ collector << "ROW_NUMBER() OVER (ORDER BY "
41
+ inject_join(o.children, collector, ", ") << ") as _row_num"
42
+ end
43
+
44
+ def visit_Arel_Nodes_SelectStatement(o, collector)
45
+ if !o.limit && !o.offset
46
+ return super
47
+ end
48
+
49
+ is_select_count = false
50
+ o.cores.each { |x|
51
+ core_order_by = row_num_literal determine_order_by(o.orders, x)
52
+ if select_count? x
53
+ x.projections = [core_order_by]
54
+ is_select_count = true
55
+ else
56
+ x.projections << core_order_by
57
+ end
58
+ }
59
+
60
+ if is_select_count
61
+ # fixme count distinct wouldn't work with limit or offset
62
+ collector << "SELECT COUNT(1) as count_id FROM ("
63
+ end
64
+
65
+ collector << "SELECT _t.* FROM ("
66
+ collector = o.cores.inject(collector) { |c, x|
67
+ visit_Arel_Nodes_SelectCore x, c
68
+ }
69
+ collector << ") as _t WHERE #{get_offset_limit_clause(o)}"
70
+
71
+ if is_select_count
72
+ collector << ") AS subquery"
73
+ else
74
+ collector
75
+ end
76
+ end
77
+
78
+ def visit_Arel_Nodes_SelectCore(o, collector)
79
+ collector = super
80
+ maybe_visit o.optimizer_hints, collector
81
+ end
82
+
83
+ def visit_Arel_Nodes_OptimizerHints(o, collector)
84
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ")
85
+ collector << "OPTION (#{hints})"
86
+ end
87
+
88
+ def get_offset_limit_clause(o)
89
+ first_row = o.offset ? o.offset.expr.to_i + 1 : 1
90
+ last_row = o.limit ? o.limit.expr.to_i - 1 + first_row : nil
91
+ if last_row
92
+ " _row_num BETWEEN #{first_row} AND #{last_row}"
93
+ else
94
+ " _row_num >= #{first_row}"
95
+ end
96
+ end
97
+
98
+ def visit_Arel_Nodes_DeleteStatement(o, collector)
99
+ collector << "DELETE "
100
+ if o.limit
101
+ collector << "TOP ("
102
+ visit o.limit.expr, collector
103
+ collector << ") "
104
+ end
105
+ collector << "FROM "
106
+ collector = visit o.relation, collector
107
+ if o.wheres.any?
108
+ collector << " WHERE "
109
+ inject_join o.wheres, collector, " AND "
110
+ else
111
+ collector
112
+ end
113
+ end
114
+
115
+ def collect_optimizer_hints(o, collector)
116
+ collector
117
+ end
118
+
119
+ def determine_order_by(orders, x)
120
+ if orders.any?
121
+ orders
122
+ elsif x.groups.any?
123
+ x.groups
124
+ else
125
+ pk = find_left_table_pk(x.froms)
126
+ pk ? [pk] : []
127
+ end
128
+ end
129
+
130
+ def row_num_literal(order_by)
131
+ RowNumber.new order_by
132
+ end
133
+
134
+ def select_count?(x)
135
+ x.projections.length == 1 && Arel::Nodes::Count === x.projections.first
136
+ end
137
+
138
+ # FIXME raise exception of there is no pk?
139
+ def find_left_table_pk(o)
140
+ if o.kind_of?(Arel::Nodes::Join)
141
+ find_left_table_pk(o.left)
142
+ elsif o.instance_of?(Arel::Table)
143
+ find_primary_key(o)
144
+ end
145
+ end
146
+
147
+ def find_primary_key(o)
148
+ @primary_keys[o.name] ||= begin
149
+ primary_key_name = @connection.primary_key(o.name)
150
+ # some tables might be without primary key
151
+ primary_key_name && o[primary_key_name]
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class MySQL < Arel::Visitors::ToSql
6
+ private
7
+ def visit_Arel_Nodes_Bin(o, collector)
8
+ collector << "BINARY "
9
+ visit o.expr, collector
10
+ end
11
+
12
+ def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
13
+ visit o.expr, collector
14
+ end
15
+
16
+ ###
17
+ # :'(
18
+ # http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
19
+ def visit_Arel_Nodes_SelectStatement(o, collector)
20
+ if o.offset && !o.limit
21
+ o.limit = Arel::Nodes::Limit.new(18446744073709551615)
22
+ end
23
+ super
24
+ end
25
+
26
+ def visit_Arel_Nodes_SelectCore(o, collector)
27
+ o.froms ||= Arel.sql("DUAL")
28
+ super
29
+ end
30
+
31
+ def visit_Arel_Nodes_Concat(o, collector)
32
+ collector << " CONCAT("
33
+ visit o.left, collector
34
+ collector << ", "
35
+ visit o.right, collector
36
+ collector << ") "
37
+ collector
38
+ end
39
+
40
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
41
+ collector = visit o.left, collector
42
+ collector << " <=> "
43
+ visit o.right, collector
44
+ end
45
+
46
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
47
+ collector << "NOT "
48
+ visit_Arel_Nodes_IsNotDistinctFrom o, collector
49
+ end
50
+
51
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
52
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
53
+ # these, we must use a subquery.
54
+ def prepare_update_statement(o)
55
+ if o.offset || has_join_sources?(o) && has_limit_or_offset_or_orders?(o)
56
+ super
57
+ else
58
+ o
59
+ end
60
+ end
61
+ alias :prepare_delete_statement :prepare_update_statement
62
+
63
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
64
+ # to give it some prompting in the form of a subsubquery.
65
+ def build_subselect(key, o)
66
+ subselect = super
67
+
68
+ # Materialize subquery by adding distinct
69
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
70
+ unless has_limit_or_offset_or_orders?(subselect)
71
+ core = subselect.cores.last
72
+ core.set_quantifier = Arel::Nodes::Distinct.new
73
+ end
74
+
75
+ Nodes::SelectStatement.new.tap do |stmt|
76
+ core = stmt.cores.last
77
+ core.froms = Nodes::Grouping.new(subselect).as("__active_record_temp")
78
+ core.projections = [Arel.sql(quote_column_name(key.name))]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class Oracle < Arel::Visitors::ToSql
6
+ private
7
+ def visit_Arel_Nodes_SelectStatement(o, collector)
8
+ o = order_hacks(o)
9
+
10
+ # if need to select first records without ORDER BY and GROUP BY and without DISTINCT
11
+ # then can use simple ROWNUM in WHERE clause
12
+ if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && o.cores.first.set_quantifier.class.to_s !~ /Distinct/
13
+ o.cores.last.wheres.push Nodes::LessThanOrEqual.new(
14
+ Nodes::SqlLiteral.new("ROWNUM"), o.limit.expr
15
+ )
16
+ return super
17
+ end
18
+
19
+ if o.limit && o.offset
20
+ o = o.dup
21
+ limit = o.limit.expr
22
+ offset = o.offset
23
+ o.offset = nil
24
+ collector << "
25
+ SELECT * FROM (
26
+ SELECT raw_sql_.*, rownum raw_rnum_
27
+ FROM ("
28
+
29
+ collector = super(o, collector)
30
+
31
+ if offset.expr.is_a? Nodes::BindParam
32
+ collector << ") raw_sql_ WHERE rownum <= ("
33
+ collector = visit offset.expr, collector
34
+ collector << " + "
35
+ collector = visit limit, collector
36
+ collector << ") ) WHERE raw_rnum_ > "
37
+ collector = visit offset.expr, collector
38
+ return collector
39
+ else
40
+ collector << ") raw_sql_
41
+ WHERE rownum <= #{offset.expr.to_i + limit}
42
+ )
43
+ WHERE "
44
+ return visit(offset, collector)
45
+ end
46
+ end
47
+
48
+ if o.limit
49
+ o = o.dup
50
+ limit = o.limit.expr
51
+ collector << "SELECT * FROM ("
52
+ collector = super(o, collector)
53
+ collector << ") WHERE ROWNUM <= "
54
+ return visit limit, collector
55
+ end
56
+
57
+ if o.offset
58
+ o = o.dup
59
+ offset = o.offset
60
+ o.offset = nil
61
+ collector << "SELECT * FROM (
62
+ SELECT raw_sql_.*, rownum raw_rnum_
63
+ FROM ("
64
+ collector = super(o, collector)
65
+ collector << ") raw_sql_
66
+ )
67
+ WHERE "
68
+ return visit offset, collector
69
+ end
70
+
71
+ super
72
+ end
73
+
74
+ def visit_Arel_Nodes_Limit(o, collector)
75
+ collector
76
+ end
77
+
78
+ def visit_Arel_Nodes_Offset(o, collector)
79
+ collector << "raw_rnum_ > "
80
+ visit o.expr, collector
81
+ end
82
+
83
+ def visit_Arel_Nodes_Except(o, collector)
84
+ collector << "( "
85
+ collector = infix_value o, collector, " MINUS "
86
+ collector << " )"
87
+ end
88
+
89
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
90
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
91
+ if o.orders.any? && o.limit.nil?
92
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
93
+ # otherwise let the user deal with the error
94
+ o = o.dup
95
+ o.orders = []
96
+ end
97
+
98
+ super
99
+ end
100
+
101
+ ###
102
+ # Hacks for the order clauses specific to Oracle
103
+ def order_hacks(o)
104
+ return o if o.orders.empty?
105
+ return o unless o.cores.any? do |core|
106
+ core.projections.any? do |projection|
107
+ /FIRST_VALUE/ === projection
108
+ end
109
+ end
110
+ # Previous version with join and split broke ORDER BY clause
111
+ # if it contained functions with several arguments (separated by ',').
112
+ #
113
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
114
+ orders = o.orders.map do |x|
115
+ string = visit(x, Arel::Collectors::SQLString.new).value
116
+ if string.include?(",")
117
+ split_order_string(string)
118
+ else
119
+ string
120
+ end
121
+ end.flatten
122
+ o.orders = []
123
+ orders.each_with_index do |order, i|
124
+ o.orders <<
125
+ Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i.match?(order)}")
126
+ end
127
+ o
128
+ end
129
+
130
+ # Split string by commas but count opening and closing brackets
131
+ # and ignore commas inside brackets.
132
+ def split_order_string(string)
133
+ array = []
134
+ i = 0
135
+ string.split(",").each do |part|
136
+ if array[i]
137
+ array[i] << "," << part
138
+ else
139
+ # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
140
+ array[i] = part.to_s
141
+ end
142
+ i += 1 if array[i].count("(") == array[i].count(")")
143
+ end
144
+ array
145
+ end
146
+
147
+ def visit_Arel_Nodes_BindParam(o, collector)
148
+ collector.add_bind(o.value) { |i| ":a#{i}" }
149
+ end
150
+
151
+ def is_distinct_from(o, collector)
152
+ collector << "DECODE("
153
+ collector = visit [o.left, o.right, 0, 1], collector
154
+ collector << ")"
155
+ end
156
+ end
157
+ end
158
+ end