activerecord 5.2.4.2 → 6.0.2.2

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