aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/arel.rb39
-rw-r--r--activerecord/lib/arel/alias_predication.rb8
-rw-r--r--activerecord/lib/arel/attributes.rb21
-rw-r--r--activerecord/lib/arel/attributes/attribute.rb36
-rw-r--r--activerecord/lib/arel/collectors/bind.rb24
-rw-r--r--activerecord/lib/arel/collectors/composite.rb32
-rw-r--r--activerecord/lib/arel/collectors/plain_string.rb19
-rw-r--r--activerecord/lib/arel/collectors/sql_string.rb24
-rw-r--r--activerecord/lib/arel/collectors/substitute_binds.rb28
-rw-r--r--activerecord/lib/arel/compatibility/wheres.rb34
-rw-r--r--activerecord/lib/arel/crud.rb42
-rw-r--r--activerecord/lib/arel/delete_manager.rb24
-rw-r--r--activerecord/lib/arel/errors.rb8
-rw-r--r--activerecord/lib/arel/expressions.rb29
-rw-r--r--activerecord/lib/arel/factory_methods.rb44
-rw-r--r--activerecord/lib/arel/insert_manager.rb48
-rw-r--r--activerecord/lib/arel/math.rb44
-rw-r--r--activerecord/lib/arel/nodes.rb66
-rw-r--r--activerecord/lib/arel/nodes/and.rb31
-rw-r--r--activerecord/lib/arel/nodes/ascending.rb24
-rw-r--r--activerecord/lib/arel/nodes/binary.rb51
-rw-r--r--activerecord/lib/arel/nodes/bind_param.rb27
-rw-r--r--activerecord/lib/arel/nodes/case.rb54
-rw-r--r--activerecord/lib/arel/nodes/casted.rb45
-rw-r--r--activerecord/lib/arel/nodes/count.rb13
-rw-r--r--activerecord/lib/arel/nodes/delete_statement.rb37
-rw-r--r--activerecord/lib/arel/nodes/descending.rb24
-rw-r--r--activerecord/lib/arel/nodes/equality.rb10
-rw-r--r--activerecord/lib/arel/nodes/extract.rb23
-rw-r--r--activerecord/lib/arel/nodes/false.rb15
-rw-r--r--activerecord/lib/arel/nodes/full_outer_join.rb7
-rw-r--r--activerecord/lib/arel/nodes/function.rb44
-rw-r--r--activerecord/lib/arel/nodes/grouping.rb7
-rw-r--r--activerecord/lib/arel/nodes/in.rb7
-rw-r--r--activerecord/lib/arel/nodes/infix_operation.rb80
-rw-r--r--activerecord/lib/arel/nodes/inner_join.rb7
-rw-r--r--activerecord/lib/arel/nodes/insert_statement.rb36
-rw-r--r--activerecord/lib/arel/nodes/join_source.rb19
-rw-r--r--activerecord/lib/arel/nodes/matches.rb17
-rw-r--r--activerecord/lib/arel/nodes/named_function.rb22
-rw-r--r--activerecord/lib/arel/nodes/node.rb59
-rw-r--r--activerecord/lib/arel/nodes/node_expression.rb11
-rw-r--r--activerecord/lib/arel/nodes/outer_join.rb7
-rw-r--r--activerecord/lib/arel/nodes/over.rb16
-rw-r--r--activerecord/lib/arel/nodes/regexp.rb15
-rw-r--r--activerecord/lib/arel/nodes/right_outer_join.rb7
-rw-r--r--activerecord/lib/arel/nodes/select_core.rb64
-rw-r--r--activerecord/lib/arel/nodes/select_statement.rb40
-rw-r--r--activerecord/lib/arel/nodes/sql_literal.rb15
-rw-r--r--activerecord/lib/arel/nodes/string_join.rb10
-rw-r--r--activerecord/lib/arel/nodes/table_alias.rb26
-rw-r--r--activerecord/lib/arel/nodes/terminal.rb15
-rw-r--r--activerecord/lib/arel/nodes/true.rb15
-rw-r--r--activerecord/lib/arel/nodes/unary.rb44
-rw-r--r--activerecord/lib/arel/nodes/unary_operation.rb20
-rw-r--r--activerecord/lib/arel/nodes/unqualified_column.rb21
-rw-r--r--activerecord/lib/arel/nodes/update_statement.rb39
-rw-r--r--activerecord/lib/arel/nodes/values.rb15
-rw-r--r--activerecord/lib/arel/nodes/values_list.rb23
-rw-r--r--activerecord/lib/arel/nodes/window.rb125
-rw-r--r--activerecord/lib/arel/nodes/with.rb11
-rw-r--r--activerecord/lib/arel/order_predications.rb14
-rw-r--r--activerecord/lib/arel/predications.rb240
-rw-r--r--activerecord/lib/arel/select_manager.rb272
-rw-r--r--activerecord/lib/arel/table.rb110
-rw-r--r--activerecord/lib/arel/tree_manager.rb37
-rw-r--r--activerecord/lib/arel/update_manager.rb58
-rw-r--r--activerecord/lib/arel/visitors.rb19
-rw-r--r--activerecord/lib/arel/visitors/depth_first.rb199
-rw-r--r--activerecord/lib/arel/visitors/dot.rb291
-rw-r--r--activerecord/lib/arel/visitors/ibm_db.rb15
-rw-r--r--activerecord/lib/arel/visitors/informix.rb55
-rw-r--r--activerecord/lib/arel/visitors/mssql.rb124
-rw-r--r--activerecord/lib/arel/visitors/mysql.rb86
-rw-r--r--activerecord/lib/arel/visitors/oracle.rb153
-rw-r--r--activerecord/lib/arel/visitors/oracle12.rb60
-rw-r--r--activerecord/lib/arel/visitors/postgresql.rb103
-rw-r--r--activerecord/lib/arel/visitors/sqlite.rb27
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb846
-rw-r--r--activerecord/lib/arel/visitors/visitor.rb41
-rw-r--r--activerecord/lib/arel/visitors/where_sql.rb22
-rw-r--r--activerecord/lib/arel/window_predications.rb10
82 files changed, 4550 insertions, 0 deletions
diff --git a/activerecord/lib/arel.rb b/activerecord/lib/arel.rb
new file mode 100644
index 0000000000..c327caeac0
--- /dev/null
+++ b/activerecord/lib/arel.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'arel/errors'
+
+require 'arel/crud'
+require 'arel/factory_methods'
+
+require 'arel/expressions'
+require 'arel/predications'
+require 'arel/window_predications'
+require 'arel/math'
+require 'arel/alias_predication'
+require 'arel/order_predications'
+require 'arel/table'
+require 'arel/attributes'
+require 'arel/compatibility/wheres'
+
+require 'arel/visitors'
+require 'arel/collectors/sql_string'
+
+require 'arel/tree_manager'
+require 'arel/insert_manager'
+require 'arel/select_manager'
+require 'arel/update_manager'
+require 'arel/delete_manager'
+require 'arel/nodes'
+
+module Arel
+ VERSION = '10.0.0'
+
+ def self.sql raw_sql
+ Arel::Nodes::SqlLiteral.new raw_sql
+ end
+
+ def self.star
+ sql '*'
+ end
+ ## Convenience Alias
+ Node = Arel::Nodes::Node
+end
diff --git a/activerecord/lib/arel/alias_predication.rb b/activerecord/lib/arel/alias_predication.rb
new file mode 100644
index 0000000000..cb50fb95be
--- /dev/null
+++ b/activerecord/lib/arel/alias_predication.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Arel
+ module AliasPredication
+ def as other
+ Nodes::As.new self, Nodes::SqlLiteral.new(other)
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/arel/attributes.rb b/activerecord/lib/arel/attributes.rb
new file mode 100644
index 0000000000..ed4739ebed
--- /dev/null
+++ b/activerecord/lib/arel/attributes.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+require 'arel/attributes/attribute'
+
+module Arel
+ module Attributes
+ ###
+ # Factory method to wrap a raw database +column+ to an Arel Attribute.
+ def self.for column
+ case column.type
+ when :string, :text, :binary then String
+ when :integer then Integer
+ when :float then Float
+ when :decimal then Decimal
+ when :date, :datetime, :timestamp, :time then Time
+ when :boolean then Boolean
+ else
+ Undefined
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/attributes/attribute.rb b/activerecord/lib/arel/attributes/attribute.rb
new file mode 100644
index 0000000000..41bc0c32c7
--- /dev/null
+++ b/activerecord/lib/arel/attributes/attribute.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+module Arel
+ module Attributes
+ class Attribute < Struct.new :relation, :name
+ include Arel::Expressions
+ include Arel::Predications
+ include Arel::AliasPredication
+ include Arel::OrderPredications
+ include Arel::Math
+
+ ###
+ # Create a node for lowering this attribute
+ def lower
+ relation.lower self
+ end
+
+ def type_cast_for_database(value)
+ relation.type_cast_for_database(name, value)
+ end
+
+ def able_to_type_cast?
+ relation.able_to_type_cast?
+ end
+ end
+
+ class String < Attribute; end
+ class Time < Attribute; end
+ class Boolean < Attribute; end
+ class Decimal < Attribute; end
+ class Float < Attribute; end
+ class Integer < Attribute; end
+ class Undefined < Attribute; end
+ end
+
+ Attribute = Attributes::Attribute
+end
diff --git a/activerecord/lib/arel/collectors/bind.rb b/activerecord/lib/arel/collectors/bind.rb
new file mode 100644
index 0000000000..d816aed90d
--- /dev/null
+++ b/activerecord/lib/arel/collectors/bind.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Arel
+ module Collectors
+ class Bind
+ def initialize
+ @binds = []
+ end
+
+ def << str
+ self
+ end
+
+ def add_bind bind
+ @binds << bind
+ self
+ end
+
+ def value
+ @binds
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/collectors/composite.rb b/activerecord/lib/arel/collectors/composite.rb
new file mode 100644
index 0000000000..4f6156fe27
--- /dev/null
+++ b/activerecord/lib/arel/collectors/composite.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Arel
+ module Collectors
+ class Composite
+ def initialize(left, right)
+ @left = left
+ @right = right
+ end
+
+ def << str
+ left << str
+ right << str
+ self
+ end
+
+ def add_bind bind, &block
+ left.add_bind bind, &block
+ right.add_bind bind, &block
+ self
+ end
+
+ def value
+ [left.value, right.value]
+ end
+
+ protected
+
+ attr_reader :left, :right
+ end
+ end
+end
diff --git a/activerecord/lib/arel/collectors/plain_string.rb b/activerecord/lib/arel/collectors/plain_string.rb
new file mode 100644
index 0000000000..1e8d2a2152
--- /dev/null
+++ b/activerecord/lib/arel/collectors/plain_string.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+module Arel
+ module Collectors
+ class PlainString
+ def initialize
+ @str = ''.dup
+ end
+
+ def value
+ @str
+ end
+
+ def << str
+ @str << str
+ self
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/collectors/sql_string.rb b/activerecord/lib/arel/collectors/sql_string.rb
new file mode 100644
index 0000000000..bcb941f6d4
--- /dev/null
+++ b/activerecord/lib/arel/collectors/sql_string.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'arel/collectors/plain_string'
+
+module Arel
+ module Collectors
+ class SQLString < PlainString
+ def initialize(*)
+ super
+ @bind_index = 1
+ end
+
+ def add_bind bind
+ self << yield(@bind_index)
+ @bind_index += 1
+ self
+ end
+
+ def compile bvs
+ value
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/collectors/substitute_binds.rb b/activerecord/lib/arel/collectors/substitute_binds.rb
new file mode 100644
index 0000000000..99d2215aaa
--- /dev/null
+++ b/activerecord/lib/arel/collectors/substitute_binds.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+module Arel
+ module Collectors
+ class SubstituteBinds
+ def initialize(quoter, delegate_collector)
+ @quoter = quoter
+ @delegate = delegate_collector
+ end
+
+ def << str
+ delegate << str
+ self
+ end
+
+ def add_bind bind
+ self << quoter.quote(bind)
+ end
+
+ def value
+ delegate.value
+ end
+
+ protected
+
+ attr_reader :quoter, :delegate
+ end
+ end
+end
diff --git a/activerecord/lib/arel/compatibility/wheres.rb b/activerecord/lib/arel/compatibility/wheres.rb
new file mode 100644
index 0000000000..3e60894bd8
--- /dev/null
+++ b/activerecord/lib/arel/compatibility/wheres.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+module Arel
+ module Compatibility # :nodoc:
+ class Wheres # :nodoc:
+ include Enumerable
+
+ module Value # :nodoc:
+ attr_accessor :visitor
+ def value
+ visitor.accept self
+ end
+
+ def name
+ super.to_sym
+ end
+ end
+
+ def initialize engine, collection
+ @engine = engine
+ @collection = collection
+ end
+
+ def each
+ to_sql = Visitors::ToSql.new @engine
+
+ @collection.each { |c|
+ c.extend(Value)
+ c.visitor = to_sql
+ yield c
+ }
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/crud.rb b/activerecord/lib/arel/crud.rb
new file mode 100644
index 0000000000..2d10432205
--- /dev/null
+++ b/activerecord/lib/arel/crud.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+module Arel
+ ###
+ # FIXME hopefully we can remove this
+ module Crud
+ def compile_update values, pk
+ um = UpdateManager.new
+
+ if Nodes::SqlLiteral === values
+ relation = @ctx.from
+ else
+ relation = values.first.first.relation
+ end
+ um.key = pk
+ um.table relation
+ um.set values
+ um.take @ast.limit.expr if @ast.limit
+ um.order(*@ast.orders)
+ um.wheres = @ctx.wheres
+ um
+ end
+
+ def compile_insert values
+ im = create_insert
+ im.insert values
+ im
+ end
+
+ def create_insert
+ InsertManager.new
+ end
+
+ def compile_delete
+ dm = DeleteManager.new
+ dm.take @ast.limit.expr if @ast.limit
+ dm.wheres = @ctx.wheres
+ dm.from @ctx.froms
+ dm
+ end
+
+ end
+end
diff --git a/activerecord/lib/arel/delete_manager.rb b/activerecord/lib/arel/delete_manager.rb
new file mode 100644
index 0000000000..aee4511249
--- /dev/null
+++ b/activerecord/lib/arel/delete_manager.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Arel
+ class DeleteManager < Arel::TreeManager
+ def initialize
+ super
+ @ast = Nodes::DeleteStatement.new
+ @ctx = @ast
+ end
+
+ def from relation
+ @ast.relation = relation
+ self
+ end
+
+ def take limit
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
+ self
+ end
+
+ def wheres= list
+ @ast.wheres = list
+ end
+ end
+end
diff --git a/activerecord/lib/arel/errors.rb b/activerecord/lib/arel/errors.rb
new file mode 100644
index 0000000000..86fbb80461
--- /dev/null
+++ b/activerecord/lib/arel/errors.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+module Arel
+ class ArelError < StandardError
+ end
+
+ class EmptyJoinError < ArelError
+ end
+end
diff --git a/activerecord/lib/arel/expressions.rb b/activerecord/lib/arel/expressions.rb
new file mode 100644
index 0000000000..612a0942f1
--- /dev/null
+++ b/activerecord/lib/arel/expressions.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+module Arel
+ module Expressions
+ def count distinct = false
+ Nodes::Count.new [self], distinct
+ end
+
+ def sum
+ Nodes::Sum.new [self]
+ end
+
+ def maximum
+ Nodes::Max.new [self]
+ end
+
+ def minimum
+ Nodes::Min.new [self]
+ end
+
+ def average
+ Nodes::Avg.new [self]
+ end
+
+ def extract field
+ Nodes::Extract.new [self], field
+ end
+
+ end
+end
diff --git a/activerecord/lib/arel/factory_methods.rb b/activerecord/lib/arel/factory_methods.rb
new file mode 100644
index 0000000000..6647c5ac44
--- /dev/null
+++ b/activerecord/lib/arel/factory_methods.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+module Arel
+ ###
+ # Methods for creating various nodes
+ module FactoryMethods
+ def create_true
+ Arel::Nodes::True.new
+ end
+
+ def create_false
+ Arel::Nodes::False.new
+ end
+
+ def create_table_alias relation, name
+ Nodes::TableAlias.new(relation, name)
+ end
+
+ def create_join to, constraint = nil, klass = Nodes::InnerJoin
+ klass.new(to, constraint)
+ end
+
+ def create_string_join to
+ create_join to, nil, Nodes::StringJoin
+ end
+
+ def create_and clauses
+ Nodes::And.new clauses
+ end
+
+ def create_on expr
+ Nodes::On.new expr
+ end
+
+ def grouping expr
+ Nodes::Grouping.new expr
+ end
+
+ ###
+ # Create a LOWER() function
+ def lower column
+ Nodes::NamedFunction.new 'LOWER', [Nodes.build_quoted(column)]
+ end
+ end
+end
diff --git a/activerecord/lib/arel/insert_manager.rb b/activerecord/lib/arel/insert_manager.rb
new file mode 100644
index 0000000000..dcbac6cb43
--- /dev/null
+++ b/activerecord/lib/arel/insert_manager.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+module Arel
+ class InsertManager < Arel::TreeManager
+ def initialize
+ super
+ @ast = Nodes::InsertStatement.new
+ end
+
+ def into table
+ @ast.relation = table
+ self
+ end
+
+ def columns; @ast.columns end
+ def values= val; @ast.values = val; end
+
+ def select select
+ @ast.select = select
+ end
+
+ def insert fields
+ return if fields.empty?
+
+ if String === fields
+ @ast.values = Nodes::SqlLiteral.new(fields)
+ else
+ @ast.relation ||= fields.first.first.relation
+
+ values = []
+
+ fields.each do |column, value|
+ @ast.columns << column
+ values << value
+ end
+ @ast.values = create_values values, @ast.columns
+ end
+ self
+ end
+
+ def create_values values, columns
+ Nodes::Values.new values, columns
+ end
+
+ def create_values_list(rows)
+ Nodes::ValuesList.new(rows)
+ end
+ end
+end
diff --git a/activerecord/lib/arel/math.rb b/activerecord/lib/arel/math.rb
new file mode 100644
index 0000000000..9e6549b58f
--- /dev/null
+++ b/activerecord/lib/arel/math.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+module Arel
+ module Math
+ def *(other)
+ Arel::Nodes::Multiplication.new(self, other)
+ end
+
+ def +(other)
+ Arel::Nodes::Grouping.new(Arel::Nodes::Addition.new(self, other))
+ end
+
+ def -(other)
+ Arel::Nodes::Grouping.new(Arel::Nodes::Subtraction.new(self, other))
+ end
+
+ def /(other)
+ Arel::Nodes::Division.new(self, other)
+ end
+
+ def &(other)
+ Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(self, other))
+ end
+
+ def |(other)
+ Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(self, other))
+ end
+
+ def ^(other)
+ Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseXor.new(self, other))
+ end
+
+ def <<(other)
+ Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftLeft.new(self, other))
+ end
+
+ def >>(other)
+ Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftRight.new(self, other))
+ end
+
+ def ~@
+ Arel::Nodes::BitwiseNot.new(self)
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes.rb b/activerecord/lib/arel/nodes.rb
new file mode 100644
index 0000000000..8c6572dd6a
--- /dev/null
+++ b/activerecord/lib/arel/nodes.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+# node
+require 'arel/nodes/node'
+require 'arel/nodes/node_expression'
+require 'arel/nodes/select_statement'
+require 'arel/nodes/select_core'
+require 'arel/nodes/insert_statement'
+require 'arel/nodes/update_statement'
+require 'arel/nodes/bind_param'
+
+# terminal
+
+require 'arel/nodes/terminal'
+require 'arel/nodes/true'
+require 'arel/nodes/false'
+
+# unary
+require 'arel/nodes/unary'
+require 'arel/nodes/grouping'
+require 'arel/nodes/ascending'
+require 'arel/nodes/descending'
+require 'arel/nodes/unqualified_column'
+require 'arel/nodes/with'
+
+# binary
+require 'arel/nodes/binary'
+require 'arel/nodes/equality'
+require 'arel/nodes/in' # Why is this subclassed from equality?
+require 'arel/nodes/join_source'
+require 'arel/nodes/delete_statement'
+require 'arel/nodes/table_alias'
+require 'arel/nodes/infix_operation'
+require 'arel/nodes/unary_operation'
+require 'arel/nodes/over'
+require 'arel/nodes/matches'
+require 'arel/nodes/regexp'
+
+# nary
+require 'arel/nodes/and'
+
+# function
+# FIXME: Function + Alias can be rewritten as a Function and Alias node.
+# We should make Function a Unary node and deprecate the use of "aliaz"
+require 'arel/nodes/function'
+require 'arel/nodes/count'
+require 'arel/nodes/extract'
+require 'arel/nodes/values'
+require 'arel/nodes/values_list'
+require 'arel/nodes/named_function'
+
+# windows
+require 'arel/nodes/window'
+
+# conditional expressions
+require 'arel/nodes/case'
+
+# joins
+require 'arel/nodes/full_outer_join'
+require 'arel/nodes/inner_join'
+require 'arel/nodes/outer_join'
+require 'arel/nodes/right_outer_join'
+require 'arel/nodes/string_join'
+
+require 'arel/nodes/sql_literal'
+
+require 'arel/nodes/casted'
diff --git a/activerecord/lib/arel/nodes/and.rb b/activerecord/lib/arel/nodes/and.rb
new file mode 100644
index 0000000000..1e2f61cf43
--- /dev/null
+++ b/activerecord/lib/arel/nodes/and.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class And < Arel::Nodes::Node
+ attr_reader :children
+
+ def initialize children
+ super()
+ @children = children
+ end
+
+ def left
+ children.first
+ end
+
+ def right
+ children[1]
+ end
+
+ def hash
+ children.hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.children == other.children
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/ascending.rb b/activerecord/lib/arel/nodes/ascending.rb
new file mode 100644
index 0000000000..adadab55e4
--- /dev/null
+++ b/activerecord/lib/arel/nodes/ascending.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Ascending < Ordering
+
+ def reverse
+ Descending.new(expr)
+ end
+
+ def direction
+ :asc
+ end
+
+ def ascending?
+ true
+ end
+
+ def descending?
+ false
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/binary.rb b/activerecord/lib/arel/nodes/binary.rb
new file mode 100644
index 0000000000..a86d4e4696
--- /dev/null
+++ b/activerecord/lib/arel/nodes/binary.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Binary < Arel::Nodes::NodeExpression
+ attr_accessor :left, :right
+
+ def initialize left, right
+ super()
+ @left = left
+ @right = right
+ end
+
+ def initialize_copy other
+ super
+ @left = @left.clone if @left
+ @right = @right.clone if @right
+ end
+
+ def hash
+ [self.class, @left, @right].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.left == other.left &&
+ self.right == other.right
+ end
+ alias :== :eql?
+ end
+
+ %w{
+ As
+ Assignment
+ Between
+ GreaterThan
+ GreaterThanOrEqual
+ Join
+ LessThan
+ LessThanOrEqual
+ NotEqual
+ NotIn
+ Or
+ Union
+ UnionAll
+ Intersect
+ Except
+ }.each do |name|
+ const_set name, Class.new(Binary)
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/bind_param.rb b/activerecord/lib/arel/nodes/bind_param.rb
new file mode 100644
index 0000000000..efa4f452d4
--- /dev/null
+++ b/activerecord/lib/arel/nodes/bind_param.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class BindParam < Node
+ attr_accessor :value
+
+ def initialize(value)
+ @value = value
+ super()
+ end
+
+ def hash
+ [self.class, self.value].hash
+ end
+
+ def eql?(other)
+ other.is_a?(BindParam) &&
+ value == other.value
+ end
+ alias :== :eql?
+
+ def nil?
+ value.nil?
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/case.rb b/activerecord/lib/arel/nodes/case.rb
new file mode 100644
index 0000000000..50ea1e0be2
--- /dev/null
+++ b/activerecord/lib/arel/nodes/case.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Case < Arel::Nodes::Node
+ attr_accessor :case, :conditions, :default
+
+ def initialize expression = nil, default = nil
+ @case = expression
+ @conditions = []
+ @default = default
+ end
+
+ def when condition, expression = nil
+ @conditions << When.new(Nodes.build_quoted(condition), expression)
+ self
+ end
+
+ def then expression
+ @conditions.last.right = Nodes.build_quoted(expression)
+ self
+ end
+
+ def else expression
+ @default = Else.new Nodes.build_quoted(expression)
+ self
+ end
+
+ def initialize_copy other
+ super
+ @case = @case.clone if @case
+ @conditions = @conditions.map { |x| x.clone }
+ @default = @default.clone if @default
+ end
+
+ def hash
+ [@case, @conditions, @default].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.case == other.case &&
+ self.conditions == other.conditions &&
+ self.default == other.default
+ end
+ alias :== :eql?
+ end
+
+ class When < Binary # :nodoc:
+ end
+
+ class Else < Unary # :nodoc:
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/casted.rb b/activerecord/lib/arel/nodes/casted.rb
new file mode 100644
index 0000000000..f945063dd2
--- /dev/null
+++ b/activerecord/lib/arel/nodes/casted.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Casted < Arel::Nodes::NodeExpression # :nodoc:
+ attr_reader :val, :attribute
+ def initialize val, attribute
+ @val = val
+ @attribute = attribute
+ super()
+ end
+
+ def nil?; @val.nil?; end
+
+ def hash
+ [self.class, val, attribute].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.val == other.val &&
+ self.attribute == other.attribute
+ end
+ alias :== :eql?
+ end
+
+ class Quoted < Arel::Nodes::Unary # :nodoc:
+ alias :val :value
+ def nil?; val.nil?; end
+ end
+
+ def self.build_quoted other, attribute = nil
+ case other
+ when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral
+ other
+ else
+ case attribute
+ when Arel::Attributes::Attribute
+ Casted.new other, attribute
+ else
+ Quoted.new other
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/count.rb b/activerecord/lib/arel/nodes/count.rb
new file mode 100644
index 0000000000..4dd9be453f
--- /dev/null
+++ b/activerecord/lib/arel/nodes/count.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Count < Arel::Nodes::Function
+ include Math
+
+ def initialize expr, distinct = false, aliaz = nil
+ super(expr, aliaz)
+ @distinct = distinct
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/delete_statement.rb b/activerecord/lib/arel/nodes/delete_statement.rb
new file mode 100644
index 0000000000..063a5341e5
--- /dev/null
+++ b/activerecord/lib/arel/nodes/delete_statement.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class DeleteStatement < Arel::Nodes::Node
+ attr_accessor :left, :right
+ attr_accessor :limit
+
+ alias :relation :left
+ alias :relation= :left=
+ alias :wheres :right
+ alias :wheres= :right=
+
+ def initialize relation = nil, wheres = []
+ super()
+ @left = relation
+ @right = wheres
+ end
+
+ def initialize_copy other
+ super
+ @left = @left.clone if @left
+ @right = @right.clone if @right
+ end
+
+ def hash
+ [self.class, @left, @right].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.left == other.left &&
+ self.right == other.right
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/descending.rb b/activerecord/lib/arel/nodes/descending.rb
new file mode 100644
index 0000000000..d7261ab583
--- /dev/null
+++ b/activerecord/lib/arel/nodes/descending.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Descending < Ordering
+
+ def reverse
+ Ascending.new(expr)
+ end
+
+ def direction
+ :desc
+ end
+
+ def ascending?
+ false
+ end
+
+ def descending?
+ true
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/equality.rb b/activerecord/lib/arel/nodes/equality.rb
new file mode 100644
index 0000000000..ef44725e24
--- /dev/null
+++ b/activerecord/lib/arel/nodes/equality.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Equality < Arel::Nodes::Binary
+ def operator; :== end
+ alias :operand1 :left
+ alias :operand2 :right
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/extract.rb b/activerecord/lib/arel/nodes/extract.rb
new file mode 100644
index 0000000000..fdf3004c6a
--- /dev/null
+++ b/activerecord/lib/arel/nodes/extract.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Extract < Arel::Nodes::Unary
+ attr_accessor :field
+
+ def initialize expr, field
+ super(expr)
+ @field = field
+ end
+
+ def hash
+ super ^ @field.hash
+ end
+
+ def eql? other
+ super &&
+ self.field == other.field
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/false.rb b/activerecord/lib/arel/nodes/false.rb
new file mode 100644
index 0000000000..58132a2f90
--- /dev/null
+++ b/activerecord/lib/arel/nodes/false.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class False < Arel::Nodes::NodeExpression
+ def hash
+ self.class.hash
+ end
+
+ def eql? other
+ self.class == other.class
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/full_outer_join.rb b/activerecord/lib/arel/nodes/full_outer_join.rb
new file mode 100644
index 0000000000..12a02d8cd9
--- /dev/null
+++ b/activerecord/lib/arel/nodes/full_outer_join.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class FullOuterJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/function.rb b/activerecord/lib/arel/nodes/function.rb
new file mode 100644
index 0000000000..b3bf8f3e51
--- /dev/null
+++ b/activerecord/lib/arel/nodes/function.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Function < Arel::Nodes::NodeExpression
+ include Arel::WindowPredications
+ attr_accessor :expressions, :alias, :distinct
+
+ def initialize expr, aliaz = nil
+ super()
+ @expressions = expr
+ @alias = aliaz && SqlLiteral.new(aliaz)
+ @distinct = false
+ end
+
+ def as aliaz
+ self.alias = SqlLiteral.new(aliaz)
+ self
+ end
+
+ def hash
+ [@expressions, @alias, @distinct].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.expressions == other.expressions &&
+ self.alias == other.alias &&
+ self.distinct == other.distinct
+ end
+ alias :== :eql?
+
+ end
+
+ %w{
+ Sum
+ Exists
+ Max
+ Min
+ Avg
+ }.each do |name|
+ const_set(name, Class.new(Function))
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/grouping.rb b/activerecord/lib/arel/nodes/grouping.rb
new file mode 100644
index 0000000000..ffe66654ce
--- /dev/null
+++ b/activerecord/lib/arel/nodes/grouping.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Grouping < Unary
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/in.rb b/activerecord/lib/arel/nodes/in.rb
new file mode 100644
index 0000000000..30cd771c40
--- /dev/null
+++ b/activerecord/lib/arel/nodes/in.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class In < Equality
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/infix_operation.rb b/activerecord/lib/arel/nodes/infix_operation.rb
new file mode 100644
index 0000000000..4eb7c5356f
--- /dev/null
+++ b/activerecord/lib/arel/nodes/infix_operation.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+
+ class InfixOperation < Binary
+ include Arel::Expressions
+ include Arel::Predications
+ include Arel::OrderPredications
+ include Arel::AliasPredication
+ include Arel::Math
+
+ attr_reader :operator
+
+ def initialize operator, left, right
+ super(left, right)
+ @operator = operator
+ end
+ end
+
+ class Multiplication < InfixOperation
+ def initialize left, right
+ super(:*, left, right)
+ end
+ end
+
+ class Division < InfixOperation
+ def initialize left, right
+ super(:/, left, right)
+ end
+ end
+
+ class Addition < InfixOperation
+ def initialize left, right
+ super(:+, left, right)
+ end
+ end
+
+ class Subtraction < InfixOperation
+ def initialize left, right
+ super(:-, left, right)
+ end
+ end
+
+ class Concat < InfixOperation
+ def initialize left, right
+ super('||', left, right)
+ end
+ end
+
+ class BitwiseAnd < InfixOperation
+ def initialize left, right
+ super(:&, left, right)
+ end
+ end
+
+ class BitwiseOr < InfixOperation
+ def initialize left, right
+ super(:|, left, right)
+ end
+ end
+
+ class BitwiseXor < InfixOperation
+ def initialize left, right
+ super(:^, left, right)
+ end
+ end
+
+ class BitwiseShiftLeft < InfixOperation
+ def initialize left, right
+ super(:<<, left, right)
+ end
+ end
+
+ class BitwiseShiftRight < InfixOperation
+ def initialize left, right
+ super(:>>, left, right)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/inner_join.rb b/activerecord/lib/arel/nodes/inner_join.rb
new file mode 100644
index 0000000000..4e398267c3
--- /dev/null
+++ b/activerecord/lib/arel/nodes/inner_join.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class InnerJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/insert_statement.rb b/activerecord/lib/arel/nodes/insert_statement.rb
new file mode 100644
index 0000000000..72793bc1ad
--- /dev/null
+++ b/activerecord/lib/arel/nodes/insert_statement.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class InsertStatement < Arel::Nodes::Node
+ attr_accessor :relation, :columns, :values, :select
+
+ def initialize
+ super()
+ @relation = nil
+ @columns = []
+ @values = nil
+ @select = nil
+ end
+
+ def initialize_copy other
+ super
+ @columns = @columns.clone
+ @values = @values.clone if @values
+ @select = @select.clone if @select
+ end
+
+ def hash
+ [@relation, @columns, @values, @select].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.relation == other.relation &&
+ self.columns == other.columns &&
+ self.select == other.select &&
+ self.values == other.values
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/join_source.rb b/activerecord/lib/arel/nodes/join_source.rb
new file mode 100644
index 0000000000..428ce8183e
--- /dev/null
+++ b/activerecord/lib/arel/nodes/join_source.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ ###
+ # Class that represents a join source
+ #
+ # http://www.sqlite.org/syntaxdiagrams.html#join-source
+
+ class JoinSource < Arel::Nodes::Binary
+ def initialize single_source, joinop = []
+ super
+ end
+
+ def empty?
+ !left && right.empty?
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/matches.rb b/activerecord/lib/arel/nodes/matches.rb
new file mode 100644
index 0000000000..3ad3850a8e
--- /dev/null
+++ b/activerecord/lib/arel/nodes/matches.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Matches < Binary
+ attr_reader :escape
+ attr_accessor :case_sensitive
+
+ def initialize(left, right, escape = nil, case_sensitive = false)
+ super(left, right)
+ @escape = escape && Nodes.build_quoted(escape)
+ @case_sensitive = case_sensitive
+ end
+ end
+
+ class DoesNotMatch < Matches; end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/named_function.rb b/activerecord/lib/arel/nodes/named_function.rb
new file mode 100644
index 0000000000..173838a7fd
--- /dev/null
+++ b/activerecord/lib/arel/nodes/named_function.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class NamedFunction < Arel::Nodes::Function
+ attr_accessor :name
+
+ def initialize name, expr, aliaz = nil
+ super(expr, aliaz)
+ @name = name
+ end
+
+ def hash
+ super ^ @name.hash
+ end
+
+ def eql? other
+ super && self.name == other.name
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/node.rb b/activerecord/lib/arel/nodes/node.rb
new file mode 100644
index 0000000000..d2e6313dda
--- /dev/null
+++ b/activerecord/lib/arel/nodes/node.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ ###
+ # Abstract base class for all AST nodes
+ class Node
+ include Arel::FactoryMethods
+ include Enumerable
+
+ if $DEBUG
+ def _caller
+ @caller
+ end
+
+ def initialize
+ @caller = caller.dup
+ end
+ end
+
+ ###
+ # Factory method to create a Nodes::Not node that has the recipient of
+ # the caller as a child.
+ def not
+ Nodes::Not.new self
+ end
+
+ ###
+ # Factory method to create a Nodes::Grouping node that has an Nodes::Or
+ # node as a child.
+ def or right
+ Nodes::Grouping.new Nodes::Or.new(self, right)
+ end
+
+ ###
+ # Factory method to create an Nodes::And node.
+ def and right
+ Nodes::And.new [self, right]
+ end
+
+ # FIXME: this method should go away. I don't like people calling
+ # to_sql on non-head nodes. This forces us to walk the AST until we
+ # can find a node that has a "relation" member.
+ #
+ # Maybe we should just use `Table.engine`? :'(
+ def to_sql engine = Table.engine
+ collector = Arel::Collectors::SQLString.new
+ collector = engine.connection.visitor.accept self, collector
+ collector.value
+ end
+
+ # Iterate through AST, nodes will be yielded depth-first
+ def each &block
+ return enum_for(:each) unless block_given?
+
+ ::Arel::Visitors::DepthFirst.new(block).accept self
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/node_expression.rb b/activerecord/lib/arel/nodes/node_expression.rb
new file mode 100644
index 0000000000..c4d4c8f428
--- /dev/null
+++ b/activerecord/lib/arel/nodes/node_expression.rb
@@ -0,0 +1,11 @@
+module Arel
+ module Nodes
+ class NodeExpression < Arel::Nodes::Node
+ include Arel::Expressions
+ include Arel::Predications
+ include Arel::AliasPredication
+ include Arel::OrderPredications
+ include Arel::Math
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/outer_join.rb b/activerecord/lib/arel/nodes/outer_join.rb
new file mode 100644
index 0000000000..c568655fe6
--- /dev/null
+++ b/activerecord/lib/arel/nodes/outer_join.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class OuterJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/over.rb b/activerecord/lib/arel/nodes/over.rb
new file mode 100644
index 0000000000..47a34e69ea
--- /dev/null
+++ b/activerecord/lib/arel/nodes/over.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+
+ class Over < Binary
+ include Arel::AliasPredication
+
+ def initialize(left, right = nil)
+ super(left, right)
+ end
+
+ def operator; 'OVER' end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/arel/nodes/regexp.rb b/activerecord/lib/arel/nodes/regexp.rb
new file mode 100644
index 0000000000..8a76185ef0
--- /dev/null
+++ b/activerecord/lib/arel/nodes/regexp.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Regexp < Binary
+ attr_accessor :case_sensitive
+
+ def initialize(left, right, case_sensitive = true)
+ super(left, right)
+ @case_sensitive = case_sensitive
+ end
+ end
+
+ class NotRegexp < Regexp; end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/right_outer_join.rb b/activerecord/lib/arel/nodes/right_outer_join.rb
new file mode 100644
index 0000000000..04ab31ebf0
--- /dev/null
+++ b/activerecord/lib/arel/nodes/right_outer_join.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class RightOuterJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/select_core.rb b/activerecord/lib/arel/nodes/select_core.rb
new file mode 100644
index 0000000000..fa1c026107
--- /dev/null
+++ b/activerecord/lib/arel/nodes/select_core.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class SelectCore < Arel::Nodes::Node
+ attr_accessor :top, :projections, :wheres, :groups, :windows
+ attr_accessor :havings, :source, :set_quantifier
+
+ def initialize
+ super()
+ @source = JoinSource.new nil
+ @top = nil
+
+ # https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier
+ @set_quantifier = nil
+ @projections = []
+ @wheres = []
+ @groups = []
+ @havings = []
+ @windows = []
+ end
+
+ def from
+ @source.left
+ end
+
+ def from= value
+ @source.left = value
+ end
+
+ alias :froms= :from=
+ alias :froms :from
+
+ def initialize_copy other
+ super
+ @source = @source.clone if @source
+ @projections = @projections.clone
+ @wheres = @wheres.clone
+ @groups = @groups.clone
+ @havings = @havings.clone
+ @windows = @windows.clone
+ end
+
+ def hash
+ [
+ @source, @top, @set_quantifier, @projections,
+ @wheres, @groups, @havings, @windows
+ ].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.source == other.source &&
+ self.top == other.top &&
+ self.set_quantifier == other.set_quantifier &&
+ self.projections == other.projections &&
+ self.wheres == other.wheres &&
+ self.groups == other.groups &&
+ self.havings == other.havings &&
+ self.windows == other.windows
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/select_statement.rb b/activerecord/lib/arel/nodes/select_statement.rb
new file mode 100644
index 0000000000..79176d4be5
--- /dev/null
+++ b/activerecord/lib/arel/nodes/select_statement.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class SelectStatement < Arel::Nodes::NodeExpression
+ attr_reader :cores
+ attr_accessor :limit, :orders, :lock, :offset, :with
+
+ def initialize cores = [SelectCore.new]
+ super()
+ @cores = cores
+ @orders = []
+ @limit = nil
+ @lock = nil
+ @offset = nil
+ @with = nil
+ end
+
+ def initialize_copy other
+ super
+ @cores = @cores.map { |x| x.clone }
+ @orders = @orders.map { |x| x.clone }
+ end
+
+ def hash
+ [@cores, @orders, @limit, @lock, @offset, @with].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.cores == other.cores &&
+ self.orders == other.orders &&
+ self.limit == other.limit &&
+ self.lock == other.lock &&
+ self.offset == other.offset &&
+ self.with == other.with
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/sql_literal.rb b/activerecord/lib/arel/nodes/sql_literal.rb
new file mode 100644
index 0000000000..73575a7d49
--- /dev/null
+++ b/activerecord/lib/arel/nodes/sql_literal.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class SqlLiteral < String
+ include Arel::Expressions
+ include Arel::Predications
+ include Arel::AliasPredication
+ include Arel::OrderPredications
+
+ def encode_with(coder)
+ coder.scalar = self.to_s
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/string_join.rb b/activerecord/lib/arel/nodes/string_join.rb
new file mode 100644
index 0000000000..21d6845c45
--- /dev/null
+++ b/activerecord/lib/arel/nodes/string_join.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class StringJoin < Arel::Nodes::Join
+ def initialize left, right = nil
+ super
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/table_alias.rb b/activerecord/lib/arel/nodes/table_alias.rb
new file mode 100644
index 0000000000..78deb175b6
--- /dev/null
+++ b/activerecord/lib/arel/nodes/table_alias.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class TableAlias < Arel::Nodes::Binary
+ alias :name :right
+ alias :relation :left
+ alias :table_alias :name
+
+ def [] name
+ Attribute.new(self, name)
+ end
+
+ def table_name
+ relation.respond_to?(:name) ? relation.name : name
+ end
+
+ def type_cast_for_database(*args)
+ relation.type_cast_for_database(*args)
+ end
+
+ def able_to_type_cast?
+ relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast?
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/terminal.rb b/activerecord/lib/arel/nodes/terminal.rb
new file mode 100644
index 0000000000..3a1cd7f0e1
--- /dev/null
+++ b/activerecord/lib/arel/nodes/terminal.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Distinct < Arel::Nodes::NodeExpression
+ def hash
+ self.class.hash
+ end
+
+ def eql? other
+ self.class == other.class
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/true.rb b/activerecord/lib/arel/nodes/true.rb
new file mode 100644
index 0000000000..fdb8ed2095
--- /dev/null
+++ b/activerecord/lib/arel/nodes/true.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class True < Arel::Nodes::NodeExpression
+ def hash
+ self.class.hash
+ end
+
+ def eql? other
+ self.class == other.class
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/unary.rb b/activerecord/lib/arel/nodes/unary.rb
new file mode 100644
index 0000000000..e458d87ab3
--- /dev/null
+++ b/activerecord/lib/arel/nodes/unary.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Unary < Arel::Nodes::NodeExpression
+ attr_accessor :expr
+ alias :value :expr
+
+ def initialize expr
+ super()
+ @expr = expr
+ end
+
+ def hash
+ @expr.hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.expr == other.expr
+ end
+ alias :== :eql?
+ end
+
+ %w{
+ Bin
+ Cube
+ DistinctOn
+ Group
+ GroupingElement
+ GroupingSet
+ Lateral
+ Limit
+ Lock
+ Not
+ Offset
+ On
+ Ordering
+ RollUp
+ Top
+ }.each do |name|
+ const_set(name, Class.new(Unary))
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/unary_operation.rb b/activerecord/lib/arel/nodes/unary_operation.rb
new file mode 100644
index 0000000000..be4e270e76
--- /dev/null
+++ b/activerecord/lib/arel/nodes/unary_operation.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+
+ class UnaryOperation < Unary
+ attr_reader :operator
+
+ def initialize operator, operand
+ super(operand)
+ @operator = operator
+ end
+ end
+
+ class BitwiseNot < UnaryOperation
+ def initialize operand
+ super(:~, operand)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/unqualified_column.rb b/activerecord/lib/arel/nodes/unqualified_column.rb
new file mode 100644
index 0000000000..f9017238c8
--- /dev/null
+++ b/activerecord/lib/arel/nodes/unqualified_column.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class UnqualifiedColumn < Arel::Nodes::Unary
+ alias :attribute :expr
+ alias :attribute= :expr=
+
+ def relation
+ @expr.relation
+ end
+
+ def column
+ @expr.column
+ end
+
+ def name
+ @expr.name
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/update_statement.rb b/activerecord/lib/arel/nodes/update_statement.rb
new file mode 100644
index 0000000000..286f0bd3ce
--- /dev/null
+++ b/activerecord/lib/arel/nodes/update_statement.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class UpdateStatement < Arel::Nodes::Node
+ attr_accessor :relation, :wheres, :values, :orders, :limit
+ attr_accessor :key
+
+ def initialize
+ @relation = nil
+ @wheres = []
+ @values = []
+ @orders = []
+ @limit = nil
+ @key = nil
+ end
+
+ def initialize_copy other
+ super
+ @wheres = @wheres.clone
+ @values = @values.clone
+ end
+
+ def hash
+ [@relation, @wheres, @values, @orders, @limit, @key].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.relation == other.relation &&
+ self.wheres == other.wheres &&
+ self.values == other.values &&
+ self.orders == other.orders &&
+ self.limit == other.limit &&
+ self.key == other.key
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/values.rb b/activerecord/lib/arel/nodes/values.rb
new file mode 100644
index 0000000000..b32d5063a2
--- /dev/null
+++ b/activerecord/lib/arel/nodes/values.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Values < Arel::Nodes::Binary
+ alias :expressions :left
+ alias :expressions= :left=
+ alias :columns :right
+ alias :columns= :right=
+
+ def initialize exprs, columns = []
+ super
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/values_list.rb b/activerecord/lib/arel/nodes/values_list.rb
new file mode 100644
index 0000000000..89cea1790d
--- /dev/null
+++ b/activerecord/lib/arel/nodes/values_list.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class ValuesList < Node
+ attr_reader :rows
+
+ def initialize(rows)
+ @rows = rows
+ super()
+ end
+
+ def hash
+ @rows.hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.rows == other.rows
+ end
+ alias :== :eql?
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/window.rb b/activerecord/lib/arel/nodes/window.rb
new file mode 100644
index 0000000000..23a005daba
--- /dev/null
+++ b/activerecord/lib/arel/nodes/window.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class Window < Arel::Nodes::Node
+ attr_accessor :orders, :framing, :partitions
+
+ def initialize
+ @orders = []
+ @partitions = []
+ @framing = nil
+ end
+
+ def order *expr
+ # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
+ @orders.concat expr.map { |x|
+ String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
+ }
+ self
+ end
+
+ def partition *expr
+ # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
+ @partitions.concat expr.map { |x|
+ String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
+ }
+ self
+ end
+
+ def frame(expr)
+ @framing = expr
+ end
+
+ def rows(expr = nil)
+ if @framing
+ Rows.new(expr)
+ else
+ frame(Rows.new(expr))
+ end
+ end
+
+ def range(expr = nil)
+ if @framing
+ Range.new(expr)
+ else
+ frame(Range.new(expr))
+ end
+ end
+
+ def initialize_copy other
+ super
+ @orders = @orders.map { |x| x.clone }
+ end
+
+ def hash
+ [@orders, @framing].hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.orders == other.orders &&
+ self.framing == other.framing &&
+ self.partitions == other.partitions
+ end
+ alias :== :eql?
+ end
+
+ class NamedWindow < Window
+ attr_accessor :name
+
+ def initialize name
+ super()
+ @name = name
+ end
+
+ def initialize_copy other
+ super
+ @name = other.name.clone
+ end
+
+ def hash
+ super ^ @name.hash
+ end
+
+ def eql? other
+ super && self.name == other.name
+ end
+ alias :== :eql?
+ end
+
+ class Rows < Unary
+ def initialize(expr = nil)
+ super(expr)
+ end
+ end
+
+ class Range < Unary
+ def initialize(expr = nil)
+ super(expr)
+ end
+ end
+
+ class CurrentRow < Node
+ def hash
+ self.class.hash
+ end
+
+ def eql? other
+ self.class == other.class
+ end
+ alias :== :eql?
+ end
+
+ class Preceding < Unary
+ def initialize(expr = nil)
+ super(expr)
+ end
+ end
+
+ class Following < Unary
+ def initialize(expr = nil)
+ super(expr)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/nodes/with.rb b/activerecord/lib/arel/nodes/with.rb
new file mode 100644
index 0000000000..def7840ea3
--- /dev/null
+++ b/activerecord/lib/arel/nodes/with.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+module Arel
+ module Nodes
+ class With < Arel::Nodes::Unary
+ alias children expr
+ end
+
+ class WithRecursive < With; end
+ end
+end
+
diff --git a/activerecord/lib/arel/order_predications.rb b/activerecord/lib/arel/order_predications.rb
new file mode 100644
index 0000000000..d84be82bb6
--- /dev/null
+++ b/activerecord/lib/arel/order_predications.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+module Arel
+ module OrderPredications
+
+ def asc
+ Nodes::Ascending.new self
+ end
+
+ def desc
+ Nodes::Descending.new self
+ end
+
+ end
+end
diff --git a/activerecord/lib/arel/predications.rb b/activerecord/lib/arel/predications.rb
new file mode 100644
index 0000000000..799c7c67b8
--- /dev/null
+++ b/activerecord/lib/arel/predications.rb
@@ -0,0 +1,240 @@
+# frozen_string_literal: true
+module Arel
+ module Predications
+ def not_eq other
+ Nodes::NotEqual.new self, quoted_node(other)
+ end
+
+ def not_eq_any others
+ grouping_any :not_eq, others
+ end
+
+ def not_eq_all others
+ grouping_all :not_eq, others
+ end
+
+ def eq other
+ Nodes::Equality.new self, quoted_node(other)
+ end
+
+ def eq_any others
+ grouping_any :eq, others
+ end
+
+ def eq_all others
+ grouping_all :eq, quoted_array(others)
+ end
+
+ def between other
+ if equals_quoted?(other.begin, -Float::INFINITY)
+ if equals_quoted?(other.end, Float::INFINITY)
+ not_in([])
+ elsif other.exclude_end?
+ lt(other.end)
+ else
+ lteq(other.end)
+ end
+ elsif equals_quoted?(other.end, Float::INFINITY)
+ gteq(other.begin)
+ elsif other.exclude_end?
+ gteq(other.begin).and(lt(other.end))
+ else
+ left = quoted_node(other.begin)
+ right = quoted_node(other.end)
+ Nodes::Between.new(self, left.and(right))
+ end
+ end
+
+ def in other
+ case other
+ when Arel::SelectManager
+ Arel::Nodes::In.new(self, other.ast)
+ when Range
+ if $VERBOSE
+ warn <<-eowarn
+Passing a range to `#in` is deprecated. Call `#between`, instead.
+ eowarn
+ end
+ between(other)
+ when Enumerable
+ Nodes::In.new self, quoted_array(other)
+ else
+ Nodes::In.new self, quoted_node(other)
+ end
+ end
+
+ def in_any others
+ grouping_any :in, others
+ end
+
+ def in_all others
+ grouping_all :in, others
+ end
+
+ def not_between other
+ if equals_quoted?(other.begin, -Float::INFINITY)
+ if equals_quoted?(other.end, Float::INFINITY)
+ self.in([])
+ elsif other.exclude_end?
+ gteq(other.end)
+ else
+ gt(other.end)
+ end
+ elsif equals_quoted?(other.end, Float::INFINITY)
+ lt(other.begin)
+ else
+ left = lt(other.begin)
+ right = if other.exclude_end?
+ gteq(other.end)
+ else
+ gt(other.end)
+ end
+ left.or(right)
+ end
+ end
+
+ def not_in other
+ case other
+ when Arel::SelectManager
+ Arel::Nodes::NotIn.new(self, other.ast)
+ when Range
+ if $VERBOSE
+ warn <<-eowarn
+Passing a range to `#not_in` is deprecated. Call `#not_between`, instead.
+ eowarn
+ end
+ not_between(other)
+ when Enumerable
+ Nodes::NotIn.new self, quoted_array(other)
+ else
+ Nodes::NotIn.new self, quoted_node(other)
+ end
+ end
+
+ def not_in_any others
+ grouping_any :not_in, others
+ end
+
+ def not_in_all others
+ grouping_all :not_in, others
+ end
+
+ def matches other, escape = nil, case_sensitive = false
+ Nodes::Matches.new self, quoted_node(other), escape, case_sensitive
+ end
+
+ def matches_regexp other, case_sensitive = true
+ Nodes::Regexp.new self, quoted_node(other), case_sensitive
+ end
+
+ def matches_any others, escape = nil, case_sensitive = false
+ grouping_any :matches, others, escape, case_sensitive
+ end
+
+ def matches_all others, escape = nil, case_sensitive = false
+ grouping_all :matches, others, escape, case_sensitive
+ end
+
+ def does_not_match other, escape = nil, case_sensitive = false
+ Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive
+ end
+
+ def does_not_match_regexp other, case_sensitive = true
+ Nodes::NotRegexp.new self, quoted_node(other), case_sensitive
+ end
+
+ def does_not_match_any others, escape = nil
+ grouping_any :does_not_match, others, escape
+ end
+
+ def does_not_match_all others, escape = nil
+ grouping_all :does_not_match, others, escape
+ end
+
+ def gteq right
+ Nodes::GreaterThanOrEqual.new self, quoted_node(right)
+ end
+
+ def gteq_any others
+ grouping_any :gteq, others
+ end
+
+ def gteq_all others
+ grouping_all :gteq, others
+ end
+
+ def gt right
+ Nodes::GreaterThan.new self, quoted_node(right)
+ end
+
+ def gt_any others
+ grouping_any :gt, others
+ end
+
+ def gt_all others
+ grouping_all :gt, others
+ end
+
+ def lt right
+ Nodes::LessThan.new self, quoted_node(right)
+ end
+
+ def lt_any others
+ grouping_any :lt, others
+ end
+
+ def lt_all others
+ grouping_all :lt, others
+ end
+
+ def lteq right
+ Nodes::LessThanOrEqual.new self, quoted_node(right)
+ end
+
+ def lteq_any others
+ grouping_any :lteq, others
+ end
+
+ def lteq_all others
+ grouping_all :lteq, others
+ end
+
+ def when right
+ Nodes::Case.new(self).when quoted_node(right)
+ end
+
+ def concat other
+ Nodes::Concat.new self, other
+ end
+
+ private
+
+ def grouping_any method_id, others, *extras
+ nodes = others.map {|expr| send(method_id, expr, *extras)}
+ Nodes::Grouping.new nodes.inject { |memo,node|
+ Nodes::Or.new(memo, node)
+ }
+ end
+
+ def grouping_all method_id, others, *extras
+ nodes = others.map {|expr| send(method_id, expr, *extras)}
+ Nodes::Grouping.new Nodes::And.new(nodes)
+ end
+
+ def quoted_node(other)
+ Nodes.build_quoted(other, self)
+ end
+
+ def quoted_array(others)
+ others.map { |v| quoted_node(v) }
+ end
+
+ def equals_quoted?(maybe_quoted, value)
+ if maybe_quoted.is_a?(Nodes::Quoted)
+ maybe_quoted.val == value
+ else
+ maybe_quoted == value
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/select_manager.rb b/activerecord/lib/arel/select_manager.rb
new file mode 100644
index 0000000000..0f3b0dc6a0
--- /dev/null
+++ b/activerecord/lib/arel/select_manager.rb
@@ -0,0 +1,272 @@
+# frozen_string_literal: true
+module Arel
+ class SelectManager < Arel::TreeManager
+ include Arel::Crud
+
+ STRING_OR_SYMBOL_CLASS = [Symbol, String]
+
+ def initialize table = nil
+ super()
+ @ast = Nodes::SelectStatement.new
+ @ctx = @ast.cores.last
+ from table
+ end
+
+ def initialize_copy other
+ super
+ @ctx = @ast.cores.last
+ end
+
+ def limit
+ @ast.limit && @ast.limit.expr
+ end
+ alias :taken :limit
+
+ def constraints
+ @ctx.wheres
+ end
+
+ def offset
+ @ast.offset && @ast.offset.expr
+ end
+
+ def skip amount
+ if amount
+ @ast.offset = Nodes::Offset.new(amount)
+ else
+ @ast.offset = nil
+ end
+ self
+ end
+ alias :offset= :skip
+
+ ###
+ # Produces an Arel::Nodes::Exists node
+ def exists
+ Arel::Nodes::Exists.new @ast
+ end
+
+ def as other
+ create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
+ end
+
+ def lock locking = Arel.sql('FOR UPDATE')
+ case locking
+ when true
+ locking = Arel.sql('FOR UPDATE')
+ when Arel::Nodes::SqlLiteral
+ when String
+ locking = Arel.sql locking
+ end
+
+ @ast.lock = Nodes::Lock.new(locking)
+ self
+ end
+
+ def locked
+ @ast.lock
+ end
+
+ def on *exprs
+ @ctx.source.right.last.right = Nodes::On.new(collapse(exprs))
+ self
+ end
+
+ def group *columns
+ columns.each do |column|
+ # FIXME: backwards compat
+ column = Nodes::SqlLiteral.new(column) if String === column
+ column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
+
+ @ctx.groups.push Nodes::Group.new column
+ end
+ self
+ end
+
+ def from table
+ table = Nodes::SqlLiteral.new(table) if String === table
+
+ case table
+ when Nodes::Join
+ @ctx.source.right << table
+ else
+ @ctx.source.left = table
+ end
+
+ self
+ end
+
+ def froms
+ @ast.cores.map { |x| x.from }.compact
+ end
+
+ def join relation, klass = Nodes::InnerJoin
+ return self unless relation
+
+ case relation
+ when String, Nodes::SqlLiteral
+ raise EmptyJoinError if relation.empty?
+ klass = Nodes::StringJoin
+ end
+
+ @ctx.source.right << create_join(relation, nil, klass)
+ self
+ end
+
+ def outer_join relation
+ join(relation, Nodes::OuterJoin)
+ end
+
+ def having expr
+ @ctx.havings << expr
+ self
+ end
+
+ def window name
+ window = Nodes::NamedWindow.new(name)
+ @ctx.windows.push window
+ window
+ end
+
+ def project *projections
+ # FIXME: converting these to SQLLiterals is probably not good, but
+ # rails tests require it.
+ @ctx.projections.concat projections.map { |x|
+ STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
+ }
+ self
+ end
+
+ def projections
+ @ctx.projections
+ end
+
+ def projections= projections
+ @ctx.projections = projections
+ end
+
+ def distinct(value = true)
+ if value
+ @ctx.set_quantifier = Arel::Nodes::Distinct.new
+ else
+ @ctx.set_quantifier = nil
+ end
+ self
+ end
+
+ def distinct_on(value)
+ if value
+ @ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value)
+ else
+ @ctx.set_quantifier = nil
+ end
+ self
+ end
+
+ def order *expr
+ # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
+ @ast.orders.concat expr.map { |x|
+ STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
+ }
+ self
+ end
+
+ def orders
+ @ast.orders
+ end
+
+ def where_sql engine = Table.engine
+ return if @ctx.wheres.empty?
+
+ viz = Visitors::WhereSql.new(engine.connection.visitor, engine.connection)
+ Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value
+ end
+
+ def union operation, other = nil
+ if other
+ node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
+ else
+ other = operation
+ node_class = Nodes::Union
+ end
+
+ node_class.new self.ast, other.ast
+ end
+
+ def intersect other
+ Nodes::Intersect.new ast, other.ast
+ end
+
+ def except other
+ Nodes::Except.new ast, other.ast
+ end
+ alias :minus :except
+
+ def lateral table_name = nil
+ base = table_name.nil? ? ast : as(table_name)
+ Nodes::Lateral.new(base)
+ end
+
+ def with *subqueries
+ if subqueries.first.is_a? Symbol
+ node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}")
+ else
+ node_class = Nodes::With
+ end
+ @ast.with = node_class.new(subqueries.flatten)
+
+ self
+ end
+
+ def take limit
+ if limit
+ @ast.limit = Nodes::Limit.new(limit)
+ @ctx.top = Nodes::Top.new(limit)
+ else
+ @ast.limit = nil
+ @ctx.top = nil
+ end
+ self
+ end
+ alias limit= take
+
+ def join_sources
+ @ctx.source.right
+ end
+
+ def source
+ @ctx.source
+ end
+
+ class Row < Struct.new(:data) # :nodoc:
+ def id
+ data['id']
+ end
+
+ def method_missing(name, *args)
+ name = name.to_s
+ return data[name] if data.key?(name)
+ super
+ end
+ end
+
+ private
+ def collapse exprs, existing = nil
+ exprs = exprs.unshift(existing.expr) if existing
+ exprs = exprs.compact.map { |expr|
+ if String === expr
+ # FIXME: Don't do this automatically
+ Arel.sql(expr)
+ else
+ expr
+ end
+ }
+
+ if exprs.length == 1
+ exprs.first
+ else
+ create_and exprs
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/table.rb b/activerecord/lib/arel/table.rb
new file mode 100644
index 0000000000..b3f2d79e5f
--- /dev/null
+++ b/activerecord/lib/arel/table.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+module Arel
+ class Table
+ include Arel::Crud
+ include Arel::FactoryMethods
+
+ @engine = nil
+ class << self; attr_accessor :engine; end
+
+ attr_accessor :name, :table_alias
+
+ # TableAlias and Table both have a #table_name which is the name of the underlying table
+ alias :table_name :name
+
+ def initialize(name, as: nil, type_caster: nil)
+ @name = name.to_s
+ @type_caster = type_caster
+
+ # Sometime AR sends an :as parameter to table, to let the table know
+ # that it is an Alias. We may want to override new, and return a
+ # TableAlias node?
+ if as.to_s == @name
+ as = nil
+ end
+ @table_alias = as
+ end
+
+ def alias name = "#{self.name}_2"
+ Nodes::TableAlias.new(self, name)
+ end
+
+ def from
+ SelectManager.new(self)
+ end
+
+ def join relation, klass = Nodes::InnerJoin
+ return from unless relation
+
+ case relation
+ when String, Nodes::SqlLiteral
+ raise EmptyJoinError if relation.empty?
+ klass = Nodes::StringJoin
+ end
+
+ from.join(relation, klass)
+ end
+
+ def outer_join relation
+ join(relation, Nodes::OuterJoin)
+ end
+
+ def group *columns
+ from.group(*columns)
+ end
+
+ def order *expr
+ from.order(*expr)
+ end
+
+ def where condition
+ from.where condition
+ end
+
+ def project *things
+ from.project(*things)
+ end
+
+ def take amount
+ from.take amount
+ end
+
+ def skip amount
+ from.skip amount
+ end
+
+ def having expr
+ from.having expr
+ end
+
+ def [] name
+ ::Arel::Attribute.new self, name
+ end
+
+ def hash
+ # Perf note: aliases and table alias is excluded from the hash
+ # aliases can have a loop back to this table breaking hashes in parent
+ # relations, for the vast majority of cases @name is unique to a query
+ @name.hash
+ end
+
+ def eql? other
+ self.class == other.class &&
+ self.name == other.name &&
+ self.table_alias == other.table_alias
+ end
+ alias :== :eql?
+
+ def type_cast_for_database(attribute_name, value)
+ type_caster.type_cast_for_database(attribute_name, value)
+ end
+
+ def able_to_type_cast?
+ !type_caster.nil?
+ end
+
+ protected
+
+ attr_reader :type_caster
+ end
+end
diff --git a/activerecord/lib/arel/tree_manager.rb b/activerecord/lib/arel/tree_manager.rb
new file mode 100644
index 0000000000..b237bf368d
--- /dev/null
+++ b/activerecord/lib/arel/tree_manager.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+module Arel
+ class TreeManager
+ include Arel::FactoryMethods
+
+ attr_reader :ast
+
+ def initialize
+ @ctx = nil
+ end
+
+ def to_dot
+ collector = Arel::Collectors::PlainString.new
+ collector = Visitors::Dot.new.accept @ast, collector
+ collector.value
+ end
+
+ def to_sql engine = Table.engine
+ collector = Arel::Collectors::SQLString.new
+ collector = engine.connection.visitor.accept @ast, collector
+ collector.value
+ end
+
+ def initialize_copy other
+ super
+ @ast = @ast.clone
+ end
+
+ def where expr
+ if Arel::TreeManager === expr
+ expr = expr.ast
+ end
+ @ctx.wheres << expr
+ self
+ end
+ end
+end
diff --git a/activerecord/lib/arel/update_manager.rb b/activerecord/lib/arel/update_manager.rb
new file mode 100644
index 0000000000..eac414eafb
--- /dev/null
+++ b/activerecord/lib/arel/update_manager.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+module Arel
+ class UpdateManager < Arel::TreeManager
+ def initialize
+ super
+ @ast = Nodes::UpdateStatement.new
+ @ctx = @ast
+ end
+
+ def take limit
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
+ self
+ end
+
+ def key= key
+ @ast.key = Nodes.build_quoted(key)
+ end
+
+ def key
+ @ast.key
+ end
+
+ def order *expr
+ @ast.orders = expr
+ self
+ end
+
+ ###
+ # UPDATE +table+
+ def table table
+ @ast.relation = table
+ self
+ end
+
+ def wheres= exprs
+ @ast.wheres = exprs
+ end
+
+ def where expr
+ @ast.wheres << expr
+ self
+ end
+
+ def set values
+ if String === values
+ @ast.values = [values]
+ else
+ @ast.values = values.map { |column,value|
+ Nodes::Assignment.new(
+ Nodes::UnqualifiedColumn.new(column),
+ value
+ )
+ }
+ end
+ self
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors.rb b/activerecord/lib/arel/visitors.rb
new file mode 100644
index 0000000000..a3404cf992
--- /dev/null
+++ b/activerecord/lib/arel/visitors.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+require 'arel/visitors/visitor'
+require 'arel/visitors/depth_first'
+require 'arel/visitors/to_sql'
+require 'arel/visitors/sqlite'
+require 'arel/visitors/postgresql'
+require 'arel/visitors/mysql'
+require 'arel/visitors/mssql'
+require 'arel/visitors/oracle'
+require 'arel/visitors/oracle12'
+require 'arel/visitors/where_sql'
+require 'arel/visitors/dot'
+require 'arel/visitors/ibm_db'
+require 'arel/visitors/informix'
+
+module Arel
+ module Visitors
+ end
+end
diff --git a/activerecord/lib/arel/visitors/depth_first.rb b/activerecord/lib/arel/visitors/depth_first.rb
new file mode 100644
index 0000000000..b3bbc9bd40
--- /dev/null
+++ b/activerecord/lib/arel/visitors/depth_first.rb
@@ -0,0 +1,199 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class DepthFirst < Arel::Visitors::Visitor
+ def initialize block = nil
+ @block = block || Proc.new
+ super()
+ end
+
+ private
+
+ def visit o
+ super
+ @block.call o
+ end
+
+ def unary o
+ visit o.expr
+ end
+ alias :visit_Arel_Nodes_Else :unary
+ alias :visit_Arel_Nodes_Group :unary
+ alias :visit_Arel_Nodes_Cube :unary
+ alias :visit_Arel_Nodes_RollUp :unary
+ alias :visit_Arel_Nodes_GroupingSet :unary
+ alias :visit_Arel_Nodes_GroupingElement :unary
+ alias :visit_Arel_Nodes_Grouping :unary
+ alias :visit_Arel_Nodes_Having :unary
+ alias :visit_Arel_Nodes_Lateral :unary
+ alias :visit_Arel_Nodes_Limit :unary
+ alias :visit_Arel_Nodes_Not :unary
+ alias :visit_Arel_Nodes_Offset :unary
+ alias :visit_Arel_Nodes_On :unary
+ alias :visit_Arel_Nodes_Ordering :unary
+ alias :visit_Arel_Nodes_Ascending :unary
+ alias :visit_Arel_Nodes_Descending :unary
+ alias :visit_Arel_Nodes_Top :unary
+ alias :visit_Arel_Nodes_UnqualifiedColumn :unary
+
+ def function o
+ visit o.expressions
+ visit o.alias
+ visit o.distinct
+ end
+ alias :visit_Arel_Nodes_Avg :function
+ alias :visit_Arel_Nodes_Exists :function
+ alias :visit_Arel_Nodes_Max :function
+ alias :visit_Arel_Nodes_Min :function
+ alias :visit_Arel_Nodes_Sum :function
+
+ def visit_Arel_Nodes_NamedFunction o
+ visit o.name
+ visit o.expressions
+ visit o.distinct
+ visit o.alias
+ end
+
+ def visit_Arel_Nodes_Count o
+ visit o.expressions
+ visit o.alias
+ visit o.distinct
+ end
+
+ def visit_Arel_Nodes_Case o
+ visit o.case
+ visit o.conditions
+ visit o.default
+ end
+
+ def nary o
+ o.children.each { |child| visit child}
+ end
+ alias :visit_Arel_Nodes_And :nary
+
+ def binary o
+ visit o.left
+ visit o.right
+ end
+ alias :visit_Arel_Nodes_As :binary
+ alias :visit_Arel_Nodes_Assignment :binary
+ alias :visit_Arel_Nodes_Between :binary
+ alias :visit_Arel_Nodes_Concat :binary
+ alias :visit_Arel_Nodes_DeleteStatement :binary
+ alias :visit_Arel_Nodes_DoesNotMatch :binary
+ alias :visit_Arel_Nodes_Equality :binary
+ alias :visit_Arel_Nodes_FullOuterJoin :binary
+ alias :visit_Arel_Nodes_GreaterThan :binary
+ alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
+ alias :visit_Arel_Nodes_In :binary
+ alias :visit_Arel_Nodes_InfixOperation :binary
+ alias :visit_Arel_Nodes_JoinSource :binary
+ alias :visit_Arel_Nodes_InnerJoin :binary
+ alias :visit_Arel_Nodes_LessThan :binary
+ alias :visit_Arel_Nodes_LessThanOrEqual :binary
+ alias :visit_Arel_Nodes_Matches :binary
+ alias :visit_Arel_Nodes_NotEqual :binary
+ alias :visit_Arel_Nodes_NotIn :binary
+ alias :visit_Arel_Nodes_NotRegexp :binary
+ alias :visit_Arel_Nodes_Or :binary
+ alias :visit_Arel_Nodes_OuterJoin :binary
+ alias :visit_Arel_Nodes_Regexp :binary
+ alias :visit_Arel_Nodes_RightOuterJoin :binary
+ alias :visit_Arel_Nodes_TableAlias :binary
+ alias :visit_Arel_Nodes_Values :binary
+ alias :visit_Arel_Nodes_When :binary
+
+ def visit_Arel_Nodes_StringJoin o
+ visit o.left
+ end
+
+ def visit_Arel_Attribute o
+ visit o.relation
+ visit o.name
+ end
+ alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_String :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Decimal :visit_Arel_Attribute
+
+ def visit_Arel_Table o
+ visit o.name
+ end
+
+ def terminal o
+ end
+ alias :visit_ActiveSupport_Multibyte_Chars :terminal
+ alias :visit_ActiveSupport_StringInquirer :terminal
+ alias :visit_Arel_Nodes_Lock :terminal
+ alias :visit_Arel_Nodes_Node :terminal
+ alias :visit_Arel_Nodes_SqlLiteral :terminal
+ alias :visit_Arel_Nodes_BindParam :terminal
+ alias :visit_Arel_Nodes_Window :terminal
+ alias :visit_Arel_Nodes_True :terminal
+ alias :visit_Arel_Nodes_False :terminal
+ alias :visit_BigDecimal :terminal
+ alias :visit_Bignum :terminal
+ alias :visit_Class :terminal
+ alias :visit_Date :terminal
+ alias :visit_DateTime :terminal
+ alias :visit_FalseClass :terminal
+ alias :visit_Fixnum :terminal
+ alias :visit_Float :terminal
+ alias :visit_Integer :terminal
+ alias :visit_NilClass :terminal
+ alias :visit_String :terminal
+ alias :visit_Symbol :terminal
+ alias :visit_Time :terminal
+ alias :visit_TrueClass :terminal
+
+ def visit_Arel_Nodes_InsertStatement o
+ visit o.relation
+ visit o.columns
+ visit o.values
+ end
+
+ def visit_Arel_Nodes_SelectCore o
+ visit o.projections
+ visit o.source
+ visit o.wheres
+ visit o.groups
+ visit o.windows
+ visit o.havings
+ end
+
+ def visit_Arel_Nodes_SelectStatement o
+ visit o.cores
+ visit o.orders
+ visit o.limit
+ visit o.lock
+ visit o.offset
+ end
+
+ def visit_Arel_Nodes_UpdateStatement o
+ visit o.relation
+ visit o.values
+ visit o.wheres
+ visit o.orders
+ visit o.limit
+ end
+
+ def visit_Array o
+ o.each { |i| visit i }
+ end
+ alias :visit_Set :visit_Array
+
+ def visit_Hash o
+ o.each { |k,v| visit(k); visit(v) }
+ end
+
+ DISPATCH = dispatch_cache
+
+ def get_dispatch_cache
+ DISPATCH
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/dot.rb b/activerecord/lib/arel/visitors/dot.rb
new file mode 100644
index 0000000000..9aa22d33f6
--- /dev/null
+++ b/activerecord/lib/arel/visitors/dot.rb
@@ -0,0 +1,291 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class Dot < Arel::Visitors::Visitor
+ class Node # :nodoc:
+ attr_accessor :name, :id, :fields
+
+ def initialize name, id, fields = []
+ @name = name
+ @id = id
+ @fields = fields
+ end
+ end
+
+ class Edge < Struct.new :name, :from, :to # :nodoc:
+ end
+
+ def initialize
+ super()
+ @nodes = []
+ @edges = []
+ @node_stack = []
+ @edge_stack = []
+ @seen = {}
+ end
+
+ def accept object, collector
+ visit object
+ collector << to_dot
+ end
+
+ private
+
+ def visit_Arel_Nodes_Ordering o
+ visit_edge o, "expr"
+ end
+
+ def visit_Arel_Nodes_TableAlias o
+ visit_edge o, "name"
+ visit_edge o, "relation"
+ end
+
+ def visit_Arel_Nodes_Count o
+ visit_edge o, "expressions"
+ visit_edge o, "distinct"
+ end
+
+ def visit_Arel_Nodes_Values o
+ visit_edge o, "expressions"
+ end
+
+ def visit_Arel_Nodes_StringJoin o
+ visit_edge o, "left"
+ end
+
+ def visit_Arel_Nodes_InnerJoin o
+ visit_edge o, "left"
+ visit_edge o, "right"
+ end
+ alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_InnerJoin
+ alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
+ alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_InnerJoin
+
+ def visit_Arel_Nodes_DeleteStatement o
+ visit_edge o, "relation"
+ visit_edge o, "wheres"
+ end
+
+ def unary o
+ visit_edge o, "expr"
+ end
+ alias :visit_Arel_Nodes_Group :unary
+ alias :visit_Arel_Nodes_Cube :unary
+ alias :visit_Arel_Nodes_RollUp :unary
+ alias :visit_Arel_Nodes_GroupingSet :unary
+ alias :visit_Arel_Nodes_GroupingElement :unary
+ alias :visit_Arel_Nodes_Grouping :unary
+ alias :visit_Arel_Nodes_Having :unary
+ alias :visit_Arel_Nodes_Limit :unary
+ alias :visit_Arel_Nodes_Not :unary
+ alias :visit_Arel_Nodes_Offset :unary
+ alias :visit_Arel_Nodes_On :unary
+ alias :visit_Arel_Nodes_Top :unary
+ alias :visit_Arel_Nodes_UnqualifiedColumn :unary
+ alias :visit_Arel_Nodes_Preceding :unary
+ alias :visit_Arel_Nodes_Following :unary
+ alias :visit_Arel_Nodes_Rows :unary
+ alias :visit_Arel_Nodes_Range :unary
+
+ def window o
+ visit_edge o, "partitions"
+ visit_edge o, "orders"
+ visit_edge o, "framing"
+ end
+ alias :visit_Arel_Nodes_Window :window
+
+ def named_window o
+ visit_edge o, "partitions"
+ visit_edge o, "orders"
+ visit_edge o, "framing"
+ visit_edge o, "name"
+ end
+ alias :visit_Arel_Nodes_NamedWindow :named_window
+
+ def function o
+ visit_edge o, "expressions"
+ visit_edge o, "distinct"
+ visit_edge o, "alias"
+ end
+ alias :visit_Arel_Nodes_Exists :function
+ alias :visit_Arel_Nodes_Min :function
+ alias :visit_Arel_Nodes_Max :function
+ alias :visit_Arel_Nodes_Avg :function
+ alias :visit_Arel_Nodes_Sum :function
+
+ def extract o
+ visit_edge o, "expressions"
+ visit_edge o, "alias"
+ end
+ alias :visit_Arel_Nodes_Extract :extract
+
+ def visit_Arel_Nodes_NamedFunction o
+ visit_edge o, "name"
+ visit_edge o, "expressions"
+ visit_edge o, "distinct"
+ visit_edge o, "alias"
+ end
+
+ def visit_Arel_Nodes_InsertStatement o
+ visit_edge o, "relation"
+ visit_edge o, "columns"
+ visit_edge o, "values"
+ end
+
+ def visit_Arel_Nodes_SelectCore o
+ visit_edge o, "source"
+ visit_edge o, "projections"
+ visit_edge o, "wheres"
+ visit_edge o, "windows"
+ end
+
+ def visit_Arel_Nodes_SelectStatement o
+ visit_edge o, "cores"
+ visit_edge o, "limit"
+ visit_edge o, "orders"
+ visit_edge o, "offset"
+ end
+
+ def visit_Arel_Nodes_UpdateStatement o
+ visit_edge o, "relation"
+ visit_edge o, "wheres"
+ visit_edge o, "values"
+ end
+
+ def visit_Arel_Table o
+ visit_edge o, "name"
+ end
+
+ def visit_Arel_Nodes_Casted o
+ visit_edge o, 'val'
+ visit_edge o, 'attribute'
+ end
+
+ def visit_Arel_Attribute o
+ visit_edge o, "relation"
+ visit_edge o, "name"
+ end
+ alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_String :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
+ alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
+
+ def nary o
+ o.children.each_with_index do |x,i|
+ edge(i) { visit x }
+ end
+ end
+ alias :visit_Arel_Nodes_And :nary
+
+ def binary o
+ visit_edge o, "left"
+ visit_edge o, "right"
+ end
+ alias :visit_Arel_Nodes_As :binary
+ alias :visit_Arel_Nodes_Assignment :binary
+ alias :visit_Arel_Nodes_Between :binary
+ alias :visit_Arel_Nodes_Concat :binary
+ alias :visit_Arel_Nodes_DoesNotMatch :binary
+ alias :visit_Arel_Nodes_Equality :binary
+ alias :visit_Arel_Nodes_GreaterThan :binary
+ alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
+ alias :visit_Arel_Nodes_In :binary
+ alias :visit_Arel_Nodes_JoinSource :binary
+ alias :visit_Arel_Nodes_LessThan :binary
+ alias :visit_Arel_Nodes_LessThanOrEqual :binary
+ alias :visit_Arel_Nodes_Matches :binary
+ alias :visit_Arel_Nodes_NotEqual :binary
+ alias :visit_Arel_Nodes_NotIn :binary
+ alias :visit_Arel_Nodes_Or :binary
+ alias :visit_Arel_Nodes_Over :binary
+
+ def visit_String o
+ @node_stack.last.fields << o
+ end
+ alias :visit_Time :visit_String
+ alias :visit_Date :visit_String
+ alias :visit_DateTime :visit_String
+ alias :visit_NilClass :visit_String
+ alias :visit_TrueClass :visit_String
+ alias :visit_FalseClass :visit_String
+ alias :visit_Integer :visit_String
+ alias :visit_Fixnum :visit_String
+ alias :visit_BigDecimal :visit_String
+ alias :visit_Float :visit_String
+ alias :visit_Symbol :visit_String
+ alias :visit_Arel_Nodes_SqlLiteral :visit_String
+
+ def visit_Arel_Nodes_BindParam o; end
+
+ def visit_Hash o
+ o.each_with_index do |pair, i|
+ edge("pair_#{i}") { visit pair }
+ end
+ end
+
+ def visit_Array o
+ o.each_with_index do |x,i|
+ edge(i) { visit x }
+ end
+ end
+ alias :visit_Set :visit_Array
+
+ def visit_edge o, method
+ edge(method) { visit o.send(method) }
+ end
+
+ def visit o
+ if node = @seen[o.object_id]
+ @edge_stack.last.to = node
+ return
+ end
+
+ node = Node.new(o.class.name, o.object_id)
+ @seen[node.id] = node
+ @nodes << node
+ with_node node do
+ super
+ end
+ end
+
+ def edge name
+ edge = Edge.new(name, @node_stack.last)
+ @edge_stack.push edge
+ @edges << edge
+ yield
+ @edge_stack.pop
+ end
+
+ def with_node node
+ if edge = @edge_stack.last
+ edge.to = node
+ end
+
+ @node_stack.push node
+ yield
+ @node_stack.pop
+ end
+
+ def quote string
+ string.to_s.gsub('"', '\"')
+ end
+
+ def to_dot
+ "digraph \"Arel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
+ @nodes.map { |node|
+ label = "<f0>#{node.name}"
+
+ node.fields.each_with_index do |field, i|
+ label += "|<f#{i + 1}>#{quote field}"
+ end
+
+ "#{node.id} [label=\"#{label}\"];"
+ }.join("\n") + "\n" + @edges.map { |edge|
+ "#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.name}\"];"
+ }.join("\n") + "\n}"
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/ibm_db.rb b/activerecord/lib/arel/visitors/ibm_db.rb
new file mode 100644
index 0000000000..e85a5a08a7
--- /dev/null
+++ b/activerecord/lib/arel/visitors/ibm_db.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class IBM_DB < Arel::Visitors::ToSql
+ private
+
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "FETCH FIRST "
+ collector = visit o.expr, collector
+ collector << " ROWS ONLY"
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/informix.rb b/activerecord/lib/arel/visitors/informix.rb
new file mode 100644
index 0000000000..44b18b550e
--- /dev/null
+++ b/activerecord/lib/arel/visitors/informix.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class Informix < Arel::Visitors::ToSql
+ private
+ def visit_Arel_Nodes_SelectStatement o, collector
+ collector << "SELECT "
+ collector = maybe_visit o.offset, collector
+ collector = maybe_visit o.limit, collector
+ collector = o.cores.inject(collector) { |c,x|
+ visit_Arel_Nodes_SelectCore x, c
+ }
+ if o.orders.any?
+ collector << "ORDER BY "
+ collector = inject_join o.orders, collector, ", "
+ end
+ collector = maybe_visit o.lock, collector
+ end
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector = inject_join o.projections, collector, ", "
+ if o.source && !o.source.empty?
+ collector << " FROM "
+ collector = visit o.source, collector
+ end
+
+ if o.wheres.any?
+ collector << " WHERE "
+ collector = inject_join o.wheres, collector, " AND "
+ end
+
+ if o.groups.any?
+ collector << "GROUP BY "
+ collector = inject_join o.groups, collector, ", "
+ end
+
+ if o.havings.any?
+ collector << " HAVING "
+ collector = inject_join o.havings, collector, " AND "
+ end
+ collector
+ end
+
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "SKIP "
+ visit o.expr, collector
+ end
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "FIRST "
+ visit o.expr, collector
+ collector << " "
+ end
+ end
+ end
+end
+
diff --git a/activerecord/lib/arel/visitors/mssql.rb b/activerecord/lib/arel/visitors/mssql.rb
new file mode 100644
index 0000000000..8347d05d06
--- /dev/null
+++ b/activerecord/lib/arel/visitors/mssql.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class MSSQL < Arel::Visitors::ToSql
+ RowNumber = Struct.new :children
+
+ def initialize(*)
+ @primary_keys = {}
+ super
+ end
+
+ private
+
+ # `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
+ # "select top 10 distinct first_name from users", which is invalid query! it should be
+ # "select distinct top 10 first_name from users"
+ def visit_Arel_Nodes_Top o
+ ""
+ end
+
+ def visit_Arel_Visitors_MSSQL_RowNumber o, collector
+ collector << "ROW_NUMBER() OVER (ORDER BY "
+ inject_join(o.children, collector, ', ') << ") as _row_num"
+ end
+
+ def visit_Arel_Nodes_SelectStatement o, collector
+ if !o.limit && !o.offset
+ return super
+ end
+
+ is_select_count = false
+ o.cores.each { |x|
+ core_order_by = row_num_literal determine_order_by(o.orders, x)
+ if select_count? x
+ x.projections = [core_order_by]
+ is_select_count = true
+ else
+ x.projections << core_order_by
+ end
+ }
+
+ if is_select_count
+ # fixme count distinct wouldn't work with limit or offset
+ collector << "SELECT COUNT(1) as count_id FROM ("
+ end
+
+ collector << "SELECT _t.* FROM ("
+ collector = o.cores.inject(collector) { |c,x|
+ visit_Arel_Nodes_SelectCore x, c
+ }
+ collector << ") as _t WHERE #{get_offset_limit_clause(o)}"
+
+ if is_select_count
+ collector << ") AS subquery"
+ else
+ collector
+ end
+ end
+
+ def get_offset_limit_clause o
+ first_row = o.offset ? o.offset.expr.to_i + 1 : 1
+ last_row = o.limit ? o.limit.expr.to_i - 1 + first_row : nil
+ if last_row
+ " _row_num BETWEEN #{first_row} AND #{last_row}"
+ else
+ " _row_num >= #{first_row}"
+ end
+ end
+
+ def visit_Arel_Nodes_DeleteStatement o, collector
+ collector << 'DELETE '
+ if o.limit
+ collector << 'TOP ('
+ visit o.limit.expr, collector
+ collector << ') '
+ end
+ collector << 'FROM '
+ collector = visit o.relation, collector
+ if o.wheres.any?
+ collector << ' WHERE '
+ inject_join o.wheres, collector, AND
+ else
+ collector
+ end
+ end
+
+ def determine_order_by orders, x
+ if orders.any?
+ orders
+ elsif x.groups.any?
+ x.groups
+ else
+ pk = find_left_table_pk(x.froms)
+ pk ? [pk] : []
+ end
+ end
+
+ def row_num_literal order_by
+ RowNumber.new order_by
+ end
+
+ def select_count? x
+ x.projections.length == 1 && Arel::Nodes::Count === x.projections.first
+ end
+
+ # FIXME raise exception of there is no pk?
+ def find_left_table_pk o
+ if o.kind_of?(Arel::Nodes::Join)
+ find_left_table_pk(o.left)
+ elsif o.instance_of?(Arel::Table)
+ find_primary_key(o)
+ end
+ end
+
+ def find_primary_key(o)
+ @primary_keys[o.name] ||= begin
+ primary_key_name = @connection.primary_key(o.name)
+ # some tables might be without primary key
+ primary_key_name && o[primary_key_name]
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb
new file mode 100644
index 0000000000..4c734f6292
--- /dev/null
+++ b/activerecord/lib/arel/visitors/mysql.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class MySQL < Arel::Visitors::ToSql
+ private
+ def visit_Arel_Nodes_Union o, collector, suppress_parens = false
+ unless suppress_parens
+ collector << "( "
+ end
+
+ collector = case o.left
+ when Arel::Nodes::Union
+ visit_Arel_Nodes_Union o.left, collector, true
+ else
+ visit o.left, collector
+ end
+
+ collector << " UNION "
+
+ collector = case o.right
+ when Arel::Nodes::Union
+ visit_Arel_Nodes_Union o.right, collector, true
+ else
+ visit o.right, collector
+ end
+
+ if suppress_parens
+ collector
+ else
+ collector << " )"
+ end
+ end
+
+ def visit_Arel_Nodes_Bin o, collector
+ collector << "BINARY "
+ visit o.expr, collector
+ end
+
+ ###
+ # :'(
+ # http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
+ def visit_Arel_Nodes_SelectStatement o, collector
+ if o.offset && !o.limit
+ o.limit = Arel::Nodes::Limit.new(18446744073709551615)
+ end
+ super
+ end
+
+ def visit_Arel_Nodes_SelectCore o, collector
+ o.froms ||= Arel.sql('DUAL')
+ super
+ end
+
+ def visit_Arel_Nodes_UpdateStatement o, collector
+ collector << "UPDATE "
+ collector = visit o.relation, collector
+
+ unless o.values.empty?
+ collector << " SET "
+ collector = inject_join o.values, collector, ', '
+ end
+
+ unless o.wheres.empty?
+ collector << " WHERE "
+ collector = inject_join o.wheres, collector, ' AND '
+ end
+
+ unless o.orders.empty?
+ collector << " ORDER BY "
+ collector = inject_join o.orders, collector, ', '
+ end
+
+ maybe_visit o.limit, collector
+ end
+
+ def visit_Arel_Nodes_Concat o, collector
+ collector << " CONCAT("
+ visit o.left, collector
+ collector << ", "
+ visit o.right, collector
+ collector << ") "
+ collector
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/oracle.rb b/activerecord/lib/arel/visitors/oracle.rb
new file mode 100644
index 0000000000..d4749bbae3
--- /dev/null
+++ b/activerecord/lib/arel/visitors/oracle.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class Oracle < Arel::Visitors::ToSql
+ private
+
+ def visit_Arel_Nodes_SelectStatement o, collector
+ o = order_hacks(o)
+
+ # if need to select first records without ORDER BY and GROUP BY and without DISTINCT
+ # then can use simple ROWNUM in WHERE clause
+ if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && o.cores.first.set_quantifier.class.to_s !~ /Distinct/
+ o.cores.last.wheres.push Nodes::LessThanOrEqual.new(
+ Nodes::SqlLiteral.new('ROWNUM'), o.limit.expr
+ )
+ return super
+ end
+
+ if o.limit && o.offset
+ o = o.dup
+ limit = o.limit.expr
+ offset = o.offset
+ o.offset = nil
+ collector << "
+ SELECT * FROM (
+ SELECT raw_sql_.*, rownum raw_rnum_
+ FROM ("
+
+ collector = super(o, collector)
+
+ if offset.expr.is_a? Nodes::BindParam
+ collector << ') raw_sql_ WHERE rownum <= ('
+ collector = visit offset.expr, collector
+ collector << ' + '
+ collector = visit limit, collector
+ collector << ") ) WHERE raw_rnum_ > "
+ collector = visit offset.expr, collector
+ return collector
+ else
+ collector << ") raw_sql_
+ WHERE rownum <= #{offset.expr.to_i + limit}
+ )
+ WHERE "
+ return visit(offset, collector)
+ end
+ end
+
+ if o.limit
+ o = o.dup
+ limit = o.limit.expr
+ collector << "SELECT * FROM ("
+ collector = super(o, collector)
+ collector << ") WHERE ROWNUM <= "
+ return visit limit, collector
+ end
+
+ if o.offset
+ o = o.dup
+ offset = o.offset
+ o.offset = nil
+ collector << "SELECT * FROM (
+ SELECT raw_sql_.*, rownum raw_rnum_
+ FROM ("
+ collector = super(o, collector)
+ collector << ") raw_sql_
+ )
+ WHERE "
+ return visit offset, collector
+ end
+
+ super
+ end
+
+ def visit_Arel_Nodes_Limit o, collector
+ collector
+ end
+
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "raw_rnum_ > "
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ collector = infix_value o, collector, " MINUS "
+ collector << " )"
+ end
+
+ def visit_Arel_Nodes_UpdateStatement o, collector
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
+ if o.orders.any? && o.limit.nil?
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
+ # otherwise let the user deal with the error
+ o = o.dup
+ o.orders = []
+ end
+
+ super
+ end
+
+ ###
+ # Hacks for the order clauses specific to Oracle
+ def order_hacks o
+ return o if o.orders.empty?
+ return o unless o.cores.any? do |core|
+ core.projections.any? do |projection|
+ /FIRST_VALUE/ === projection
+ end
+ end
+ # Previous version with join and split broke ORDER BY clause
+ # if it contained functions with several arguments (separated by ',').
+ #
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
+ orders = o.orders.map do |x|
+ string = visit(x, Arel::Collectors::SQLString.new).value
+ if string.include?(',')
+ split_order_string(string)
+ else
+ string
+ end
+ end.flatten
+ o.orders = []
+ orders.each_with_index do |order, i|
+ o.orders <<
+ Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i === order}")
+ end
+ o
+ end
+
+ # Split string by commas but count opening and closing brackets
+ # and ignore commas inside brackets.
+ def split_order_string(string)
+ array = []
+ i = 0
+ string.split(',').each do |part|
+ if array[i]
+ array[i] << ',' << part
+ else
+ # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
+ array[i] = part.to_s
+ end
+ i += 1 if array[i].count('(') == array[i].count(')')
+ end
+ array
+ end
+
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind(o.value) { |i| ":a#{i}" }
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/oracle12.rb b/activerecord/lib/arel/visitors/oracle12.rb
new file mode 100644
index 0000000000..648047ae61
--- /dev/null
+++ b/activerecord/lib/arel/visitors/oracle12.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class Oracle12 < Arel::Visitors::ToSql
+ private
+
+ def visit_Arel_Nodes_SelectStatement o, collector
+ # Oracle does not allow LIMIT clause with select for update
+ if o.limit && o.lock
+ raise ArgumentError, <<-MSG
+ 'Combination of limit and lock is not supported.
+ because generated SQL statements
+ `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.`
+ MSG
+ end
+ super
+ end
+
+ def visit_Arel_Nodes_SelectOptions o, collector
+ collector = maybe_visit o.offset, collector
+ collector = maybe_visit o.limit, collector
+ collector = maybe_visit o.lock, collector
+ end
+
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "FETCH FIRST "
+ collector = visit o.expr, collector
+ collector << " ROWS ONLY"
+ end
+
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "OFFSET "
+ visit o.expr, collector
+ collector << " ROWS"
+ end
+
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ collector = infix_value o, collector, " MINUS "
+ collector << " )"
+ end
+
+ def visit_Arel_Nodes_UpdateStatement o, collector
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
+ if o.orders.any? && o.limit.nil?
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
+ # otherwise let the user deal with the error
+ o = o.dup
+ o.orders = []
+ end
+
+ super
+ end
+
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind(o.value) { |i| ":a#{i}" }
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/postgresql.rb b/activerecord/lib/arel/visitors/postgresql.rb
new file mode 100644
index 0000000000..047f71aaa6
--- /dev/null
+++ b/activerecord/lib/arel/visitors/postgresql.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class PostgreSQL < Arel::Visitors::ToSql
+ CUBE = 'CUBE'
+ ROLLUP = 'ROLLUP'
+ GROUPING_SET = 'GROUPING SET'
+ LATERAL = 'LATERAL'
+
+ private
+
+ def visit_Arel_Nodes_Matches o, collector
+ op = o.case_sensitive ? ' LIKE ' : ' ILIKE '
+ collector = infix_value o, collector, op
+ if o.escape
+ collector << ' ESCAPE '
+ visit o.escape, collector
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_DoesNotMatch o, collector
+ op = o.case_sensitive ? ' NOT LIKE ' : ' NOT ILIKE '
+ collector = infix_value o, collector, op
+ if o.escape
+ collector << ' ESCAPE '
+ visit o.escape, collector
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_Regexp o, collector
+ op = o.case_sensitive ? ' ~ ' : ' ~* '
+ infix_value o, collector, op
+ end
+
+ def visit_Arel_Nodes_NotRegexp o, collector
+ op = o.case_sensitive ? ' !~ ' : ' !~* '
+ infix_value o, collector, op
+ end
+
+ def visit_Arel_Nodes_DistinctOn o, collector
+ collector << "DISTINCT ON ( "
+ visit(o.expr, collector) << " )"
+ end
+
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind(o.value) { |i| "$#{i}" }
+ end
+
+ def visit_Arel_Nodes_GroupingElement o, collector
+ collector << "( "
+ visit(o.expr, collector) << " )"
+ end
+
+ def visit_Arel_Nodes_Cube o, collector
+ collector << CUBE
+ grouping_array_or_grouping_element o, collector
+ end
+
+ def visit_Arel_Nodes_RollUp o, collector
+ collector << ROLLUP
+ grouping_array_or_grouping_element o, collector
+ end
+
+ def visit_Arel_Nodes_GroupingSet o, collector
+ collector << GROUPING_SET
+ grouping_array_or_grouping_element o, collector
+ end
+
+ def visit_Arel_Nodes_Lateral o, collector
+ collector << LATERAL
+ collector << SPACE
+ grouping_parentheses o, collector
+ end
+
+ # Used by Lateral visitor to enclose select queries in parentheses
+ def grouping_parentheses o, collector
+ if o.expr.is_a? Nodes::SelectStatement
+ collector << "("
+ visit o.expr, collector
+ collector << ")"
+ else
+ visit o.expr, collector
+ end
+ end
+
+ # Utilized by GroupingSet, Cube & RollUp visitors to
+ # handle grouping aggregation semantics
+ def grouping_array_or_grouping_element o, collector
+ if o.expr.is_a? Array
+ collector << "( "
+ visit o.expr, collector
+ collector << " )"
+ else
+ visit o.expr, collector
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/sqlite.rb b/activerecord/lib/arel/visitors/sqlite.rb
new file mode 100644
index 0000000000..4ae093968b
--- /dev/null
+++ b/activerecord/lib/arel/visitors/sqlite.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class SQLite < Arel::Visitors::ToSql
+ private
+
+ # Locks are not supported in SQLite
+ def visit_Arel_Nodes_Lock o, collector
+ collector
+ end
+
+ def visit_Arel_Nodes_SelectStatement o, collector
+ o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
+ super
+ end
+
+ def visit_Arel_Nodes_True o, collector
+ collector << "1"
+ end
+
+ def visit_Arel_Nodes_False o, collector
+ collector << "0"
+ end
+
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
new file mode 100644
index 0000000000..2b5c43b173
--- /dev/null
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -0,0 +1,846 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class UnsupportedVisitError < StandardError
+ def initialize(object)
+ super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead."
+ end
+ end
+
+ class ToSql < Arel::Visitors::Visitor
+ ##
+ # This is some roflscale crazy stuff. I'm roflscaling this because
+ # building SQL queries is a hotspot. I will explain the roflscale so that
+ # others will not rm this code.
+ #
+ # In YARV, string literals in a method body will get duped when the byte
+ # code is executed. Let's take a look:
+ #
+ # > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm
+ #
+ # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>=====
+ # 0000 trace 8
+ # 0002 trace 1
+ # 0004 putstring "bar"
+ # 0006 trace 16
+ # 0008 leave
+ #
+ # The `putstring` bytecode will dup the string and push it on the stack.
+ # In many cases in our SQL visitor, that string is never mutated, so there
+ # is no need to dup the literal.
+ #
+ # If we change to a constant lookup, the string will not be duped, and we
+ # can reduce the objects in our system:
+ #
+ # > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm
+ #
+ # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>========
+ # 0000 trace 8
+ # 0002 trace 1
+ # 0004 getinlinecache 11, <ic:0>
+ # 0007 getconstant :BAR
+ # 0009 setinlinecache <ic:0>
+ # 0011 trace 16
+ # 0013 leave
+ #
+ # `getconstant` should be a hash lookup, and no object is duped when the
+ # value of the constant is pushed on the stack. Hence the crazy
+ # constants below.
+ #
+ # `matches` and `doesNotMatch` operate case-insensitively via Visitor subclasses
+ # specialized for specific databases when necessary.
+ #
+
+ WHERE = ' WHERE ' # :nodoc:
+ SPACE = ' ' # :nodoc:
+ COMMA = ', ' # :nodoc:
+ GROUP_BY = ' GROUP BY ' # :nodoc:
+ ORDER_BY = ' ORDER BY ' # :nodoc:
+ WINDOW = ' WINDOW ' # :nodoc:
+ AND = ' AND ' # :nodoc:
+
+ DISTINCT = 'DISTINCT' # :nodoc:
+
+ def initialize connection
+ super()
+ @connection = connection
+ end
+
+ def compile node, &block
+ accept(node, Arel::Collectors::SQLString.new, &block).value
+ end
+
+ private
+
+ def visit_Arel_Nodes_DeleteStatement o, collector
+ collector << 'DELETE FROM '
+ collector = visit o.relation, collector
+ if o.wheres.any?
+ collector << WHERE
+ collector = inject_join o.wheres, collector, AND
+ end
+
+ maybe_visit o.limit, collector
+ end
+
+ # FIXME: we should probably have a 2-pass visitor for this
+ def build_subselect key, o
+ stmt = Nodes::SelectStatement.new
+ core = stmt.cores.first
+ core.froms = o.relation
+ core.wheres = o.wheres
+ core.projections = [key]
+ stmt.limit = o.limit
+ stmt.orders = o.orders
+ stmt
+ end
+
+ def visit_Arel_Nodes_UpdateStatement o, collector
+ if o.orders.empty? && o.limit.nil?
+ wheres = o.wheres
+ else
+ wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
+ end
+
+ collector << "UPDATE "
+ collector = visit o.relation, collector
+ unless o.values.empty?
+ collector << " SET "
+ collector = inject_join o.values, collector, ", "
+ end
+
+ unless wheres.empty?
+ collector << " WHERE "
+ collector = inject_join wheres, collector, " AND "
+ end
+
+ collector
+ end
+
+ def visit_Arel_Nodes_InsertStatement o, collector
+ collector << "INSERT INTO "
+ collector = visit o.relation, collector
+ if o.columns.any?
+ collector << " (#{o.columns.map { |x|
+ quote_column_name x.name
+ }.join ', '})"
+ end
+
+ if o.values
+ maybe_visit o.values, collector
+ elsif o.select
+ maybe_visit o.select, collector
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_Exists o, collector
+ collector << "EXISTS ("
+ collector = visit(o.expressions, collector) << ")"
+ if o.alias
+ collector << " AS "
+ visit o.alias, collector
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_Casted o, collector
+ collector << quoted(o.val, o.attribute).to_s
+ end
+
+ def visit_Arel_Nodes_Quoted o, collector
+ collector << quoted(o.expr, nil).to_s
+ end
+
+ def visit_Arel_Nodes_True o, collector
+ collector << "TRUE"
+ end
+
+ def visit_Arel_Nodes_False o, collector
+ collector << "FALSE"
+ end
+
+ def visit_Arel_Nodes_ValuesList o, collector
+ collector << "VALUES "
+
+ len = o.rows.length - 1
+ o.rows.each_with_index { |row, i|
+ collector << '('
+ row_len = row.length - 1
+ row.each_with_index do |value, k|
+ case value
+ when Nodes::SqlLiteral, Nodes::BindParam
+ collector = visit(value, collector)
+ else
+ collector << quote(value)
+ end
+ collector << COMMA unless k == row_len
+ end
+ collector << ')'
+ collector << COMMA unless i == len
+ }
+ collector
+ end
+
+ def visit_Arel_Nodes_Values o, collector
+ collector << "VALUES ("
+
+ len = o.expressions.length - 1
+ o.expressions.each_with_index { |value, i|
+ case value
+ when Nodes::SqlLiteral, Nodes::BindParam
+ collector = visit value, collector
+ else
+ collector << quote(value).to_s
+ end
+ unless i == len
+ collector << COMMA
+ end
+ }
+
+ collector << ")"
+ end
+
+ def visit_Arel_Nodes_SelectStatement o, collector
+ if o.with
+ collector = visit o.with, collector
+ collector << SPACE
+ end
+
+ collector = o.cores.inject(collector) { |c,x|
+ visit_Arel_Nodes_SelectCore(x, c)
+ }
+
+ unless o.orders.empty?
+ collector << ORDER_BY
+ len = o.orders.length - 1
+ o.orders.each_with_index { |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ }
+ end
+
+ visit_Arel_Nodes_SelectOptions(o, collector)
+
+ collector
+ end
+
+ def visit_Arel_Nodes_SelectOptions o, collector
+ collector = maybe_visit o.limit, collector
+ collector = maybe_visit o.offset, collector
+ collector = maybe_visit o.lock, collector
+ end
+
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector << "SELECT"
+
+ collector = maybe_visit o.top, collector
+
+ collector = maybe_visit o.set_quantifier, collector
+
+ collect_nodes_for o.projections, collector, SPACE
+
+ if o.source && !o.source.empty?
+ collector << " FROM "
+ collector = visit o.source, collector
+ end
+
+ collect_nodes_for o.wheres, collector, WHERE, AND
+ collect_nodes_for o.groups, collector, GROUP_BY
+ unless o.havings.empty?
+ collector << " HAVING "
+ inject_join o.havings, collector, AND
+ end
+ collect_nodes_for o.windows, collector, WINDOW
+
+ collector
+ end
+
+ def collect_nodes_for nodes, collector, spacer, connector = COMMA
+ unless nodes.empty?
+ collector << spacer
+ len = nodes.length - 1
+ nodes.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << connector unless len == i
+ end
+ end
+ end
+
+ def visit_Arel_Nodes_Bin o, collector
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_Distinct o, collector
+ collector << DISTINCT
+ end
+
+ def visit_Arel_Nodes_DistinctOn o, collector
+ raise NotImplementedError, 'DISTINCT ON not implemented for this db'
+ end
+
+ def visit_Arel_Nodes_With o, collector
+ collector << "WITH "
+ inject_join o.children, collector, COMMA
+ end
+
+ def visit_Arel_Nodes_WithRecursive o, collector
+ collector << "WITH RECURSIVE "
+ inject_join o.children, collector, COMMA
+ end
+
+ def visit_Arel_Nodes_Union o, collector
+ collector << "( "
+ infix_value(o, collector, " UNION ") << " )"
+ end
+
+ def visit_Arel_Nodes_UnionAll o, collector
+ collector << "( "
+ infix_value(o, collector, " UNION ALL ") << " )"
+ end
+
+ def visit_Arel_Nodes_Intersect o, collector
+ collector << "( "
+ infix_value(o, collector, " INTERSECT ") << " )"
+ end
+
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ infix_value(o, collector, " EXCEPT ") << " )"
+ end
+
+ def visit_Arel_Nodes_NamedWindow o, collector
+ collector << quote_column_name(o.name)
+ collector << " AS "
+ visit_Arel_Nodes_Window o, collector
+ end
+
+ def visit_Arel_Nodes_Window o, collector
+ collector << "("
+
+ if o.partitions.any?
+ collector << "PARTITION BY "
+ collector = inject_join o.partitions, collector, ", "
+ end
+
+ if o.orders.any?
+ collector << SPACE if o.partitions.any?
+ collector << "ORDER BY "
+ collector = inject_join o.orders, collector, ", "
+ end
+
+ if o.framing
+ collector << SPACE if o.partitions.any? or o.orders.any?
+ collector = visit o.framing, collector
+ end
+
+ collector << ")"
+ end
+
+ def visit_Arel_Nodes_Rows o, collector
+ if o.expr
+ collector << "ROWS "
+ visit o.expr, collector
+ else
+ collector << "ROWS"
+ end
+ end
+
+ def visit_Arel_Nodes_Range o, collector
+ if o.expr
+ collector << "RANGE "
+ visit o.expr, collector
+ else
+ collector << "RANGE"
+ end
+ end
+
+ def visit_Arel_Nodes_Preceding o, collector
+ collector = if o.expr
+ visit o.expr, collector
+ else
+ collector << "UNBOUNDED"
+ end
+
+ collector << " PRECEDING"
+ end
+
+ def visit_Arel_Nodes_Following o, collector
+ collector = if o.expr
+ visit o.expr, collector
+ else
+ collector << "UNBOUNDED"
+ end
+
+ collector << " FOLLOWING"
+ end
+
+ def visit_Arel_Nodes_CurrentRow o, collector
+ collector << "CURRENT ROW"
+ end
+
+ def visit_Arel_Nodes_Over o, collector
+ case o.right
+ when nil
+ visit(o.left, collector) << " OVER ()"
+ when Arel::Nodes::SqlLiteral
+ infix_value o, collector, " OVER "
+ when String, Symbol
+ visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}"
+ else
+ infix_value o, collector, " OVER "
+ end
+ end
+
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "OFFSET "
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "LIMIT "
+ visit o.expr, collector
+ end
+
+ # FIXME: this does nothing on most databases, but does on MSSQL
+ def visit_Arel_Nodes_Top o, collector
+ collector
+ end
+
+ def visit_Arel_Nodes_Lock o, collector
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_Grouping o, collector
+ if o.expr.is_a? Nodes::Grouping
+ visit(o.expr, collector)
+ else
+ collector << "("
+ visit(o.expr, collector) << ")"
+ end
+ end
+
+ def visit_Arel_SelectManager o, collector
+ collector << '('
+ visit(o.ast, collector) << ')'
+ end
+
+ def visit_Arel_Nodes_Ascending o, collector
+ visit(o.expr, collector) << " ASC"
+ end
+
+ def visit_Arel_Nodes_Descending o, collector
+ visit(o.expr, collector) << " DESC"
+ end
+
+ def visit_Arel_Nodes_Group o, collector
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_NamedFunction o, collector
+ collector << o.name
+ collector << "("
+ collector << "DISTINCT " if o.distinct
+ collector = inject_join(o.expressions, collector, ", ") << ")"
+ if o.alias
+ collector << " AS "
+ visit o.alias, collector
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_Extract o, collector
+ collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
+ visit(o.expr, collector) << ")"
+ end
+
+ def visit_Arel_Nodes_Count o, collector
+ aggregate "COUNT", o, collector
+ end
+
+ def visit_Arel_Nodes_Sum o, collector
+ aggregate "SUM", o, collector
+ end
+
+ def visit_Arel_Nodes_Max o, collector
+ aggregate "MAX", o, collector
+ end
+
+ def visit_Arel_Nodes_Min o, collector
+ aggregate "MIN", o, collector
+ end
+
+ def visit_Arel_Nodes_Avg o, collector
+ aggregate "AVG", o, collector
+ end
+
+ def visit_Arel_Nodes_TableAlias o, collector
+ collector = visit o.relation, collector
+ collector << " "
+ collector << quote_table_name(o.name)
+ end
+
+ def visit_Arel_Nodes_Between o, collector
+ collector = visit o.left, collector
+ collector << " BETWEEN "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_GreaterThanOrEqual o, collector
+ collector = visit o.left, collector
+ collector << " >= "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_GreaterThan o, collector
+ collector = visit o.left, collector
+ collector << " > "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_LessThanOrEqual o, collector
+ collector = visit o.left, collector
+ collector << " <= "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_LessThan o, collector
+ collector = visit o.left, collector
+ collector << " < "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_Matches o, collector
+ collector = visit o.left, collector
+ collector << " LIKE "
+ collector = visit o.right, collector
+ if o.escape
+ collector << ' ESCAPE '
+ visit o.escape, collector
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_DoesNotMatch o, collector
+ collector = visit o.left, collector
+ collector << " NOT LIKE "
+ collector = visit o.right, collector
+ if o.escape
+ collector << ' ESCAPE '
+ visit o.escape, collector
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_JoinSource o, collector
+ if o.left
+ collector = visit o.left, collector
+ end
+ if o.right.any?
+ collector << SPACE if o.left
+ collector = inject_join o.right, collector, SPACE
+ end
+ collector
+ end
+
+ def visit_Arel_Nodes_Regexp o, collector
+ raise NotImplementedError, '~ not implemented for this db'
+ end
+
+ def visit_Arel_Nodes_NotRegexp o, collector
+ raise NotImplementedError, '!~ not implemented for this db'
+ end
+
+ def visit_Arel_Nodes_StringJoin o, collector
+ visit o.left, collector
+ end
+
+ def visit_Arel_Nodes_FullOuterJoin o, collector
+ collector << "FULL OUTER JOIN "
+ collector = visit o.left, collector
+ collector << SPACE
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_OuterJoin o, collector
+ collector << "LEFT OUTER JOIN "
+ collector = visit o.left, collector
+ collector << " "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_RightOuterJoin o, collector
+ collector << "RIGHT OUTER JOIN "
+ collector = visit o.left, collector
+ collector << SPACE
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_InnerJoin o, collector
+ collector << "INNER JOIN "
+ collector = visit o.left, collector
+ if o.right
+ collector << SPACE
+ visit(o.right, collector)
+ else
+ collector
+ end
+ end
+
+ def visit_Arel_Nodes_On o, collector
+ collector << "ON "
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_Not o, collector
+ collector << "NOT ("
+ visit(o.expr, collector) << ")"
+ end
+
+ def visit_Arel_Table o, collector
+ if o.table_alias
+ collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
+ else
+ collector << quote_table_name(o.name)
+ end
+ end
+
+ def visit_Arel_Nodes_In o, collector
+ if Array === o.right && o.right.empty?
+ collector << '1=0'
+ else
+ collector = visit o.left, collector
+ collector << " IN ("
+ visit(o.right, collector) << ")"
+ end
+ end
+
+ def visit_Arel_Nodes_NotIn o, collector
+ if Array === o.right && o.right.empty?
+ collector << '1=1'
+ else
+ collector = visit o.left, collector
+ collector << " NOT IN ("
+ collector = visit o.right, collector
+ collector << ")"
+ end
+ end
+
+ def visit_Arel_Nodes_And o, collector
+ inject_join o.children, collector, " AND "
+ end
+
+ def visit_Arel_Nodes_Or o, collector
+ collector = visit o.left, collector
+ collector << " OR "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_Assignment o, collector
+ case o.right
+ when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute, Arel::Nodes::BindParam
+ collector = visit o.left, collector
+ collector << " = "
+ visit o.right, collector
+ else
+ collector = visit o.left, collector
+ collector << " = "
+ collector << quote(o.right).to_s
+ end
+ end
+
+ def visit_Arel_Nodes_Equality o, collector
+ right = o.right
+
+ collector = visit o.left, collector
+
+ if right.nil?
+ collector << " IS NULL"
+ else
+ collector << " = "
+ visit right, collector
+ end
+ end
+
+ def visit_Arel_Nodes_NotEqual o, collector
+ right = o.right
+
+ collector = visit o.left, collector
+
+ if right.nil?
+ collector << " IS NOT NULL"
+ else
+ collector << " != "
+ visit right, collector
+ end
+ end
+
+ def visit_Arel_Nodes_As o, collector
+ collector = visit o.left, collector
+ collector << " AS "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_Case o, collector
+ collector << "CASE "
+ if o.case
+ visit o.case, collector
+ collector << " "
+ end
+ o.conditions.each do |condition|
+ visit condition, collector
+ collector << " "
+ end
+ if o.default
+ visit o.default, collector
+ collector << " "
+ end
+ collector << "END"
+ end
+
+ def visit_Arel_Nodes_When o, collector
+ collector << "WHEN "
+ visit o.left, collector
+ collector << " THEN "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_Else o, collector
+ collector << "ELSE "
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_UnqualifiedColumn o, collector
+ collector << "#{quote_column_name o.name}"
+ collector
+ end
+
+ def visit_Arel_Attributes_Attribute o, collector
+ join_name = o.relation.table_alias || o.relation.name
+ collector << "#{quote_table_name join_name}.#{quote_column_name o.name}"
+ end
+ alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
+ alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
+ alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute
+ alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute
+ alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
+ alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
+
+ def literal o, collector; collector << o.to_s; end
+
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind(o.value) { "?" }
+ end
+
+ alias :visit_Arel_Nodes_SqlLiteral :literal
+ alias :visit_Bignum :literal
+ alias :visit_Fixnum :literal
+ alias :visit_Integer :literal
+
+ def quoted o, a
+ if a && a.able_to_type_cast?
+ quote(a.type_cast_for_database(o))
+ else
+ quote(o)
+ end
+ end
+
+ def unsupported o, collector
+ raise UnsupportedVisitError.new(o)
+ end
+
+ alias :visit_ActiveSupport_Multibyte_Chars :unsupported
+ alias :visit_ActiveSupport_StringInquirer :unsupported
+ alias :visit_BigDecimal :unsupported
+ alias :visit_Class :unsupported
+ alias :visit_Date :unsupported
+ alias :visit_DateTime :unsupported
+ alias :visit_FalseClass :unsupported
+ alias :visit_Float :unsupported
+ alias :visit_Hash :unsupported
+ alias :visit_NilClass :unsupported
+ alias :visit_String :unsupported
+ alias :visit_Symbol :unsupported
+ alias :visit_Time :unsupported
+ alias :visit_TrueClass :unsupported
+
+ def visit_Arel_Nodes_InfixOperation o, collector
+ collector = visit o.left, collector
+ collector << " #{o.operator} "
+ visit o.right, collector
+ end
+
+ alias :visit_Arel_Nodes_Addition :visit_Arel_Nodes_InfixOperation
+ alias :visit_Arel_Nodes_Subtraction :visit_Arel_Nodes_InfixOperation
+ alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
+ alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
+
+ def visit_Arel_Nodes_UnaryOperation o, collector
+ collector << " #{o.operator} "
+ visit o.expr, collector
+ end
+
+ def visit_Array o, collector
+ inject_join o, collector, ", "
+ end
+ alias :visit_Set :visit_Array
+
+ def quote value
+ return value if Arel::Nodes::SqlLiteral === value
+ @connection.quote value
+ end
+
+ def quote_table_name name
+ return name if Arel::Nodes::SqlLiteral === name
+ @connection.quote_table_name(name)
+ end
+
+ def quote_column_name name
+ return name if Arel::Nodes::SqlLiteral === name
+ @connection.quote_column_name(name)
+ end
+
+ def maybe_visit thing, collector
+ return collector unless thing
+ collector << " "
+ visit thing, collector
+ end
+
+ def inject_join list, collector, join_str
+ len = list.length - 1
+ list.each_with_index.inject(collector) { |c, (x,i)|
+ if i == len
+ visit x, c
+ else
+ visit(x, c) << join_str
+ end
+ }
+ end
+
+ def infix_value o, collector, value
+ collector = visit o.left, collector
+ collector << value
+ visit o.right, collector
+ end
+
+ def aggregate name, o, collector
+ collector << "#{name}("
+ if o.distinct
+ collector << "DISTINCT "
+ end
+ collector = inject_join(o.expressions, collector, ", ") << ")"
+ if o.alias
+ collector << " AS "
+ visit o.alias, collector
+ else
+ collector
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/visitor.rb b/activerecord/lib/arel/visitors/visitor.rb
new file mode 100644
index 0000000000..f156be9a0a
--- /dev/null
+++ b/activerecord/lib/arel/visitors/visitor.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class Visitor
+ def initialize
+ @dispatch = get_dispatch_cache
+ end
+
+ def accept object, *args
+ visit object, *args
+ end
+
+ private
+
+ attr_reader :dispatch
+
+ def self.dispatch_cache
+ Hash.new do |hash, klass|
+ hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
+ end
+ end
+
+ def get_dispatch_cache
+ self.class.dispatch_cache
+ end
+
+ def visit object, *args
+ dispatch_method = dispatch[object.class]
+ send dispatch_method, object, *args
+ rescue NoMethodError => e
+ raise e if respond_to?(dispatch_method, true)
+ superklass = object.class.ancestors.find { |klass|
+ respond_to?(dispatch[klass], true)
+ }
+ raise(TypeError, "Cannot visit #{object.class}") unless superklass
+ dispatch[object.class] = dispatch[superklass]
+ retry
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/visitors/where_sql.rb b/activerecord/lib/arel/visitors/where_sql.rb
new file mode 100644
index 0000000000..55e6ca9a21
--- /dev/null
+++ b/activerecord/lib/arel/visitors/where_sql.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+module Arel
+ module Visitors
+ class WhereSql < Arel::Visitors::ToSql
+ def initialize(inner_visitor, *args, &block)
+ @inner_visitor = inner_visitor
+ super(*args, &block)
+ end
+
+ private
+
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector << "WHERE "
+ wheres = o.wheres.map do |where|
+ Nodes::SqlLiteral.new(@inner_visitor.accept(where, collector.class.new).value)
+ end
+
+ inject_join wheres, collector, ' AND '
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/arel/window_predications.rb b/activerecord/lib/arel/window_predications.rb
new file mode 100644
index 0000000000..f93dede036
--- /dev/null
+++ b/activerecord/lib/arel/window_predications.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Arel
+ module WindowPredications
+
+ def over(expr = nil)
+ Nodes::Over.new(self, expr)
+ end
+
+ end
+end \ No newline at end of file