activerecord 6.0.0.beta3 → 6.0.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +286 -6
- data/README.rdoc +3 -1
- data/lib/active_record.rb +0 -1
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +3 -13
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_proxy.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations/preloader/association.rb +32 -30
- data/lib/active_record/associations/preloader/through_association.rb +48 -28
- data/lib/active_record/attribute_methods.rb +4 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +42 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +13 -3
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +84 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +70 -14
- data/lib/active_record/connection_adapters/abstract_adapter.rb +56 -11
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -69
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -27
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +50 -112
- data/lib/active_record/connection_handling.rb +17 -10
- data/lib/active_record/core.rb +15 -20
- data/lib/active_record/database_configurations.rb +14 -14
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +12 -12
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +6 -0
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/insert_all.rb +180 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +10 -0
- data/lib/active_record/persistence.rb +206 -13
- data/lib/active_record/querying.rb +17 -12
- data/lib/active_record/railties/databases.rake +68 -6
- data/lib/active_record/reflection.rb +2 -2
- data/lib/active_record/relation.rb +98 -20
- data/lib/active_record/relation/calculations.rb +39 -39
- data/lib/active_record/relation/delegation.rb +22 -30
- data/lib/active_record/relation/finder_methods.rb +3 -9
- data/lib/active_record/relation/merger.rb +7 -16
- data/lib/active_record/relation/query_methods.rb +153 -38
- data/lib/active_record/relation/where_clause.rb +9 -5
- data/lib/active_record/sanitization.rb +3 -2
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +6 -7
- data/lib/active_record/scoping/named.rb +1 -1
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +3 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -1
- data/lib/active_record/touch_later.rb +2 -2
- data/lib/active_record/transactions.rb +52 -41
- data/lib/active_record/validations/uniqueness.rb +3 -5
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +6 -1
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +87 -108
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +12 -11
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -25,7 +25,7 @@ module ActiveRecord
|
|
25
25
|
if finder_class.primary_key
|
26
26
|
relation = relation.where.not(finder_class.primary_key => record.id_in_database)
|
27
27
|
else
|
28
|
-
raise UnknownPrimaryKey.new(finder_class, "
|
28
|
+
raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.")
|
29
29
|
end
|
30
30
|
end
|
31
31
|
relation = scope_relation(record, relation)
|
@@ -60,10 +60,8 @@ module ActiveRecord
|
|
60
60
|
comparison = relation.bind_attribute(attribute, value) do |attr, bind|
|
61
61
|
return relation.none! if bind.unboundable?
|
62
62
|
|
63
|
-
if bind.nil?
|
64
|
-
|
65
|
-
elsif !options.key?(:case_sensitive)
|
66
|
-
klass.connection.default_uniqueness_comparison(attr, bind)
|
63
|
+
if !options.key?(:case_sensitive) || bind.nil?
|
64
|
+
klass.connection.default_uniqueness_comparison(attr, bind, klass)
|
67
65
|
elsif options[:case_sensitive]
|
68
66
|
klass.connection.case_sensitive_comparison(attr, bind)
|
69
67
|
else
|
data/lib/arel/insert_manager.rb
CHANGED
@@ -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
|
36
|
+
@ast.values = create_values(values)
|
37
37
|
end
|
38
38
|
self
|
39
39
|
end
|
40
40
|
|
41
|
-
def create_values(values
|
42
|
-
Nodes::
|
41
|
+
def create_values(values)
|
42
|
+
Nodes::ValuesList.new([values])
|
43
43
|
end
|
44
44
|
|
45
45
|
def create_values_list(rows)
|
data/lib/arel/nodes.rb
CHANGED
@@ -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
|
11
|
+
@source = JoinSource.new nil
|
12
12
|
|
13
13
|
# https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier
|
14
|
-
@set_quantifier
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
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
|
data/lib/arel/nodes/unary.rb
CHANGED
@@ -2,23 +2,8 @@
|
|
2
2
|
|
3
3
|
module Arel # :nodoc: all
|
4
4
|
module Nodes
|
5
|
-
class ValuesList <
|
6
|
-
|
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
|
data/lib/arel/select_manager.rb
CHANGED
@@ -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
|
-
|
241
|
-
|
242
|
-
|
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
|
@@ -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
|
data/lib/arel/visitors/dot.rb
CHANGED
@@ -46,8 +46,8 @@ module Arel # :nodoc: all
|
|
46
46
|
visit_edge o, "distinct"
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
50
|
-
visit_edge o, "
|
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
|
data/lib/arel/visitors/ibm_db.rb
CHANGED
@@ -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
|
data/lib/arel/visitors/mssql.rb
CHANGED
@@ -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,
|
12
|
-
|
13
|
-
|
14
|
-
|
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 <<
|
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
|
|
data/lib/arel/visitors/to_sql.rb
CHANGED
@@ -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
|
@@ -159,39 +106,20 @@ module Arel # :nodoc: all
|
|
159
106
|
when Nodes::SqlLiteral, Nodes::BindParam
|
160
107
|
collector = visit(value, collector)
|
161
108
|
else
|
162
|
-
collector << quote(value)
|
109
|
+
collector << quote(value).to_s
|
163
110
|
end
|
164
|
-
collector <<
|
111
|
+
collector << ", " unless k == row_len
|
165
112
|
end
|
166
113
|
collector << ")"
|
167
|
-
collector <<
|
114
|
+
collector << ", " unless i == len
|
168
115
|
}
|
169
116
|
collector
|
170
117
|
end
|
171
118
|
|
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
119
|
def visit_Arel_Nodes_SelectStatement(o, collector)
|
192
120
|
if o.with
|
193
121
|
collector = visit o.with, collector
|
194
|
-
collector <<
|
122
|
+
collector << " "
|
195
123
|
end
|
196
124
|
|
197
125
|
collector = o.cores.inject(collector) { |c, x|
|
@@ -199,11 +127,11 @@ module Arel # :nodoc: all
|
|
199
127
|
}
|
200
128
|
|
201
129
|
unless o.orders.empty?
|
202
|
-
collector <<
|
130
|
+
collector << " ORDER BY "
|
203
131
|
len = o.orders.length - 1
|
204
132
|
o.orders.each_with_index { |x, i|
|
205
133
|
collector = visit(x, collector)
|
206
|
-
collector <<
|
134
|
+
collector << ", " unless len == i
|
207
135
|
}
|
208
136
|
end
|
209
137
|
|
@@ -219,24 +147,34 @@ module Arel # :nodoc: all
|
|
219
147
|
def visit_Arel_Nodes_SelectCore(o, collector)
|
220
148
|
collector << "SELECT"
|
221
149
|
|
150
|
+
collector = collect_optimizer_hints(o, collector)
|
222
151
|
collector = maybe_visit o.set_quantifier, collector
|
223
152
|
|
224
|
-
collect_nodes_for o.projections, collector,
|
153
|
+
collect_nodes_for o.projections, collector, " "
|
225
154
|
|
226
155
|
if o.source && !o.source.empty?
|
227
156
|
collector << " FROM "
|
228
157
|
collector = visit o.source, collector
|
229
158
|
end
|
230
159
|
|
231
|
-
collect_nodes_for o.wheres, collector, WHERE, AND
|
232
|
-
collect_nodes_for o.groups, collector,
|
233
|
-
collect_nodes_for o.havings, collector, " HAVING ", AND
|
234
|
-
collect_nodes_for o.windows, collector, WINDOW
|
160
|
+
collect_nodes_for o.wheres, collector, " WHERE ", " AND "
|
161
|
+
collect_nodes_for o.groups, collector, " GROUP BY "
|
162
|
+
collect_nodes_for o.havings, collector, " HAVING ", " AND "
|
163
|
+
collect_nodes_for o.windows, collector, " WINDOW "
|
235
164
|
|
236
|
-
collector
|
165
|
+
maybe_visit o.comment, collector
|
166
|
+
end
|
167
|
+
|
168
|
+
def visit_Arel_Nodes_OptimizerHints(o, collector)
|
169
|
+
hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(" ")
|
170
|
+
collector << "/*+ #{hints} */"
|
237
171
|
end
|
238
172
|
|
239
|
-
def
|
173
|
+
def visit_Arel_Nodes_Comment(o, collector)
|
174
|
+
collector << o.values.map { |v| "/* #{sanitize_as_sql_comment(v)} */" }.join(" ")
|
175
|
+
end
|
176
|
+
|
177
|
+
def collect_nodes_for(nodes, collector, spacer, connector = ", ")
|
240
178
|
unless nodes.empty?
|
241
179
|
collector << spacer
|
242
180
|
inject_join nodes, collector, connector
|
@@ -248,7 +186,7 @@ module Arel # :nodoc: all
|
|
248
186
|
end
|
249
187
|
|
250
188
|
def visit_Arel_Nodes_Distinct(o, collector)
|
251
|
-
collector << DISTINCT
|
189
|
+
collector << "DISTINCT"
|
252
190
|
end
|
253
191
|
|
254
192
|
def visit_Arel_Nodes_DistinctOn(o, collector)
|
@@ -257,12 +195,12 @@ module Arel # :nodoc: all
|
|
257
195
|
|
258
196
|
def visit_Arel_Nodes_With(o, collector)
|
259
197
|
collector << "WITH "
|
260
|
-
inject_join o.children, collector,
|
198
|
+
inject_join o.children, collector, ", "
|
261
199
|
end
|
262
200
|
|
263
201
|
def visit_Arel_Nodes_WithRecursive(o, collector)
|
264
202
|
collector << "WITH RECURSIVE "
|
265
|
-
inject_join o.children, collector,
|
203
|
+
inject_join o.children, collector, ", "
|
266
204
|
end
|
267
205
|
|
268
206
|
def visit_Arel_Nodes_Union(o, collector)
|
@@ -295,13 +233,13 @@ module Arel # :nodoc: all
|
|
295
233
|
collect_nodes_for o.partitions, collector, "PARTITION BY "
|
296
234
|
|
297
235
|
if o.orders.any?
|
298
|
-
collector <<
|
236
|
+
collector << " " if o.partitions.any?
|
299
237
|
collector << "ORDER BY "
|
300
238
|
collector = inject_join o.orders, collector, ", "
|
301
239
|
end
|
302
240
|
|
303
241
|
if o.framing
|
304
|
-
collector <<
|
242
|
+
collector << " " if o.partitions.any? || o.orders.any?
|
305
243
|
collector = visit o.framing, collector
|
306
244
|
end
|
307
245
|
|
@@ -506,8 +444,8 @@ module Arel # :nodoc: all
|
|
506
444
|
collector = visit o.left, collector
|
507
445
|
end
|
508
446
|
if o.right.any?
|
509
|
-
collector <<
|
510
|
-
collector = inject_join o.right, collector,
|
447
|
+
collector << " " if o.left
|
448
|
+
collector = inject_join o.right, collector, " "
|
511
449
|
end
|
512
450
|
collector
|
513
451
|
end
|
@@ -527,7 +465,7 @@ module Arel # :nodoc: all
|
|
527
465
|
def visit_Arel_Nodes_FullOuterJoin(o, collector)
|
528
466
|
collector << "FULL OUTER JOIN "
|
529
467
|
collector = visit o.left, collector
|
530
|
-
collector <<
|
468
|
+
collector << " "
|
531
469
|
visit o.right, collector
|
532
470
|
end
|
533
471
|
|
@@ -541,7 +479,7 @@ module Arel # :nodoc: all
|
|
541
479
|
def visit_Arel_Nodes_RightOuterJoin(o, collector)
|
542
480
|
collector << "RIGHT OUTER JOIN "
|
543
481
|
collector = visit o.left, collector
|
544
|
-
collector <<
|
482
|
+
collector << " "
|
545
483
|
visit o.right, collector
|
546
484
|
end
|
547
485
|
|
@@ -549,7 +487,7 @@ module Arel # :nodoc: all
|
|
549
487
|
collector << "INNER JOIN "
|
550
488
|
collector = visit o.left, collector
|
551
489
|
if o.right
|
552
|
-
collector <<
|
490
|
+
collector << " "
|
553
491
|
visit(o.right, collector)
|
554
492
|
else
|
555
493
|
collector
|
@@ -575,34 +513,66 @@ module Arel # :nodoc: all
|
|
575
513
|
end
|
576
514
|
|
577
515
|
def visit_Arel_Nodes_In(o, collector)
|
578
|
-
|
516
|
+
unless Array === o.right
|
517
|
+
return collect_in_clause(o.left, o.right, collector)
|
518
|
+
end
|
519
|
+
|
520
|
+
unless o.right.empty?
|
579
521
|
o.right.delete_if { |value| unboundable?(value) }
|
580
522
|
end
|
581
523
|
|
582
|
-
|
583
|
-
|
524
|
+
return collector << "1=0" if o.right.empty?
|
525
|
+
|
526
|
+
in_clause_length = @connection.in_clause_length
|
527
|
+
|
528
|
+
if !in_clause_length || o.right.length <= in_clause_length
|
529
|
+
collect_in_clause(o.left, o.right, collector)
|
584
530
|
else
|
585
|
-
collector
|
586
|
-
|
587
|
-
|
531
|
+
collector << "("
|
532
|
+
o.right.each_slice(in_clause_length).each_with_index do |right, i|
|
533
|
+
collector << " OR " unless i == 0
|
534
|
+
collect_in_clause(o.left, right, collector)
|
535
|
+
end
|
536
|
+
collector << ")"
|
588
537
|
end
|
589
538
|
end
|
590
539
|
|
540
|
+
def collect_in_clause(left, right, collector)
|
541
|
+
collector = visit left, collector
|
542
|
+
collector << " IN ("
|
543
|
+
visit(right, collector) << ")"
|
544
|
+
end
|
545
|
+
|
591
546
|
def visit_Arel_Nodes_NotIn(o, collector)
|
592
|
-
|
547
|
+
unless Array === o.right
|
548
|
+
return collect_not_in_clause(o.left, o.right, collector)
|
549
|
+
end
|
550
|
+
|
551
|
+
unless o.right.empty?
|
593
552
|
o.right.delete_if { |value| unboundable?(value) }
|
594
553
|
end
|
595
554
|
|
596
|
-
|
597
|
-
|
555
|
+
return collector << "1=1" if o.right.empty?
|
556
|
+
|
557
|
+
in_clause_length = @connection.in_clause_length
|
558
|
+
|
559
|
+
if !in_clause_length || o.right.length <= in_clause_length
|
560
|
+
collect_not_in_clause(o.left, o.right, collector)
|
598
561
|
else
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
562
|
+
o.right.each_slice(in_clause_length).each_with_index do |right, i|
|
563
|
+
collector << " AND " unless i == 0
|
564
|
+
collect_not_in_clause(o.left, right, collector)
|
565
|
+
end
|
566
|
+
collector
|
603
567
|
end
|
604
568
|
end
|
605
569
|
|
570
|
+
def collect_not_in_clause(left, right, collector)
|
571
|
+
collector = visit left, collector
|
572
|
+
collector << " NOT IN ("
|
573
|
+
visit(right, collector) << ")"
|
574
|
+
end
|
575
|
+
|
606
576
|
def visit_Arel_Nodes_And(o, collector)
|
607
577
|
inject_join o.children, collector, " AND "
|
608
578
|
end
|
@@ -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 << " "
|