aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml17
-rw-r--r--History.txt6
-rw-r--r--README.md13
-rw-r--r--arel.gemspec5
-rw-r--r--arel.gemspec.erb3
-rw-r--r--lib/arel.rb2
-rw-r--r--lib/arel/collectors/bind.rb23
-rw-r--r--lib/arel/collectors/composite.rb32
-rw-r--r--lib/arel/collectors/sql_string.rb1
-rw-r--r--lib/arel/collectors/substitute_binds.rb28
-rw-r--r--lib/arel/insert_manager.rb4
-rw-r--r--lib/arel/nodes.rb2
-rw-r--r--lib/arel/nodes/binary.rb2
-rw-r--r--lib/arel/nodes/bind_param.rb21
-rw-r--r--lib/arel/nodes/case.rb4
-rw-r--r--lib/arel/nodes/casted.rb4
-rw-r--r--lib/arel/nodes/count.rb2
-rw-r--r--lib/arel/nodes/delete_statement.rb21
-rw-r--r--lib/arel/nodes/extract.rb3
-rw-r--r--lib/arel/nodes/false.rb3
-rw-r--r--lib/arel/nodes/function.rb6
-rw-r--r--lib/arel/nodes/grouping.rb1
-rw-r--r--lib/arel/nodes/node.rb2
-rw-r--r--lib/arel/nodes/node_expression.rb11
-rw-r--r--lib/arel/nodes/select_statement.rb2
-rw-r--r--lib/arel/nodes/terminal.rb3
-rw-r--r--lib/arel/nodes/true.rb3
-rw-r--r--lib/arel/nodes/unary.rb2
-rw-r--r--lib/arel/nodes/unary_operation.rb8
-rw-r--r--lib/arel/nodes/values_list.rb23
-rw-r--r--lib/arel/nodes/window.rb1
-rw-r--r--lib/arel/select_manager.rb2
-rw-r--r--lib/arel/tree_manager.rb9
-rw-r--r--lib/arel/visitors/bind_substitute.rb10
-rw-r--r--lib/arel/visitors/bind_visitor.rb40
-rw-r--r--lib/arel/visitors/informix.rb2
-rw-r--r--lib/arel/visitors/oracle.rb10
-rw-r--r--lib/arel/visitors/oracle12.rb2
-rw-r--r--lib/arel/visitors/postgresql.rb2
-rw-r--r--lib/arel/visitors/reduce.rb26
-rw-r--r--lib/arel/visitors/to_sql.rb33
-rw-r--r--lib/arel/visitors/visitor.rb17
-rw-r--r--test/attributes/test_attribute.rb12
-rw-r--r--test/collectors/test_bind.rb38
-rw-r--r--test/collectors/test_bind_collector.rb71
-rw-r--r--test/collectors/test_composite.rb46
-rw-r--r--test/collectors/test_sql_string.rb11
-rw-r--r--test/collectors/test_substitute_bind_collector.rb47
-rw-r--r--test/helper.rb1
-rw-r--r--test/nodes/test_binary.rb1
-rw-r--r--test/nodes/test_bind_param.rb11
-rw-r--r--test/nodes/test_count.rb9
-rw-r--r--test/nodes/test_table_alias.rb1
-rw-r--r--test/test_insert_manager.rb65
-rw-r--r--test/test_nodes.rb35
-rw-r--r--test/test_select_manager.rb11
-rw-r--r--test/test_update_manager.rb2
-rw-r--r--test/visitors/test_bind_visitor.rb61
-rw-r--r--test/visitors/test_depth_first.rb1
-rw-r--r--test/visitors/test_dispatch_contamination.rb49
-rw-r--r--test/visitors/test_dot.rb2
-rw-r--r--test/visitors/test_oracle.rb10
-rw-r--r--test/visitors/test_oracle12.rb4
-rw-r--r--test/visitors/test_postgres.rb4
-rw-r--r--test/visitors/test_to_sql.rb8
65 files changed, 567 insertions, 344 deletions
diff --git a/.travis.yml b/.travis.yml
index 32aeb83ea9..4e1bca45c7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,23 +8,18 @@ env:
global:
- JRUBY_OPTS='--dev -J-Xmx1024M'
rvm:
- - rbx-2
- - jruby-9.0.5.0
- - jruby-head
- - 2.0.0
- - 2.1
- - 2.2.5
- - 2.3.1
- - 2.4.0
+ - 2.2.8
+ - 2.3.5
+ - 2.4.2
- ruby-head
+ - jruby-9.1.12.0
+ - jruby-head
matrix:
fast_finish: true
allow_failures:
- - rvm: jruby-9.0.5.0
- - rvm: jruby-head
- rvm: ruby-head
+ - rvm: jruby-9.1.12.0
- rvm: jruby-head
- - rvm: rbx-2
bundler_args: --jobs 3 --retry 3
notifications:
email: false
diff --git a/History.txt b/History.txt
index 6985da4f49..11ca9e8659 100644
--- a/History.txt
+++ b/History.txt
@@ -1,3 +1,9 @@
+=== 9.0.0 / 2017-11-14
+
+* Enhancements
+ * `InsertManager#insert` is now chainable
+ * Support multiple inserts
+
=== 8.0.0 / 2017-02-21
* Enhancements
diff --git a/README.md b/README.md
index af768361ab..3214dc16b3 100644
--- a/README.md
+++ b/README.md
@@ -150,7 +150,13 @@ The `OR` operator works like this:
users.where(users[:name].eq('bob').or(users[:age].lt(25)))
```
-The `AND` operator behaves similarly. Here is an example of the `DISTINCT` operator:
+The `AND` operator behaves similarly (same exact behaviour as chained calls to `.where`):
+
+```ruby
+users.where(users[:name].eq('bob').and(users[:age].lt(25)))
+```
+
+Here is an example of the `DISTINCT` operator:
```ruby
posts = Arel::Table.new(:posts)
@@ -188,7 +194,7 @@ users.project(users[:age].average.as("mean_age"))
# => SELECT AVG(users.age) AS mean_age FROM users
```
-### The Crazy Features
+### The Advanced Features
The examples above are fairly simple and other libraries match or come close to matching the expressiveness of Arel (e.g. `Sequel` in Ruby).
@@ -215,6 +221,7 @@ products.
#### Complex Joins
+##### Alias
Where Arel really shines is in its ability to handle complex joins and aggregations. As a first example, let's consider an "adjacency list", a tree represented in a table. Suppose we have a table `comments`, representing a threaded discussion:
```ruby
@@ -240,6 +247,7 @@ comments_with_replies = \
This will return the reply for the first comment.
+##### CTE
[Common Table Expressions (CTE)](https://en.wikipedia.org/wiki/Common_table_expressions#Common_table_expression) support via:
Create a `CTE`
@@ -262,6 +270,7 @@ users.
# FROM users INNER JOIN cte_table ON users.id = cte_table.user_id
```
+#### Write SQL strings
When your query is too complex for `Arel`, you can use `Arel::SqlLiteral`:
```ruby
diff --git a/arel.gemspec b/arel.gemspec
index 3924bd15d2..4ed41c3d4c 100644
--- a/arel.gemspec
+++ b/arel.gemspec
@@ -1,4 +1,3 @@
-# # -*- encoding: utf-8 -*-
# frozen_string_literal: true
$:.push File.expand_path("../lib", __FILE__)
require "arel"
@@ -13,14 +12,16 @@ Gem::Specification.new do |s|
s.description = "Arel Really Exasperates Logicians\n\nArel is a SQL AST manager for Ruby. It\n\n1. Simplifies the generation of complex SQL queries\n2. Adapts to various RDBMSes\n\nIt is intended to be a framework framework; that is, you can build your own ORM\nwith it, focusing on innovative object and collection modeling as opposed to\ndatabase compatibility and query generation."
s.summary = "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby"
s.license = %q{MIT}
+ s.required_ruby_version = ">= 2.2.2"
s.rdoc_options = ["--main", "README.md"]
s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "README.md"]
- s.files = ["History.txt","MIT-LICENSE.txt","README.md","lib/arel.rb","lib/arel/alias_predication.rb","lib/arel/attributes.rb","lib/arel/attributes/attribute.rb","lib/arel/collectors/bind.rb","lib/arel/collectors/plain_string.rb","lib/arel/collectors/sql_string.rb","lib/arel/compatibility/wheres.rb","lib/arel/crud.rb","lib/arel/delete_manager.rb","lib/arel/errors.rb","lib/arel/expressions.rb","lib/arel/factory_methods.rb","lib/arel/insert_manager.rb","lib/arel/math.rb","lib/arel/nodes.rb","lib/arel/nodes/and.rb","lib/arel/nodes/ascending.rb","lib/arel/nodes/binary.rb","lib/arel/nodes/bind_param.rb","lib/arel/nodes/case.rb","lib/arel/nodes/casted.rb","lib/arel/nodes/count.rb","lib/arel/nodes/delete_statement.rb","lib/arel/nodes/descending.rb","lib/arel/nodes/equality.rb","lib/arel/nodes/extract.rb","lib/arel/nodes/false.rb","lib/arel/nodes/full_outer_join.rb","lib/arel/nodes/function.rb","lib/arel/nodes/grouping.rb","lib/arel/nodes/in.rb","lib/arel/nodes/infix_operation.rb","lib/arel/nodes/inner_join.rb","lib/arel/nodes/insert_statement.rb","lib/arel/nodes/join_source.rb","lib/arel/nodes/matches.rb","lib/arel/nodes/named_function.rb","lib/arel/nodes/node.rb","lib/arel/nodes/outer_join.rb","lib/arel/nodes/over.rb","lib/arel/nodes/regexp.rb","lib/arel/nodes/right_outer_join.rb","lib/arel/nodes/select_core.rb","lib/arel/nodes/select_statement.rb","lib/arel/nodes/sql_literal.rb","lib/arel/nodes/string_join.rb","lib/arel/nodes/table_alias.rb","lib/arel/nodes/terminal.rb","lib/arel/nodes/true.rb","lib/arel/nodes/unary.rb","lib/arel/nodes/unary_operation.rb","lib/arel/nodes/unqualified_column.rb","lib/arel/nodes/update_statement.rb","lib/arel/nodes/values.rb","lib/arel/nodes/window.rb","lib/arel/nodes/with.rb","lib/arel/order_predications.rb","lib/arel/predications.rb","lib/arel/select_manager.rb","lib/arel/table.rb","lib/arel/tree_manager.rb","lib/arel/update_manager.rb","lib/arel/visitors.rb","lib/arel/visitors/bind_substitute.rb","lib/arel/visitors/bind_visitor.rb","lib/arel/visitors/depth_first.rb","lib/arel/visitors/dot.rb","lib/arel/visitors/ibm_db.rb","lib/arel/visitors/informix.rb","lib/arel/visitors/mssql.rb","lib/arel/visitors/mysql.rb","lib/arel/visitors/oracle.rb","lib/arel/visitors/oracle12.rb","lib/arel/visitors/postgresql.rb","lib/arel/visitors/reduce.rb","lib/arel/visitors/sqlite.rb","lib/arel/visitors/to_sql.rb","lib/arel/visitors/visitor.rb","lib/arel/visitors/where_sql.rb","lib/arel/window_predications.rb"]
+ s.files = ["History.txt","MIT-LICENSE.txt","README.md","lib/arel.rb","lib/arel/alias_predication.rb","lib/arel/attributes.rb","lib/arel/attributes/attribute.rb","lib/arel/collectors/bind.rb","lib/arel/collectors/composite.rb","lib/arel/collectors/plain_string.rb","lib/arel/collectors/sql_string.rb","lib/arel/collectors/substitute_binds.rb","lib/arel/compatibility/wheres.rb","lib/arel/crud.rb","lib/arel/delete_manager.rb","lib/arel/errors.rb","lib/arel/expressions.rb","lib/arel/factory_methods.rb","lib/arel/insert_manager.rb","lib/arel/math.rb","lib/arel/nodes.rb","lib/arel/nodes/and.rb","lib/arel/nodes/ascending.rb","lib/arel/nodes/binary.rb","lib/arel/nodes/bind_param.rb","lib/arel/nodes/case.rb","lib/arel/nodes/casted.rb","lib/arel/nodes/count.rb","lib/arel/nodes/delete_statement.rb","lib/arel/nodes/descending.rb","lib/arel/nodes/equality.rb","lib/arel/nodes/extract.rb","lib/arel/nodes/false.rb","lib/arel/nodes/full_outer_join.rb","lib/arel/nodes/function.rb","lib/arel/nodes/grouping.rb","lib/arel/nodes/in.rb","lib/arel/nodes/infix_operation.rb","lib/arel/nodes/inner_join.rb","lib/arel/nodes/insert_statement.rb","lib/arel/nodes/join_source.rb","lib/arel/nodes/matches.rb","lib/arel/nodes/named_function.rb","lib/arel/nodes/node.rb","lib/arel/nodes/outer_join.rb","lib/arel/nodes/over.rb","lib/arel/nodes/regexp.rb","lib/arel/nodes/right_outer_join.rb","lib/arel/nodes/select_core.rb","lib/arel/nodes/select_statement.rb","lib/arel/nodes/sql_literal.rb","lib/arel/nodes/string_join.rb","lib/arel/nodes/table_alias.rb","lib/arel/nodes/terminal.rb","lib/arel/nodes/true.rb","lib/arel/nodes/unary.rb","lib/arel/nodes/unary_operation.rb","lib/arel/nodes/unqualified_column.rb","lib/arel/nodes/update_statement.rb","lib/arel/nodes/values.rb","lib/arel/nodes/values_list.rb","lib/arel/nodes/window.rb","lib/arel/nodes/with.rb","lib/arel/order_predications.rb","lib/arel/predications.rb","lib/arel/select_manager.rb","lib/arel/table.rb","lib/arel/tree_manager.rb","lib/arel/update_manager.rb","lib/arel/visitors.rb","lib/arel/visitors/depth_first.rb","lib/arel/visitors/dot.rb","lib/arel/visitors/ibm_db.rb","lib/arel/visitors/informix.rb","lib/arel/visitors/mssql.rb","lib/arel/visitors/mysql.rb","lib/arel/visitors/oracle.rb","lib/arel/visitors/oracle12.rb","lib/arel/visitors/postgresql.rb","lib/arel/visitors/sqlite.rb","lib/arel/visitors/to_sql.rb","lib/arel/visitors/visitor.rb","lib/arel/visitors/where_sql.rb","lib/arel/window_predications.rb"]
s.require_paths = ["lib"]
s.add_development_dependency('minitest', '~> 5.4')
s.add_development_dependency('rdoc', '~> 4.0')
s.add_development_dependency('rake')
+ s.add_development_dependency('concurrent-ruby', '~> 1.0')
end
diff --git a/arel.gemspec.erb b/arel.gemspec.erb
index f7ff99469c..4698e8bae7 100644
--- a/arel.gemspec.erb
+++ b/arel.gemspec.erb
@@ -1,4 +1,3 @@
-# # -*- encoding: utf-8 -*-
# frozen_string_literal: true
$:.push File.expand_path("../lib", __FILE__)
require "arel"
@@ -13,6 +12,7 @@ Gem::Specification.new do |s|
s.description = "Arel Really Exasperates Logicians\n\nArel is a SQL AST manager for Ruby. It\n\n1. Simplifies the generation of complex SQL queries\n2. Adapts to various RDBMSes\n\nIt is intended to be a framework framework; that is, you can build your own ORM\nwith it, focusing on innovative object and collection modeling as opposed to\ndatabase compatibility and query generation."
s.summary = "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby"
s.license = %q{MIT}
+ s.required_ruby_version = ">= 2.2.2"
s.rdoc_options = ["--main", "README.md"]
s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "README.md"]
@@ -23,4 +23,5 @@ Gem::Specification.new do |s|
s.add_development_dependency('minitest', '~> 5.4')
s.add_development_dependency('rdoc', '~> 4.0')
s.add_development_dependency('rake')
+ s.add_development_dependency('concurrent-ruby', '~> 1.0')
end
diff --git a/lib/arel.rb b/lib/arel.rb
index f0d2bdce78..e7c6fc7fd3 100644
--- a/lib/arel.rb
+++ b/lib/arel.rb
@@ -24,7 +24,7 @@ require 'arel/delete_manager'
require 'arel/nodes'
module Arel
- VERSION = '8.0.0'
+ VERSION = '9.0.0'
def self.sql raw_sql
Arel::Nodes::SqlLiteral.new raw_sql
diff --git a/lib/arel/collectors/bind.rb b/lib/arel/collectors/bind.rb
index dfa79d1001..d816aed90d 100644
--- a/lib/arel/collectors/bind.rb
+++ b/lib/arel/collectors/bind.rb
@@ -1,36 +1,23 @@
# frozen_string_literal: true
+
module Arel
module Collectors
class Bind
def initialize
- @parts = []
+ @binds = []
end
def << str
- @parts << str
self
end
def add_bind bind
- @parts << bind
+ @binds << bind
self
end
- def value; @parts; end
-
- def substitute_binds bvs
- bvs = bvs.dup
- @parts.map do |val|
- if Arel::Nodes::BindParam === val
- bvs.shift
- else
- val
- end
- end
- end
-
- def compile bvs
- substitute_binds(bvs).join
+ def value
+ @binds
end
end
end
diff --git a/lib/arel/collectors/composite.rb b/lib/arel/collectors/composite.rb
new file mode 100644
index 0000000000..4f6156fe27
--- /dev/null
+++ b/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/lib/arel/collectors/sql_string.rb b/lib/arel/collectors/sql_string.rb
index 5f42117331..bcb941f6d4 100644
--- a/lib/arel/collectors/sql_string.rb
+++ b/lib/arel/collectors/sql_string.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
# frozen_string_literal: true
require 'arel/collectors/plain_string'
diff --git a/lib/arel/collectors/substitute_binds.rb b/lib/arel/collectors/substitute_binds.rb
new file mode 100644
index 0000000000..99d2215aaa
--- /dev/null
+++ b/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/lib/arel/insert_manager.rb b/lib/arel/insert_manager.rb
index f9a598e8b7..dcbac6cb43 100644
--- a/lib/arel/insert_manager.rb
+++ b/lib/arel/insert_manager.rb
@@ -40,5 +40,9 @@ module Arel
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/lib/arel/nodes.rb b/lib/arel/nodes.rb
index 8c9815a96b..8c6572dd6a 100644
--- a/lib/arel/nodes.rb
+++ b/lib/arel/nodes.rb
@@ -1,6 +1,7 @@
# 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'
@@ -44,6 +45,7 @@ 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
diff --git a/lib/arel/nodes/binary.rb b/lib/arel/nodes/binary.rb
index 3001788774..a86d4e4696 100644
--- a/lib/arel/nodes/binary.rb
+++ b/lib/arel/nodes/binary.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Arel
module Nodes
- class Binary < Arel::Nodes::Node
+ class Binary < Arel::Nodes::NodeExpression
attr_accessor :left, :right
def initialize left, right
diff --git a/lib/arel/nodes/bind_param.rb b/lib/arel/nodes/bind_param.rb
index 9e297831cd..efa4f452d4 100644
--- a/lib/arel/nodes/bind_param.rb
+++ b/lib/arel/nodes/bind_param.rb
@@ -2,8 +2,25 @@
module Arel
module Nodes
class BindParam < Node
- def ==(other)
- other.is_a?(BindParam)
+ 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
diff --git a/lib/arel/nodes/case.rb b/lib/arel/nodes/case.rb
index 1edca40001..50ea1e0be2 100644
--- a/lib/arel/nodes/case.rb
+++ b/lib/arel/nodes/case.rb
@@ -2,10 +2,6 @@
module Arel
module Nodes
class Case < Arel::Nodes::Node
- include Arel::OrderPredications
- include Arel::Predications
- include Arel::AliasPredication
-
attr_accessor :case, :conditions, :default
def initialize expression = nil, default = nil
diff --git a/lib/arel/nodes/casted.rb b/lib/arel/nodes/casted.rb
index 290e4dd38c..f945063dd2 100644
--- a/lib/arel/nodes/casted.rb
+++ b/lib/arel/nodes/casted.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Arel
module Nodes
- class Casted < Arel::Nodes::Node # :nodoc:
+ class Casted < Arel::Nodes::NodeExpression # :nodoc:
attr_reader :val, :attribute
def initialize val, attribute
@val = val
@@ -30,7 +30,7 @@ module Arel
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
+ when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral
other
else
case attribute
diff --git a/lib/arel/nodes/count.rb b/lib/arel/nodes/count.rb
index a7c6236a22..4dd9be453f 100644
--- a/lib/arel/nodes/count.rb
+++ b/lib/arel/nodes/count.rb
@@ -2,6 +2,8 @@
module Arel
module Nodes
class Count < Arel::Nodes::Function
+ include Math
+
def initialize expr, distinct = false, aliaz = nil
super(expr, aliaz)
@distinct = distinct
diff --git a/lib/arel/nodes/delete_statement.rb b/lib/arel/nodes/delete_statement.rb
index 593ce9bddf..063a5341e5 100644
--- a/lib/arel/nodes/delete_statement.rb
+++ b/lib/arel/nodes/delete_statement.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module Arel
module Nodes
- class DeleteStatement < Arel::Nodes::Binary
+ class DeleteStatement < Arel::Nodes::Node
+ attr_accessor :left, :right
attr_accessor :limit
alias :relation :left
@@ -10,13 +11,27 @@ module Arel
alias :wheres= :right=
def initialize relation = nil, wheres = []
- super
+ super()
+ @left = relation
+ @right = wheres
end
def initialize_copy other
super
- @right = @right.clone
+ @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/lib/arel/nodes/extract.rb b/lib/arel/nodes/extract.rb
index 4e797b6770..fdf3004c6a 100644
--- a/lib/arel/nodes/extract.rb
+++ b/lib/arel/nodes/extract.rb
@@ -2,9 +2,6 @@
module Arel
module Nodes
class Extract < Arel::Nodes::Unary
- include Arel::AliasPredication
- include Arel::Predications
-
attr_accessor :field
def initialize expr, field
diff --git a/lib/arel/nodes/false.rb b/lib/arel/nodes/false.rb
index 26b4e5db97..58132a2f90 100644
--- a/lib/arel/nodes/false.rb
+++ b/lib/arel/nodes/false.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Arel
module Nodes
- class False < Arel::Nodes::Node
+ class False < Arel::Nodes::NodeExpression
def hash
self.class.hash
end
@@ -9,6 +9,7 @@ module Arel
def eql? other
self.class == other.class
end
+ alias :== :eql?
end
end
end
diff --git a/lib/arel/nodes/function.rb b/lib/arel/nodes/function.rb
index 28a394e9f3..b3bf8f3e51 100644
--- a/lib/arel/nodes/function.rb
+++ b/lib/arel/nodes/function.rb
@@ -1,10 +1,8 @@
# frozen_string_literal: true
module Arel
module Nodes
- class Function < Arel::Nodes::Node
- include Arel::Predications
+ class Function < Arel::Nodes::NodeExpression
include Arel::WindowPredications
- include Arel::OrderPredications
attr_accessor :expressions, :alias, :distinct
def initialize expr, aliaz = nil
@@ -29,6 +27,8 @@ module Arel
self.alias == other.alias &&
self.distinct == other.distinct
end
+ alias :== :eql?
+
end
%w{
diff --git a/lib/arel/nodes/grouping.rb b/lib/arel/nodes/grouping.rb
index 16911eb3b6..ffe66654ce 100644
--- a/lib/arel/nodes/grouping.rb
+++ b/lib/arel/nodes/grouping.rb
@@ -2,7 +2,6 @@
module Arel
module Nodes
class Grouping < Unary
- include Arel::Predications
end
end
end
diff --git a/lib/arel/nodes/node.rb b/lib/arel/nodes/node.rb
index 34e71063af..d2e6313dda 100644
--- a/lib/arel/nodes/node.rb
+++ b/lib/arel/nodes/node.rb
@@ -1,6 +1,4 @@
# frozen_string_literal: true
-require 'arel/collectors/sql_string'
-
module Arel
module Nodes
###
diff --git a/lib/arel/nodes/node_expression.rb b/lib/arel/nodes/node_expression.rb
new file mode 100644
index 0000000000..c4d4c8f428
--- /dev/null
+++ b/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/lib/arel/nodes/select_statement.rb b/lib/arel/nodes/select_statement.rb
index 641a08405d..79176d4be5 100644
--- a/lib/arel/nodes/select_statement.rb
+++ b/lib/arel/nodes/select_statement.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Arel
module Nodes
- class SelectStatement < Arel::Nodes::Node
+ class SelectStatement < Arel::Nodes::NodeExpression
attr_reader :cores
attr_accessor :limit, :orders, :lock, :offset, :with
diff --git a/lib/arel/nodes/terminal.rb b/lib/arel/nodes/terminal.rb
index 6f60fe006f..3a1cd7f0e1 100644
--- a/lib/arel/nodes/terminal.rb
+++ b/lib/arel/nodes/terminal.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Arel
module Nodes
- class Distinct < Arel::Nodes::Node
+ class Distinct < Arel::Nodes::NodeExpression
def hash
self.class.hash
end
@@ -9,6 +9,7 @@ module Arel
def eql? other
self.class == other.class
end
+ alias :== :eql?
end
end
end
diff --git a/lib/arel/nodes/true.rb b/lib/arel/nodes/true.rb
index 796b5b9348..fdb8ed2095 100644
--- a/lib/arel/nodes/true.rb
+++ b/lib/arel/nodes/true.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Arel
module Nodes
- class True < Arel::Nodes::Node
+ class True < Arel::Nodes::NodeExpression
def hash
self.class.hash
end
@@ -9,6 +9,7 @@ module Arel
def eql? other
self.class == other.class
end
+ alias :== :eql?
end
end
end
diff --git a/lib/arel/nodes/unary.rb b/lib/arel/nodes/unary.rb
index 60cff1defe..e458d87ab3 100644
--- a/lib/arel/nodes/unary.rb
+++ b/lib/arel/nodes/unary.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Arel
module Nodes
- class Unary < Arel::Nodes::Node
+ class Unary < Arel::Nodes::NodeExpression
attr_accessor :expr
alias :value :expr
diff --git a/lib/arel/nodes/unary_operation.rb b/lib/arel/nodes/unary_operation.rb
index 3c56ef2026..be4e270e76 100644
--- a/lib/arel/nodes/unary_operation.rb
+++ b/lib/arel/nodes/unary_operation.rb
@@ -3,12 +3,6 @@ module Arel
module Nodes
class UnaryOperation < Unary
- include Arel::Expressions
- include Arel::Predications
- include Arel::OrderPredications
- include Arel::AliasPredication
- include Arel::Math
-
attr_reader :operator
def initialize operator, operand
@@ -23,4 +17,4 @@ module Arel
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/arel/nodes/values_list.rb b/lib/arel/nodes/values_list.rb
new file mode 100644
index 0000000000..89cea1790d
--- /dev/null
+++ b/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/lib/arel/nodes/window.rb b/lib/arel/nodes/window.rb
index 535c0c6238..23a005daba 100644
--- a/lib/arel/nodes/window.rb
+++ b/lib/arel/nodes/window.rb
@@ -107,6 +107,7 @@ module Arel
def eql? other
self.class == other.class
end
+ alias :== :eql?
end
class Preceding < Unary
diff --git a/lib/arel/select_manager.rb b/lib/arel/select_manager.rb
index 73fa5da6ed..0f3b0dc6a0 100644
--- a/lib/arel/select_manager.rb
+++ b/lib/arel/select_manager.rb
@@ -1,6 +1,4 @@
# frozen_string_literal: true
-require 'arel/collectors/sql_string'
-
module Arel
class SelectManager < Arel::TreeManager
include Arel::Crud
diff --git a/lib/arel/tree_manager.rb b/lib/arel/tree_manager.rb
index cc3f1eeee4..b237bf368d 100644
--- a/lib/arel/tree_manager.rb
+++ b/lib/arel/tree_manager.rb
@@ -1,17 +1,12 @@
# frozen_string_literal: true
-require 'arel/collectors/sql_string'
-
module Arel
class TreeManager
include Arel::FactoryMethods
- attr_reader :ast, :engine
-
- attr_accessor :bind_values
+ attr_reader :ast
def initialize
- @ctx = nil
- @bind_values = []
+ @ctx = nil
end
def to_dot
diff --git a/lib/arel/visitors/bind_substitute.rb b/lib/arel/visitors/bind_substitute.rb
deleted file mode 100644
index 52c96b0d72..0000000000
--- a/lib/arel/visitors/bind_substitute.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-module Arel
- module Visitors
- class BindSubstitute
- def initialize delegate
- @delegate = delegate
- end
- end
- end
-end
diff --git a/lib/arel/visitors/bind_visitor.rb b/lib/arel/visitors/bind_visitor.rb
deleted file mode 100644
index 8a5570cf5c..0000000000
--- a/lib/arel/visitors/bind_visitor.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-module Arel
- module Visitors
- module BindVisitor
- def initialize target
- @block = nil
- super
- end
-
- def accept node, collector, &block
- @block = block if block_given?
- super
- end
-
- private
-
- def visit_Arel_Nodes_Assignment o, collector
- if o.right.is_a? Arel::Nodes::BindParam
- collector = visit o.left, collector
- collector << " = "
- visit o.right, collector
- else
- super
- end
- end
-
- def visit_Arel_Nodes_BindParam o, collector
- if @block
- val = @block.call
- if String === val
- collector << val
- end
- else
- super
- end
- end
-
- end
- end
-end
diff --git a/lib/arel/visitors/informix.rb b/lib/arel/visitors/informix.rb
index b53ab18b82..44b18b550e 100644
--- a/lib/arel/visitors/informix.rb
+++ b/lib/arel/visitors/informix.rb
@@ -18,9 +18,7 @@ module Arel
end
def visit_Arel_Nodes_SelectCore o, collector
collector = inject_join o.projections, collector, ", "
- froms = false
if o.source && !o.source.empty?
- froms = true
collector << " FROM "
collector = visit o.source, collector
end
diff --git a/lib/arel/visitors/oracle.rb b/lib/arel/visitors/oracle.rb
index 3b452836db..d4749bbae3 100644
--- a/lib/arel/visitors/oracle.rb
+++ b/lib/arel/visitors/oracle.rb
@@ -29,12 +29,12 @@ module Arel
collector = super(o, collector)
if offset.expr.is_a? Nodes::BindParam
- offset_bind = nil
collector << ') raw_sql_ WHERE rownum <= ('
- collector.add_bind(offset.expr) { |i| offset_bind = ":a#{i}" }
+ collector = visit offset.expr, collector
collector << ' + '
- collector.add_bind(limit) { |i| ":a#{i}" }
- collector << ") ) WHERE raw_rnum_ > #{offset_bind}"
+ collector = visit limit, collector
+ collector << ") ) WHERE raw_rnum_ > "
+ collector = visit offset.expr, collector
return collector
else
collector << ") raw_sql_
@@ -145,7 +145,7 @@ module Arel
end
def visit_Arel_Nodes_BindParam o, collector
- collector.add_bind(o) { |i| ":a#{i}" }
+ collector.add_bind(o.value) { |i| ":a#{i}" }
end
end
diff --git a/lib/arel/visitors/oracle12.rb b/lib/arel/visitors/oracle12.rb
index ce90e994ae..648047ae61 100644
--- a/lib/arel/visitors/oracle12.rb
+++ b/lib/arel/visitors/oracle12.rb
@@ -53,7 +53,7 @@ module Arel
end
def visit_Arel_Nodes_BindParam o, collector
- collector.add_bind(o) { |i| ":a#{i}" }
+ collector.add_bind(o.value) { |i| ":a#{i}" }
end
end
end
diff --git a/lib/arel/visitors/postgresql.rb b/lib/arel/visitors/postgresql.rb
index bd4421bd58..047f71aaa6 100644
--- a/lib/arel/visitors/postgresql.rb
+++ b/lib/arel/visitors/postgresql.rb
@@ -47,7 +47,7 @@ module Arel
end
def visit_Arel_Nodes_BindParam o, collector
- collector.add_bind(o) { |i| "$#{i}" }
+ collector.add_bind(o.value) { |i| "$#{i}" }
end
def visit_Arel_Nodes_GroupingElement o, collector
diff --git a/lib/arel/visitors/reduce.rb b/lib/arel/visitors/reduce.rb
deleted file mode 100644
index 7948758e2f..0000000000
--- a/lib/arel/visitors/reduce.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-require 'arel/visitors/visitor'
-
-module Arel
- module Visitors
- class Reduce < Arel::Visitors::Visitor
- def accept object, collector
- visit object, collector
- end
-
- private
-
- def visit object, collector
- send dispatch[object.class], object, collector
- rescue NoMethodError => e
- raise e if respond_to?(dispatch[object.class], 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/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb
index 486c51a183..2b5c43b173 100644
--- a/lib/arel/visitors/to_sql.rb
+++ b/lib/arel/visitors/to_sql.rb
@@ -1,8 +1,4 @@
# frozen_string_literal: true
-require 'bigdecimal'
-require 'date'
-require 'arel/visitors/reduce'
-
module Arel
module Visitors
class UnsupportedVisitError < StandardError
@@ -11,7 +7,7 @@ module Arel
end
end
- class ToSql < Arel::Visitors::Reduce
+ 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
@@ -166,6 +162,28 @@ module Arel
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 ("
@@ -405,7 +423,8 @@ module Arel
end
def visit_Arel_SelectManager o, collector
- collector << "(#{o.to_sql.rstrip})"
+ collector << '('
+ visit(o.ast, collector) << ')'
end
def visit_Arel_Nodes_Ascending o, collector
@@ -715,7 +734,7 @@ module Arel
def literal o, collector; collector << o.to_s; end
def visit_Arel_Nodes_BindParam o, collector
- collector.add_bind(o) { "?" }
+ collector.add_bind(o.value) { "?" }
end
alias :visit_Arel_Nodes_SqlLiteral :literal
diff --git a/lib/arel/visitors/visitor.rb b/lib/arel/visitors/visitor.rb
index b96b8238a7..f156be9a0a 100644
--- a/lib/arel/visitors/visitor.rb
+++ b/lib/arel/visitors/visitor.rb
@@ -6,12 +6,14 @@ module Arel
@dispatch = get_dispatch_cache
end
- def accept object
- visit object
+ 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('::', '_')}"
@@ -22,14 +24,11 @@ module Arel
self.class.dispatch_cache
end
- def dispatch
- @dispatch
- end
-
- def visit object
- send dispatch[object.class], object
+ def visit object, *args
+ dispatch_method = dispatch[object.class]
+ send dispatch_method, object, *args
rescue NoMethodError => e
- raise e if respond_to?(dispatch[object.class], true)
+ raise e if respond_to?(dispatch_method, true)
superklass = object.class.ancestors.find { |klass|
respond_to?(dispatch[klass], true)
}
diff --git a/test/attributes/test_attribute.rb b/test/attributes/test_attribute.rb
index 2b971ce54a..a67ef53a4c 100644
--- a/test/attributes/test_attribute.rb
+++ b/test/attributes/test_attribute.rb
@@ -997,6 +997,18 @@ module Arel
assert table.able_to_type_cast?
condition.to_sql.must_equal %("foo"."id" = 1 AND "foo"."other_id" = '2')
end
+
+ it 'does not type cast SqlLiteral nodes' do
+ fake_caster = Object.new
+ def fake_caster.type_cast_for_database(attr_name, value)
+ value.to_i
+ end
+ table = Table.new(:foo, type_caster: fake_caster)
+ condition = table["id"].eq(Arel.sql("(select 1)"))
+
+ assert table.able_to_type_cast?
+ condition.to_sql.must_equal %("foo"."id" = (select 1))
+ end
end
end
end
diff --git a/test/collectors/test_bind.rb b/test/collectors/test_bind.rb
new file mode 100644
index 0000000000..62fd911a0f
--- /dev/null
+++ b/test/collectors/test_bind.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+require 'helper'
+
+module Arel
+ module Collectors
+ class TestBind < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def collect node
+ @visitor.accept(node, Collectors::Bind.new)
+ end
+
+ def compile node
+ collect(node).value
+ end
+
+ def ast_with_binds bvs
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.where(table[:age].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.where(table[:name].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.ast
+ end
+
+ def test_compile_gathers_all_bind_params
+ binds = compile(ast_with_binds(["hello", "world"]))
+ assert_equal ["hello", "world"], binds
+
+ binds = compile(ast_with_binds(["hello2", "world3"]))
+ assert_equal ["hello2", "world3"], binds
+ end
+ end
+ end
+end
diff --git a/test/collectors/test_bind_collector.rb b/test/collectors/test_bind_collector.rb
deleted file mode 100644
index 877aa20043..0000000000
--- a/test/collectors/test_bind_collector.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-require 'helper'
-require 'arel/collectors/bind'
-
-module Arel
- module Collectors
- class TestBindCollector < Arel::Test
- def setup
- @conn = FakeRecord::Base.new
- @visitor = Visitors::ToSql.new @conn.connection
- super
- end
-
- def collect node
- @visitor.accept(node, Collectors::Bind.new)
- end
-
- def compile node
- collect(node).value
- end
-
- def ast_with_binds bv
- table = Table.new(:users)
- manager = Arel::SelectManager.new table
- manager.where(table[:age].eq(bv))
- manager.where(table[:name].eq(bv))
- manager.ast
- end
-
- def test_leaves_binds
- node = Nodes::BindParam.new
- list = compile node
- assert_equal node, list.first
- assert_equal node.class, list.first.class
- end
-
- def test_adds_strings
- bv = Nodes::BindParam.new
- list = compile ast_with_binds bv
- assert_operator list.length, :>, 0
- assert_equal bv, list.grep(Nodes::BindParam).first
- assert_equal bv.class, list.grep(Nodes::BindParam).first.class
- end
-
- def test_substitute_binds
- bv = Nodes::BindParam.new
- collector = collect ast_with_binds bv
-
- values = collector.value
-
- offsets = values.map.with_index { |v,i|
- [v,i]
- }.find_all { |(v,_)| Nodes::BindParam === v }.map(&:last)
-
- list = collector.substitute_binds ["hello", "world"]
- assert_equal "hello", list[offsets[0]]
- assert_equal "world", list[offsets[1]]
-
- assert_equal 'SELECT FROM "users" WHERE "users"."age" = hello AND "users"."name" = world', list.join
- end
-
- def test_compile
- bv = Nodes::BindParam.new
- collector = collect ast_with_binds bv
-
- sql = collector.compile ["hello", "world"]
- assert_equal 'SELECT FROM "users" WHERE "users"."age" = hello AND "users"."name" = world', sql
- end
- end
- end
-end
diff --git a/test/collectors/test_composite.rb b/test/collectors/test_composite.rb
new file mode 100644
index 0000000000..3d49b390e8
--- /dev/null
+++ b/test/collectors/test_composite.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+require 'helper'
+
+require 'arel/collectors/bind'
+require 'arel/collectors/composite'
+
+module Arel
+ module Collectors
+ class TestComposite < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def collect node
+ sql_collector = Collectors::SQLString.new
+ bind_collector = Collectors::Bind.new
+ collector = Collectors::Composite.new(sql_collector, bind_collector)
+ @visitor.accept(node, collector)
+ end
+
+ def compile node
+ collect(node).value
+ end
+
+ def ast_with_binds bvs
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.where(table[:age].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.where(table[:name].eq(Nodes::BindParam.new(bvs.shift)))
+ manager.ast
+ end
+
+ def test_composite_collector_performs_multiple_collections_at_once
+ sql, binds = compile(ast_with_binds(["hello", "world"]))
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
+ assert_equal ["hello", "world"], binds
+
+ sql, binds = compile(ast_with_binds(["hello2", "world3"]))
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
+ assert_equal ["hello2", "world3"], binds
+ end
+ end
+ end
+end
diff --git a/test/collectors/test_sql_string.rb b/test/collectors/test_sql_string.rb
index 92f1bf0fba..0185f2ab17 100644
--- a/test/collectors/test_sql_string.rb
+++ b/test/collectors/test_sql_string.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'helper'
-require 'arel/collectors/bind'
module Arel
module Collectors
@@ -28,12 +27,20 @@ module Arel
end
def test_compile
- bv = Nodes::BindParam.new
+ bv = Nodes::BindParam.new(1)
collector = collect ast_with_binds bv
sql = collector.compile ["hello", "world"]
assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
end
+
+ def test_returned_sql_uses_utf8_encoding
+ bv = Nodes::BindParam.new(1)
+ collector = collect ast_with_binds bv
+
+ sql = collector.compile ["hello", "world"]
+ assert_equal sql.encoding, Encoding::UTF_8
+ end
end
end
end
diff --git a/test/collectors/test_substitute_bind_collector.rb b/test/collectors/test_substitute_bind_collector.rb
new file mode 100644
index 0000000000..de3e3655da
--- /dev/null
+++ b/test/collectors/test_substitute_bind_collector.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+require 'helper'
+require 'arel/collectors/substitute_binds'
+require 'arel/collectors/sql_string'
+
+module Arel
+ module Collectors
+ class TestSubstituteBindCollector < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def ast_with_binds
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new table
+ manager.where(table[:age].eq(Nodes::BindParam.new("hello")))
+ manager.where(table[:name].eq(Nodes::BindParam.new("world")))
+ manager.ast
+ end
+
+ def compile(node, quoter)
+ collector = Collectors::SubstituteBinds.new(quoter, Collectors::SQLString.new)
+ @visitor.accept(node, collector).value
+ end
+
+ def test_compile
+ quoter = Object.new
+ def quoter.quote(val)
+ val.to_s
+ end
+ sql = compile(ast_with_binds, quoter)
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = hello AND "users"."name" = world', sql
+ end
+
+ def test_quoting_is_delegated_to_quoter
+ quoter = Object.new
+ def quoter.quote(val)
+ val.inspect
+ end
+ sql = compile(ast_with_binds, quoter)
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = "hello" AND "users"."name" = "world"', sql
+ end
+ end
+ end
+end
diff --git a/test/helper.rb b/test/helper.rb
index 022ba1dae6..3eecfb79b6 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'rubygems'
require 'minitest/autorun'
-require 'fileutils'
require 'arel'
require 'support/fake_record'
diff --git a/test/nodes/test_binary.rb b/test/nodes/test_binary.rb
index 8e3025a440..ef23a3930b 100644
--- a/test/nodes/test_binary.rb
+++ b/test/nodes/test_binary.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'helper'
-require 'set'
module Arel
module Nodes
diff --git a/test/nodes/test_bind_param.rb b/test/nodes/test_bind_param.rb
index 011de6410c..482bb24f33 100644
--- a/test/nodes/test_bind_param.rb
+++ b/test/nodes/test_bind_param.rb
@@ -4,12 +4,17 @@ require 'helper'
module Arel
module Nodes
describe 'BindParam' do
- it 'is equal to other bind params' do
- BindParam.new.must_equal(BindParam.new)
+ it 'is equal to other bind params with the same value' do
+ BindParam.new(1).must_equal(BindParam.new(1))
+ BindParam.new("foo").must_equal(BindParam.new("foo"))
end
it 'is not equal to other nodes' do
- BindParam.new.wont_equal(Node.new)
+ BindParam.new(nil).wont_equal(Node.new)
+ end
+
+ it 'is not equal to bind params with different values' do
+ BindParam.new(1).wont_equal(BindParam.new(2))
end
end
end
diff --git a/test/nodes/test_count.rb b/test/nodes/test_count.rb
index 85e93b1927..29883df681 100644
--- a/test/nodes/test_count.rb
+++ b/test/nodes/test_count.rb
@@ -31,4 +31,13 @@ describe Arel::Nodes::Count do
assert_equal 2, array.uniq.size
end
end
+
+ describe 'math' do
+ it 'allows mathematical functions' do
+ table = Arel::Table.new :users
+ (table[:id].count + 1).to_sql.must_be_like %{
+ (COUNT("users"."id") + 1)
+ }
+ end
+ end
end
diff --git a/test/nodes/test_table_alias.rb b/test/nodes/test_table_alias.rb
index 39040e6352..911114d938 100644
--- a/test/nodes/test_table_alias.rb
+++ b/test/nodes/test_table_alias.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'helper'
-require 'ostruct'
module Arel
module Nodes
diff --git a/test/test_insert_manager.rb b/test/test_insert_manager.rb
index b9ee6f76ac..8b61a2791e 100644
--- a/test/test_insert_manager.rb
+++ b/test/test_insert_manager.rb
@@ -28,6 +28,60 @@ module Arel
}
end
+ it 'works with multiple values' do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.columns << table[:id]
+ manager.columns << table[:name]
+
+ manager.values = manager.create_values_list([
+ %w{1 david},
+ %w{2 kir},
+ ["3", Arel.sql('DEFAULT')],
+ ])
+
+ manager.to_sql.must_be_like %{
+ INSERT INTO \"users\" (\"id\", \"name\") VALUES ('1', 'david'), ('2', 'kir'), ('3', DEFAULT)
+ }
+ end
+
+ it 'literals in multiple values are not escaped' do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.columns << table[:name]
+
+ manager.values = manager.create_values_list([
+ [Arel.sql('*')],
+ [Arel.sql('DEFAULT')],
+ ])
+
+ manager.to_sql.must_be_like %{
+ INSERT INTO \"users\" (\"name\") VALUES (*), (DEFAULT)
+ }
+ end
+
+ it 'works with multiple single values' do
+ table = Table.new(:users)
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.columns << table[:name]
+
+ manager.values = manager.create_values_list([
+ %w{david},
+ %w{kir},
+ [Arel.sql('DEFAULT')],
+ ])
+
+ manager.to_sql.must_be_like %{
+ INSERT INTO \"users\" (\"name\") VALUES ('david'), ('kir'), (DEFAULT)
+ }
+ end
+
it "inserts false" do
table = Table.new(:users)
manager = Arel::InsertManager.new
@@ -136,6 +190,17 @@ module Arel
INSERT INTO "users" VALUES (1)
}
end
+
+ it "accepts sql literals" do
+ table = Table.new :users
+ manager = Arel::InsertManager.new
+ manager.into table
+
+ manager.values = Arel.sql("DEFAULT VALUES")
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" DEFAULT VALUES
+ }
+ end
end
describe "combo" do
diff --git a/test/test_nodes.rb b/test/test_nodes.rb
new file mode 100644
index 0000000000..1060f150ff
--- /dev/null
+++ b/test/test_nodes.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+require 'helper'
+
+module Arel
+ module Nodes
+ class TestNodes < Minitest::Test
+ def test_every_arel_nodes_have_hash_eql_eqeq_from_same_class
+ # #descendants code from activesupport
+ node_descendants = []
+ ObjectSpace.each_object(Arel::Nodes::Node.singleton_class) do |k|
+ next if k.respond_to?(:singleton_class?) && k.singleton_class?
+ node_descendants.unshift k unless k == self
+ end
+ node_descendants.delete(Arel::Nodes::Node)
+ node_descendants.delete(Arel::Nodes::NodeExpression)
+
+ bad_node_descendants = node_descendants.reject do |subnode|
+ eqeq_owner = subnode.instance_method(:==).owner
+ eql_owner = subnode.instance_method(:eql?).owner
+ hash_owner = subnode.instance_method(:hash).owner
+
+ eqeq_owner < Arel::Nodes::Node &&
+ eqeq_owner == eql_owner &&
+ eqeq_owner == hash_owner
+ end
+
+ problem_msg = 'Some subclasses of Arel::Nodes::Node do not have a' \
+ ' #== or #eql? or #hash defined from the same class as the others'
+ assert_empty bad_node_descendants, problem_msg
+ end
+
+
+ end
+ end
+end
diff --git a/test/test_select_manager.rb b/test/test_select_manager.rb
index 076b732984..cc7452c239 100644
--- a/test/test_select_manager.rb
+++ b/test/test_select_manager.rb
@@ -10,13 +10,6 @@ module Arel
assert_equal "SELECT FROM 'foo'", manager.to_sql
end
- def test_manager_stores_bind_values
- manager = Arel::SelectManager.new
- assert_equal [], manager.bind_values
- manager.bind_values = [1]
- assert_equal [1], manager.bind_values
- end
-
describe 'backwards compatibility' do
describe 'project' do
it 'accepts symbols as sql literals' do
@@ -226,7 +219,7 @@ module Arel
table = Table.new(:users)
manager = Arel::SelectManager.new table
manager.project Nodes::SqlLiteral.new '*'
- m2 = Arel::SelectManager.new(manager.engine)
+ m2 = Arel::SelectManager.new
m2.project manager.exists
m2.to_sql.must_be_like %{ SELECT EXISTS (#{manager.to_sql}) }
end
@@ -235,7 +228,7 @@ module Arel
table = Table.new(:users)
manager = Arel::SelectManager.new table
manager.project Nodes::SqlLiteral.new '*'
- m2 = Arel::SelectManager.new(manager.engine)
+ m2 = Arel::SelectManager.new
m2.project manager.exists.as('foo')
m2.to_sql.must_be_like %{ SELECT EXISTS (#{manager.to_sql}) AS foo }
end
diff --git a/test/test_update_manager.rb b/test/test_update_manager.rb
index 85abbf3875..4a373d1ff7 100644
--- a/test/test_update_manager.rb
+++ b/test/test_update_manager.rb
@@ -13,7 +13,7 @@ module Arel
table = Table.new(:users)
um = Arel::UpdateManager.new
um.table table
- um.set [[table[:name], Arel::Nodes::BindParam.new]]
+ um.set [[table[:name], Arel::Nodes::BindParam.new(1)]]
um.to_sql.must_be_like %{ UPDATE "users" SET "name" = ? }
end
diff --git a/test/visitors/test_bind_visitor.rb b/test/visitors/test_bind_visitor.rb
deleted file mode 100644
index 3e0578a6a1..0000000000
--- a/test/visitors/test_bind_visitor.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-require 'helper'
-require 'arel/visitors/bind_visitor'
-require 'support/fake_record'
-
-module Arel
- module Visitors
- class TestBindVisitor < Arel::Test
- attr_reader :collector
-
- def setup
- @collector = Collectors::SQLString.new
- super
- end
-
- ##
- # Tests visit_Arel_Nodes_Assignment correctly
- # substitutes binds with values from block
- def test_assignment_binds_are_substituted
- table = Table.new(:users)
- um = Arel::UpdateManager.new
- bp = Nodes::BindParam.new
- um.set [[table[:name], bp]]
- visitor = Class.new(Arel::Visitors::ToSql) {
- include Arel::Visitors::BindVisitor
- }.new Table.engine.connection
-
- assignment = um.ast.values[0]
- actual = visitor.accept(assignment, collector) {
- "replace"
- }
- assert actual
- value = actual.value
- assert_like "\"name\" = replace", value
- end
-
- def test_visitor_yields_on_binds
- visitor = Class.new(Arel::Visitors::ToSql) {
- include Arel::Visitors::BindVisitor
- }.new nil
-
- bp = Nodes::BindParam.new
- called = false
- visitor.accept(bp, collector) { called = true }
- assert called
- end
-
- def test_visitor_only_yields_on_binds
- visitor = Class.new(Arel::Visitors::ToSql) {
- include Arel::Visitors::BindVisitor
- }.new(nil)
-
- bp = Arel.sql 'omg'
- called = false
-
- visitor.accept(bp, collector) { called = true }
- refute called
- end
- end
- end
-end
diff --git a/test/visitors/test_depth_first.rb b/test/visitors/test_depth_first.rb
index 0c7f7ccd34..832843265c 100644
--- a/test/visitors/test_depth_first.rb
+++ b/test/visitors/test_depth_first.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'helper'
-require 'set'
module Arel
module Visitors
diff --git a/test/visitors/test_dispatch_contamination.rb b/test/visitors/test_dispatch_contamination.rb
index 6422a6dff3..71792594d6 100644
--- a/test/visitors/test_dispatch_contamination.rb
+++ b/test/visitors/test_dispatch_contamination.rb
@@ -1,8 +1,42 @@
# frozen_string_literal: true
require 'helper'
+require 'concurrent'
module Arel
module Visitors
+ class DummyVisitor < Visitor
+ def initialize
+ super
+ @barrier = Concurrent::CyclicBarrier.new(2)
+ end
+
+ def visit_Arel_Visitors_DummySuperNode node
+ 42
+ end
+
+ # This is terrible, but it's the only way to reliably reproduce
+ # the possible race where two threads attempt to correct the
+ # dispatch hash at the same time.
+ def send *args
+ super
+ rescue
+ # Both threads try (and fail) to dispatch to the subclass's name
+ @barrier.wait
+ raise
+ ensure
+ # Then one thread successfully completes (updating the dispatch
+ # table in the process) before the other finishes raising its
+ # exception.
+ Thread.current[:delay].wait if Thread.current[:delay]
+ end
+ end
+
+ class DummySuperNode
+ end
+
+ class DummySubNode < DummySuperNode
+ end
+
describe 'avoiding contamination between visitor dispatch tables' do
before do
@connection = Table.engine.connection
@@ -17,6 +51,21 @@ module Arel
assert_equal "( TRUE UNION FALSE )", node.to_sql
end
+
+ it 'is threadsafe when implementing superclass fallback' do
+ visitor = DummyVisitor.new
+ main_thread_finished = Concurrent::Event.new
+
+ racing_thread = Thread.new do
+ Thread.current[:delay] = main_thread_finished
+ visitor.accept DummySubNode.new
+ end
+
+ assert_equal 42, visitor.accept(DummySubNode.new)
+ main_thread_finished.set
+
+ assert_equal 42, racing_thread.value
+ end
end
end
end
diff --git a/test/visitors/test_dot.rb b/test/visitors/test_dot.rb
index 1d27d1a5cb..3c4a038116 100644
--- a/test/visitors/test_dot.rb
+++ b/test/visitors/test_dot.rb
@@ -74,7 +74,7 @@ module Arel
end
def test_Arel_Nodes_BindParam
- node = Arel::Nodes::BindParam.new
+ node = Arel::Nodes::BindParam.new(1)
collector = Collectors::PlainString.new
assert_match '[label="<f0>Arel::Nodes::BindParam"]', @visitor.accept(node, collector).value
end
diff --git a/test/visitors/test_oracle.rb b/test/visitors/test_oracle.rb
index b1921f0cbc..fce6606d35 100644
--- a/test/visitors/test_oracle.rb
+++ b/test/visitors/test_oracle.rb
@@ -127,8 +127,8 @@ module Arel
it 'creates a subquery when there is limit and offset with BindParams' do
stmt = Nodes::SelectStatement.new
- stmt.limit = Nodes::Limit.new(Nodes::BindParam.new)
- stmt.offset = Nodes::Offset.new(Nodes::BindParam.new)
+ stmt.limit = Nodes::Limit.new(Nodes::BindParam.new(1))
+ stmt.offset = Nodes::Offset.new(Nodes::BindParam.new(1))
sql = compile stmt
sql.must_be_like %{
SELECT * FROM (
@@ -136,7 +136,7 @@ module Arel
FROM (SELECT ) raw_sql_
WHERE rownum <= (:a1 + :a2)
)
- WHERE raw_rnum_ > :a1
+ WHERE raw_rnum_ > :a3
}
end
@@ -184,8 +184,8 @@ module Arel
describe "Nodes::BindParam" do
it "increments each bind param" do
- query = @table[:name].eq(Arel::Nodes::BindParam.new)
- .and(@table[:id].eq(Arel::Nodes::BindParam.new))
+ query = @table[:name].eq(Arel::Nodes::BindParam.new(1))
+ .and(@table[:id].eq(Arel::Nodes::BindParam.new(1)))
compile(query).must_be_like %{
"users"."name" = :a1 AND "users"."id" = :a2
}
diff --git a/test/visitors/test_oracle12.rb b/test/visitors/test_oracle12.rb
index c908a51d4f..62658bc595 100644
--- a/test/visitors/test_oracle12.rb
+++ b/test/visitors/test_oracle12.rb
@@ -48,8 +48,8 @@ module Arel
describe "Nodes::BindParam" do
it "increments each bind param" do
- query = @table[:name].eq(Arel::Nodes::BindParam.new)
- .and(@table[:id].eq(Arel::Nodes::BindParam.new))
+ query = @table[:name].eq(Arel::Nodes::BindParam.new(1))
+ .and(@table[:id].eq(Arel::Nodes::BindParam.new(1)))
compile(query).must_be_like %{
"users"."name" = :a1 AND "users"."id" = :a2
}
diff --git a/test/visitors/test_postgres.rb b/test/visitors/test_postgres.rb
index d3cab623c4..b28c0f3c18 100644
--- a/test/visitors/test_postgres.rb
+++ b/test/visitors/test_postgres.rb
@@ -190,8 +190,8 @@ module Arel
describe "Nodes::BindParam" do
it "increments each bind param" do
- query = @table[:name].eq(Arel::Nodes::BindParam.new)
- .and(@table[:id].eq(Arel::Nodes::BindParam.new))
+ query = @table[:name].eq(Arel::Nodes::BindParam.new(1))
+ .and(@table[:id].eq(Arel::Nodes::BindParam.new(1)))
compile(query).must_be_like %{
"users"."name" = $1 AND "users"."id" = $2
}
diff --git a/test/visitors/test_to_sql.rb b/test/visitors/test_to_sql.rb
index 31279b0ae2..77756b9e99 100644
--- a/test/visitors/test_to_sql.rb
+++ b/test/visitors/test_to_sql.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'helper'
-require 'set'
+require 'bigdecimal'
module Arel
module Visitors
@@ -17,13 +17,13 @@ module Arel
end
it 'works with BindParams' do
- node = Nodes::BindParam.new
+ node = Nodes::BindParam.new(1)
sql = compile node
sql.must_be_like '?'
end
it 'does not quote BindParams used as part of a Values' do
- bp = Nodes::BindParam.new
+ bp = Nodes::BindParam.new(1)
values = Nodes::Values.new([bp])
sql = compile values
sql.must_be_like 'VALUES (?)'
@@ -31,7 +31,7 @@ module Arel
it 'can define a dispatch method' do
visited = false
- viz = Class.new(Arel::Visitors::Reduce) {
+ viz = Class.new(Arel::Visitors::Visitor) {
define_method(:hello) do |node, c|
visited = true
end