activerecord 6.0.0.beta2 → 6.0.2.rc1

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +471 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +10 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +6 -2
  16. data/lib/active_record/associations/collection_proxy.rb +2 -2
  17. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  18. data/lib/active_record/associations/join_dependency.rb +14 -9
  19. data/lib/active_record/associations/join_dependency/join_association.rb +12 -3
  20. data/lib/active_record/associations/preloader.rb +13 -8
  21. data/lib/active_record/associations/preloader/association.rb +34 -30
  22. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  23. data/lib/active_record/attribute_methods.rb +3 -53
  24. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  25. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  26. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  27. data/lib/active_record/attribute_methods/query.rb +2 -3
  28. data/lib/active_record/attribute_methods/read.rb +3 -9
  29. data/lib/active_record/attribute_methods/write.rb +6 -12
  30. data/lib/active_record/attributes.rb +13 -0
  31. data/lib/active_record/autosave_association.rb +21 -7
  32. data/lib/active_record/base.rb +0 -1
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -11
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +88 -61
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -4
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  39. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  40. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +79 -22
  41. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
  42. data/lib/active_record/connection_adapters/abstract_adapter.rb +114 -34
  43. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +78 -73
  44. data/lib/active_record/connection_adapters/column.rb +17 -13
  45. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  46. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  49. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +10 -7
  51. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  53. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  54. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  55. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  56. data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
  57. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  58. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  59. data/lib/active_record/connection_adapters/postgresql_adapter.rb +68 -27
  60. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  61. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  62. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  63. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  64. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -114
  66. data/lib/active_record/connection_handling.rb +31 -13
  67. data/lib/active_record/core.rb +23 -24
  68. data/lib/active_record/database_configurations.rb +73 -44
  69. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  70. data/lib/active_record/database_configurations/url_config.rb +12 -12
  71. data/lib/active_record/dynamic_matchers.rb +1 -1
  72. data/lib/active_record/enum.rb +15 -0
  73. data/lib/active_record/errors.rb +1 -1
  74. data/lib/active_record/fixtures.rb +11 -6
  75. data/lib/active_record/gem_version.rb +2 -2
  76. data/lib/active_record/insert_all.rb +179 -0
  77. data/lib/active_record/integration.rb +13 -1
  78. data/lib/active_record/internal_metadata.rb +5 -1
  79. data/lib/active_record/locking/optimistic.rb +3 -4
  80. data/lib/active_record/log_subscriber.rb +1 -1
  81. data/lib/active_record/middleware/database_selector.rb +3 -3
  82. data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
  83. data/lib/active_record/migration.rb +62 -44
  84. data/lib/active_record/migration/command_recorder.rb +28 -14
  85. data/lib/active_record/migration/compatibility.rb +10 -0
  86. data/lib/active_record/model_schema.rb +3 -0
  87. data/lib/active_record/persistence.rb +206 -13
  88. data/lib/active_record/querying.rb +17 -12
  89. data/lib/active_record/railtie.rb +0 -1
  90. data/lib/active_record/railties/databases.rake +127 -25
  91. data/lib/active_record/reflection.rb +3 -3
  92. data/lib/active_record/relation.rb +99 -20
  93. data/lib/active_record/relation/calculations.rb +38 -40
  94. data/lib/active_record/relation/delegation.rb +22 -30
  95. data/lib/active_record/relation/finder_methods.rb +17 -12
  96. data/lib/active_record/relation/merger.rb +11 -16
  97. data/lib/active_record/relation/query_methods.rb +228 -76
  98. data/lib/active_record/relation/where_clause.rb +9 -5
  99. data/lib/active_record/sanitization.rb +33 -4
  100. data/lib/active_record/schema.rb +1 -1
  101. data/lib/active_record/schema_dumper.rb +10 -1
  102. data/lib/active_record/schema_migration.rb +1 -1
  103. data/lib/active_record/scoping/default.rb +6 -7
  104. data/lib/active_record/scoping/named.rb +3 -2
  105. data/lib/active_record/statement_cache.rb +2 -2
  106. data/lib/active_record/store.rb +48 -0
  107. data/lib/active_record/table_metadata.rb +9 -13
  108. data/lib/active_record/tasks/database_tasks.rb +109 -6
  109. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  110. data/lib/active_record/test_databases.rb +1 -16
  111. data/lib/active_record/test_fixtures.rb +1 -0
  112. data/lib/active_record/timestamp.rb +26 -16
  113. data/lib/active_record/touch_later.rb +4 -2
  114. data/lib/active_record/transactions.rb +56 -46
  115. data/lib/active_record/type_caster/connection.rb +16 -10
  116. data/lib/active_record/validations.rb +1 -0
  117. data/lib/active_record/validations/uniqueness.rb +3 -5
  118. data/lib/arel.rb +12 -5
  119. data/lib/arel/insert_manager.rb +3 -3
  120. data/lib/arel/nodes.rb +2 -1
  121. data/lib/arel/nodes/comment.rb +29 -0
  122. data/lib/arel/nodes/select_core.rb +16 -12
  123. data/lib/arel/nodes/unary.rb +1 -0
  124. data/lib/arel/nodes/values_list.rb +2 -17
  125. data/lib/arel/select_manager.rb +10 -10
  126. data/lib/arel/visitors/depth_first.rb +7 -2
  127. data/lib/arel/visitors/dot.rb +7 -2
  128. data/lib/arel/visitors/ibm_db.rb +13 -0
  129. data/lib/arel/visitors/informix.rb +6 -0
  130. data/lib/arel/visitors/mssql.rb +15 -1
  131. data/lib/arel/visitors/oracle12.rb +4 -5
  132. data/lib/arel/visitors/postgresql.rb +4 -10
  133. data/lib/arel/visitors/to_sql.rb +107 -131
  134. data/lib/arel/visitors/visitor.rb +9 -5
  135. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  136. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  137. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  138. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  139. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  140. metadata +16 -12
  141. data/lib/active_record/collection_cache_key.rb +0 -53
  142. data/lib/arel/nodes/values.rb +0 -16
@@ -33,13 +33,13 @@ module Arel # :nodoc: all
33
33
  @ast.columns << column
34
34
  values << value
35
35
  end
36
- @ast.values = create_values values, @ast.columns
36
+ @ast.values = create_values(values)
37
37
  end
38
38
  self
39
39
  end
40
40
 
41
- def create_values(values, columns)
42
- Nodes::Values.new values, columns
41
+ def create_values(values)
42
+ Nodes::ValuesList.new([values])
43
43
  end
44
44
 
45
45
  def create_values_list(rows)
@@ -45,7 +45,6 @@ require "arel/nodes/and"
45
45
  require "arel/nodes/function"
46
46
  require "arel/nodes/count"
47
47
  require "arel/nodes/extract"
48
- require "arel/nodes/values"
49
48
  require "arel/nodes/values_list"
50
49
  require "arel/nodes/named_function"
51
50
 
@@ -62,6 +61,8 @@ require "arel/nodes/outer_join"
62
61
  require "arel/nodes/right_outer_join"
63
62
  require "arel/nodes/string_join"
64
63
 
64
+ require "arel/nodes/comment"
65
+
65
66
  require "arel/nodes/sql_literal"
66
67
 
67
68
  require "arel/nodes/casted"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Nodes
5
+ class Comment < Arel::Nodes::Node
6
+ attr_reader :values
7
+
8
+ def initialize(values)
9
+ super()
10
+ @values = values
11
+ end
12
+
13
+ def initialize_copy(other)
14
+ super
15
+ @values = @values.clone
16
+ end
17
+
18
+ def hash
19
+ [@values].hash
20
+ end
21
+
22
+ def eql?(other)
23
+ self.class == other.class &&
24
+ self.values == other.values
25
+ end
26
+ alias :== :eql?
27
+ end
28
+ end
29
+ end
@@ -3,20 +3,22 @@
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
5
  class SelectCore < Arel::Nodes::Node
6
- attr_accessor :projections, :wheres, :groups, :windows
7
- attr_accessor :havings, :source, :set_quantifier
6
+ attr_accessor :projections, :wheres, :groups, :windows, :comment
7
+ attr_accessor :havings, :source, :set_quantifier, :optimizer_hints
8
8
 
9
9
  def initialize
10
10
  super()
11
- @source = JoinSource.new nil
11
+ @source = JoinSource.new nil
12
12
 
13
13
  # https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier
14
- @set_quantifier = nil
15
- @projections = []
16
- @wheres = []
17
- @groups = []
18
- @havings = []
19
- @windows = []
14
+ @set_quantifier = nil
15
+ @optimizer_hints = nil
16
+ @projections = []
17
+ @wheres = []
18
+ @groups = []
19
+ @havings = []
20
+ @windows = []
21
+ @comment = nil
20
22
  end
21
23
 
22
24
  def from
@@ -42,8 +44,8 @@ module Arel # :nodoc: all
42
44
 
43
45
  def hash
44
46
  [
45
- @source, @set_quantifier, @projections,
46
- @wheres, @groups, @havings, @windows
47
+ @source, @set_quantifier, @projections, @optimizer_hints,
48
+ @wheres, @groups, @havings, @windows, @comment
47
49
  ].hash
48
50
  end
49
51
 
@@ -51,11 +53,13 @@ module Arel # :nodoc: all
51
53
  self.class == other.class &&
52
54
  self.source == other.source &&
53
55
  self.set_quantifier == other.set_quantifier &&
56
+ self.optimizer_hints == other.optimizer_hints &&
54
57
  self.projections == other.projections &&
55
58
  self.wheres == other.wheres &&
56
59
  self.groups == other.groups &&
57
60
  self.havings == other.havings &&
58
- self.windows == other.windows
61
+ self.windows == other.windows &&
62
+ self.comment == other.comment
59
63
  end
60
64
  alias :== :eql?
61
65
  end
@@ -35,6 +35,7 @@ module Arel # :nodoc: all
35
35
  Not
36
36
  Offset
37
37
  On
38
+ OptimizerHints
38
39
  Ordering
39
40
  RollUp
40
41
  }.each do |name|
@@ -2,23 +2,8 @@
2
2
 
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
- class ValuesList < Node
6
- attr_reader :rows
7
-
8
- def initialize(rows)
9
- @rows = rows
10
- super()
11
- end
12
-
13
- def hash
14
- @rows.hash
15
- end
16
-
17
- def eql?(other)
18
- self.class == other.class &&
19
- self.rows == other.rows
20
- end
21
- alias :== :eql?
5
+ class ValuesList < Unary
6
+ alias :rows :expr
22
7
  end
23
8
  end
24
9
  end
@@ -146,6 +146,13 @@ module Arel # :nodoc: all
146
146
  @ctx.projections = projections
147
147
  end
148
148
 
149
+ def optimizer_hints(*hints)
150
+ unless hints.empty?
151
+ @ctx.optimizer_hints = Arel::Nodes::OptimizerHints.new(hints)
152
+ end
153
+ self
154
+ end
155
+
149
156
  def distinct(value = true)
150
157
  if value
151
158
  @ctx.set_quantifier = Arel::Nodes::Distinct.new
@@ -237,16 +244,9 @@ module Arel # :nodoc: all
237
244
  @ctx.source
238
245
  end
239
246
 
240
- class Row < Struct.new(:data) # :nodoc:
241
- def id
242
- data["id"]
243
- end
244
-
245
- def method_missing(name, *args)
246
- name = name.to_s
247
- return data[name] if data.key?(name)
248
- super
249
- end
247
+ def comment(*values)
248
+ @ctx.comment = Nodes::Comment.new(values)
249
+ self
250
250
  end
251
251
 
252
252
  private
@@ -10,7 +10,7 @@ module Arel # :nodoc: all
10
10
 
11
11
  private
12
12
 
13
- def visit(o)
13
+ def visit(o, _ = nil)
14
14
  super
15
15
  @block.call o
16
16
  end
@@ -35,6 +35,8 @@ module Arel # :nodoc: all
35
35
  alias :visit_Arel_Nodes_Ascending :unary
36
36
  alias :visit_Arel_Nodes_Descending :unary
37
37
  alias :visit_Arel_Nodes_UnqualifiedColumn :unary
38
+ alias :visit_Arel_Nodes_OptimizerHints :unary
39
+ alias :visit_Arel_Nodes_ValuesList :unary
38
40
 
39
41
  def function(o)
40
42
  visit o.expressions
@@ -102,7 +104,6 @@ module Arel # :nodoc: all
102
104
  alias :visit_Arel_Nodes_Regexp :binary
103
105
  alias :visit_Arel_Nodes_RightOuterJoin :binary
104
106
  alias :visit_Arel_Nodes_TableAlias :binary
105
- alias :visit_Arel_Nodes_Values :binary
106
107
  alias :visit_Arel_Nodes_When :binary
107
108
 
108
109
  def visit_Arel_Nodes_StringJoin(o)
@@ -180,6 +181,10 @@ module Arel # :nodoc: all
180
181
  visit o.limit
181
182
  end
182
183
 
184
+ def visit_Arel_Nodes_Comment(o)
185
+ visit o.values
186
+ end
187
+
183
188
  def visit_Array(o)
184
189
  o.each { |i| visit i }
185
190
  end
@@ -46,8 +46,8 @@ module Arel # :nodoc: all
46
46
  visit_edge o, "distinct"
47
47
  end
48
48
 
49
- def visit_Arel_Nodes_Values(o)
50
- visit_edge o, "expressions"
49
+ def visit_Arel_Nodes_ValuesList(o)
50
+ visit_edge o, "rows"
51
51
  end
52
52
 
53
53
  def visit_Arel_Nodes_StringJoin(o)
@@ -82,6 +82,7 @@ module Arel # :nodoc: all
82
82
  alias :visit_Arel_Nodes_Offset :unary
83
83
  alias :visit_Arel_Nodes_On :unary
84
84
  alias :visit_Arel_Nodes_UnqualifiedColumn :unary
85
+ alias :visit_Arel_Nodes_OptimizerHints :unary
85
86
  alias :visit_Arel_Nodes_Preceding :unary
86
87
  alias :visit_Arel_Nodes_Following :unary
87
88
  alias :visit_Arel_Nodes_Rows :unary
@@ -233,6 +234,10 @@ module Arel # :nodoc: all
233
234
  end
234
235
  alias :visit_Set :visit_Array
235
236
 
237
+ def visit_Arel_Nodes_Comment(o)
238
+ visit_edge(o, "values")
239
+ end
240
+
236
241
  def visit_edge(o, method)
237
242
  edge(method) { visit o.send(method) }
238
243
  end
@@ -4,6 +4,15 @@ module Arel # :nodoc: all
4
4
  module Visitors
5
5
  class IBM_DB < Arel::Visitors::ToSql
6
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
7
16
 
8
17
  def visit_Arel_Nodes_Limit(o, collector)
9
18
  collector << "FETCH FIRST "
@@ -16,6 +25,10 @@ module Arel # :nodoc: all
16
25
  collector = visit [o.left, o.right, 0, 1], collector
17
26
  collector << ")"
18
27
  end
28
+
29
+ def collect_optimizer_hints(o, collector)
30
+ collector
31
+ end
19
32
  end
20
33
  end
21
34
  end
@@ -42,10 +42,16 @@ module Arel # :nodoc: all
42
42
  collector
43
43
  end
44
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
+
45
50
  def visit_Arel_Nodes_Offset(o, collector)
46
51
  collector << "SKIP "
47
52
  visit o.expr, collector
48
53
  end
54
+
49
55
  def visit_Arel_Nodes_Limit(o, collector)
50
56
  collector << "FIRST "
51
57
  visit o.expr, collector
@@ -76,6 +76,16 @@ module Arel # :nodoc: all
76
76
  end
77
77
  end
78
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
+
79
89
  def get_offset_limit_clause(o)
80
90
  first_row = o.offset ? o.offset.expr.to_i + 1 : 1
81
91
  last_row = o.limit ? o.limit.expr.to_i - 1 + first_row : nil
@@ -97,12 +107,16 @@ module Arel # :nodoc: all
97
107
  collector = visit o.relation, collector
98
108
  if o.wheres.any?
99
109
  collector << " WHERE "
100
- inject_join o.wheres, collector, AND
110
+ inject_join o.wheres, collector, " AND "
101
111
  else
102
112
  collector
103
113
  end
104
114
  end
105
115
 
116
+ def collect_optimizer_hints(o, collector)
117
+ collector
118
+ end
119
+
106
120
  def determine_order_by(orders, x)
107
121
  if orders.any?
108
122
  orders
@@ -8,11 +8,10 @@ module Arel # :nodoc: all
8
8
  def visit_Arel_Nodes_SelectStatement(o, collector)
9
9
  # Oracle does not allow LIMIT clause with select for update
10
10
  if o.limit && o.lock
11
- raise ArgumentError, <<-MSG
12
- 'Combination of limit and lock is not supported.
13
- because generated SQL statements
14
- `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.`
15
- MSG
11
+ raise ArgumentError, <<~MSG
12
+ Combination of limit and lock is not supported. Because generated SQL statements
13
+ `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.
14
+ MSG
16
15
  end
17
16
  super
18
17
  end
@@ -3,11 +3,6 @@
3
3
  module Arel # :nodoc: all
4
4
  module Visitors
5
5
  class PostgreSQL < Arel::Visitors::ToSql
6
- CUBE = "CUBE"
7
- ROLLUP = "ROLLUP"
8
- GROUPING_SETS = "GROUPING SETS"
9
- LATERAL = "LATERAL"
10
-
11
6
  private
12
7
 
13
8
  def visit_Arel_Nodes_Matches(o, collector)
@@ -57,23 +52,22 @@ module Arel # :nodoc: all
57
52
  end
58
53
 
59
54
  def visit_Arel_Nodes_Cube(o, collector)
60
- collector << CUBE
55
+ collector << "CUBE"
61
56
  grouping_array_or_grouping_element o, collector
62
57
  end
63
58
 
64
59
  def visit_Arel_Nodes_RollUp(o, collector)
65
- collector << ROLLUP
60
+ collector << "ROLLUP"
66
61
  grouping_array_or_grouping_element o, collector
67
62
  end
68
63
 
69
64
  def visit_Arel_Nodes_GroupingSet(o, collector)
70
- collector << GROUPING_SETS
65
+ collector << "GROUPING SETS"
71
66
  grouping_array_or_grouping_element o, collector
72
67
  end
73
68
 
74
69
  def visit_Arel_Nodes_Lateral(o, collector)
75
- collector << LATERAL
76
- collector << SPACE
70
+ collector << "LATERAL "
77
71
  grouping_parentheses o, collector
78
72
  end
79
73
 
@@ -9,59 +9,6 @@ module Arel # :nodoc: all
9
9
  end
10
10
 
11
11
  class ToSql < Arel::Visitors::Visitor
12
- ##
13
- # This is some roflscale crazy stuff. I'm roflscaling this because
14
- # building SQL queries is a hotspot. I will explain the roflscale so that
15
- # others will not rm this code.
16
- #
17
- # In YARV, string literals in a method body will get duped when the byte
18
- # code is executed. Let's take a look:
19
- #
20
- # > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm
21
- #
22
- # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>=====
23
- # 0000 trace 8
24
- # 0002 trace 1
25
- # 0004 putstring "bar"
26
- # 0006 trace 16
27
- # 0008 leave
28
- #
29
- # The `putstring` bytecode will dup the string and push it on the stack.
30
- # In many cases in our SQL visitor, that string is never mutated, so there
31
- # is no need to dup the literal.
32
- #
33
- # If we change to a constant lookup, the string will not be duped, and we
34
- # can reduce the objects in our system:
35
- #
36
- # > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm
37
- #
38
- # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>========
39
- # 0000 trace 8
40
- # 0002 trace 1
41
- # 0004 getinlinecache 11, <ic:0>
42
- # 0007 getconstant :BAR
43
- # 0009 setinlinecache <ic:0>
44
- # 0011 trace 16
45
- # 0013 leave
46
- #
47
- # `getconstant` should be a hash lookup, and no object is duped when the
48
- # value of the constant is pushed on the stack. Hence the crazy
49
- # constants below.
50
- #
51
- # `matches` and `doesNotMatch` operate case-insensitively via Visitor subclasses
52
- # specialized for specific databases when necessary.
53
- #
54
-
55
- WHERE = " WHERE " # :nodoc:
56
- SPACE = " " # :nodoc:
57
- COMMA = ", " # :nodoc:
58
- GROUP_BY = " GROUP BY " # :nodoc:
59
- ORDER_BY = " ORDER BY " # :nodoc:
60
- WINDOW = " WINDOW " # :nodoc:
61
- AND = " AND " # :nodoc:
62
-
63
- DISTINCT = "DISTINCT" # :nodoc:
64
-
65
12
  def initialize(connection)
66
13
  super()
67
14
  @connection = connection
@@ -105,10 +52,14 @@ module Arel # :nodoc: all
105
52
  def visit_Arel_Nodes_InsertStatement(o, collector)
106
53
  collector << "INSERT INTO "
107
54
  collector = visit o.relation, collector
108
- if o.columns.any?
109
- collector << " (#{o.columns.map { |x|
110
- quote_column_name x.name
111
- }.join ', '})"
55
+
56
+ unless o.columns.empty?
57
+ collector << " ("
58
+ o.columns.each_with_index do |x, i|
59
+ collector << ", " unless i == 0
60
+ collector << quote_column_name(x.name)
61
+ end
62
+ collector << ")"
112
63
  end
113
64
 
114
65
  if o.values
@@ -150,48 +101,27 @@ module Arel # :nodoc: all
150
101
  def visit_Arel_Nodes_ValuesList(o, collector)
151
102
  collector << "VALUES "
152
103
 
153
- len = o.rows.length - 1
154
- o.rows.each_with_index { |row, i|
104
+ o.rows.each_with_index do |row, i|
105
+ collector << ", " unless i == 0
155
106
  collector << "("
156
- row_len = row.length - 1
157
107
  row.each_with_index do |value, k|
108
+ collector << ", " unless k == 0
158
109
  case value
159
110
  when Nodes::SqlLiteral, Nodes::BindParam
160
111
  collector = visit(value, collector)
161
112
  else
162
- collector << quote(value)
113
+ collector << quote(value).to_s
163
114
  end
164
- collector << COMMA unless k == row_len
165
115
  end
166
116
  collector << ")"
167
- collector << COMMA unless i == len
168
- }
117
+ end
169
118
  collector
170
119
  end
171
120
 
172
- def visit_Arel_Nodes_Values(o, collector)
173
- collector << "VALUES ("
174
-
175
- len = o.expressions.length - 1
176
- o.expressions.each_with_index { |value, i|
177
- case value
178
- when Nodes::SqlLiteral, Nodes::BindParam
179
- collector = visit value, collector
180
- else
181
- collector << quote(value).to_s
182
- end
183
- unless i == len
184
- collector << COMMA
185
- end
186
- }
187
-
188
- collector << ")"
189
- end
190
-
191
121
  def visit_Arel_Nodes_SelectStatement(o, collector)
192
122
  if o.with
193
123
  collector = visit o.with, collector
194
- collector << SPACE
124
+ collector << " "
195
125
  end
196
126
 
197
127
  collector = o.cores.inject(collector) { |c, x|
@@ -199,12 +129,11 @@ module Arel # :nodoc: all
199
129
  }
200
130
 
201
131
  unless o.orders.empty?
202
- collector << ORDER_BY
203
- len = o.orders.length - 1
204
- o.orders.each_with_index { |x, i|
132
+ collector << " ORDER BY "
133
+ o.orders.each_with_index do |x, i|
134
+ collector << ", " unless i == 0
205
135
  collector = visit(x, collector)
206
- collector << COMMA unless len == i
207
- }
136
+ end
208
137
  end
209
138
 
210
139
  visit_Arel_Nodes_SelectOptions(o, collector)
@@ -219,24 +148,34 @@ module Arel # :nodoc: all
219
148
  def visit_Arel_Nodes_SelectCore(o, collector)
220
149
  collector << "SELECT"
221
150
 
151
+ collector = collect_optimizer_hints(o, collector)
222
152
  collector = maybe_visit o.set_quantifier, collector
223
153
 
224
- collect_nodes_for o.projections, collector, SPACE
154
+ collect_nodes_for o.projections, collector, " "
225
155
 
226
156
  if o.source && !o.source.empty?
227
157
  collector << " FROM "
228
158
  collector = visit o.source, collector
229
159
  end
230
160
 
231
- collect_nodes_for o.wheres, collector, WHERE, AND
232
- collect_nodes_for o.groups, collector, GROUP_BY
233
- collect_nodes_for o.havings, collector, " HAVING ", AND
234
- collect_nodes_for o.windows, collector, WINDOW
161
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
162
+ collect_nodes_for o.groups, collector, " GROUP BY "
163
+ collect_nodes_for o.havings, collector, " HAVING ", " AND "
164
+ collect_nodes_for o.windows, collector, " WINDOW "
235
165
 
236
- collector
166
+ maybe_visit o.comment, collector
167
+ end
168
+
169
+ def visit_Arel_Nodes_OptimizerHints(o, collector)
170
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(" ")
171
+ collector << "/*+ #{hints} */"
172
+ end
173
+
174
+ def visit_Arel_Nodes_Comment(o, collector)
175
+ collector << o.values.map { |v| "/* #{sanitize_as_sql_comment(v)} */" }.join(" ")
237
176
  end
238
177
 
239
- def collect_nodes_for(nodes, collector, spacer, connector = COMMA)
178
+ def collect_nodes_for(nodes, collector, spacer, connector = ", ")
240
179
  unless nodes.empty?
241
180
  collector << spacer
242
181
  inject_join nodes, collector, connector
@@ -248,7 +187,7 @@ module Arel # :nodoc: all
248
187
  end
249
188
 
250
189
  def visit_Arel_Nodes_Distinct(o, collector)
251
- collector << DISTINCT
190
+ collector << "DISTINCT"
252
191
  end
253
192
 
254
193
  def visit_Arel_Nodes_DistinctOn(o, collector)
@@ -257,12 +196,12 @@ module Arel # :nodoc: all
257
196
 
258
197
  def visit_Arel_Nodes_With(o, collector)
259
198
  collector << "WITH "
260
- inject_join o.children, collector, COMMA
199
+ inject_join o.children, collector, ", "
261
200
  end
262
201
 
263
202
  def visit_Arel_Nodes_WithRecursive(o, collector)
264
203
  collector << "WITH RECURSIVE "
265
- inject_join o.children, collector, COMMA
204
+ inject_join o.children, collector, ", "
266
205
  end
267
206
 
268
207
  def visit_Arel_Nodes_Union(o, collector)
@@ -295,13 +234,13 @@ module Arel # :nodoc: all
295
234
  collect_nodes_for o.partitions, collector, "PARTITION BY "
296
235
 
297
236
  if o.orders.any?
298
- collector << SPACE if o.partitions.any?
237
+ collector << " " if o.partitions.any?
299
238
  collector << "ORDER BY "
300
239
  collector = inject_join o.orders, collector, ", "
301
240
  end
302
241
 
303
242
  if o.framing
304
- collector << SPACE if o.partitions.any? || o.orders.any?
243
+ collector << " " if o.partitions.any? || o.orders.any?
305
244
  collector = visit o.framing, collector
306
245
  end
307
246
 
@@ -506,8 +445,8 @@ module Arel # :nodoc: all
506
445
  collector = visit o.left, collector
507
446
  end
508
447
  if o.right.any?
509
- collector << SPACE if o.left
510
- collector = inject_join o.right, collector, SPACE
448
+ collector << " " if o.left
449
+ collector = inject_join o.right, collector, " "
511
450
  end
512
451
  collector
513
452
  end
@@ -527,7 +466,7 @@ module Arel # :nodoc: all
527
466
  def visit_Arel_Nodes_FullOuterJoin(o, collector)
528
467
  collector << "FULL OUTER JOIN "
529
468
  collector = visit o.left, collector
530
- collector << SPACE
469
+ collector << " "
531
470
  visit o.right, collector
532
471
  end
533
472
 
@@ -541,7 +480,7 @@ module Arel # :nodoc: all
541
480
  def visit_Arel_Nodes_RightOuterJoin(o, collector)
542
481
  collector << "RIGHT OUTER JOIN "
543
482
  collector = visit o.left, collector
544
- collector << SPACE
483
+ collector << " "
545
484
  visit o.right, collector
546
485
  end
547
486
 
@@ -549,7 +488,7 @@ module Arel # :nodoc: all
549
488
  collector << "INNER JOIN "
550
489
  collector = visit o.left, collector
551
490
  if o.right
552
- collector << SPACE
491
+ collector << " "
553
492
  visit(o.right, collector)
554
493
  else
555
494
  collector
@@ -568,41 +507,73 @@ module Arel # :nodoc: all
568
507
 
569
508
  def visit_Arel_Table(o, collector)
570
509
  if o.table_alias
571
- collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
510
+ collector << quote_table_name(o.name) << " " << quote_table_name(o.table_alias)
572
511
  else
573
512
  collector << quote_table_name(o.name)
574
513
  end
575
514
  end
576
515
 
577
516
  def visit_Arel_Nodes_In(o, collector)
578
- if Array === o.right && !o.right.empty?
517
+ unless Array === o.right
518
+ return collect_in_clause(o.left, o.right, collector)
519
+ end
520
+
521
+ unless o.right.empty?
579
522
  o.right.delete_if { |value| unboundable?(value) }
580
523
  end
581
524
 
582
- if Array === o.right && o.right.empty?
583
- collector << "1=0"
525
+ return collector << "1=0" if o.right.empty?
526
+
527
+ in_clause_length = @connection.in_clause_length
528
+
529
+ if !in_clause_length || o.right.length <= in_clause_length
530
+ collect_in_clause(o.left, o.right, collector)
584
531
  else
585
- collector = visit o.left, collector
586
- collector << " IN ("
587
- visit(o.right, collector) << ")"
532
+ collector << "("
533
+ o.right.each_slice(in_clause_length).each_with_index do |right, i|
534
+ collector << " OR " unless i == 0
535
+ collect_in_clause(o.left, right, collector)
536
+ end
537
+ collector << ")"
588
538
  end
589
539
  end
590
540
 
541
+ def collect_in_clause(left, right, collector)
542
+ collector = visit left, collector
543
+ collector << " IN ("
544
+ visit(right, collector) << ")"
545
+ end
546
+
591
547
  def visit_Arel_Nodes_NotIn(o, collector)
592
- if Array === o.right && !o.right.empty?
548
+ unless Array === o.right
549
+ return collect_not_in_clause(o.left, o.right, collector)
550
+ end
551
+
552
+ unless o.right.empty?
593
553
  o.right.delete_if { |value| unboundable?(value) }
594
554
  end
595
555
 
596
- if Array === o.right && o.right.empty?
597
- collector << "1=1"
556
+ return collector << "1=1" if o.right.empty?
557
+
558
+ in_clause_length = @connection.in_clause_length
559
+
560
+ if !in_clause_length || o.right.length <= in_clause_length
561
+ collect_not_in_clause(o.left, o.right, collector)
598
562
  else
599
- collector = visit o.left, collector
600
- collector << " NOT IN ("
601
- collector = visit o.right, collector
602
- collector << ")"
563
+ o.right.each_slice(in_clause_length).each_with_index do |right, i|
564
+ collector << " AND " unless i == 0
565
+ collect_not_in_clause(o.left, right, collector)
566
+ end
567
+ collector
603
568
  end
604
569
  end
605
570
 
571
+ def collect_not_in_clause(left, right, collector)
572
+ collector = visit left, collector
573
+ collector << " NOT IN ("
574
+ visit(right, collector) << ")"
575
+ end
576
+
606
577
  def visit_Arel_Nodes_And(o, collector)
607
578
  inject_join o.children, collector, " AND "
608
579
  end
@@ -712,13 +683,12 @@ module Arel # :nodoc: all
712
683
  end
713
684
 
714
685
  def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
715
- collector << "#{quote_column_name o.name}"
716
- collector
686
+ collector << quote_column_name(o.name)
717
687
  end
718
688
 
719
689
  def visit_Arel_Attributes_Attribute(o, collector)
720
690
  join_name = o.relation.table_alias || o.relation.name
721
- collector << "#{quote_table_name join_name}.#{quote_column_name o.name}"
691
+ collector << quote_table_name(join_name) << "." << quote_column_name(o.name)
722
692
  end
723
693
  alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
724
694
  alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
@@ -799,6 +769,15 @@ module Arel # :nodoc: all
799
769
  @connection.quote_column_name(name)
800
770
  end
801
771
 
772
+ def sanitize_as_sql_comment(value)
773
+ return value if Arel::Nodes::SqlLiteral === value
774
+ @connection.sanitize_as_sql_comment(value)
775
+ end
776
+
777
+ def collect_optimizer_hints(o, collector)
778
+ maybe_visit o.optimizer_hints, collector
779
+ end
780
+
802
781
  def maybe_visit(thing, collector)
803
782
  return collector unless thing
804
783
  collector << " "
@@ -806,14 +785,11 @@ module Arel # :nodoc: all
806
785
  end
807
786
 
808
787
  def inject_join(list, collector, join_str)
809
- len = list.length - 1
810
- list.each_with_index.inject(collector) { |c, (x, i)|
811
- if i == len
812
- visit x, c
813
- else
814
- visit(x, c) << join_str
815
- end
816
- }
788
+ list.each_with_index do |x, i|
789
+ collector << join_str unless i == 0
790
+ collector = visit(x, collector)
791
+ end
792
+ collector
817
793
  end
818
794
 
819
795
  def unboundable?(value)