aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml15
-rw-r--r--Gemfile7
-rw-r--r--History.txt104
-rw-r--r--Manifest.txt18
-rw-r--r--README.markdown199
-rw-r--r--Rakefile5
-rw-r--r--arel.gemspec43
-rw-r--r--lib/arel.rb13
-rw-r--r--lib/arel/collectors/bind.rb36
-rw-r--r--lib/arel/collectors/plain_string.rb18
-rw-r--r--lib/arel/collectors/sql_string.rb18
-rw-r--r--lib/arel/crud.rb36
-rw-r--r--lib/arel/deprecated.rb4
-rw-r--r--lib/arel/expression.rb5
-rw-r--r--lib/arel/expressions.rb9
-rw-r--r--lib/arel/factory_methods.rb2
-rw-r--r--lib/arel/insert_manager.rb6
-rw-r--r--lib/arel/nodes.rb41
-rw-r--r--lib/arel/nodes/and.rb7
-rw-r--r--lib/arel/nodes/binary.rb3
-rw-r--r--lib/arel/nodes/extract.rb17
-rw-r--r--lib/arel/nodes/full_outer_join.rb6
-rw-r--r--lib/arel/nodes/function.rb2
-rw-r--r--lib/arel/nodes/insert_statement.rb8
-rw-r--r--lib/arel/nodes/node.rb16
-rw-r--r--lib/arel/nodes/right_outer_join.rb6
-rw-r--r--lib/arel/nodes/select_core.rb1
-rw-r--r--lib/arel/nodes/select_statement.rb1
-rw-r--r--lib/arel/nodes/sql_literal.rb4
-rw-r--r--lib/arel/nodes/unary.rb1
-rw-r--r--lib/arel/nodes/window.rb28
-rw-r--r--lib/arel/predications.rb66
-rw-r--r--lib/arel/select_manager.rb105
-rw-r--r--lib/arel/sql/engine.rb10
-rw-r--r--lib/arel/sql_literal.rb4
-rw-r--r--lib/arel/table.rb50
-rw-r--r--lib/arel/tree_manager.rb13
-rw-r--r--lib/arel/update_manager.rb4
-rw-r--r--lib/arel/visitors.rb2
-rw-r--r--lib/arel/visitors/bind_substitute.rb9
-rw-r--r--lib/arel/visitors/bind_visitor.rb21
-rw-r--r--lib/arel/visitors/depth_first.rb7
-rw-r--r--lib/arel/visitors/dot.rb22
-rw-r--r--lib/arel/visitors/ibm_db.rb6
-rw-r--r--lib/arel/visitors/informix.rb62
-rw-r--r--lib/arel/visitors/join_sql.rb19
-rw-r--r--lib/arel/visitors/mssql.rb62
-rw-r--r--lib/arel/visitors/mysql.rb70
-rw-r--r--lib/arel/visitors/oracle.rb58
-rw-r--r--lib/arel/visitors/order_clauses.rb11
-rw-r--r--lib/arel/visitors/postgresql.rb21
-rw-r--r--lib/arel/visitors/reduce.rb25
-rw-r--r--lib/arel/visitors/sqlite.rb5
-rw-r--r--lib/arel/visitors/to_sql.rb793
-rw-r--r--lib/arel/visitors/visitor.rb9
-rw-r--r--lib/arel/visitors/where_sql.rb5
-rw-r--r--test/attributes/test_attribute.rb76
-rw-r--r--test/collectors/test_bind_collector.rb70
-rw-r--r--test/collectors/test_sql_string.rb38
-rw-r--r--test/helper.rb11
-rw-r--r--test/nodes/test_ascending.rb2
-rw-r--r--test/nodes/test_bin.rb6
-rw-r--r--test/nodes/test_count.rb6
-rw-r--r--test/nodes/test_descending.rb2
-rw-r--r--test/nodes/test_equality.rb2
-rw-r--r--test/nodes/test_extract.rb8
-rw-r--r--test/nodes/test_grouping.rb2
-rw-r--r--test/nodes/test_infix_operation.rb4
-rw-r--r--test/nodes/test_named_function.rb2
-rw-r--r--test/nodes/test_node.rb2
-rw-r--r--test/nodes/test_select_core.rb16
-rw-r--r--test/nodes/test_sql_literal.rb24
-rw-r--r--test/nodes/test_window.rb12
-rw-r--r--test/support/fake_record.rb26
-rw-r--r--test/test_activerecord_compat.rb18
-rw-r--r--test/test_crud.rb2
-rw-r--r--test/test_factory_methods.rb4
-rw-r--r--test/test_insert_manager.rb39
-rw-r--r--test/test_select_manager.rb389
-rw-r--r--test/test_table.rb53
-rw-r--r--test/test_update_manager.rb9
-rw-r--r--test/visitors/test_bind_visitor.rb41
-rw-r--r--test/visitors/test_depth_first.rb14
-rw-r--r--test/visitors/test_dispatch_contamination.rb22
-rw-r--r--test/visitors/test_dot.rb10
-rw-r--r--test/visitors/test_ibm_db.rb16
-rw-r--r--test/visitors/test_informix.rb22
-rw-r--r--test/visitors/test_join_sql.rb42
-rw-r--r--test/visitors/test_mssql.rb20
-rw-r--r--test/visitors/test_mysql.rb29
-rw-r--r--test/visitors/test_oracle.rb62
-rw-r--r--test/visitors/test_postgres.rb90
-rw-r--r--test/visitors/test_sqlite.rb4
-rw-r--r--test/visitors/test_to_sql.rb358
94 files changed, 2526 insertions, 1165 deletions
diff --git a/.travis.yml b/.travis.yml
index e76e753520..8e5462c440 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,17 @@
-script: "rake test"
+language: ruby
+script:
+ - "rake test"
+ - "gem build arel.gemspec"
rvm:
- - 1.8.7
- - rbx-18mode
- - rbx-19mode
+ - rbx
- jruby
- - 1.9.2
- 1.9.3
+ - 2.0.0
+ - 2.1
- ruby-head
+matrix:
+ allow_failures:
+ - rvm: rbx
notifications:
email: false
irc:
diff --git a/Gemfile b/Gemfile
index 3c2680734c..12c57d3737 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,10 +2,11 @@
# DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`.
-source :gemcutter
+source "https://rubygems.org/"
-gem "minitest", "~>2.2", :group => [:development, :test]
-gem "hoe", "~>2.10", :group => [:development, :test]
+gem "minitest", "~>5.1", :group => [:development, :test]
+gem "rdoc", "~>4.0", :group => [:development, :test]
+gem "hoe", "~>3.5", :group => [:development, :test]
# vim: syntax=ruby
diff --git a/History.txt b/History.txt
index 66e2c73be7..4fc06816a3 100644
--- a/History.txt
+++ b/History.txt
@@ -1,3 +1,95 @@
+=== NEXT / 2014-02-10
+
+* Enhancements
+
+ * Remove deprecated `Arel::Expression`
+ * Remove deprecated `Arel::SqlLiteral`
+ * Remove deprecated `SelectManager#joins`
+ * Remove deprecated `SelectManager#to_a`
+ * Remove deprecated `Arel::Sql::Engine`
+ * Remove deprecated `Arel::InnerJoin` constant
+ * Remove deprecated `Arel::OuterJoin` constant
+
+== 5.0.0 / 2013-12-04
+
+* Enhancements
+
+ * Remove deprecated code
+
+* Bug Fixes
+
+ * Fix serializing a relation when calling `to_yaml`
+
+=== 4.0.2 / 2014-02-05
+
+ * Bug Fixes
+
+ * Fix `SqlLiteral` YAML serialization
+ * PostgreSQL bugfix for invalid SQL in subqueries
+
+== 4.0.1 / 2013-10-22
+
+* Enhancements
+
+ * Cache visitor dispatch on a per-visitor basis
+ * Improve performance of #uniq across a large number of nodes
+
+* Bug Fixes
+
+ * Make visitors threadsafe by removing @last_column
+ * Support `columns_for_distinct` with Oracle adapter
+
+== 3.0.3 / 2013-11-12
+
+* Enhancements
+
+ * Support ANSI 2003 window functions
+
+* Bug Fixes
+
+ * Fix joins in Informix
+
+== 3.0.2 / 2012-02-21
+
+* Enhancements
+
+ * Added a module for visiting and transforming bind values
+ * Fix in [] to be false, not in [] to be true
+
+* Bug Fixes
+
+ * Revert fix for LIMIT / OFFSET when query is ordered in Oracle
+
+== 3.0.1 / 2012-02-17
+
+* Bug Fixes
+
+ * Fixed LIMIT / OFFSET when query is ordered in Oracle
+
+== 3.0.0 / 2012-01-12
+
+* Enhancements
+
+ * Support connection pool and schema cache
+
+* Bug Fixes
+
+ * Conditions with no column can be followed by other conditions in Postgres
+
+== 2.2.3 / 2012-02-21
+
+* Enhancements
+
+ * Added a module for visiting and transforming bind values
+
+== 2.2.2 / 2012-02-20
+
+* Enhancements
+
+ * Support LOCK
+ * Allow using non-table alias as a right-hand relation name
+ * Added SelectManager#distinct
+
== 2.2.1 / 2011-09-15
* Enhancements
@@ -19,11 +111,11 @@
* Bug Fixes
* Fix depth-first traversal to understand ascending / descending nodes.
- * Parentheis are suppressed with nested unions in MySQL. Thanks jhtwong!
+ * Parenthesis are suppressed with nested unions in MySQL. Thanks jhtwong!
== 2.1.3 / 2011-06-27
-* Bug Fixues
+* Bug Fixes
* Fixed broken gem build.
@@ -31,7 +123,7 @@
* Bug Fixes
- * Visitors can define their own cache strategey so caches are not shared.
+ * Visitors can define their own cache strategy so caches are not shared.
Fixes #57
* Informix support fixed. Thanks Khronos.
* Ordering nodes broken to subclasses. Thanks Ernie Miller!
@@ -144,7 +236,7 @@
* Deprecations
- * Support for Subclasses of core classes will be removed in ARel version
+ * Support for Subclasses of core classes will be removed in Arel version
2.2.0
== 2.0.4
@@ -184,7 +276,7 @@
* Introduced "SQL compilers" for query generation.
* Added support for Oracle (Raimonds Simanovskis) and IBM/DB (Praveen Devarao).
- * Improvements to give better support to ActiveRecord.
+ * Improvements to give better support to Active Record.
== 0.2.1 / 2010-02-05
@@ -195,7 +287,7 @@
== 0.2.0 / 2010-01-31
* Ruby 1.9 compatibility
- * Many improvements to support the Arel integration into ActiveRecord (see `git log v0.1.0..v0.2.0`)
+ * Many improvements to support the Arel integration into Active Record (see `git log v0.1.0..v0.2.0`)
* Thanks to Emilio Tagua and Pratik Naik for many significant contributions!
== 0.1.0 / 2009-08-06
diff --git a/Manifest.txt b/Manifest.txt
index b70d706ba9..32e1dd43a6 100644
--- a/Manifest.txt
+++ b/Manifest.txt
@@ -12,11 +12,12 @@ 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/deprecated.rb
-lib/arel/expression.rb
lib/arel/expressions.rb
lib/arel/factory_methods.rb
lib/arel/insert_manager.rb
@@ -31,6 +32,7 @@ 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
@@ -42,6 +44,7 @@ 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/right_outer_join.rb
lib/arel/nodes/select_core.rb
lib/arel/nodes/select_statement.rb
lib/arel/nodes/sql_literal.rb
@@ -58,29 +61,29 @@ lib/arel/nodes/with.rb
lib/arel/order_predications.rb
lib/arel/predications.rb
lib/arel/select_manager.rb
-lib/arel/sql/engine.rb
-lib/arel/sql_literal.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/join_sql.rb
lib/arel/visitors/mssql.rb
lib/arel/visitors/mysql.rb
lib/arel/visitors/oracle.rb
-lib/arel/visitors/order_clauses.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
test/attributes/test_attribute.rb
+test/collectors/test_bind_collector.rb
+test/collectors/test_sql_string.rb
test/helper.rb
test/nodes/test_and.rb
test/nodes/test_as.rb
@@ -110,7 +113,6 @@ test/nodes/test_true.rb
test/nodes/test_update_statement.rb
test/nodes/test_window.rb
test/support/fake_record.rb
-test/test_activerecord_compat.rb
test/test_attributes.rb
test/test_crud.rb
test/test_delete_manager.rb
@@ -121,10 +123,10 @@ test/test_table.rb
test/test_update_manager.rb
test/visitors/test_bind_visitor.rb
test/visitors/test_depth_first.rb
+test/visitors/test_dispatch_contamination.rb
test/visitors/test_dot.rb
test/visitors/test_ibm_db.rb
test/visitors/test_informix.rb
-test/visitors/test_join_sql.rb
test/visitors/test_mssql.rb
test/visitors/test_mysql.rb
test/visitors/test_oracle.rb
diff --git a/README.markdown b/README.markdown
index afef04212a..1776330da8 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,13 +1,15 @@
-# ARel [![Build Status](https://secure.travis-ci.org/rails/arel.png)](http://travis-ci.org/rails/arel) [![Dependency Status](https://gemnasium.com/rails/arel.png)](https://gemnasium.com/rails/arel)
+# Arel [![Build Status](https://secure.travis-ci.org/rails/arel.svg?branch=master)](http://travis-ci.org/rails/arel) [![Dependency Status](https://gemnasium.com/rails/arel.svg)](https://gemnasium.com/rails/arel)
* http://github.com/rails/arel
## DESCRIPTION
+Arel Really Exasperates Logicians
+
Arel is a SQL AST manager for Ruby. It
1. Simplifies the generation of complex SQL queries
-2. Adapts to various RDBMS systems
+2. Adapts to various RDBMSes
It is intended to be a framework framework; that is, you can build your own ORM
with it, focusing on innovative object and collection modeling as opposed to
@@ -15,68 +17,122 @@ database compatibility and query generation.
## Status
-For the moment, Arel uses ActiveRecord's connection adapters to connect to the various engines, connection pooling, perform quoting, and do type conversion.
+For the moment, Arel uses Active Record's connection adapters to connect to the various engines, connection pooling, perform quoting, and do type conversion.
## A Gentle Introduction
-Generating a query with ARel is simple. For example, in order to produce
+Generating a query with Arel is simple. For example, in order to produce
- SELECT * FROM users
+```sql
+SELECT * FROM users
+```
you construct a table relation and convert it to sql:
- users = Arel::Table.new(:users)
- query = users.project(Arel.sql('*'))
- query.to_sql
+```ruby
+users = Arel::Table.new(:users)
+query = users.project(Arel.sql('*'))
+query.to_sql
+```
### More Sophisticated Queries
-Here is a whirlwind tour through the most common relational operators. These will probably cover 80% of all interaction with the database.
+Here is a whirlwind tour through the most common SQL operators. These will probably cover 80% of all interaction with the database.
First is the 'restriction' operator, `where`:
- users.where(users[:name].eq('amy'))
- # => SELECT * FROM users WHERE users.name = 'amy'
+```ruby
+users.where(users[:name].eq('amy'))
+# => SELECT * FROM users WHERE users.name = 'amy'
+```
What would, in SQL, be part of the `SELECT` clause is called in Arel a `projection`:
- users.project(users[:id]) # => SELECT users.id FROM users
+```ruby
+users.project(users[:id])
+# => SELECT users.id FROM users
+```
+
+Comparison operators `=`, `!=`, `<`, `>`, `<=`, `>=`, `IN`:
+
+```ruby
+users.where(users[:age].eq(10)).project(Arel.sql('*')) # => SELECT * FROM "users" WHERE "users"."age" = 10
+users.where(users[:age].not_eq(10)).project(Arel.sql('*')) # => SELECT * FROM "users" WHERE "users"."age" != 10
+users.where(users[:age].lt(10)).project(Arel.sql('*')) # => SELECT * FROM "users" WHERE "users"."age" < 10
+users.where(users[:age].gt(10)).project(Arel.sql('*')) # => SELECT * FROM "users" WHERE "users"."age" > 10
+users.where(users[:age].lteq(10)).project(Arel.sql('*')) # => SELECT * FROM "users" WHERE "users"."age" <= 10
+users.where(users[:age].gteq(10)).project(Arel.sql('*')) # => SELECT * FROM "users" WHERE "users"."age" >= 10
+users.where(users[:age].in([20, 16, 17])).project(Arel.sql('*')) # => SELECT * FROM "users" WHERE "users"."age" IN (20, 16, 17)
+```
Joins resemble SQL strongly:
- users.join(photos).on(users[:id].eq(photos[:user_id]))
- # => SELECT * FROM users INNER JOIN photos ON users.id = photos.user_id
+```ruby
+users.join(photos).on(users[:id].eq(photos[:user_id]))
+# => SELECT * FROM users INNER JOIN photos ON users.id = photos.user_id
+```
+
+Left Joins
+
+```ruby
+users.join(photos, Arel::Nodes::OuterJoin).on(users[:id].eq(photos[:user_id]))
+# => SELECT FROM users LEFT OUTER JOIN photos ON users.id = photos.user_id
+```
What are called `LIMIT` and `OFFSET` in SQL are called `take` and `skip` in Arel:
- users.take(5) # => SELECT * FROM users LIMIT 5
- users.skip(4) # => SELECT * FROM users OFFSET 4
+```ruby
+users.take(5) # => SELECT * FROM users LIMIT 5
+users.skip(4) # => SELECT * FROM users OFFSET 4
+```
`GROUP BY` is called `group`:
- users.group(users[:name]) # => SELECT * FROM users GROUP BY name
+```ruby
+users.project(users[:name]).group(users[:name])
+# => SELECT users.name FROM users GROUP BY users.name
+```
-The best property of the Relational Algebra is its "composability", or closure under all operations. For example, to restrict AND project, just "chain" the method invocations:
+The best property of arel is its "composability", or closure under all operations. For example, to restrict AND project, just "chain" the method invocations:
- users \
- .where(users[:name].eq('amy')) \
- .project(users[:id]) \
- # => SELECT users.id FROM users WHERE users.name = 'amy'
+```ruby
+users \
+ .where(users[:name].eq('amy')) \
+ .project(users[:id]) \
+# => SELECT users.id FROM users WHERE users.name = 'amy'
+```
All operators are chainable in this way, and they are chainable any number of times, in any order.
- users.where(users[:name].eq('bob')).where(users[:age].lt(25))
-
-Of course, many of the operators take multiple arguments, so the last example can be written more tersely:
-
- users.where(users[:name].eq('bob'), users[:age].lt(25))
+```ruby
+users.where(users[:name].eq('bob')).where(users[:age].lt(25))
+```
The `OR` operator works like this:
- users.where(users[:name].eq('bob').or(users[:age].lt(25)))
+```ruby
+users.where(users[:name].eq('bob').or(users[:age].lt(25)))
+```
The `AND` operator behaves similarly.
+Aggregate functions `AVG`, `SUM`, `COUNT`, `MIN`, `MAX`, `HAVING`:
+
+```ruby
+photos.group(photos[:user_id]).having(photos[:id].count.gt(5)) # => SELECT FROM photos GROUP BY photos.user_id HAVING COUNT(photos.id) > 5
+users.project(users[:age].sum) # => SELECT SUM(users.age) FROM users
+users.project(users[:age].average) # => SELECT AVG(users.age) FROM users
+users.project(users[:age].maximum) # => SELECT MAX(users.age) FROM users
+users.project(users[:age].minimum) # => SELECT MIN(users.age) FROM users
+users.project(users[:age].count) # => SELECT COUNT(users.age) FROM users
+```
+
+Aliasing Aggregate Functions:
+
+```ruby
+users.project(users[:age].average.as("mean_age")) # => SELECT AVG(users.age) AS mean_age FROM users
+```
+
### The Crazy 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).
@@ -85,34 +141,87 @@ The examples above are fairly simple and other libraries match or come close to
Suppose we have a table `products` with prices in different currencies. And we have a table `currency_rates`, of constantly changing currency rates. In Arel:
- products = Arel::Table.new(:products)
- products.columns # => [products[:id], products[:name], products[:price], products[:currency_id]]
+```ruby
+products = Arel::Table.new(:products)
+# Attributes: [:id, :name, :price, :currency_id]
- currency_rates = Arel::Table.new(:currency_rates)
- currency_rates.columns # => [currency_rates[:from_id], currency_rates[:to_id], currency_rates[:date], currency_rates[:rate]]
+currency_rates = Arel::Table.new(:currency_rates)
+# Attributes: [:from_id, :to_id, :date, :rate]
+```
Now, to order products by price in user preferred currency simply call:
- products.
- join(:currency_rates).on(products[:currency_id].eq(currency_rates[:from_id])).
- where(currency_rates[:to_id].eq(user_preferred_currency), currency_rates[:date].eq(Date.today)).
- order(products[:price] * currency_rates[:rate])
+```ruby
+products.
+ join(:currency_rates).on(products[:currency_id].eq(currency_rates[:from_id])).
+ where(currency_rates[:to_id].eq(user_preferred_currency), currency_rates[:date].eq(Date.today)).
+ order(products[:price] * currency_rates[:rate])
+```
#### Complex Joins
-Where Arel really shines 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:
+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:
- comments = Arel::Table.new(:comments)
+```ruby
+comments = Arel::Table.new(:comments)
+```
And this table has the following attributes:
- comments.columns # => [comments[:id], comments[:body], comments[:parent_id]]
+```ruby
+# [:id, :body, :parent_id]
+```
+
+The `parent_id` column is a foreign key from the `comments` table to itself.
+Joining a table to itself requires aliasing in SQL. This aliasing can be handled from Arel as below:
+
+```ruby
+replies = comments.alias
+comments_with_replies = \
+ comments.join(replies).on(replies[:parent_id].eq(comments[:id])).where(comments[:id].eq(1))
+# => SELECT * FROM comments INNER JOIN comments AS comments_2 WHERE comments_2.parent_id = comments.id AND comments.id = 1
+```
+
+This will return the reply for the first comment.
+
+[Common Table Expresssions(CTE)](https://en.wikipedia.org/wiki/Common_table_expressions#Common_table_expression) support via:
+
+Create a `CTE`
+
+```ruby
+cte_table = Arel::Table.new(:cte_table)
+composed_cte = Arel::Nodes::As.new(cte_table, photos.where(photos[:created_at].gt(Date.current)))
+```
+
+Use the created `CTE`:
+
+```ruby
+users.
+ join(cte_table).on(users[:id].eq(cte_table[:user_id])).
+ project(users[:id], cte_table[:click].sum).
+ with(composed_cte)
+
+# => WITH cte_table AS (SELECT FROM photos WHERE photos.created_at > '2014-05-02') SELECT users.id, SUM(cte_table.click) FROM users INNER JOIN cte_table ON users.id = cte_table.user_id
+```
+
+When your query is too complex for `Arel`, you can use `Arel::SqlLiteral`:
-The `parent_id` column is a foreign key from the `comments` table to itself. Now, joining a table to itself requires aliasing in SQL. In fact, you may alias in Arel as well:
+```ruby
+photo_clicks = Arel::Nodes::SqlLiteral.new(<<-SQL
+ CASE WHEN condition1 THEN calculation1
+ WHEN condition2 THEN calculation2
+ WHEN condition3 THEN calculation3
+ ELSE default_calculation END
+SQL
+)
+photos.project(photo_clicks.as("photo_clicks"))
+# => SELECT CASE WHEN condition1 THEN calculation1
+ WHEN condition2 THEN calculation2
+ WHEN condition3 THEN calculation3
+ ELSE default_calculation END
+ FROM "photos"
+```
- replies = comments.alias
- comments_with_replies = \
- comments.join(replies).on(replies[:parent_id].eq(comments[:id]))
- # => SELECT * FROM comments INNER JOIN comments AS comments_2 WHERE comments_2.parent_id = comments.id
+### License
-This will return the first comment's reply's body.
+Arel is released under the [MIT License](http://opensource.org/licenses/MIT).
diff --git a/Rakefile b/Rakefile
index bbf67415da..f9257d696f 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,5 +1,5 @@
require "rubygems"
-gem 'hoe', '>= 2.1.0'
+gem 'hoe', '>= 3.3.1'
require 'hoe'
Hoe.plugins.delete :rubyforge
@@ -10,10 +10,11 @@ Hoe.plugin :bundler # `gem install hoe-bundler`
Hoe.spec 'arel' do
developer('Aaron Patterson', 'aaron@tenderlovemaking.com')
- developer('Bryan Halmkamp', 'bryan@brynary.com')
+ developer('Bryan Helmkamp', 'bryan@brynary.com')
developer('Emilio Tagua', 'miloops@gmail.com')
developer('Nick Kallen', 'nick@example.org') # FIXME: need Nick's email
+ self.licenses = ['MIT']
self.readme_file = 'README.markdown'
self.extra_rdoc_files = FileList['README.markdown']
end
diff --git a/arel.gemspec b/arel.gemspec
index 711f01e598..832eb12f49 100644
--- a/arel.gemspec
+++ b/arel.gemspec
@@ -1,39 +1,40 @@
# -*- encoding: utf-8 -*-
+# stub: arel 6.0.0.beta1.20140817224534 ruby lib
Gem::Specification.new do |s|
s.name = "arel"
- s.version = "3.0.2.20120819075748"
+ s.version = "6.0.0.beta1.20140817224534"
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
- s.authors = ["Aaron Patterson", "Bryan Halmkamp", "Emilio Tagua", "Nick Kallen"]
- s.date = "2012-08-19"
- s.description = "Arel is a SQL AST manager for Ruby. It\n\n1. Simplifies the generation of complex SQL queries\n2. Adapts to various RDBMS systems\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.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
+ s.require_paths = ["lib"]
+ s.authors = ["Aaron Patterson", "Bryan Helmkamp", "Emilio Tagua", "Nick Kallen"]
+ s.date = "2014-08-18"
+ 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.email = ["aaron@tenderlovemaking.com", "bryan@brynary.com", "miloops@gmail.com", "nick@example.org"]
s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.markdown"]
- s.files = [".autotest", ".gemtest", ".travis.yml", "Gemfile", "History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.markdown", "Rakefile", "arel.gemspec", "lib/arel.rb", "lib/arel/alias_predication.rb", "lib/arel/attributes.rb", "lib/arel/attributes/attribute.rb", "lib/arel/compatibility/wheres.rb", "lib/arel/crud.rb", "lib/arel/delete_manager.rb", "lib/arel/deprecated.rb", "lib/arel/expression.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/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/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/named_function.rb", "lib/arel/nodes/node.rb", "lib/arel/nodes/outer_join.rb", "lib/arel/nodes/over.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/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/sql/engine.rb", "lib/arel/sql_literal.rb", "lib/arel/table.rb", "lib/arel/tree_manager.rb", "lib/arel/update_manager.rb", "lib/arel/visitors.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/join_sql.rb", "lib/arel/visitors/mssql.rb", "lib/arel/visitors/mysql.rb", "lib/arel/visitors/oracle.rb", "lib/arel/visitors/order_clauses.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", "test/attributes/test_attribute.rb", "test/helper.rb", "test/nodes/test_and.rb", "test/nodes/test_as.rb", "test/nodes/test_ascending.rb", "test/nodes/test_bin.rb", "test/nodes/test_count.rb", "test/nodes/test_delete_statement.rb", "test/nodes/test_descending.rb", "test/nodes/test_distinct.rb", "test/nodes/test_equality.rb", "test/nodes/test_extract.rb", "test/nodes/test_false.rb", "test/nodes/test_grouping.rb", "test/nodes/test_infix_operation.rb", "test/nodes/test_insert_statement.rb", "test/nodes/test_named_function.rb", "test/nodes/test_node.rb", "test/nodes/test_not.rb", "test/nodes/test_or.rb", "test/nodes/test_over.rb", "test/nodes/test_select_core.rb", "test/nodes/test_select_statement.rb", "test/nodes/test_sql_literal.rb", "test/nodes/test_sum.rb", "test/nodes/test_table_alias.rb", "test/nodes/test_true.rb", "test/nodes/test_update_statement.rb", "test/nodes/test_window.rb", "test/support/fake_record.rb", "test/test_activerecord_compat.rb", "test/test_attributes.rb", "test/test_crud.rb", "test/test_delete_manager.rb", "test/test_factory_methods.rb", "test/test_insert_manager.rb", "test/test_select_manager.rb", "test/test_table.rb", "test/test_update_manager.rb", "test/visitors/test_bind_visitor.rb", "test/visitors/test_depth_first.rb", "test/visitors/test_dot.rb", "test/visitors/test_ibm_db.rb", "test/visitors/test_informix.rb", "test/visitors/test_join_sql.rb", "test/visitors/test_mssql.rb", "test/visitors/test_mysql.rb", "test/visitors/test_oracle.rb", "test/visitors/test_postgres.rb", "test/visitors/test_sqlite.rb", "test/visitors/test_to_sql.rb"]
+ s.files = [".autotest", ".gemtest", ".travis.yml", "Gemfile", "History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.markdown", "Rakefile", "arel.gemspec", "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/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/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/named_function.rb", "lib/arel/nodes/node.rb", "lib/arel/nodes/outer_join.rb", "lib/arel/nodes/over.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/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/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", "test/attributes/test_attribute.rb", "test/collectors/test_bind_collector.rb", "test/collectors/test_sql_string.rb", "test/helper.rb", "test/nodes/test_and.rb", "test/nodes/test_as.rb", "test/nodes/test_ascending.rb", "test/nodes/test_bin.rb", "test/nodes/test_count.rb", "test/nodes/test_delete_statement.rb", "test/nodes/test_descending.rb", "test/nodes/test_distinct.rb", "test/nodes/test_equality.rb", "test/nodes/test_extract.rb", "test/nodes/test_false.rb", "test/nodes/test_grouping.rb", "test/nodes/test_infix_operation.rb", "test/nodes/test_insert_statement.rb", "test/nodes/test_named_function.rb", "test/nodes/test_node.rb", "test/nodes/test_not.rb", "test/nodes/test_or.rb", "test/nodes/test_over.rb", "test/nodes/test_select_core.rb", "test/nodes/test_select_statement.rb", "test/nodes/test_sql_literal.rb", "test/nodes/test_sum.rb", "test/nodes/test_table_alias.rb", "test/nodes/test_true.rb", "test/nodes/test_update_statement.rb", "test/nodes/test_window.rb", "test/support/fake_record.rb", "test/test_attributes.rb", "test/test_crud.rb", "test/test_delete_manager.rb", "test/test_factory_methods.rb", "test/test_insert_manager.rb", "test/test_select_manager.rb", "test/test_table.rb", "test/test_update_manager.rb", "test/visitors/test_bind_visitor.rb", "test/visitors/test_depth_first.rb", "test/visitors/test_dispatch_contamination.rb", "test/visitors/test_dot.rb", "test/visitors/test_ibm_db.rb", "test/visitors/test_informix.rb", "test/visitors/test_mssql.rb", "test/visitors/test_mysql.rb", "test/visitors/test_oracle.rb", "test/visitors/test_postgres.rb", "test/visitors/test_sqlite.rb", "test/visitors/test_to_sql.rb"]
s.homepage = "http://github.com/rails/arel"
+ s.licenses = ["MIT"]
s.rdoc_options = ["--main", "README.markdown"]
- s.require_paths = ["lib"]
- s.rubyforge_project = "arel"
- s.rubygems_version = "1.8.24"
- s.summary = "Arel is a SQL AST manager for Ruby"
- s.test_files = ["test/attributes/test_attribute.rb", "test/nodes/test_and.rb", "test/nodes/test_as.rb", "test/nodes/test_ascending.rb", "test/nodes/test_bin.rb", "test/nodes/test_count.rb", "test/nodes/test_delete_statement.rb", "test/nodes/test_descending.rb", "test/nodes/test_distinct.rb", "test/nodes/test_equality.rb", "test/nodes/test_extract.rb", "test/nodes/test_false.rb", "test/nodes/test_grouping.rb", "test/nodes/test_infix_operation.rb", "test/nodes/test_insert_statement.rb", "test/nodes/test_named_function.rb", "test/nodes/test_node.rb", "test/nodes/test_not.rb", "test/nodes/test_or.rb", "test/nodes/test_over.rb", "test/nodes/test_select_core.rb", "test/nodes/test_select_statement.rb", "test/nodes/test_sql_literal.rb", "test/nodes/test_sum.rb", "test/nodes/test_table_alias.rb", "test/nodes/test_true.rb", "test/nodes/test_update_statement.rb", "test/nodes/test_window.rb", "test/test_activerecord_compat.rb", "test/test_attributes.rb", "test/test_crud.rb", "test/test_delete_manager.rb", "test/test_factory_methods.rb", "test/test_insert_manager.rb", "test/test_select_manager.rb", "test/test_table.rb", "test/test_update_manager.rb", "test/visitors/test_bind_visitor.rb", "test/visitors/test_depth_first.rb", "test/visitors/test_dot.rb", "test/visitors/test_ibm_db.rb", "test/visitors/test_informix.rb", "test/visitors/test_join_sql.rb", "test/visitors/test_mssql.rb", "test/visitors/test_mysql.rb", "test/visitors/test_oracle.rb", "test/visitors/test_postgres.rb", "test/visitors/test_sqlite.rb", "test/visitors/test_to_sql.rb"]
+ s.rubygems_version = "2.2.2"
+ s.summary = "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby"
+ s.test_files = ["test/attributes/test_attribute.rb", "test/collectors/test_bind_collector.rb", "test/collectors/test_sql_string.rb", "test/nodes/test_and.rb", "test/nodes/test_as.rb", "test/nodes/test_ascending.rb", "test/nodes/test_bin.rb", "test/nodes/test_count.rb", "test/nodes/test_delete_statement.rb", "test/nodes/test_descending.rb", "test/nodes/test_distinct.rb", "test/nodes/test_equality.rb", "test/nodes/test_extract.rb", "test/nodes/test_false.rb", "test/nodes/test_grouping.rb", "test/nodes/test_infix_operation.rb", "test/nodes/test_insert_statement.rb", "test/nodes/test_named_function.rb", "test/nodes/test_node.rb", "test/nodes/test_not.rb", "test/nodes/test_or.rb", "test/nodes/test_over.rb", "test/nodes/test_select_core.rb", "test/nodes/test_select_statement.rb", "test/nodes/test_sql_literal.rb", "test/nodes/test_sum.rb", "test/nodes/test_table_alias.rb", "test/nodes/test_true.rb", "test/nodes/test_update_statement.rb", "test/nodes/test_window.rb", "test/test_attributes.rb", "test/test_crud.rb", "test/test_delete_manager.rb", "test/test_factory_methods.rb", "test/test_insert_manager.rb", "test/test_select_manager.rb", "test/test_table.rb", "test/test_update_manager.rb", "test/visitors/test_bind_visitor.rb", "test/visitors/test_depth_first.rb", "test/visitors/test_dispatch_contamination.rb", "test/visitors/test_dot.rb", "test/visitors/test_ibm_db.rb", "test/visitors/test_informix.rb", "test/visitors/test_mssql.rb", "test/visitors/test_mysql.rb", "test/visitors/test_oracle.rb", "test/visitors/test_postgres.rb", "test/visitors/test_sqlite.rb", "test/visitors/test_to_sql.rb"]
if s.respond_to? :specification_version then
- s.specification_version = 3
+ s.specification_version = 4
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
- s.add_development_dependency(%q<minitest>, ["~> 2.12"])
- s.add_development_dependency(%q<rdoc>, ["~> 3.10"])
- s.add_development_dependency(%q<hoe>, ["~> 2.16"])
+ s.add_development_dependency(%q<minitest>, ["~> 5.4"])
+ s.add_development_dependency(%q<rdoc>, ["~> 4.0"])
+ s.add_development_dependency(%q<hoe>, ["~> 3.12"])
else
- s.add_dependency(%q<minitest>, ["~> 2.12"])
- s.add_dependency(%q<rdoc>, ["~> 3.10"])
- s.add_dependency(%q<hoe>, ["~> 2.16"])
+ s.add_dependency(%q<minitest>, ["~> 5.4"])
+ s.add_dependency(%q<rdoc>, ["~> 4.0"])
+ s.add_dependency(%q<hoe>, ["~> 3.12"])
end
else
- s.add_dependency(%q<minitest>, ["~> 2.12"])
- s.add_dependency(%q<rdoc>, ["~> 3.10"])
- s.add_dependency(%q<hoe>, ["~> 2.16"])
+ s.add_dependency(%q<minitest>, ["~> 5.4"])
+ s.add_dependency(%q<rdoc>, ["~> 4.0"])
+ s.add_dependency(%q<hoe>, ["~> 3.12"])
end
end
diff --git a/lib/arel.rb b/lib/arel.rb
index 6d7aec64b1..80677953df 100644
--- a/lib/arel.rb
+++ b/lib/arel.rb
@@ -11,10 +11,6 @@ require 'arel/table'
require 'arel/attributes'
require 'arel/compatibility/wheres'
-#### these are deprecated
-require 'arel/expression'
-####
-
require 'arel/visitors'
require 'arel/tree_manager'
@@ -24,15 +20,8 @@ require 'arel/update_manager'
require 'arel/delete_manager'
require 'arel/nodes'
-
-#### these are deprecated
-require 'arel/deprecated'
-require 'arel/sql/engine'
-require 'arel/sql_literal'
-####
-
module Arel
- VERSION = '3.0.2'
+ VERSION = '6.0.0.beta1'
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
new file mode 100644
index 0000000000..05cd966509
--- /dev/null
+++ b/lib/arel/collectors/bind.rb
@@ -0,0 +1,36 @@
+module Arel
+ module Collectors
+ class Bind
+ def initialize
+ @parts = []
+ end
+
+ def << str
+ @parts << str
+ self
+ end
+
+ def add_bind bind
+ @parts << 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
+ end
+ end
+ end
+end
diff --git a/lib/arel/collectors/plain_string.rb b/lib/arel/collectors/plain_string.rb
new file mode 100644
index 0000000000..2505bc376e
--- /dev/null
+++ b/lib/arel/collectors/plain_string.rb
@@ -0,0 +1,18 @@
+module Arel
+ module Collectors
+ class PlainString
+ def initialize
+ @str = ''
+ end
+
+ def value
+ @str
+ end
+
+ def << str
+ @str << str
+ self
+ end
+ end
+ end
+end
diff --git a/lib/arel/collectors/sql_string.rb b/lib/arel/collectors/sql_string.rb
new file mode 100644
index 0000000000..8ca89ca7bd
--- /dev/null
+++ b/lib/arel/collectors/sql_string.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+require 'arel/collectors/plain_string'
+
+module Arel
+ module Collectors
+ class SQLString < PlainString
+ def add_bind bind
+ self << bind.to_s
+ self
+ end
+
+ def compile bvs
+ value
+ end
+ end
+ end
+end
diff --git a/lib/arel/crud.rb b/lib/arel/crud.rb
index 6c29d5fee4..6f4962cbfe 100644
--- a/lib/arel/crud.rb
+++ b/lib/arel/crud.rb
@@ -2,7 +2,7 @@ module Arel
###
# FIXME hopefully we can remove this
module Crud
- def compile_update values
+ def compile_update values, pk
um = UpdateManager.new @engine
if Nodes::SqlLiteral === values
@@ -10,6 +10,7 @@ module Arel
else
relation = values.first.first.relation
end
+ um.key = pk
um.table relation
um.set values
um.take @ast.limit.expr if @ast.limit
@@ -18,19 +19,6 @@ module Arel
um
end
- # FIXME: this method should go away
- def update values
- if $VERBOSE
- warn <<-eowarn
-update (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_update`
- eowarn
- end
-
- um = compile_update values
- @engine.connection.update um.to_sql, 'AREL'
- end
-
def compile_insert values
im = create_insert
im.insert values
@@ -41,17 +29,6 @@ switch to `compile_update`
InsertManager.new @engine
end
- # FIXME: this method should go away
- def insert values
- if $VERBOSE
- warn <<-eowarn
-insert (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_insert`
- eowarn
- end
- @engine.connection.insert compile_insert(values).to_sql
- end
-
def compile_delete
dm = DeleteManager.new @engine
dm.wheres = @ctx.wheres
@@ -59,14 +36,5 @@ switch to `compile_insert`
dm
end
- def delete
- if $VERBOSE
- warn <<-eowarn
-delete (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_delete`
- eowarn
- end
- @engine.connection.delete compile_delete.to_sql, 'AREL'
- end
end
end
diff --git a/lib/arel/deprecated.rb b/lib/arel/deprecated.rb
deleted file mode 100644
index 31db11bd2c..0000000000
--- a/lib/arel/deprecated.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module Arel
- InnerJoin = Nodes::InnerJoin
- OuterJoin = Nodes::OuterJoin
-end
diff --git a/lib/arel/expression.rb b/lib/arel/expression.rb
deleted file mode 100644
index 3884d6ede6..0000000000
--- a/lib/arel/expression.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Arel
- module Expression
- include Arel::OrderPredications
- end
-end
diff --git a/lib/arel/expressions.rb b/lib/arel/expressions.rb
index fa18f15b67..d40268c292 100644
--- a/lib/arel/expressions.rb
+++ b/lib/arel/expressions.rb
@@ -5,23 +5,24 @@ module Arel
end
def sum
- Nodes::Sum.new [self], Nodes::SqlLiteral.new('sum_id')
+ Nodes::Sum.new [self]
end
def maximum
- Nodes::Max.new [self], Nodes::SqlLiteral.new('max_id')
+ Nodes::Max.new [self]
end
def minimum
- Nodes::Min.new [self], Nodes::SqlLiteral.new('min_id')
+ Nodes::Min.new [self]
end
def average
- Nodes::Avg.new [self], Nodes::SqlLiteral.new('avg_id')
+ Nodes::Avg.new [self]
end
def extract field
Nodes::Extract.new [self], field
end
+
end
end
diff --git a/lib/arel/factory_methods.rb b/lib/arel/factory_methods.rb
index 3b16feae10..cb66f6f888 100644
--- a/lib/arel/factory_methods.rb
+++ b/lib/arel/factory_methods.rb
@@ -37,7 +37,7 @@ module Arel
###
# Create a LOWER() function
def lower column
- Nodes::NamedFunction.new 'LOWER', [column]
+ Nodes::NamedFunction.new 'LOWER', [Nodes.build_quoted(column)]
end
end
end
diff --git a/lib/arel/insert_manager.rb b/lib/arel/insert_manager.rb
index d6a11b7be0..8839dd8181 100644
--- a/lib/arel/insert_manager.rb
+++ b/lib/arel/insert_manager.rb
@@ -13,11 +13,15 @@ module Arel
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 = SqlLiteral.new(fields)
+ @ast.values = Nodes::SqlLiteral.new(fields)
else
@ast.relation ||= fields.first.first.relation
diff --git a/lib/arel/nodes.rb b/lib/arel/nodes.rb
index 54caea69a1..c6bde8c3cc 100644
--- a/lib/arel/nodes.rb
+++ b/lib/arel/nodes.rb
@@ -45,8 +45,49 @@ require 'arel/nodes/named_function'
require 'arel/nodes/window'
# 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'
+
+module Arel
+ module Nodes
+ class Casted < Arel::Nodes::Node # :nodoc:
+ attr_reader :val, :attribute
+ def initialize val, attribute
+ @val = val
+ @attribute = attribute
+ super()
+ end
+
+ def nil?; @val.nil?; 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:
+ end
+
+ def self.build_quoted other, attribute = nil
+ case other
+ when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager
+ other
+ else
+ case attribute
+ when Arel::Attributes::Attribute
+ Casted.new other, attribute
+ else
+ Quoted.new other
+ end
+ end
+ end
+ end
+end
diff --git a/lib/arel/nodes/and.rb b/lib/arel/nodes/and.rb
index 0d0fb3ee82..8e1afda709 100644
--- a/lib/arel/nodes/and.rb
+++ b/lib/arel/nodes/and.rb
@@ -3,11 +3,8 @@ module Arel
class And < Arel::Nodes::Node
attr_reader :children
- def initialize children, right = nil
- unless Array === children
- warn "(#{caller.first}) AND nodes should be created with a list"
- children = [children, right]
- end
+ def initialize children
+ super()
@children = children
end
diff --git a/lib/arel/nodes/binary.rb b/lib/arel/nodes/binary.rb
index d55c7a5478..939684957f 100644
--- a/lib/arel/nodes/binary.rb
+++ b/lib/arel/nodes/binary.rb
@@ -4,6 +4,7 @@ module Arel
attr_accessor :left, :right
def initialize left, right
+ super()
@left = left
@right = right
end
@@ -39,7 +40,9 @@ module Arel
Matches
NotEqual
NotIn
+ NotRegexp
Or
+ Regexp
Union
UnionAll
Intersect
diff --git a/lib/arel/nodes/extract.rb b/lib/arel/nodes/extract.rb
index 92fbde62e1..7ed678ca08 100644
--- a/lib/arel/nodes/extract.rb
+++ b/lib/arel/nodes/extract.rb
@@ -1,32 +1,23 @@
module Arel
module Nodes
-
class Extract < Arel::Nodes::Unary
- include Arel::Expression
+ include Arel::AliasPredication
include Arel::Predications
attr_accessor :field
- attr_accessor :alias
- def initialize expr, field, aliaz = nil
+ def initialize expr, field
super(expr)
@field = field
- @alias = aliaz && SqlLiteral.new(aliaz)
- end
-
- def as aliaz
- self.alias = SqlLiteral.new(aliaz)
- self
end
def hash
- super ^ [@field, @alias].hash
+ super ^ @field.hash
end
def eql? other
super &&
- self.field == other.field &&
- self.alias == other.alias
+ self.field == other.field
end
alias :== :eql?
end
diff --git a/lib/arel/nodes/full_outer_join.rb b/lib/arel/nodes/full_outer_join.rb
new file mode 100644
index 0000000000..708f161c9a
--- /dev/null
+++ b/lib/arel/nodes/full_outer_join.rb
@@ -0,0 +1,6 @@
+module Arel
+ module Nodes
+ class FullOuterJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/lib/arel/nodes/function.rb b/lib/arel/nodes/function.rb
index 90bbf4a77b..733a00df46 100644
--- a/lib/arel/nodes/function.rb
+++ b/lib/arel/nodes/function.rb
@@ -1,12 +1,12 @@
module Arel
module Nodes
class Function < Arel::Nodes::Node
- include Arel::Expression
include Arel::Predications
include Arel::WindowPredications
attr_accessor :expressions, :alias, :distinct
def initialize expr, aliaz = nil
+ super()
@expressions = expr
@alias = aliaz && SqlLiteral.new(aliaz)
@distinct = false
diff --git a/lib/arel/nodes/insert_statement.rb b/lib/arel/nodes/insert_statement.rb
index 518160cce4..ada4fcc562 100644
--- a/lib/arel/nodes/insert_statement.rb
+++ b/lib/arel/nodes/insert_statement.rb
@@ -1,28 +1,32 @@
module Arel
module Nodes
class InsertStatement < Arel::Nodes::Node
- attr_accessor :relation, :columns, :values
+ 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].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?
diff --git a/lib/arel/nodes/node.rb b/lib/arel/nodes/node.rb
index 84dcb1cdf5..239c4fd766 100644
--- a/lib/arel/nodes/node.rb
+++ b/lib/arel/nodes/node.rb
@@ -1,3 +1,5 @@
+require 'arel/collectors/sql_string'
+
module Arel
module Nodes
###
@@ -6,6 +8,16 @@ module Arel
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.
@@ -32,7 +44,9 @@ module Arel
#
# Maybe we should just use `Table.engine`? :'(
def to_sql engine = Table.engine
- engine.connection.visitor.accept self
+ collector = Arel::Collectors::SQLString.new
+ collector = engine.connection.visitor.accept self, collector
+ collector.value
end
# Iterate through AST, nodes will be yielded depth-first
diff --git a/lib/arel/nodes/right_outer_join.rb b/lib/arel/nodes/right_outer_join.rb
new file mode 100644
index 0000000000..ea1ddb7d52
--- /dev/null
+++ b/lib/arel/nodes/right_outer_join.rb
@@ -0,0 +1,6 @@
+module Arel
+ module Nodes
+ class RightOuterJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/lib/arel/nodes/select_core.rb b/lib/arel/nodes/select_core.rb
index 3b400c768d..09ae420aa1 100644
--- a/lib/arel/nodes/select_core.rb
+++ b/lib/arel/nodes/select_core.rb
@@ -5,6 +5,7 @@ module Arel
attr_accessor :having, :source, :set_quantifier
def initialize
+ super()
@source = JoinSource.new nil
@top = nil
diff --git a/lib/arel/nodes/select_statement.rb b/lib/arel/nodes/select_statement.rb
index 32bdd7080c..830ac27046 100644
--- a/lib/arel/nodes/select_statement.rb
+++ b/lib/arel/nodes/select_statement.rb
@@ -5,6 +5,7 @@ module Arel
attr_accessor :limit, :orders, :lock, :offset, :with
def initialize cores = [SelectCore.new]
+ super()
@cores = cores
@orders = []
@limit = nil
diff --git a/lib/arel/nodes/sql_literal.rb b/lib/arel/nodes/sql_literal.rb
index 1bae8c9366..b43288b29c 100644
--- a/lib/arel/nodes/sql_literal.rb
+++ b/lib/arel/nodes/sql_literal.rb
@@ -5,6 +5,10 @@ module Arel
include Arel::Predications
include Arel::AliasPredication
include Arel::OrderPredications
+
+ def encode_with(coder)
+ coder.scalar = self.to_s
+ end
end
class BindParam < SqlLiteral
diff --git a/lib/arel/nodes/unary.rb b/lib/arel/nodes/unary.rb
index 42c31267dd..3d4a4b014a 100644
--- a/lib/arel/nodes/unary.rb
+++ b/lib/arel/nodes/unary.rb
@@ -5,6 +5,7 @@ module Arel
alias :value :expr
def initialize expr
+ super()
@expr = expr
end
diff --git a/lib/arel/nodes/window.rb b/lib/arel/nodes/window.rb
index 3c05f47f14..fee8eeff7a 100644
--- a/lib/arel/nodes/window.rb
+++ b/lib/arel/nodes/window.rb
@@ -1,11 +1,12 @@
module Arel
module Nodes
class Window < Arel::Nodes::Node
- include Arel::Expression
- attr_accessor :orders, :framing
+ attr_accessor :orders, :framing, :partitions
def initialize
@orders = []
+ @partitions = []
+ @framing = nil
end
def order *expr
@@ -16,16 +17,32 @@ module Arel
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)
- frame(Rows.new(expr))
+ if @framing
+ Rows.new(expr)
+ else
+ frame(Rows.new(expr))
+ end
end
def range(expr = nil)
- frame(Range.new(expr))
+ if @framing
+ Range.new(expr)
+ else
+ frame(Range.new(expr))
+ end
end
def initialize_copy other
@@ -40,7 +57,8 @@ module Arel
def eql? other
self.class == other.class &&
self.orders == other.orders &&
- self.framing == other.framing
+ self.framing == other.framing &&
+ self.partitions == other.partitions
end
alias :== :eql?
end
diff --git a/lib/arel/predications.rb b/lib/arel/predications.rb
index e3f72d46a2..1941383068 100644
--- a/lib/arel/predications.rb
+++ b/lib/arel/predications.rb
@@ -1,7 +1,7 @@
module Arel
module Predications
def not_eq other
- Nodes::NotEqual.new self, other
+ Nodes::NotEqual.new self, Nodes.build_quoted(other, self)
end
def not_eq_any others
@@ -13,7 +13,7 @@ module Arel
end
def eq other
- Nodes::Equality.new self, other
+ Nodes::Equality.new self, Nodes.build_quoted(other, self)
end
def eq_any others
@@ -21,7 +21,7 @@ module Arel
end
def eq_all others
- grouping_all :eq, others
+ grouping_all :eq, others.map { |x| Nodes.build_quoted(x, self) }
end
def in other
@@ -29,15 +29,27 @@ module Arel
when Arel::SelectManager
Arel::Nodes::In.new(self, other.ast)
when Range
- if other.exclude_end?
- left = Nodes::GreaterThanOrEqual.new(self, other.begin)
- right = Nodes::LessThan.new(self, other.end)
+ if other.begin == -Float::INFINITY
+ if other.end == Float::INFINITY
+ Nodes::NotIn.new self, []
+ elsif other.exclude_end?
+ Nodes::LessThan.new(self, Nodes.build_quoted(other.end, self))
+ else
+ Nodes::LessThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
+ end
+ elsif other.end == Float::INFINITY
+ Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.begin, self))
+ elsif other.exclude_end?
+ left = Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.begin, self))
+ right = Nodes::LessThan.new(self, Nodes.build_quoted(other.end, self))
Nodes::And.new [left, right]
else
- Nodes::Between.new(self, Nodes::And.new([other.begin, other.end]))
+ Nodes::Between.new(self, Nodes::And.new([Nodes.build_quoted(other.begin, self), Nodes.build_quoted(other.end, self)]))
end
+ when Array
+ Nodes::In.new self, other.map { |x| Nodes.build_quoted(x, self) }
else
- Nodes::In.new self, other
+ Nodes::In.new self, Nodes.build_quoted(other, self)
end
end
@@ -54,17 +66,29 @@ module Arel
when Arel::SelectManager
Arel::Nodes::NotIn.new(self, other.ast)
when Range
- if other.exclude_end?
- left = Nodes::LessThan.new(self, other.begin)
- right = Nodes::GreaterThanOrEqual.new(self, other.end)
- Nodes::Or.new left, right
+ if other.begin == -Float::INFINITY # The range begins with negative infinity
+ if other.end == Float::INFINITY
+ Nodes::In.new self, [] # The range is infinite, so return an empty range
+ elsif other.exclude_end?
+ Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
+ else
+ Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, self))
+ end
+ elsif other.end == Float::INFINITY
+ Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self))
else
- left = Nodes::LessThan.new(self, other.begin)
- right = Nodes::GreaterThan.new(self, other.end)
+ left = Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self))
+ if other.exclude_end?
+ right = Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
+ else
+ right = Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, self))
+ end
Nodes::Or.new left, right
end
+ when Array
+ Nodes::NotIn.new self, other.map { |x| Nodes.build_quoted(x, self) }
else
- Nodes::NotIn.new self, other
+ Nodes::NotIn.new self, Nodes.build_quoted(other, self)
end
end
@@ -77,7 +101,7 @@ module Arel
end
def matches other
- Nodes::Matches.new self, other
+ Nodes::Matches.new self, Nodes.build_quoted(other, self)
end
def matches_any others
@@ -89,7 +113,7 @@ module Arel
end
def does_not_match other
- Nodes::DoesNotMatch.new self, other
+ Nodes::DoesNotMatch.new self, Nodes.build_quoted(other, self)
end
def does_not_match_any others
@@ -101,7 +125,7 @@ module Arel
end
def gteq right
- Nodes::GreaterThanOrEqual.new self, right
+ Nodes::GreaterThanOrEqual.new self, Nodes.build_quoted(right, self)
end
def gteq_any others
@@ -113,7 +137,7 @@ module Arel
end
def gt right
- Nodes::GreaterThan.new self, right
+ Nodes::GreaterThan.new self, Nodes.build_quoted(right, self)
end
def gt_any others
@@ -125,7 +149,7 @@ module Arel
end
def lt right
- Nodes::LessThan.new self, right
+ Nodes::LessThan.new self, Nodes.build_quoted(right, self)
end
def lt_any others
@@ -137,7 +161,7 @@ module Arel
end
def lteq right
- Nodes::LessThanOrEqual.new self, right
+ Nodes::LessThanOrEqual.new self, Nodes.build_quoted(right, self)
end
def lteq_any others
diff --git a/lib/arel/select_manager.rb b/lib/arel/select_manager.rb
index d20faa6eb3..5a05e7e181 100644
--- a/lib/arel/select_manager.rb
+++ b/lib/arel/select_manager.rb
@@ -1,7 +1,11 @@
+require 'arel/collectors/sql_string'
+
module Arel
class SelectManager < Arel::TreeManager
include Arel::Crud
+ STRING_OR_SYMBOL_CLASS = [Symbol, String]
+
def initialize engine, table = nil
super(engine)
@ast = Nodes::SelectStatement.new
@@ -15,7 +19,7 @@ module Arel
end
def limit
- @ast.limit && @ast.limit.expr
+ @ast.limit && @ast.limit.expr.expr
end
alias :taken :limit
@@ -47,14 +51,6 @@ module Arel
create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
end
- def where_clauses
- if $VERBOSE
- warn "(#{caller.first}) where_clauses is deprecated and will be removed in arel 4.0.0 with no replacement"
- end
- to_sql = Visitors::ToSql.new @engine.connection
- @ctx.wheres.map { |c| to_sql.accept c }
- end
-
def lock locking = Arel.sql('FOR UPDATE')
case locking
when true
@@ -90,9 +86,6 @@ module Arel
def from table
table = Nodes::SqlLiteral.new(table) if String === table
- # FIXME: this is a hack to support
- # test_with_two_tables_in_from_without_getting_double_quoted
- # from the AR tests.
case table
when Nodes::Join
@@ -113,7 +106,7 @@ module Arel
case relation
when String, Nodes::SqlLiteral
- raise if relation.blank?
+ raise if relation.empty?
klass = Nodes::StringJoin
end
@@ -121,6 +114,10 @@ module Arel
self
end
+ def outer_join relation
+ join(relation, Nodes::OuterJoin)
+ end
+
def having *exprs
@ctx.having = Nodes::Having.new(collapse(exprs, @ctx.having))
self
@@ -136,11 +133,15 @@ module Arel
# FIXME: converting these to SQLLiterals is probably not good, but
# rails tests require it.
@ctx.projections.concat projections.map { |x|
- [Symbol, String].include?(x.class) ? SqlLiteral.new(x.to_s) : 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
@@ -151,12 +152,22 @@ module Arel
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 === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
+ STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
}
self
end
@@ -165,16 +176,11 @@ module Arel
@ast.orders
end
- def wheres
- warn "#{caller[0]}: SelectManager#wheres is deprecated and will be removed in ARel 4.0.0 with no replacement"
- Compatibility::Wheres.new @engine.connection, @ctx.wheres
- end
-
def where_sql
return if @ctx.wheres.empty?
viz = Visitors::WhereSql.new @engine.connection
- Nodes::SqlLiteral.new viz.accept @ctx
+ Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value
end
def union operation, other = nil
@@ -210,8 +216,8 @@ module Arel
def take limit
if limit
- @ast.limit = Nodes::Limit.new(limit)
- @ctx.top = Nodes::Top.new(limit)
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit))
+ @ctx.top = Nodes::Top.new(Nodes.build_quoted(limit))
else
@ast.limit = nil
@ctx.top = nil
@@ -220,20 +226,6 @@ module Arel
end
alias limit= take
- def join_sql
- return nil if @ctx.source.right.empty?
-
- sql = visitor.dup.extend(Visitors::JoinSql).accept @ctx
- Nodes::SqlLiteral.new sql
- end
-
- def order_clauses
- visitor = Visitors::OrderClauses.new(@engine.connection)
- visitor.accept(@ast).map { |x|
- Nodes::SqlLiteral.new x
- }
- end
-
def join_sources
@ctx.source.right
end
@@ -242,14 +234,6 @@ module Arel
@ctx.source
end
- def joins manager
- if $VERBOSE
- warn "joins is deprecated and will be removed in 4.0.0"
- warn "please remove your call to joins from #{caller.first}"
- end
- manager.join_sql
- end
-
class Row < Struct.new(:data) # :nodoc:
def id
data['id']
@@ -262,37 +246,6 @@ module Arel
end
end
- def to_a # :nodoc:
- warn "to_a is deprecated. Please remove it from #{caller[0]}"
- # FIXME: I think `select` should be made public...
- @engine.connection.send(:select, to_sql, 'AREL').map { |x| Row.new(x) }
- end
-
- # FIXME: this method should go away
- def insert values
- if $VERBOSE
- warn <<-eowarn
-insert (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_insert`
- eowarn
- end
-
- im = compile_insert(values)
- table = @ctx.froms
-
- primary_key = table.primary_key
- primary_key_name = primary_key.name if primary_key
-
- # FIXME: in AR tests values sometimes were Array and not Hash therefore is_a?(Hash) check is added
- primary_key_value = primary_key && values.is_a?(Hash) && values[primary_key]
- im.into table
- # Oracle adapter needs primary key name to generate RETURNING ... INTO ... clause
- # for tables which assign primary key value using trigger.
- # RETURNING ... INTO ... clause will be added only if primary_key_value is nil
- # therefore it is necessary to pass primary key value as well
- @engine.connection.insert im.to_sql, 'AREL', primary_key_name, primary_key_value
- end
-
private
def collapse exprs, existing = nil
exprs = exprs.unshift(existing.expr) if existing
diff --git a/lib/arel/sql/engine.rb b/lib/arel/sql/engine.rb
deleted file mode 100644
index 8917f5f294..0000000000
--- a/lib/arel/sql/engine.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Arel
- module Sql
- class Engine
- def self.new thing
- #warn "#{caller.first} -- Engine will be removed"
- thing
- end
- end
- end
-end
diff --git a/lib/arel/sql_literal.rb b/lib/arel/sql_literal.rb
deleted file mode 100644
index 5cb4973117..0000000000
--- a/lib/arel/sql_literal.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module Arel
- class SqlLiteral < Nodes::SqlLiteral
- end
-end
diff --git a/lib/arel/table.rb b/lib/arel/table.rb
index 6f1ab7e90f..01d4561ff1 100644
--- a/lib/arel/table.rb
+++ b/lib/arel/table.rb
@@ -32,7 +32,7 @@ module Arel
def primary_key
if $VERBOSE
warn <<-eowarn
-primary_key (#{caller.first}) is deprecated and will be removed in ARel 4.0.0
+primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0
eowarn
end
@primary_key ||= begin
@@ -52,26 +52,22 @@ primary_key (#{caller.first}) is deprecated and will be removed in ARel 4.0.0
SelectManager.new(@engine, table)
end
- def joins manager
- if $VERBOSE
- warn "joins is deprecated and will be removed in 4.0.0"
- warn "please remove your call to joins from #{caller.first}"
- end
- nil
- end
-
def join relation, klass = Nodes::InnerJoin
return from(self) unless relation
case relation
when String, Nodes::SqlLiteral
- raise if relation.blank?
+ raise if relation.empty?
klass = Nodes::StringJoin
end
from(self).join(relation, klass)
end
+ def outer_join relation
+ join(relation, Nodes::OuterJoin)
+ end
+
def group *columns
from(self).group(*columns)
end
@@ -100,17 +96,6 @@ primary_key (#{caller.first}) is deprecated and will be removed in ARel 4.0.0
from(self).having expr
end
- def columns
- if $VERBOSE
- warn <<-eowarn
-(#{caller.first}) Arel::Table#columns is deprecated and will be removed in
-Arel 4.0.0 with no replacement. PEW PEW PEW!!!
- eowarn
- end
- @columns ||=
- attributes_for @engine.connection.columns(@name, "#{@name} Columns")
- end
-
def [] name
::Arel::Attribute.new self, name
end
@@ -123,8 +108,19 @@ Arel 4.0.0 with no replacement. PEW PEW PEW!!!
InsertManager.new(@engine)
end
+ def update_manager
+ UpdateManager.new(@engine)
+ end
+
+ def delete_manager
+ DeleteManager.new(@engine)
+ end
+
def hash
- [@name, @engine, @aliases, @table_alias].hash
+ # Perf note: aliases, table alias and engine 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
@@ -146,15 +142,5 @@ Arel 4.0.0 with no replacement. PEW PEW PEW!!!
end
end
- @@table_cache = nil
- def self.table_cache engine # :nodoc:
- if $VERBOSE
- warn <<-eowarn
-(#{caller.first}) Arel::Table.table_cache is deprecated and will be removed in
-Arel 4.0.0 with no replacement. PEW PEW PEW!!!
- eowarn
- end
- @@table_cache ||= Hash[engine.connection.tables.map { |x| [x,true] }]
- end
end
end
diff --git a/lib/arel/tree_manager.rb b/lib/arel/tree_manager.rb
index 21a52d8a60..8bff97af78 100644
--- a/lib/arel/tree_manager.rb
+++ b/lib/arel/tree_manager.rb
@@ -1,16 +1,23 @@
+require 'arel/collectors/sql_string'
+
module Arel
class TreeManager
include Arel::FactoryMethods
attr_reader :ast, :engine
+ attr_accessor :bind_values
+
def initialize engine
@engine = engine
@ctx = nil
+ @bind_values = []
end
def to_dot
- Visitors::Dot.new.accept @ast
+ collector = Arel::Collectors::PlainString.new
+ collector = Visitors::Dot.new.accept @ast, collector
+ collector.value
end
def visitor
@@ -18,7 +25,9 @@ module Arel
end
def to_sql
- visitor.accept @ast
+ collector = Arel::Collectors::SQLString.new
+ collector = visitor.accept @ast, collector
+ collector.value
end
def initialize_copy other
diff --git a/lib/arel/update_manager.rb b/lib/arel/update_manager.rb
index 56e219040c..db8cf05f76 100644
--- a/lib/arel/update_manager.rb
+++ b/lib/arel/update_manager.rb
@@ -7,12 +7,12 @@ module Arel
end
def take limit
- @ast.limit = Nodes::Limit.new(limit) if limit
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
self
end
def key= key
- @ast.key = key
+ @ast.key = Nodes.build_quoted(key)
end
def key
diff --git a/lib/arel/visitors.rb b/lib/arel/visitors.rb
index 8276eace2b..4a8d254ba7 100644
--- a/lib/arel/visitors.rb
+++ b/lib/arel/visitors.rb
@@ -6,9 +6,7 @@ require 'arel/visitors/postgresql'
require 'arel/visitors/mysql'
require 'arel/visitors/mssql'
require 'arel/visitors/oracle'
-require 'arel/visitors/join_sql'
require 'arel/visitors/where_sql'
-require 'arel/visitors/order_clauses'
require 'arel/visitors/dot'
require 'arel/visitors/ibm_db'
require 'arel/visitors/informix'
diff --git a/lib/arel/visitors/bind_substitute.rb b/lib/arel/visitors/bind_substitute.rb
new file mode 100644
index 0000000000..ce0fb5c924
--- /dev/null
+++ b/lib/arel/visitors/bind_substitute.rb
@@ -0,0 +1,9 @@
+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
index 0f1e38315b..c336e87395 100644
--- a/lib/arel/visitors/bind_visitor.rb
+++ b/lib/arel/visitors/bind_visitor.rb
@@ -6,19 +6,34 @@ module Arel
super
end
- def accept node, &block
+ def accept node, collector, &block
@block = block if block_given?
super
end
private
- def visit_Arel_Nodes_BindParam o
+
+ 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
- @block.call
+ val = @block.call
+ if String === val
+ collector << val
+ end
else
super
end
end
+
end
end
end
diff --git a/lib/arel/visitors/depth_first.rb b/lib/arel/visitors/depth_first.rb
index 2894bea19c..eab20ac831 100644
--- a/lib/arel/visitors/depth_first.rb
+++ b/lib/arel/visitors/depth_first.rb
@@ -53,7 +53,7 @@ module Arel
end
def nary o
- o.children.each { |child| visit child }
+ o.children.each { |child| visit child}
end
alias :visit_Arel_Nodes_And :nary
@@ -67,6 +67,7 @@ module Arel
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
@@ -78,8 +79,11 @@ module Arel
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
@@ -112,7 +116,6 @@ module Arel
alias :visit_Arel_Nodes_SqlLiteral :terminal
alias :visit_Arel_Nodes_BindParam :terminal
alias :visit_Arel_Nodes_Window :terminal
- alias :visit_Arel_SqlLiteral :terminal
alias :visit_BigDecimal :terminal
alias :visit_Bignum :terminal
alias :visit_Class :terminal
diff --git a/lib/arel/visitors/dot.rb b/lib/arel/visitors/dot.rb
index 9bf9f88d18..12cce1e266 100644
--- a/lib/arel/visitors/dot.rb
+++ b/lib/arel/visitors/dot.rb
@@ -22,12 +22,13 @@ module Arel
@seen = {}
end
- def accept object
- super
- to_dot
+ def accept object, collector
+ visit object
+ collector << to_dot
end
private
+
def visit_Arel_Nodes_Ordering o
visit_edge o, "expr"
end
@@ -54,7 +55,9 @@ module Arel
visit_edge o, "left"
visit_edge o, "right"
end
- alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
+ 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"
@@ -65,7 +68,6 @@ module Arel
visit_edge o, "expr"
end
alias :visit_Arel_Nodes_Group :unary
- alias :visit_Arel_Nodes_BindParam :unary
alias :visit_Arel_Nodes_Grouping :unary
alias :visit_Arel_Nodes_Having :unary
alias :visit_Arel_Nodes_Limit :unary
@@ -80,15 +82,17 @@ module Arel
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"
+ visit_edge o, "name"
end
alias :visit_Arel_Nodes_NamedWindow :named_window
@@ -126,7 +130,7 @@ module Arel
visit_edge o, "source"
visit_edge o, "projections"
visit_edge o, "wheres"
- visit_edge o, "windows"
+ visit_edge o, "windows"
end
def visit_Arel_Nodes_SelectStatement o
@@ -194,7 +198,7 @@ module Arel
alias :visit_NilClass :visit_String
alias :visit_TrueClass :visit_String
alias :visit_FalseClass :visit_String
- alias :visit_Arel_SqlLiteral :visit_String
+ alias :visit_Arel_Nodes_BindParam :visit_String
alias :visit_Fixnum :visit_String
alias :visit_BigDecimal :visit_String
alias :visit_Float :visit_String
@@ -255,7 +259,7 @@ module Arel
end
def to_dot
- "digraph \"ARel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
+ "digraph \"Arel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
@nodes.map { |node|
label = "<f0>#{node.name}"
diff --git a/lib/arel/visitors/ibm_db.rb b/lib/arel/visitors/ibm_db.rb
index 0c26a3ae9e..f1d126790d 100644
--- a/lib/arel/visitors/ibm_db.rb
+++ b/lib/arel/visitors/ibm_db.rb
@@ -3,8 +3,10 @@ module Arel
class IBM_DB < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_Limit o
- "FETCH FIRST #{visit o.expr} ROWS ONLY"
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "FETCH FIRST "
+ collector = visit o.expr, collector
+ collector << " ROWS ONLY"
end
end
diff --git a/lib/arel/visitors/informix.rb b/lib/arel/visitors/informix.rb
index 984098cdf3..8de05f60f6 100644
--- a/lib/arel/visitors/informix.rb
+++ b/lib/arel/visitors/informix.rb
@@ -2,32 +2,50 @@ module Arel
module Visitors
class Informix < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_SelectStatement o
- [
- "SELECT",
- (visit(o.offset) if o.offset),
- (visit(o.limit) if o.limit),
- o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
- ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
- (visit(o.lock) if o.lock),
- ].compact.join ' '
+ 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
- [
- "#{o.projections.map { |x| visit x }.join ', '}",
- ("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
- ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
- (visit(o.having) if o.having),
- ].compact.join ' '
+ 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
+
+ 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
+
+ maybe_visit o.having, collector
end
- def visit_Arel_Nodes_Offset o
- "SKIP #{visit o.expr}"
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "SKIP "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Limit o
- "LIMIT #{visit o.expr}"
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "LIMIT "
+ visit o.expr, collector
+ collector << " "
end
end
end
-end
+end
diff --git a/lib/arel/visitors/join_sql.rb b/lib/arel/visitors/join_sql.rb
deleted file mode 100644
index 1cdd7eb5ca..0000000000
--- a/lib/arel/visitors/join_sql.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Arel
- module Visitors
- ###
- # This class produces SQL for JOIN clauses but omits the "single-source"
- # part of the Join grammar:
- #
- # http://www.sqlite.org/syntaxdiagrams.html#join-source
- #
- # This visitor is used in SelectManager#join_sql and is for backwards
- # compatibility with Arel V1.0
- module JoinSql
- private
-
- def visit_Arel_Nodes_SelectCore o
- o.source.right.map { |j| visit j }.join ' '
- end
- end
- end
-end
diff --git a/lib/arel/visitors/mssql.rb b/lib/arel/visitors/mssql.rb
index 23dc06a936..0e5b75ec59 100644
--- a/lib/arel/visitors/mssql.rb
+++ b/lib/arel/visitors/mssql.rb
@@ -1,6 +1,8 @@
module Arel
module Visitors
class MSSQL < Arel::Visitors::ToSql
+ RowNumber = Struct.new :children
+
private
# `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
@@ -10,30 +12,43 @@ module Arel
""
end
- def visit_Arel_Nodes_SelectStatement o
+ 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 o
+ return super
end
- select_order_by = "ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?
-
is_select_count = false
- sql = o.cores.map { |x|
- core_order_by = select_order_by || determine_order_by(x)
+ o.cores.each { |x|
+ core_order_by = row_num_literal determine_order_by(o.orders, x)
if select_count? x
- x.projections = [row_num_literal(core_order_by)]
+ x.projections = [core_order_by]
is_select_count = true
else
- x.projections << row_num_literal(core_order_by)
+ x.projections << core_order_by
end
+ }
- visit_Arel_Nodes_SelectCore x
- }.join
+ 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)}"
- sql = "SELECT _t.* FROM (#{sql}) as _t WHERE #{get_offset_limit_clause(o)}"
- # fixme count distinct wouldn't work with limit or offset
- sql = "SELECT COUNT(1) as count_id FROM (#{sql}) AS subquery" if is_select_count
- sql
+ if is_select_count
+ collector << ") AS subquery"
+ else
+ collector
+ end
end
def get_offset_limit_clause o
@@ -46,26 +61,29 @@ module Arel
end
end
- def determine_order_by x
- unless x.groups.empty?
- "ORDER BY #{x.groups.map { |g| visit g }.join ', ' }"
+ def determine_order_by orders, x
+ if orders.any?
+ orders
+ elsif x.groups.any?
+ x.groups
else
- "ORDER BY #{find_left_table_pk(x.froms)}"
+ pk = find_left_table_pk(x.froms)
+ pk ? [pk] : []
end
end
def row_num_literal order_by
- Nodes::SqlLiteral.new("ROW_NUMBER() OVER (#{order_by}) as _row_num")
+ 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?
- # fixme!! Table.primary_key will be depricated. What is the replacement??
+ # FIXME raise exception of there is no pk?
+ # FIXME!! Table.primary_key will be deprecated. What is the replacement??
def find_left_table_pk o
- return visit o.primary_key if o.instance_of? Arel::Table
+ return o.primary_key if o.instance_of? Arel::Table
find_left_table_pk o.left if o.kind_of? Arel::Nodes::Join
end
end
diff --git a/lib/arel/visitors/mysql.rb b/lib/arel/visitors/mysql.rb
index ee8483372a..70a37582c2 100644
--- a/lib/arel/visitors/mysql.rb
+++ b/lib/arel/visitors/mysql.rb
@@ -2,53 +2,79 @@ module Arel
module Visitors
class MySQL < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_Union o, suppress_parens = false
- left_result = case o.left
+ 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, true
+ visit_Arel_Nodes_Union o.left, collector, true
else
- visit o.left
+ visit o.left, collector
end
- right_result = case o.right
+ collector << " UNION "
+
+ collector = case o.right
when Arel::Nodes::Union
- visit_Arel_Nodes_Union o.right, true
+ visit_Arel_Nodes_Union o.right, collector, true
else
- visit o.right
+ visit o.right, collector
end
if suppress_parens
- "#{left_result} UNION #{right_result}"
+ collector
else
- "( #{left_result} UNION #{right_result} )"
+ collector << " )"
end
end
- def visit_Arel_Nodes_Bin o
- "BINARY #{visit o.expr}"
+ 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
- o.limit = Arel::Nodes::Limit.new(18446744073709551615) if o.offset && !o.limit
+ def visit_Arel_Nodes_SelectStatement o, collector
+ if o.offset && !o.limit
+ o.limit = Arel::Nodes::Limit.new(Nodes.build_quoted(18446744073709551615))
+ end
super
end
- def visit_Arel_Nodes_SelectCore o
+ def visit_Arel_Nodes_SelectCore o, collector
o.froms ||= Arel.sql('DUAL')
super
end
- def visit_Arel_Nodes_UpdateStatement o
- [
- "UPDATE #{visit o.relation}",
- ("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?),
- ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
- (visit(o.limit) if o.limit),
- ].compact.join ' '
+ 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
+
+ if o.limit
+ collector << " "
+ visit(o.limit, collector)
+ else
+ collector
+ end
end
end
diff --git a/lib/arel/visitors/oracle.rb b/lib/arel/visitors/oracle.rb
index 1441a20dbc..91f6e0223e 100644
--- a/lib/arel/visitors/oracle.rb
+++ b/lib/arel/visitors/oracle.rb
@@ -3,12 +3,12 @@ module Arel
class Oracle < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_SelectStatement o
+ 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.offset && o.cores.first.projections.first !~ /^DISTINCT /
+ 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
)
@@ -17,55 +17,65 @@ module Arel
if o.limit && o.offset
o = o.dup
- limit = o.limit.expr.to_i
+ limit = o.limit.expr.expr
offset = o.offset
o.offset = nil
- sql = super(o)
- return <<-eosql
+ collector << "
SELECT * FROM (
SELECT raw_sql_.*, rownum raw_rnum_
- FROM (#{sql}) raw_sql_
+ FROM ("
+
+ collector = super(o, collector)
+ collector << ") raw_sql_
+ WHERE rownum <= #{offset.expr.to_i + limit}
)
- WHERE raw_rnum_ between #{offset.expr.to_i + 1 } and #{offset.expr.to_i + limit}
- eosql
+ WHERE "
+ return visit(offset, collector)
end
if o.limit
o = o.dup
limit = o.limit.expr
- return "SELECT * FROM (#{super(o)}) WHERE ROWNUM <= #{visit limit}"
+ 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
- sql = super(o)
- return <<-eosql
- SELECT * FROM (
+ collector << "SELECT * FROM (
SELECT raw_sql_.*, rownum raw_rnum_
- FROM (#{sql}) raw_sql_
+ FROM ("
+ collector = super(o, collector)
+ collector << ") raw_sql_
)
- WHERE #{visit offset}
- eosql
+ WHERE "
+ return visit offset, collector
end
super
end
- def visit_Arel_Nodes_Limit o
+ def visit_Arel_Nodes_Limit o, collector
+ collector
end
- def visit_Arel_Nodes_Offset o
- "raw_rnum_ > #{visit o.expr}"
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "raw_rnum_ > "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Except o
- "( #{visit o.left} MINUS #{visit o.right} )"
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ collector = infix_value o, collector, " MINUS "
+ collector << " )"
end
- def visit_Arel_Nodes_UpdateStatement o
- # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
+ 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
@@ -82,7 +92,7 @@ module Arel
return o if o.orders.empty?
return o unless o.cores.any? do |core|
core.projections.any? do |projection|
- /DISTINCT.*FIRST_VALUE/ === projection
+ /FIRST_VALUE/ === projection
end
end
# Previous version with join and split broke ORDER BY clause
@@ -90,7 +100,7 @@ module Arel
#
# orders = o.orders.map { |x| visit x }.join(', ').split(',')
orders = o.orders.map do |x|
- string = visit x
+ string = visit(x, Arel::Collectors::SQLString.new).value
if string.include?(',')
split_order_string(string)
else
diff --git a/lib/arel/visitors/order_clauses.rb b/lib/arel/visitors/order_clauses.rb
deleted file mode 100644
index 11dbfdad2a..0000000000
--- a/lib/arel/visitors/order_clauses.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Arel
- module Visitors
- class OrderClauses < Arel::Visitors::ToSql
- private
-
- def visit_Arel_Nodes_SelectStatement o
- o.orders.map { |x| visit x }
- end
- end
- end
-end
diff --git a/lib/arel/visitors/postgresql.rb b/lib/arel/visitors/postgresql.rb
index 812710181c..60878ddd20 100644
--- a/lib/arel/visitors/postgresql.rb
+++ b/lib/arel/visitors/postgresql.rb
@@ -3,16 +3,25 @@ module Arel
class PostgreSQL < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_Matches o
- "#{visit o.left} ILIKE #{visit o.right}"
+ def visit_Arel_Nodes_Matches o, collector
+ infix_value o, collector, ' ILIKE '
end
- def visit_Arel_Nodes_DoesNotMatch o
- "#{visit o.left} NOT ILIKE #{visit o.right}"
+ def visit_Arel_Nodes_DoesNotMatch o, collector
+ infix_value o, collector, ' NOT ILIKE '
end
- def visit_Arel_Nodes_DistinctOn o
- "DISTINCT ON ( #{visit o.expr} )"
+ def visit_Arel_Nodes_Regexp o, collector
+ infix_value o, collector, ' ~ '
+ end
+
+ def visit_Arel_Nodes_NotRegexp o, collector
+ infix_value o, collector, ' !~ '
+ end
+
+ def visit_Arel_Nodes_DistinctOn o, collector
+ collector << "DISTINCT ON ( "
+ visit(o.expr, collector) << " )"
end
end
end
diff --git a/lib/arel/visitors/reduce.rb b/lib/arel/visitors/reduce.rb
new file mode 100644
index 0000000000..9670cad27c
--- /dev/null
+++ b/lib/arel/visitors/reduce.rb
@@ -0,0 +1,25 @@
+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/sqlite.rb b/lib/arel/visitors/sqlite.rb
index 2a509e95b5..ff6fc1fea4 100644
--- a/lib/arel/visitors/sqlite.rb
+++ b/lib/arel/visitors/sqlite.rb
@@ -4,10 +4,11 @@ module Arel
private
# Locks are not supported in SQLite
- def visit_Arel_Nodes_Lock o
+ def visit_Arel_Nodes_Lock o, collector
+ collector
end
- def visit_Arel_Nodes_SelectStatement o
+ def visit_Arel_Nodes_SelectStatement o, collector
o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
super
end
diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb
index 6dd652a709..095aa5279a 100644
--- a/lib/arel/visitors/to_sql.rb
+++ b/lib/arel/visitors/to_sql.rb
@@ -1,30 +1,85 @@
require 'bigdecimal'
require 'date'
+require 'arel/visitors/reduce'
module Arel
module Visitors
- class ToSql < Arel::Visitors::Visitor
- attr_accessor :last_column
+ class ToSql < Arel::Visitors::Reduce
+ ##
+ # 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
@connection = connection
@schema_cache = connection.schema_cache
@quoted_tables = {}
@quoted_columns = {}
- @last_column = nil
end
- def accept object
- self.last_column = nil
- super
+ def compile node, &block
+ accept(node, Arel::Collectors::SQLString.new, &block).value
end
private
- def visit_Arel_Nodes_DeleteStatement o
- [
- "DELETE FROM #{visit o.relation}",
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?)
- ].compact.join ' '
+
+ def visit_Arel_Nodes_DeleteStatement o, collector
+ collector << "DELETE FROM "
+ collector = visit o.relation, collector
+ if o.wheres.any?
+ collector << " WHERE "
+ inject_join o.wheres, collector, AND
+ else
+ collector
+ end
end
# FIXME: we should probably have a 2-pass visitor for this
@@ -39,53 +94,71 @@ module Arel
stmt
end
- def visit_Arel_Nodes_UpdateStatement o
+ def visit_Arel_Nodes_UpdateStatement o, collector
if o.orders.empty? && o.limit.nil?
wheres = o.wheres
else
- key = o.key
- unless key
- warn(<<-eowarn) if $VERBOSE
-(#{caller.first}) Using UpdateManager without setting UpdateManager#key is
-deprecated and support will be removed in ARel 4.0.0. Please set the primary
-key on UpdateManager using UpdateManager#key=
- eowarn
- key = o.relation.primary_key
- end
+ 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
- wheres = [Nodes::In.new(key, [build_subselect(key, o)])]
+ unless wheres.empty?
+ collector << " WHERE "
+ collector = inject_join wheres, collector, " AND "
end
- [
- "UPDATE #{visit o.relation}",
- ("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
- ("WHERE #{wheres.map { |x| visit x }.join ' AND '}" unless wheres.empty?),
- ].compact.join ' '
+ collector
end
- def visit_Arel_Nodes_InsertStatement o
- [
- "INSERT INTO #{visit o.relation}",
+ 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
- ("(#{o.columns.map { |x|
- quote_column_name x.name
- }.join ', '})" unless o.columns.empty?),
+ 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
- (visit o.values if o.values),
- ].compact.join ' '
+ def visit_Arel_Nodes_Casted o, collector
+ collector << quoted(o.val, o.attribute).to_s
end
- def visit_Arel_Nodes_Exists o
- "EXISTS (#{visit o.expressions})#{
- o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Quoted o, collector
+ collector << quoted(o.expr, nil).to_s
end
- def visit_Arel_Nodes_True o
- "TRUE"
+ def visit_Arel_Nodes_True o, collector
+ collector << "TRUE"
end
- def visit_Arel_Nodes_False o
- "FALSE"
+ def visit_Arel_Nodes_False o, collector
+ collector << "FALSE"
end
def table_exists? name
@@ -93,343 +166,537 @@ key on UpdateManager using UpdateManager#key=
end
def column_for attr
+ return unless attr
name = attr.name.to_s
table = attr.relation.table_name
return nil unless table_exists? table
- column_cache[table][name]
+ column_cache(table)[name]
end
- def column_cache
- @schema_cache.columns_hash
+ def column_cache(table)
+ @schema_cache.columns_hash(table)
end
- def visit_Arel_Nodes_Values o
- "VALUES (#{o.expressions.zip(o.columns).map { |value, attr|
+ def visit_Arel_Nodes_Values o, collector
+ collector << "VALUES ("
+
+ len = o.expressions.length - 1
+ o.expressions.zip(o.columns).each_with_index { |(value, attr), i|
if Nodes::SqlLiteral === value
- visit value
+ collector = visit value, collector
else
- quote(value, attr && column_for(attr))
+ collector << quote(value, attr && column_for(attr)).to_s
+ end
+ unless i == len
+ collector << ', '
end
- }.join ', '})"
+ }
+
+ collector << ")"
end
- def visit_Arel_Nodes_SelectStatement o
- [
- (visit(o.with) if o.with),
- o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
- ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
- (visit(o.limit) if o.limit),
- (visit(o.offset) if o.offset),
- (visit(o.lock) if o.lock),
- ].compact.join ' '
+ 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 << SPACE
+ 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
+
+ collector = maybe_visit o.limit, collector
+ collector = maybe_visit o.offset, collector
+ collector = maybe_visit o.lock, collector
+
+ collector
end
- def visit_Arel_Nodes_SelectCore o
- [
- "SELECT",
- (visit(o.top) if o.top),
- (visit(o.set_quantifier) if o.set_quantifier),
- ("#{o.projections.map { |x| visit x }.join ', '}" unless o.projections.empty?),
- ("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
- ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
- (visit(o.having) if o.having),
- ("WINDOW #{o.windows.map { |x| visit x }.join ', ' }" unless o.windows.empty?)
- ].compact.join ' '
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector << "SELECT"
+
+ if o.top
+ collector << " "
+ collector = visit o.top, collector
+ end
+
+ if o.set_quantifier
+ collector << " "
+ collector = visit o.set_quantifier, collector
+ end
+
+ unless o.projections.empty?
+ collector << SPACE
+ len = o.projections.length - 1
+ o.projections.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ end
+ end
+
+ if o.source && !o.source.empty?
+ collector << " FROM "
+ collector = visit o.source, collector
+ end
+
+ unless o.wheres.empty?
+ collector << WHERE
+ len = o.wheres.length - 1
+ o.wheres.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << AND unless len == i
+ end
+ end
+
+ unless o.groups.empty?
+ collector << GROUP_BY
+ len = o.groups.length - 1
+ o.groups.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ end
+ end
+
+ if o.having
+ collector << " "
+ collector = visit(o.having, collector)
+ end
+
+ unless o.windows.empty?
+ collector << WINDOW
+ len = o.windows.length - 1
+ o.windows.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ end
+ end
+
+ collector
end
- def visit_Arel_Nodes_Bin o
- visit o.expr
+ def visit_Arel_Nodes_Bin o, collector
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Distinct o
- 'DISTINCT'
+ def visit_Arel_Nodes_Distinct o, collector
+ collector << DISTINCT
end
- def visit_Arel_Nodes_DistinctOn o
+ def visit_Arel_Nodes_DistinctOn o, collector
raise NotImplementedError, 'DISTINCT ON not implemented for this db'
end
- def visit_Arel_Nodes_With o
- "WITH #{o.children.map { |x| visit x }.join(', ')}"
+ def visit_Arel_Nodes_With o, collector
+ collector << "WITH "
+ inject_join o.children, collector, ', '
end
- def visit_Arel_Nodes_WithRecursive o
- "WITH RECURSIVE #{o.children.map { |x| visit x }.join(', ')}"
+ def visit_Arel_Nodes_WithRecursive o, collector
+ collector << "WITH RECURSIVE "
+ inject_join o.children, collector, ', '
end
- def visit_Arel_Nodes_Union o
- "( #{visit o.left} UNION #{visit o.right} )"
+ def visit_Arel_Nodes_Union o, collector
+ collector << "( "
+ infix_value(o, collector, " UNION ") << " )"
end
- def visit_Arel_Nodes_UnionAll o
- "( #{visit o.left} UNION ALL #{visit o.right} )"
+ def visit_Arel_Nodes_UnionAll o, collector
+ collector << "( "
+ infix_value(o, collector, " UNION ALL ") << " )"
end
- def visit_Arel_Nodes_Intersect o
- "( #{visit o.left} INTERSECT #{visit o.right} )"
+ def visit_Arel_Nodes_Intersect o, collector
+ collector << "( "
+ infix_value(o, collector, " INTERSECT ") << " )"
end
- def visit_Arel_Nodes_Except o
- "( #{visit o.left} EXCEPT #{visit o.right} )"
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ infix_value(o, collector, " EXCEPT ") << " )"
end
- def visit_Arel_Nodes_NamedWindow o
- "#{quote_column_name o.name} AS #{visit_Arel_Nodes_Window o}"
+ 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
- s = [
- ("ORDER BY #{o.orders.map { |x| visit(x) }.join(', ')}" unless o.orders.empty?),
- (visit o.framing if o.framing)
- ].compact.join ' '
- "(#{s})"
+ 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 << ' ' if o.partitions.any?
+ collector << "ORDER BY "
+ collector = inject_join o.orders, collector, ", "
+ end
+
+ if o.framing
+ collector << ' ' if o.partitions.any? or o.orders.any?
+ collector = visit o.framing, collector
+ end
+
+ collector << ")"
end
- def visit_Arel_Nodes_Rows o
+ def visit_Arel_Nodes_Rows o, collector
if o.expr
- "ROWS #{visit o.expr}"
+ collector << "ROWS "
+ visit o.expr, collector
else
- "ROWS"
+ collector << "ROWS"
end
end
- def visit_Arel_Nodes_Range o
+ def visit_Arel_Nodes_Range o, collector
if o.expr
- "RANGE #{visit o.expr}"
+ collector << "RANGE "
+ visit o.expr, collector
else
- "RANGE"
+ collector << "RANGE"
end
end
- def visit_Arel_Nodes_Preceding o
- "#{o.expr ? visit(o.expr) : 'UNBOUNDED'} PRECEDING"
+ 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
- "#{o.expr ? visit(o.expr) : 'UNBOUNDED'} FOLLOWING"
+ 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
- "CURRENT ROW"
+ def visit_Arel_Nodes_CurrentRow o, collector
+ collector << "CURRENT ROW"
end
- def visit_Arel_Nodes_Over o
+ def visit_Arel_Nodes_Over o, collector
case o.right
- when nil
- "#{visit o.left} OVER ()"
- when Arel::Nodes::SqlLiteral
- "#{visit o.left} OVER #{visit o.right}"
- when String, Symbol
- "#{visit o.left} OVER #{quote_column_name o.right.to_s}"
- else
- "#{visit o.left} OVER #{visit 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_Having o
- "HAVING #{visit o.expr}"
+ def visit_Arel_Nodes_Having o, collector
+ collector << "HAVING "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Offset o
- "OFFSET #{visit o.expr}"
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "OFFSET "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Limit o
- "LIMIT #{visit o.expr}"
+ 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
- ""
+ 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 << "(#{o.to_sql.rstrip})"
end
- def visit_Arel_Nodes_Lock o
- visit o.expr
+ def visit_Arel_Nodes_Ascending o, collector
+ visit(o.expr, collector) << " ASC"
end
- def visit_Arel_Nodes_Grouping o
- "(#{visit o.expr})"
+ def visit_Arel_Nodes_Descending o, collector
+ visit(o.expr, collector) << " DESC"
end
- def visit_Arel_Nodes_Ascending o
- "#{visit o.expr} ASC"
+ 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_Descending o
- "#{visit o.expr} DESC"
+ def visit_Arel_Nodes_Extract o, collector
+ collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
+ visit(o.expr, collector) << ")"
end
- def visit_Arel_Nodes_Group o
- visit o.expr
+ def visit_Arel_Nodes_Count o, collector
+ aggregate "COUNT", o, collector
end
- def visit_Arel_Nodes_NamedFunction o
- "#{o.name}(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
- visit x
- }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Sum o, collector
+ aggregate "SUM", o, collector
end
- def visit_Arel_Nodes_Extract o
- "EXTRACT(#{o.field.to_s.upcase} FROM #{visit o.expr})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Max o, collector
+ aggregate "MAX", o, collector
end
- def visit_Arel_Nodes_Count o
- "COUNT(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
- visit x
- }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Min o, collector
+ aggregate "MIN", o, collector
end
- def visit_Arel_Nodes_Sum o
- "SUM(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Avg o, collector
+ aggregate "AVG", o, collector
end
- def visit_Arel_Nodes_Max o
- "MAX(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_TableAlias o, collector
+ collector = visit o.relation, collector
+ collector << " "
+ collector << quote_table_name(o.name)
end
- def visit_Arel_Nodes_Min o
- "MIN(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Between o, collector
+ collector = visit o.left, collector
+ collector << " BETWEEN "
+ visit o.right, collector
end
- def visit_Arel_Nodes_Avg o
- "AVG(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_GreaterThanOrEqual o, collector
+ collector = visit o.left, collector
+ collector << " >= "
+ visit o.right, collector
end
- def visit_Arel_Nodes_TableAlias o
- "#{visit o.relation} #{quote_table_name o.name}"
+ def visit_Arel_Nodes_GreaterThan o, collector
+ collector = visit o.left, collector
+ collector << " > "
+ visit o.right, collector
end
- def visit_Arel_Nodes_Between o
- "#{visit o.left} BETWEEN #{visit o.right}"
+ def visit_Arel_Nodes_LessThanOrEqual o, collector
+ collector = visit o.left, collector
+ collector << " <= "
+ visit o.right, collector
end
- def visit_Arel_Nodes_GreaterThanOrEqual o
- "#{visit o.left} >= #{visit o.right}"
+ def visit_Arel_Nodes_LessThan o, collector
+ collector = visit o.left, collector
+ collector << " < "
+ visit o.right, collector
end
- def visit_Arel_Nodes_GreaterThan o
- "#{visit o.left} > #{visit o.right}"
+ def visit_Arel_Nodes_Matches o, collector
+ collector = visit o.left, collector
+ collector << " LIKE "
+ visit o.right, collector
end
- def visit_Arel_Nodes_LessThanOrEqual o
- "#{visit o.left} <= #{visit o.right}"
+ def visit_Arel_Nodes_DoesNotMatch o, collector
+ collector = visit o.left, collector
+ collector << " NOT LIKE "
+ visit o.right, collector
end
- def visit_Arel_Nodes_LessThan o
- "#{visit o.left} < #{visit o.right}"
+ def visit_Arel_Nodes_JoinSource o, collector
+ if o.left
+ collector = visit o.left, collector
+ end
+ if o.right.any?
+ collector << " " if o.left
+ collector = inject_join o.right, collector, ' '
+ end
+ collector
end
- def visit_Arel_Nodes_Matches o
- "#{visit o.left} LIKE #{visit o.right}"
+ def visit_Arel_Nodes_Regexp o, collector
+ raise NotImplementedError, '~ not implemented for this db'
end
- def visit_Arel_Nodes_DoesNotMatch o
- "#{visit o.left} NOT LIKE #{visit o.right}"
+ def visit_Arel_Nodes_NotRegexp o, collector
+ raise NotImplementedError, '!~ not implemented for this db'
end
- def visit_Arel_Nodes_JoinSource o
- [
- (visit(o.left) if o.left),
- o.right.map { |j| visit j }.join(' ')
- ].compact.join ' '
+ def visit_Arel_Nodes_StringJoin o, collector
+ visit o.left, collector
end
- def visit_Arel_Nodes_StringJoin o
- visit o.left
+ def visit_Arel_Nodes_FullOuterJoin o
+ "FULL OUTER JOIN #{visit o.left} #{visit o.right}"
end
- def visit_Arel_Nodes_OuterJoin o
- "LEFT OUTER JOIN #{visit o.left} #{visit o.right}"
+ 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_InnerJoin o
- "INNER JOIN #{visit o.left} #{visit o.right if o.right}"
+ def visit_Arel_Nodes_RightOuterJoin o
+ "RIGHT OUTER JOIN #{visit o.left} #{visit o.right}"
end
- def visit_Arel_Nodes_On o
- "ON #{visit o.expr}"
+ 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_Not o
- "NOT (#{visit o.expr})"
+ def visit_Arel_Nodes_On o, collector
+ collector << "ON "
+ visit o.expr, collector
end
- def visit_Arel_Table o
+ def visit_Arel_Nodes_Not o, collector
+ collector << "NOT ("
+ visit(o.expr, collector) << ")"
+ end
+
+ def visit_Arel_Table o, collector
if o.table_alias
- "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
+ collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
else
- quote_table_name o.name
+ collector << quote_table_name(o.name)
end
end
- def visit_Arel_Nodes_In o
+ def visit_Arel_Nodes_In o, collector
if Array === o.right && o.right.empty?
- '1=0'
+ collector << '1=0'
else
- "#{visit o.left} IN (#{visit o.right})"
+ collector = visit o.left, collector
+ collector << " IN ("
+ visit(o.right, collector) << ")"
end
end
- def visit_Arel_Nodes_NotIn o
+ def visit_Arel_Nodes_NotIn o, collector
if Array === o.right && o.right.empty?
- '1=1'
+ collector << '1=1'
else
- "#{visit o.left} NOT IN (#{visit o.right})"
+ collector = visit o.left, collector
+ collector << " NOT IN ("
+ collector = visit o.right, collector
+ collector << ")"
end
end
- def visit_Arel_Nodes_And o
- o.children.map { |x| visit x }.join ' AND '
+ def visit_Arel_Nodes_And o, collector
+ inject_join o.children, collector, " AND "
end
- def visit_Arel_Nodes_Or o
- "#{visit o.left} OR #{visit o.right}"
+ 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
- right = quote(o.right, column_for(o.left))
- "#{visit o.left} = #{right}"
+ 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, column_for(o.left)).to_s
+ end
end
- def visit_Arel_Nodes_Equality o
+ def visit_Arel_Nodes_Equality o, collector
right = o.right
+ collector = visit o.left, collector
+
if right.nil?
- "#{visit o.left} IS NULL"
+ collector << " IS NULL"
else
- "#{visit o.left} = #{visit right}"
+ collector << " = "
+ visit right, collector
end
end
- def visit_Arel_Nodes_NotEqual o
+ def visit_Arel_Nodes_NotEqual o, collector
right = o.right
+ collector = visit o.left, collector
+
if right.nil?
- "#{visit o.left} IS NOT NULL"
+ collector << " IS NOT NULL"
else
- "#{visit o.left} != #{visit right}"
+ collector << " != "
+ visit right, collector
end
end
- def visit_Arel_Nodes_As o
- "#{visit o.left} AS #{visit o.right}"
+ def visit_Arel_Nodes_As o, collector
+ collector = visit o.left, collector
+ collector << " AS "
+ visit o.right, collector
end
- def visit_Arel_Nodes_UnqualifiedColumn o
- "#{quote_column_name o.name}"
+ def visit_Arel_Nodes_UnqualifiedColumn o, collector
+ collector << "#{quote_column_name o.name}"
+ collector
end
- def visit_Arel_Attributes_Attribute o
- self.last_column = column_for o
+ def visit_Arel_Attributes_Attribute o, collector
join_name = o.relation.table_alias || o.relation.name
- "#{quote_table_name join_name}.#{quote_column_name o.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
@@ -438,35 +705,43 @@ key on UpdateManager using UpdateManager#key=
alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
- def literal o; o end
+ def literal o, collector; collector << o.to_s; end
+
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind o
+ end
- alias :visit_Arel_Nodes_BindParam :literal
alias :visit_Arel_Nodes_SqlLiteral :literal
- alias :visit_Arel_SqlLiteral :literal # This is deprecated
alias :visit_Bignum :literal
alias :visit_Fixnum :literal
- def quoted o
- quote(o, last_column)
+ def quoted o, a
+ quote(o, column_for(a))
+ end
+
+ def unsupported o, collector
+ raise "unsupported: #{o.class.name}"
end
- alias :visit_ActiveSupport_Multibyte_Chars :quoted
- alias :visit_ActiveSupport_StringInquirer :quoted
- alias :visit_BigDecimal :quoted
- alias :visit_Class :quoted
- alias :visit_Date :quoted
- alias :visit_DateTime :quoted
- alias :visit_FalseClass :quoted
- alias :visit_Float :quoted
- alias :visit_Hash :quoted
- alias :visit_NilClass :quoted
- alias :visit_String :quoted
- alias :visit_Symbol :quoted
- alias :visit_Time :quoted
- alias :visit_TrueClass :quoted
+ 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
- "#{visit o.left} #{o.operator} #{visit o.right}"
+ 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
@@ -474,12 +749,13 @@ key on UpdateManager using UpdateManager#key=
alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
- def visit_Array o
- o.map { |x| visit x }.join(', ')
+ def visit_Array o, collector
+ inject_join o, collector, ", "
end
alias :visit_Set :visit_Array
def quote value, column = nil
+ return value if Arel::Nodes::SqlLiteral === value
@connection.quote value, column
end
@@ -491,6 +767,43 @@ key on UpdateManager using UpdateManager#key=
def quote_column_name name
@quoted_columns[name] ||= Arel::Nodes::SqlLiteral === name ? 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/lib/arel/visitors/visitor.rb b/lib/arel/visitors/visitor.rb
index 8f9dd929e1..0730c15794 100644
--- a/lib/arel/visitors/visitor.rb
+++ b/lib/arel/visitors/visitor.rb
@@ -7,12 +7,15 @@ module Arel
private
- DISPATCH = Hash.new do |hash, klass|
- hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
+ DISPATCH = Hash.new do |hash, visitor_class|
+ hash[visitor_class] =
+ Hash.new do |method_hash, node_class|
+ method_hash[node_class] = "visit_#{(node_class.name || '').gsub('::', '_')}"
+ end
end
def dispatch
- DISPATCH
+ DISPATCH[self.class]
end
def visit object
diff --git a/lib/arel/visitors/where_sql.rb b/lib/arel/visitors/where_sql.rb
index 9816fa7a70..27dde73673 100644
--- a/lib/arel/visitors/where_sql.rb
+++ b/lib/arel/visitors/where_sql.rb
@@ -1,8 +1,9 @@
module Arel
module Visitors
class WhereSql < Arel::Visitors::ToSql
- def visit_Arel_Nodes_SelectCore o
- "WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }"
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector << "WHERE "
+ inject_join o.wheres, collector, ' AND '
end
end
end
diff --git a/test/attributes/test_attribute.rb b/test/attributes/test_attribute.rb
index 901850ff4b..b50bb60b1e 100644
--- a/test/attributes/test_attribute.rb
+++ b/test/attributes/test_attribute.rb
@@ -66,7 +66,7 @@ module Arel
relation[:id].gt(10).must_be_kind_of Nodes::GreaterThan
end
- it 'should generate >= in sql' do
+ it 'should generate > in sql' do
relation = Table.new(:users)
mgr = relation.project relation[:id]
mgr.where relation[:id].gt(10)
@@ -74,6 +74,28 @@ module Arel
SELECT "users"."id" FROM "users" WHERE "users"."id" > 10
}
end
+
+ it 'should handle comparing with a subquery' do
+ users = Table.new(:users)
+
+ avg = users.project(users[:karma].average)
+ mgr = users.project(Arel.star).where(users[:karma].gt(avg))
+
+ mgr.to_sql.must_be_like %{
+ SELECT * FROM "users" WHERE "users"."karma" > (SELECT AVG("users"."karma") FROM "users")
+ }
+ end
+
+ it 'should accept various data types.' do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].gt('fake_name')
+ mgr.to_sql.must_match %{"users"."name" > 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].gt(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" > '#{current_time}'}
+ end
end
describe '#gt_any' do
@@ -122,6 +144,17 @@ module Arel
SELECT "users"."id" FROM "users" WHERE "users"."id" >= 10
}
end
+
+ it 'should accept various data types.' do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].gteq('fake_name')
+ mgr.to_sql.must_match %{"users"."name" >= 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].gteq(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" >= '#{current_time}'}
+ end
end
describe '#gteq_any' do
@@ -170,6 +203,17 @@ module Arel
SELECT "users"."id" FROM "users" WHERE "users"."id" < 10
}
end
+
+ it 'should accept various data types.' do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].lt('fake_name')
+ mgr.to_sql.must_match %{"users"."name" < 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].lt(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" < '#{current_time}'}
+ end
end
describe '#lt_any' do
@@ -218,6 +262,17 @@ module Arel
SELECT "users"."id" FROM "users" WHERE "users"."id" <= 10
}
end
+
+ it 'should accept various data types.' do
+ relation = Table.new(:users)
+ mgr = relation.project relation[:id]
+ mgr.where relation[:name].lteq('fake_name')
+ mgr.to_sql.must_match %{"users"."name" <= 'fake_name'}
+
+ current_time = ::Time.now
+ mgr.where relation[:created_at].lteq(current_time)
+ mgr.to_sql.must_match %{"users"."created_at" <= '#{current_time}'}
+ end
end
describe '#lteq_any' do
@@ -258,12 +313,11 @@ module Arel
relation[:id].average.must_be_kind_of Nodes::Avg
end
- # FIXME: backwards compat. Is this really necessary?
- it 'should set the alias to "avg_id"' do
+ it 'should generate the proper SQL' do
relation = Table.new(:users)
mgr = relation.project relation[:id].average
mgr.to_sql.must_be_like %{
- SELECT AVG("users"."id") AS avg_id
+ SELECT AVG("users"."id")
FROM "users"
}
end
@@ -275,12 +329,11 @@ module Arel
relation[:id].maximum.must_be_kind_of Nodes::Max
end
- # FIXME: backwards compat. Is this really necessary?
- it 'should set the alias to "max_id"' do
+ it 'should generate the proper SQL' do
relation = Table.new(:users)
mgr = relation.project relation[:id].maximum
mgr.to_sql.must_be_like %{
- SELECT MAX("users"."id") AS max_id
+ SELECT MAX("users"."id")
FROM "users"
}
end
@@ -299,12 +352,11 @@ module Arel
relation[:id].sum.must_be_kind_of Nodes::Sum
end
- # FIXME: backwards compat. Is this really necessary?
- it 'should set the alias to "sum_id"' do
+ it 'should generate the proper SQL' do
relation = Table.new(:users)
mgr = relation.project relation[:id].sum
mgr.to_sql.must_be_like %{
- SELECT SUM("users"."id") AS sum_id
+ SELECT SUM("users"."id")
FROM "users"
}
end
@@ -329,7 +381,7 @@ module Arel
attribute = Attribute.new nil, nil
equality = attribute.eq 1
equality.left.must_equal attribute
- equality.right.must_equal 1
+ equality.right.val.must_equal 1
equality.must_be_kind_of Nodes::Equality
end
@@ -550,8 +602,6 @@ module Arel
end
describe '#not_in' do
- it 'can be constructed with a list' do
- end
it 'should return a NotIn node' do
attribute = Attribute.new nil, nil
diff --git a/test/collectors/test_bind_collector.rb b/test/collectors/test_bind_collector.rb
new file mode 100644
index 0000000000..60532f061c
--- /dev/null
+++ b/test/collectors/test_bind_collector.rb
@@ -0,0 +1,70 @@
+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.engine, table
+ manager.where(table[:age].eq(bv))
+ manager.where(table[:name].eq(bv))
+ manager.ast
+ end
+
+ def test_leaves_binds
+ node = Nodes::BindParam.new 'omg'
+ 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_sql_string.rb b/test/collectors/test_sql_string.rb
new file mode 100644
index 0000000000..6d2e23151b
--- /dev/null
+++ b/test/collectors/test_sql_string.rb
@@ -0,0 +1,38 @@
+require 'helper'
+require 'arel/collectors/bind'
+
+module Arel
+ module Collectors
+ class TestSqlString < Arel::Test
+ def setup
+ @conn = FakeRecord::Base.new
+ @visitor = Visitors::ToSql.new @conn.connection
+ super
+ end
+
+ def collect node
+ @visitor.accept(node, Collectors::SQLString.new)
+ end
+
+ def compile node
+ collect(node).value
+ end
+
+ def ast_with_binds bv
+ table = Table.new(:users)
+ manager = Arel::SelectManager.new Table.engine, table
+ manager.where(table[:age].eq(bv))
+ manager.where(table[:name].eq(bv))
+ manager.ast
+ 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" = ? AND "users"."name" = ?', sql
+ end
+ end
+ end
+end
diff --git a/test/helper.rb b/test/helper.rb
index f13596f9ec..6e8ac836fc 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -4,10 +4,19 @@ require 'fileutils'
require 'arel'
require 'support/fake_record'
-Arel::Table.engine = Arel::Sql::Engine.new(FakeRecord::Base.new)
+Arel::Table.engine = FakeRecord::Base.new
class Object
def must_be_like other
gsub(/\s+/, ' ').strip.must_equal other.gsub(/\s+/, ' ').strip
end
end
+
+module Arel
+ class Test < MiniTest::Test
+ def assert_like expected, actual
+ assert_equal expected.gsub(/\s+/, ' ').strip,
+ actual.gsub(/\s+/, ' ').strip
+ end
+ end
+end
diff --git a/test/nodes/test_ascending.rb b/test/nodes/test_ascending.rb
index 63d758a8a8..2991d46583 100644
--- a/test/nodes/test_ascending.rb
+++ b/test/nodes/test_ascending.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module Nodes
- class TestAscending < MiniTest::Unit::TestCase
+ class TestAscending < Minitest::Test
def test_construct
ascending = Ascending.new 'zomg'
assert_equal 'zomg', ascending.expr
diff --git a/test/nodes/test_bin.rb b/test/nodes/test_bin.rb
index c370c5755f..0dcc5f7bb8 100644
--- a/test/nodes/test_bin.rb
+++ b/test/nodes/test_bin.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module Nodes
- class TestBin < MiniTest::Unit::TestCase
+ class TestBin < Minitest::Test
def test_new
assert Arel::Nodes::Bin.new('zomg')
end
@@ -10,13 +10,13 @@ module Arel
def test_default_to_sql
viz = Arel::Visitors::ToSql.new Table.engine.connection_pool
node = Arel::Nodes::Bin.new(Arel.sql('zomg'))
- assert_equal 'zomg', viz.accept(node)
+ assert_equal 'zomg', viz.accept(node, Collectors::SQLString.new).value
end
def test_mysql_to_sql
viz = Arel::Visitors::MySQL.new Table.engine.connection_pool
node = Arel::Nodes::Bin.new(Arel.sql('zomg'))
- assert_equal 'BINARY zomg', viz.accept(node)
+ assert_equal 'BINARY zomg', viz.accept(node, Collectors::SQLString.new).value
end
def test_equality_with_same_ivars
diff --git a/test/nodes/test_count.rb b/test/nodes/test_count.rb
index 88d2a694c8..a9a329420e 100644
--- a/test/nodes/test_count.rb
+++ b/test/nodes/test_count.rb
@@ -1,12 +1,6 @@
require 'helper'
describe Arel::Nodes::Count do
- describe 'backwards compatibility' do
- it 'must be an expression' do
- Arel::Nodes::Count.new('foo').must_be_kind_of Arel::Expression
- end
- end
-
describe "as" do
it 'should alias the count' do
table = Arel::Table.new :users
diff --git a/test/nodes/test_descending.rb b/test/nodes/test_descending.rb
index 22b456fd8d..fce71d69e9 100644
--- a/test/nodes/test_descending.rb
+++ b/test/nodes/test_descending.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module Nodes
- class TestDescending < MiniTest::Unit::TestCase
+ class TestDescending < Minitest::Test
def test_construct
descending = Descending.new 'zomg'
assert_equal 'zomg', descending.expr
diff --git a/test/nodes/test_equality.rb b/test/nodes/test_equality.rb
index 79764cc7d3..42a156b051 100644
--- a/test/nodes/test_equality.rb
+++ b/test/nodes/test_equality.rb
@@ -43,7 +43,7 @@ module Arel
attr = Table.new(:users)[:id]
test = attr.eq(10)
test.to_sql engine
- engine.connection.quote_count.must_equal 2
+ engine.connection.quote_count.must_equal 3
end
end
end
diff --git a/test/nodes/test_extract.rb b/test/nodes/test_extract.rb
index 80bb465f24..eb98553268 100644
--- a/test/nodes/test_extract.rb
+++ b/test/nodes/test_extract.rb
@@ -15,6 +15,14 @@ describe Arel::Nodes::Extract do
EXTRACT(DATE FROM "users"."timestamp") AS foo
}
end
+
+ it 'should not mutate the extract' do
+ table = Arel::Table.new :users
+ extract = table[:timestamp].extract('date')
+ before = extract.dup
+ extract.as('foo')
+ assert_equal extract, before
+ end
end
describe 'equality' do
diff --git a/test/nodes/test_grouping.rb b/test/nodes/test_grouping.rb
index b7aa51d37f..febf0bee40 100644
--- a/test/nodes/test_grouping.rb
+++ b/test/nodes/test_grouping.rb
@@ -4,7 +4,7 @@ module Arel
module Nodes
describe 'Grouping' do
it 'should create Equality nodes' do
- grouping = Grouping.new('foo')
+ grouping = Grouping.new(Nodes.build_quoted('foo'))
grouping.eq('foo').to_sql.must_be_like %q{('foo') = 'foo'}
end
diff --git a/test/nodes/test_infix_operation.rb b/test/nodes/test_infix_operation.rb
index b7b6b02d0f..40616024e5 100644
--- a/test/nodes/test_infix_operation.rb
+++ b/test/nodes/test_infix_operation.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module Nodes
- class TestInfixOperation < MiniTest::Unit::TestCase
+ class TestInfixOperation < Minitest::Test
def test_construct
operation = InfixOperation.new :+, 1, 2
assert_equal :+, operation.operator
@@ -18,7 +18,7 @@ module Arel
assert_equal 'zomg', aliaz.right
end
- def test_opertaion_ordering
+ def test_operation_ordering
operation = InfixOperation.new :+, 1, 2
ordering = operation.desc
assert_kind_of Descending, ordering
diff --git a/test/nodes/test_named_function.rb b/test/nodes/test_named_function.rb
index 9d16f9cbee..33830d9d43 100644
--- a/test/nodes/test_named_function.rb
+++ b/test/nodes/test_named_function.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module Nodes
- class TestNamedFunction < MiniTest::Unit::TestCase
+ class TestNamedFunction < Minitest::Test
def test_construct
function = NamedFunction.new 'omg', 'zomg'
assert_equal 'omg', function.name
diff --git a/test/nodes/test_node.rb b/test/nodes/test_node.rb
index 335cba8aab..056df7a584 100644
--- a/test/nodes/test_node.rb
+++ b/test/nodes/test_node.rb
@@ -1,7 +1,7 @@
require 'helper'
module Arel
- class TestNode < MiniTest::Unit::TestCase
+ class TestNode < Minitest::Test
def test_includes_factory_methods
assert Node.new.respond_to?(:create_join)
end
diff --git a/test/nodes/test_select_core.rb b/test/nodes/test_select_core.rb
index e4ed06b853..ca4f070444 100644
--- a/test/nodes/test_select_core.rb
+++ b/test/nodes/test_select_core.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module Nodes
- class TestSelectCore < MiniTest::Unit::TestCase
+ class TestSelectCore < Minitest::Test
def test_clone
core = Arel::Nodes::SelectCore.new
core.froms = %w[a b c]
@@ -11,20 +11,20 @@ module Arel
dolly = core.clone
- dolly.froms.must_equal core.froms
- dolly.projections.must_equal core.projections
- dolly.wheres.must_equal core.wheres
+ assert_equal core.froms, dolly.froms
+ assert_equal core.projections, dolly.projections
+ assert_equal core.wheres, dolly.wheres
- dolly.froms.wont_be_same_as core.froms
- dolly.projections.wont_be_same_as core.projections
- dolly.wheres.wont_be_same_as core.wheres
+ refute_same core.froms, dolly.froms
+ refute_same core.projections, dolly.projections
+ refute_same core.wheres, dolly.wheres
end
def test_set_quantifier
core = Arel::Nodes::SelectCore.new
core.set_quantifier = Arel::Nodes::Distinct.new
viz = Arel::Visitors::ToSql.new Table.engine.connection_pool
- assert_match 'DISTINCT', viz.accept(core)
+ assert_match 'DISTINCT', viz.accept(core, Collectors::SQLString.new).value
end
def test_equality_with_same_ivars
diff --git a/test/nodes/test_sql_literal.rb b/test/nodes/test_sql_literal.rb
index 9deb8e5d8d..ed602cc47d 100644
--- a/test/nodes/test_sql_literal.rb
+++ b/test/nodes/test_sql_literal.rb
@@ -1,10 +1,15 @@
require 'helper'
+require 'yaml'
module Arel
module Nodes
describe 'sql literal' do
before do
- @visitor = Visitors::ToSql.new Table.engine.connection_pool
+ @visitor = Visitors::ToSql.new Table.engine.connection
+ end
+
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
end
describe 'sql' do
@@ -17,19 +22,19 @@ module Arel
describe 'count' do
it 'makes a count node' do
node = SqlLiteral.new('*').count
- @visitor.accept(node).must_be_like %{ COUNT(*) }
+ compile(node).must_be_like %{ COUNT(*) }
end
it 'makes a distinct node' do
node = SqlLiteral.new('*').count true
- @visitor.accept(node).must_be_like %{ COUNT(DISTINCT *) }
+ compile(node).must_be_like %{ COUNT(DISTINCT *) }
end
end
describe 'equality' do
it 'makes an equality node' do
node = SqlLiteral.new('foo').eq(1)
- @visitor.accept(node).must_be_like %{ foo = 1 }
+ compile(node).must_be_like %{ foo = 1 }
end
it 'is equal with equal contents' do
@@ -46,14 +51,21 @@ module Arel
describe 'grouped "or" equality' do
it 'makes a grouping node with an or node' do
node = SqlLiteral.new('foo').eq_any([1,2])
- @visitor.accept(node).must_be_like %{ (foo = 1 OR foo = 2) }
+ compile(node).must_be_like %{ (foo = 1 OR foo = 2) }
end
end
describe 'grouped "and" equality' do
it 'makes a grouping node with an or node' do
node = SqlLiteral.new('foo').eq_all([1,2])
- @visitor.accept(node).must_be_like %{ (foo = 1 AND foo = 2) }
+ compile(node).must_be_like %{ (foo = 1 AND foo = 2) }
+ end
+ end
+
+ describe 'serialization' do
+ it 'serializes into YAML' do
+ yaml_literal = SqlLiteral.new('foo').to_yaml
+ assert_equal('foo', YAML.load(yaml_literal))
end
end
end
diff --git a/test/nodes/test_window.rb b/test/nodes/test_window.rb
index f09d16e441..9ec42be59f 100644
--- a/test/nodes/test_window.rb
+++ b/test/nodes/test_window.rb
@@ -7,9 +7,11 @@ module Arel
it 'is equal with equal ivars' do
window1 = Window.new
window1.orders = [1, 2]
+ window1.partitions = [1]
window1.frame 3
window2 = Window.new
window2.orders = [1, 2]
+ window2.partitions = [1]
window2.frame 3
array = [window1, window2]
assert_equal 1, array.uniq.size
@@ -18,9 +20,11 @@ module Arel
it 'is not equal with different ivars' do
window1 = Window.new
window1.orders = [1, 2]
+ window1.partitions = [1]
window1.frame 3
window2 = Window.new
window2.orders = [1, 2]
+ window1.partitions = [1]
window2.frame 4
array = [window1, window2]
assert_equal 2, array.uniq.size
@@ -33,9 +37,11 @@ module Arel
it 'is equal with equal ivars' do
window1 = NamedWindow.new 'foo'
window1.orders = [1, 2]
+ window1.partitions = [1]
window1.frame 3
window2 = NamedWindow.new 'foo'
window2.orders = [1, 2]
+ window2.partitions = [1]
window2.frame 3
array = [window1, window2]
assert_equal 1, array.uniq.size
@@ -44,9 +50,11 @@ module Arel
it 'is not equal with different ivars' do
window1 = NamedWindow.new 'foo'
window1.orders = [1, 2]
+ window1.partitions = [1]
window1.frame 3
window2 = NamedWindow.new 'bar'
window2.orders = [1, 2]
+ window2.partitions = [1]
window2.frame 3
array = [window1, window2]
assert_equal 2, array.uniq.size
@@ -68,6 +76,4 @@ module Arel
end
end
end
-end
-
-
+end \ No newline at end of file
diff --git a/test/support/fake_record.rb b/test/support/fake_record.rb
index 79182f86bd..035a7a46cf 100644
--- a/test/support/fake_record.rb
+++ b/test/support/fake_record.rb
@@ -3,7 +3,7 @@ module FakeRecord
end
class Connection
- attr_reader :tables, :columns_hash
+ attr_reader :tables
attr_accessor :visitor
def initialize(visitor = nil)
@@ -31,6 +31,10 @@ module FakeRecord
@visitor = visitor
end
+ def columns_hash table_name
+ @columns_hash[table_name]
+ end
+
def primary_key name
@primary_keys[name.to_s]
end
@@ -56,12 +60,20 @@ module FakeRecord
end
def quote thing, column = nil
- if column && column.type == :integer
- return 'NULL' if thing.nil?
- return thing.to_i
+ if column && !thing.nil?
+ case column.type
+ when :integer
+ thing = thing.to_i
+ when :string
+ thing = thing.to_s
+ end
end
case thing
+ when DateTime
+ "'#{thing.strftime("%Y-%m-%d %H:%M:%S")}'"
+ when Date
+ "'#{thing.strftime("%Y-%m-%d")}'"
when true
"'t'"
when false
@@ -71,7 +83,7 @@ module FakeRecord
when Numeric
thing
else
- "'#{thing}'"
+ "'#{thing.to_s.gsub("'", "\\\\'")}'"
end
end
end
@@ -103,6 +115,10 @@ module FakeRecord
def schema_cache
connection
end
+
+ def quote thing, column = nil
+ connection.quote thing, column
+ end
end
class Base
diff --git a/test/test_activerecord_compat.rb b/test/test_activerecord_compat.rb
deleted file mode 100644
index d881209610..0000000000
--- a/test/test_activerecord_compat.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'helper'
-
-module Arel
- describe 'activerecord compatibility' do
- describe 'select manager' do
- it 'provides wheres' do
- table = Table.new :users
- manager = Arel::SelectManager.new Table.engine
- manager.where table[:id].eq 1
- manager.where table[:name].eq 'Aaron'
-
- manager.wheres.map { |x|
- x.value
- }.join(', ').must_equal "\"users\".\"id\" = 1, \"users\".\"name\" = 'Aaron'"
- end
- end
- end
-end
diff --git a/test/test_crud.rb b/test/test_crud.rb
index fe3e4f2e02..5c470155ac 100644
--- a/test/test_crud.rb
+++ b/test/test_crud.rb
@@ -45,7 +45,7 @@ module Arel
table = Table.new :users
fc = FakeCrudder.new
fc.from table
- stmt = fc.compile_update [[table[:id], 'foo']]
+ stmt = fc.compile_update [[table[:id], 'foo']], Arel::Attributes::Attribute.new(table, 'id')
assert_instance_of Arel::UpdateManager, stmt
end
end
diff --git a/test/test_factory_methods.rb b/test/test_factory_methods.rb
index 21671cd239..3e23b090b4 100644
--- a/test/test_factory_methods.rb
+++ b/test/test_factory_methods.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module FactoryMethods
- class TestFactoryMethods < MiniTest::Unit::TestCase
+ class TestFactoryMethods < Minitest::Test
class Factory
include Arel::FactoryMethods
end
@@ -37,7 +37,7 @@ module Arel
lower = @factory.lower :one
assert_instance_of Nodes::NamedFunction, lower
assert_equal 'LOWER', lower.name
- assert_equal [:one], lower.expressions
+ assert_equal [:one], lower.expressions.map(&:expr)
end
end
end
diff --git a/test/test_insert_manager.rb b/test/test_insert_manager.rb
index 4878a33c90..9cfd01262b 100644
--- a/test/test_insert_manager.rb
+++ b/test/test_insert_manager.rb
@@ -10,7 +10,6 @@ module Arel
describe 'insert' do
it 'can create a Values node' do
- table = Table.new(:users)
manager = Arel::InsertManager.new Table.engine
values = manager.create_values %w{ a b }, %w{ c d }
@@ -20,11 +19,11 @@ module Arel
end
it 'allows sql literals' do
- table = Table.new(:users)
manager = Arel::InsertManager.new Table.engine
+ manager.into Table.new(:users)
manager.values = manager.create_values [Arel.sql('*')], %w{ a }
manager.to_sql.must_be_like %{
- INSERT INTO NULL VALUES (*)
+ INSERT INTO \"users\" VALUES (*)
}
end
@@ -79,14 +78,19 @@ module Arel
}
end
- it 'takes an empty list' do
+ it 'noop for empty list' do
+ table = Table.new(:users)
manager = Arel::InsertManager.new Table.engine
+ manager.insert [[table[:id], 1]]
manager.insert []
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id") VALUES (1)
+ }
end
end
describe 'into' do
- it 'takes an engine' do
+ it 'takes a Table and chains' do
manager = Arel::InsertManager.new Table.engine
manager.into(Table.new(:users)).must_equal manager
end
@@ -127,7 +131,7 @@ module Arel
end
describe "combo" do
- it "puts shit together" do
+ it "combines columns and values list in order" do
table = Table.new :users
manager = Arel::InsertManager.new Table.engine
manager.into table
@@ -140,5 +144,28 @@ module Arel
}
end
end
+
+ describe "select" do
+
+ it "accepts a select query in place of a VALUES clause" do
+ table = Table.new :users
+
+ manager = Arel::InsertManager.new Table.engine
+ manager.into table
+
+ select = Arel::SelectManager.new Table.engine
+ select.project Arel.sql('1')
+ select.project Arel.sql('"aaron"')
+
+ manager.select select
+ manager.columns << table[:id]
+ manager.columns << table[:name]
+ manager.to_sql.must_be_like %{
+ INSERT INTO "users" ("id", "name") (SELECT 1, "aaron")
+ }
+ end
+
+ end
+
end
end
diff --git a/test/test_select_manager.rb b/test/test_select_manager.rb
index d68deb3061..1ffb56fd9f 100644
--- a/test/test_select_manager.rb
+++ b/test/test_select_manager.rb
@@ -1,66 +1,21 @@
require 'helper'
module Arel
- class EngineProxy
- attr_reader :executed
- attr_reader :connection_pool
- attr_reader :spec
- attr_reader :config
-
- def initialize engine
- @engine = engine
- @executed = []
- @connection_pool = self
- @spec = self
- @config = { :adapter => 'sqlite3' }
- end
-
- def with_connection
- yield self
- end
-
- def connection
- self
- end
-
- def quote_table_name thing; @engine.connection.quote_table_name thing end
- def quote_column_name thing; @engine.connection.quote_column_name thing end
- def quote thing, column; @engine.connection.quote thing, column end
- def columns table, message = nil
- @engine.connection.columns table, message
- end
-
- def columns_hash
- @engine.connection.columns_hash
- end
-
- def table_exists? name
- @engine.connection.table_exists? name
- end
-
- def tables
- @engine.connection.tables
- end
-
- def visitor
- @engine.connection.visitor
- end
-
- def execute sql, name = nil, *args
- @executed << sql
- end
- alias :update :execute
- alias :delete :execute
- alias :insert :execute
- end
describe 'select manager' do
def test_join_sources
manager = Arel::SelectManager.new Table.engine
- manager.join_sources << Arel::Nodes::StringJoin.new('foo')
+ manager.join_sources << Arel::Nodes::StringJoin.new(Nodes.build_quoted('foo'))
assert_equal "SELECT FROM 'foo'", manager.to_sql
end
+ def test_manager_stores_bind_values
+ manager = Arel::SelectManager.new Table.engine
+ 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
@@ -78,7 +33,7 @@ module Arel
it 'accepts symbols' do
table = Table.new :users
manager = Arel::SelectManager.new Table.engine
- manager.project SqlLiteral.new '*'
+ manager.project Nodes::SqlLiteral.new '*'
manager.from table
manager.order :foo
manager.to_sql.must_be_like %{ SELECT * FROM "users" ORDER BY foo }
@@ -119,7 +74,7 @@ module Arel
manager = Arel::SelectManager.new Table.engine
manager.project Arel.sql('name')
manager.from as
- manager.to_sql.must_be_like "SELECT name FROM (SELECT * FROM zomg ) foo"
+ manager.to_sql.must_be_like "SELECT name FROM (SELECT * FROM zomg) foo"
end
end
@@ -147,7 +102,7 @@ module Arel
manager1.from(as)
manager1.to_sql.must_be_like %{
- SELECT lol FROM (SELECT * FROM "users" ) omg
+ SELECT lol FROM (SELECT * FROM "users") omg
}
end
end
@@ -185,7 +140,7 @@ module Arel
mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg }
end
- it 'converts to sqlliterals' do
+ it 'converts to sqlliterals with multiple items' do
table = Table.new :users
right = table.alias
mgr = table.from table
@@ -197,7 +152,7 @@ module Arel
describe 'clone' do
it 'creates new cores' do
- table = Table.new :users, :engine => Table.engine, :as => 'foo'
+ table = Table.new :users, :as => 'foo'
mgr = table.from table
m2 = mgr.clone
m2.project "foo"
@@ -205,7 +160,7 @@ module Arel
end
it 'makes updates to the correct copy' do
- table = Table.new :users, :engine => Table.engine, :as => 'foo'
+ table = Table.new :users, :as => 'foo'
mgr = table.from table
m2 = mgr.clone
m3 = m2.clone
@@ -217,7 +172,7 @@ module Arel
describe 'initialize' do
it 'uses alias in sql' do
- table = Table.new :users, :engine => Table.engine, :as => 'foo'
+ table = Table.new :users, :as => 'foo'
mgr = table.from table
mgr.skip 10
mgr.to_sql.must_be_like %{ SELECT FROM "users" "foo" OFFSET 10 }
@@ -269,7 +224,7 @@ module Arel
it 'should create an exists clause' do
table = Table.new(:users)
manager = Arel::SelectManager.new Table.engine, table
- manager.project SqlLiteral.new '*'
+ manager.project Nodes::SqlLiteral.new '*'
m2 = Arel::SelectManager.new(manager.engine)
m2.project manager.exists
m2.to_sql.must_be_like %{ SELECT EXISTS (#{manager.to_sql}) }
@@ -278,7 +233,7 @@ module Arel
it 'can be aliased' do
table = Table.new(:users)
manager = Arel::SelectManager.new Table.engine, table
- manager.project SqlLiteral.new '*'
+ manager.project Nodes::SqlLiteral.new '*'
m2 = Arel::SelectManager.new(manager.engine)
m2.project manager.exists.as('foo')
m2.to_sql.must_be_like %{ SELECT EXISTS (#{manager.to_sql}) AS foo }
@@ -375,6 +330,20 @@ module Arel
end
describe 'with' do
+ it 'should support basic WITH' do
+ users = Table.new(:users)
+ users_top = Table.new(:users_top)
+ comments = Table.new(:comments)
+
+ top = users.project(users[:id]).where(users[:karma].gt(100))
+ users_as = Arel::Nodes::As.new(users_top, top)
+ select_manager = comments.project(Arel.star).with(users_as)
+ .where(comments[:author_id].in(users_top.project(users_top[:id])))
+
+ select_manager.to_sql.must_be_like %{
+ WITH "users_top" AS (SELECT "users"."id" FROM "users" WHERE "users"."karma" > 100) SELECT * FROM "comments" WHERE "comments"."author_id" IN (SELECT "users_top"."id" FROM "users_top")
+ }
+ end
it "should support WITH RECURSIVE" do
comments = Table.new(:comments)
@@ -413,9 +382,9 @@ module Arel
it 'should return the ast' do
table = Table.new :users
mgr = table.from table
- ast = mgr.ast
- mgr.visitor.accept(ast).must_equal mgr.to_sql
+ assert mgr.ast
end
+
it 'should allow orders to work when the ast is grepped' do
table = Table.new :users
mgr = table.from table
@@ -435,20 +404,6 @@ module Arel
end
end
- describe 'insert' do
- it 'uses the select FROM' do
- engine = EngineProxy.new Table.engine
- table = Table.new :users
- manager = Arel::SelectManager.new engine
- manager.from table
- manager.insert 'VALUES(NULL)'
-
- engine.executed.last.must_be_like %{
- INSERT INTO "users" VALUES(NULL)
- }
- end
- end
-
describe 'lock' do
# This should fail on other databases
it 'adds a lock node' do
@@ -472,7 +427,7 @@ module Arel
it 'generates order clauses' do
table = Table.new :users
manager = Arel::SelectManager.new Table.engine
- manager.project SqlLiteral.new '*'
+ manager.project Nodes::SqlLiteral.new '*'
manager.from table
manager.order table[:id]
manager.to_sql.must_be_like %{
@@ -484,7 +439,7 @@ module Arel
it 'takes *args' do
table = Table.new :users
manager = Arel::SelectManager.new Table.engine
- manager.project SqlLiteral.new '*'
+ manager.project Nodes::SqlLiteral.new '*'
manager.from table
manager.order table[:id], table[:name]
manager.to_sql.must_be_like %{
@@ -501,25 +456,13 @@ module Arel
it 'has order attributes' do
table = Table.new :users
manager = Arel::SelectManager.new Table.engine
- manager.project SqlLiteral.new '*'
+ manager.project Nodes::SqlLiteral.new '*'
manager.from table
manager.order table[:id].desc
manager.to_sql.must_be_like %{
SELECT * FROM "users" ORDER BY "users"."id" DESC
}
end
-
- it 'has order attributes for expressions' do
- table = Table.new :users
- manager = Arel::SelectManager.new Table.engine
- manager.project SqlLiteral.new '*'
- manager.from table
- manager.order table[:id].count.desc
- manager.to_sql.must_be_like %{
- SELECT * FROM "users" ORDER BY COUNT("users"."id") DESC
- }
- end
-
end
describe 'on' do
@@ -588,7 +531,15 @@ module Arel
assert_equal 'bar', join.right
end
- it 'should create join nodes with a klass' do
+ it 'should create join nodes with a full outer join klass' do
+ relation = Arel::SelectManager.new Table.engine
+ join = relation.create_join 'foo', 'bar', Arel::Nodes::FullOuterJoin
+ assert_kind_of Arel::Nodes::FullOuterJoin, join
+ assert_equal 'foo', join.left
+ assert_equal 'bar', join.right
+ end
+
+ it 'should create join nodes with a outer join klass' do
relation = Arel::SelectManager.new Table.engine
join = relation.create_join 'foo', 'bar', Arel::Nodes::OuterJoin
assert_kind_of Arel::Nodes::OuterJoin, join
@@ -596,6 +547,14 @@ module Arel
assert_equal 'bar', join.right
end
+ it 'should create join nodes with a right outer join klass' do
+ relation = Arel::SelectManager.new Table.engine
+ join = relation.create_join 'foo', 'bar', Arel::Nodes::RightOuterJoin
+ assert_kind_of Arel::Nodes::RightOuterJoin, join
+ assert_equal 'foo', join.left
+ assert_equal 'bar', join.right
+ end
+
describe 'join' do
it 'responds to join' do
left = Table.new :users
@@ -633,15 +592,37 @@ module Arel
end
end
+ describe 'outer join' do
+ it 'responds to join' do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
+ manager = Arel::SelectManager.new Table.engine
+
+ manager.from left
+ manager.outer_join(right).on(predicate)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users"
+ LEFT OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+
+ it 'noops on nil' do
+ manager = Arel::SelectManager.new Table.engine
+ manager.outer_join(nil).must_equal manager
+ end
+ end
+
describe 'joins' do
- it 'returns join sql' do
+
+ it 'returns inner join sql' do
table = Table.new :users
aliaz = table.alias
manager = Arel::SelectManager.new Table.engine
manager.from Nodes::InnerJoin.new(aliaz, table[:id].eq(aliaz[:id]))
- manager.join_sql.must_be_like %{
- INNER JOIN "users" "users_2" "users"."id" = "users_2"."id"
- }
+ assert_match 'INNER JOIN "users" "users_2" "users"."id" = "users_2"."id"',
+ manager.to_sql
end
it 'returns outer join sql' do
@@ -649,9 +630,8 @@ module Arel
aliaz = table.alias
manager = Arel::SelectManager.new Table.engine
manager.from Nodes::OuterJoin.new(aliaz, table[:id].eq(aliaz[:id]))
- manager.join_sql.must_be_like %{
- LEFT OUTER JOIN "users" "users_2" "users"."id" = "users_2"."id"
- }
+ assert_match 'LEFT OUTER JOIN "users" "users_2" "users"."id" = "users_2"."id"',
+ manager.to_sql
end
it 'can have a non-table alias as relation name' do
@@ -671,26 +651,26 @@ module Arel
}
end
- it 'returns string join sql' do
- table = Table.new :users
- manager = Arel::SelectManager.new Table.engine
- manager.from Nodes::StringJoin.new('hello')
- manager.join_sql.must_be_like %{ 'hello' }
- end
+ it "joins itself" do
+ left = Table.new :users
+ right = left.alias
+ predicate = left[:id].eq(right[:id])
- it 'returns nil join sql' do
- manager = Arel::SelectManager.new Table.engine
- manager.join_sql.must_be_nil
+ mgr = left.join(right)
+ mgr.project Nodes::SqlLiteral.new('*')
+ mgr.on(predicate).must_equal mgr
+
+ mgr.to_sql.must_be_like %{
+ SELECT * FROM "users"
+ INNER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
end
- end
- describe 'order_clauses' do
- it 'returns order clauses as a list' do
- table = Table.new :users
+ it 'returns string join sql' do
manager = Arel::SelectManager.new Table.engine
- manager.from table
- manager.order table[:id]
- manager.order_clauses.first.must_be_like %{ "users"."id" }
+ manager.from Nodes::StringJoin.new(Nodes.build_quoted('hello'))
+ assert_match "'hello'", manager.to_sql
end
end
@@ -752,6 +732,47 @@ module Arel
}
end
+ it 'takes an order with multiple columns' do
+ table = Table.new :users
+ manager = Arel::SelectManager.new Table.engine
+ manager.from table
+ manager.window('a_window').order(table['foo'].asc, table['bar'].desc)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (ORDER BY "users"."foo" ASC, "users"."bar" DESC)
+ }
+ end
+
+ it 'takes a partition' do
+ table = Table.new :users
+ manager = Arel::SelectManager.new Table.engine
+ manager.from table
+ manager.window('a_window').partition(table['bar'])
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (PARTITION BY "users"."bar")
+ }
+ end
+
+ it 'takes a partition and an order' do
+ table = Table.new :users
+ manager = Arel::SelectManager.new Table.engine
+ manager.from table
+ manager.window('a_window').partition(table['foo']).order(table['foo'].asc)
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (PARTITION BY "users"."foo"
+ ORDER BY "users"."foo" ASC)
+ }
+ end
+
+ it 'takes a partition with multiple columns' do
+ table = Table.new :users
+ manager = Arel::SelectManager.new Table.engine
+ manager.from table
+ manager.window('a_window').partition(table['bar'], table['baz'])
+ manager.to_sql.must_be_like %{
+ SELECT FROM "users" WINDOW "a_window" AS (PARTITION BY "users"."bar", "users"."baz")
+ }
+ end
+
it 'takes a rows frame, unbounded preceding' do
table = Table.new :users
manager = Arel::SelectManager.new Table.engine
@@ -889,9 +910,8 @@ module Arel
describe 'delete' do
it "copies from" do
- engine = EngineProxy.new Table.engine
table = Table.new :users
- manager = Arel::SelectManager.new engine
+ manager = Arel::SelectManager.new Table.engine
manager.from table
stmt = manager.compile_delete
@@ -899,9 +919,8 @@ module Arel
end
it "copies where" do
- engine = EngineProxy.new Table.engine
table = Table.new :users
- manager = Arel::SelectManager.new engine
+ manager = Arel::SelectManager.new Table.engine
manager.from table
manager.where table[:id].eq 10
stmt = manager.compile_delete
@@ -930,13 +949,33 @@ module Arel
end
describe 'update' do
+
+ it 'creates an update statement' do
+ table = Table.new :users
+ manager = Arel::SelectManager.new Table.engine
+ manager.from table
+ stmt = manager.compile_update({table[:id] => 1}, Arel::Attributes::Attribute.new(table, 'id'))
+
+ stmt.to_sql.must_be_like %{
+ UPDATE "users" SET "id" = 1
+ }
+ end
+
+ it 'takes a string' do
+ table = Table.new :users
+ manager = Arel::SelectManager.new Table.engine
+ manager.from table
+ stmt = manager.compile_update(Nodes::SqlLiteral.new('foo = bar'), Arel::Attributes::Attribute.new(table, 'id'))
+
+ stmt.to_sql.must_be_like %{ UPDATE "users" SET foo = bar }
+ end
+
it 'copies limits' do
- engine = EngineProxy.new Table.engine
table = Table.new :users
- manager = Arel::SelectManager.new engine
+ manager = Arel::SelectManager.new Table.engine
manager.from table
manager.take 1
- stmt = manager.compile_update(SqlLiteral.new('foo = bar'))
+ stmt = manager.compile_update(Nodes::SqlLiteral.new('foo = bar'), Arel::Attributes::Attribute.new(table, 'id'))
stmt.key = table['id']
stmt.to_sql.must_be_like %{
@@ -946,12 +985,11 @@ module Arel
end
it 'copies order' do
- engine = EngineProxy.new Table.engine
table = Table.new :users
- manager = Arel::SelectManager.new engine
+ manager = Arel::SelectManager.new Table.engine
manager.from table
manager.order :foo
- stmt = manager.compile_update(SqlLiteral.new('foo = bar'))
+ stmt = manager.compile_update(Nodes::SqlLiteral.new('foo = bar'), Arel::Attributes::Attribute.new(table, 'id'))
stmt.key = table['id']
stmt.to_sql.must_be_like %{
@@ -960,23 +998,12 @@ module Arel
}
end
- it 'takes a string' do
- engine = EngineProxy.new Table.engine
- table = Table.new :users
- manager = Arel::SelectManager.new engine
- manager.from table
- stmt = manager.compile_update(SqlLiteral.new('foo = bar'))
-
- stmt.to_sql.must_be_like %{ UPDATE "users" SET foo = bar }
- end
-
it 'copies where clauses' do
- engine = EngineProxy.new Table.engine
table = Table.new :users
- manager = Arel::SelectManager.new engine
+ manager = Arel::SelectManager.new Table.engine
manager.where table[:id].eq 10
manager.from table
- stmt = manager.compile_update(table[:id] => 1)
+ stmt = manager.compile_update({table[:id] => 1}, Arel::Attributes::Attribute.new(table, 'id'))
stmt.to_sql.must_be_like %{
UPDATE "users" SET "id" = 1 WHERE "users"."id" = 10
@@ -984,33 +1011,27 @@ module Arel
end
it 'copies where clauses when nesting is triggered' do
- engine = EngineProxy.new Table.engine
table = Table.new :users
- manager = Arel::SelectManager.new engine
+ manager = Arel::SelectManager.new Table.engine
manager.where table[:foo].eq 10
manager.take 42
manager.from table
- stmt = manager.compile_update(table[:id] => 1)
+ stmt = manager.compile_update({table[:id] => 1}, Arel::Attributes::Attribute.new(table, 'id'))
stmt.to_sql.must_be_like %{
UPDATE "users" SET "id" = 1 WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE "users"."foo" = 10 LIMIT 42)
}
end
- it 'executes an update statement' do
- engine = EngineProxy.new Table.engine
- table = Table.new :users
- manager = Arel::SelectManager.new engine
- manager.from table
- stmt = manager.compile_update(table[:id] => 1)
-
- stmt.to_sql.must_be_like %{
- UPDATE "users" SET "id" = 1
- }
- end
end
describe 'project' do
+ it "takes sql literals" do
+ manager = Arel::SelectManager.new Table.engine
+ manager.project Nodes::SqlLiteral.new '*'
+ manager.to_sql.must_be_like %{ SELECT * }
+ end
+
it 'takes multiple args' do
manager = Arel::SelectManager.new Table.engine
manager.project Nodes::SqlLiteral.new('foo'),
@@ -1020,16 +1041,17 @@ module Arel
it 'takes strings' do
manager = Arel::SelectManager.new Table.engine
- manager.project Nodes::SqlLiteral.new('*')
+ manager.project '*'
manager.to_sql.must_be_like %{ SELECT * }
end
- it "takes sql literals" do
+ end
+
+ describe 'projections' do
+ it 'reads projections' do
manager = Arel::SelectManager.new Table.engine
- manager.project Nodes::SqlLiteral.new '*'
- manager.to_sql.must_be_like %{
- SELECT *
- }
+ manager.project Arel.sql('foo'), Arel.sql('bar')
+ manager.projections.must_equal [Arel.sql('foo'), Arel.sql('bar')]
end
end
@@ -1094,24 +1116,6 @@ module Arel
end
end
- describe "join" do
- it "joins itself" do
- left = Table.new :users
- right = left.alias
- predicate = left[:id].eq(right[:id])
-
- mgr = left.join(right)
- mgr.project Nodes::SqlLiteral.new('*')
- mgr.on(predicate).must_equal mgr
-
- mgr.to_sql.must_be_like %{
- SELECT * FROM "users"
- INNER JOIN "users" "users_2"
- ON "users"."id" = "users_2"."id"
- }
- end
- end
-
describe 'from' do
it "makes sql" do
table = Table.new :users
@@ -1132,7 +1136,6 @@ module Arel
describe 'source' do
it 'returns the join source of the select core' do
- table = Table.new :users
manager = Arel::SelectManager.new Table.engine
manager.source.must_equal manager.ast.cores.last.source
end
@@ -1140,7 +1143,6 @@ module Arel
describe 'distinct' do
it 'sets the quantifier' do
- table = Table.new :users
manager = Arel::SelectManager.new Table.engine
manager.distinct
@@ -1149,6 +1151,33 @@ module Arel
manager.distinct(false)
manager.ast.cores.last.set_quantifier.must_equal nil
end
+
+ it "chains" do
+ manager = Arel::SelectManager.new Table.engine
+ manager.distinct.must_equal manager
+ manager.distinct(false).must_equal manager
+ end
+ end
+
+ describe 'distinct_on' do
+ it 'sets the quantifier' do
+ manager = Arel::SelectManager.new Table.engine
+ table = Table.new :users
+
+ manager.distinct_on(table['id'])
+ manager.ast.cores.last.set_quantifier.must_equal Arel::Nodes::DistinctOn.new(table['id'])
+
+ manager.distinct_on(false)
+ manager.ast.cores.last.set_quantifier.must_equal nil
+ end
+
+ it "chains" do
+ manager = Arel::SelectManager.new Table.engine
+ table = Table.new :users
+
+ manager.distinct_on(table['id']).must_equal manager
+ manager.distinct_on(false).must_equal manager
+ end
end
end
end
diff --git a/test/test_table.rb b/test/test_table.rb
index 5db8cdd6c0..14256475ec 100644
--- a/test/test_table.rb
+++ b/test/test_table.rb
@@ -20,16 +20,31 @@ module Arel
end
it 'should create join nodes with a klass' do
+ join = @relation.create_join 'foo', 'bar', Arel::Nodes::FullOuterJoin
+ assert_kind_of Arel::Nodes::FullOuterJoin, join
+ assert_equal 'foo', join.left
+ assert_equal 'bar', join.right
+ end
+
+ it 'should create join nodes with a klass' do
join = @relation.create_join 'foo', 'bar', Arel::Nodes::OuterJoin
assert_kind_of Arel::Nodes::OuterJoin, join
assert_equal 'foo', join.left
assert_equal 'bar', join.right
end
+ it 'should create join nodes with a klass' do
+ join = @relation.create_join 'foo', 'bar', Arel::Nodes::RightOuterJoin
+ assert_kind_of Arel::Nodes::RightOuterJoin, join
+ assert_equal 'foo', join.left
+ assert_equal 'bar', join.right
+ end
+
it 'should return an insert manager' do
im = @relation.compile_insert 'VALUES(NULL)'
assert_kind_of Arel::InsertManager, im
- assert_equal 'INSERT INTO NULL VALUES(NULL)', im.to_sql
+ im.into Table.new(:users)
+ assert_equal "INSERT INTO \"users\" VALUES(NULL)", im.to_sql
end
it 'should return IM from insert_manager' do
@@ -52,6 +67,22 @@ module Arel
end
end
+ describe 'update_manager' do
+ it 'should return an update manager' do
+ um = @relation.update_manager
+ assert_kind_of Arel::UpdateManager, um
+ assert_equal um.engine, @relation.engine
+ end
+ end
+
+ describe 'delete_manager' do
+ it 'should return a delete manager' do
+ dm = @relation.delete_manager
+ assert_kind_of Arel::DeleteManager, dm
+ assert_equal dm.engine, @relation.engine
+ end
+ end
+
describe 'having' do
it 'adds a having clause' do
mgr = @relation.having @relation[:id].eq(10)
@@ -81,6 +112,20 @@ module Arel
}
end
end
+
+ describe 'join' do
+ it 'creates an outer join' do
+ right = @relation.alias
+ predicate = @relation[:id].eq(right[:id])
+ mgr = @relation.outer_join(right).on(predicate)
+
+ mgr.to_sql.must_be_like %{
+ SELECT FROM "users"
+ LEFT OUTER JOIN "users" "users_2"
+ ON "users"."id" = "users_2"."id"
+ }
+ end
+ end
end
describe 'group' do
@@ -130,19 +175,19 @@ module Arel
describe 'take' do
it "should add a limit" do
manager = @relation.take 1
- manager.project SqlLiteral.new '*'
+ manager.project Nodes::SqlLiteral.new '*'
manager.to_sql.must_be_like %{ SELECT * FROM "users" LIMIT 1 }
end
end
describe 'project' do
it 'can project' do
- manager = @relation.project SqlLiteral.new '*'
+ manager = @relation.project Nodes::SqlLiteral.new '*'
manager.to_sql.must_be_like %{ SELECT * FROM "users" }
end
it 'takes multiple parameters' do
- manager = @relation.project SqlLiteral.new('*'), SqlLiteral.new('*')
+ manager = @relation.project Nodes::SqlLiteral.new('*'), Nodes::SqlLiteral.new('*')
manager.to_sql.must_be_like %{ SELECT *, * FROM "users" }
end
end
diff --git a/test/test_update_manager.rb b/test/test_update_manager.rb
index f9704af425..f1a019970d 100644
--- a/test/test_update_manager.rb
+++ b/test/test_update_manager.rb
@@ -8,9 +8,18 @@ module Arel
end
end
+ it "should not quote sql literals" do
+ table = Table.new(:users)
+ um = Arel::UpdateManager.new Table.engine
+ um.table table
+ um.set [[table[:name], (Arel::Nodes::BindParam.new '?')]]
+ um.to_sql.must_be_like %{ UPDATE "users" SET "name" = ? }
+ end
+
it 'handles limit properly' do
table = Table.new(:users)
um = Arel::UpdateManager.new Table.engine
+ um.key = 'id'
um.take 10
um.table table
um.set [[table[:name], nil]]
diff --git a/test/visitors/test_bind_visitor.rb b/test/visitors/test_bind_visitor.rb
index 92e5d1612c..5171bbe57c 100644
--- a/test/visitors/test_bind_visitor.rb
+++ b/test/visitors/test_bind_visitor.rb
@@ -1,11 +1,40 @@
require 'helper'
require 'arel/visitors/bind_visitor'
+require 'support/fake_record'
module Arel
module Visitors
- class TestBindVisitor < MiniTest::Unit::TestCase
+ 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 Table.engine
+ 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::Visitor) {
+ visitor = Class.new(Arel::Visitors::ToSql) {
def initialize omg
end
@@ -14,12 +43,12 @@ module Arel
bp = Nodes::BindParam.new 'omg'
called = false
- visitor.accept(bp) { called = true }
+ visitor.accept(bp, collector) { called = true }
assert called
end
def test_visitor_only_yields_on_binds
- visitor = Class.new(Arel::Visitors::Visitor) {
+ visitor = Class.new(Arel::Visitors::ToSql) {
def initialize omg
end
@@ -29,9 +58,7 @@ module Arel
bp = Arel.sql 'omg'
called = false
- assert_raises(TypeError) {
- visitor.accept(bp) { called = true }
- }
+ visitor.accept(bp, collector) { called = true }
refute called
end
end
diff --git a/test/visitors/test_depth_first.rb b/test/visitors/test_depth_first.rb
index 38d12b487c..d50ea3e59a 100644
--- a/test/visitors/test_depth_first.rb
+++ b/test/visitors/test_depth_first.rb
@@ -3,7 +3,7 @@ require 'set'
module Arel
module Visitors
- class TestDepthFirst < MiniTest::Unit::TestCase
+ class TestDepthFirst < Minitest::Test
Collector = Struct.new(:calls) do
def call object
calls << object
@@ -82,12 +82,24 @@ module Arel
assert_equal [:a, :b, join], @collector.calls
end
+ def test_full_outer_join
+ join = Nodes::FullOuterJoin.new :a, :b
+ @visitor.accept join
+ assert_equal [:a, :b, join], @collector.calls
+ end
+
def test_outer_join
join = Nodes::OuterJoin.new :a, :b
@visitor.accept join
assert_equal [:a, :b, join], @collector.calls
end
+ def test_right_outer_join
+ join = Nodes::RightOuterJoin.new :a, :b
+ @visitor.accept join
+ assert_equal [:a, :b, join], @collector.calls
+ end
+
[
Arel::Nodes::Assignment,
Arel::Nodes::Between,
diff --git a/test/visitors/test_dispatch_contamination.rb b/test/visitors/test_dispatch_contamination.rb
new file mode 100644
index 0000000000..d3c9e8af2e
--- /dev/null
+++ b/test/visitors/test_dispatch_contamination.rb
@@ -0,0 +1,22 @@
+require 'helper'
+
+module Arel
+ module Visitors
+ describe 'avoiding contamination between visitor dispatch tables' do
+ before do
+ @connection = Table.engine.connection
+ @table = Table.new(:users)
+ end
+
+ it 'dispatches properly after failing upwards' do
+ node = Nodes::Union.new(Nodes::True.new, Nodes::False.new)
+ assert_equal "( TRUE UNION FALSE )", node.to_sql
+
+ node.first # from Nodes::Node's Enumerable mixin
+
+ assert_equal "( TRUE UNION FALSE )", node.to_sql
+ end
+ end
+ end
+end
+
diff --git a/test/visitors/test_dot.rb b/test/visitors/test_dot.rb
index 362e39339c..7763350f5c 100644
--- a/test/visitors/test_dot.rb
+++ b/test/visitors/test_dot.rb
@@ -2,7 +2,7 @@ require 'helper'
module Arel
module Visitors
- class TestDot < MiniTest::Unit::TestCase
+ class TestDot < Minitest::Test
def setup
@visitor = Visitors::Dot.new
end
@@ -17,13 +17,13 @@ module Arel
].each do |klass|
define_method("test_#{klass.name.gsub('::', '_')}") do
op = klass.new(:a, "z")
- @visitor.accept op
+ @visitor.accept op, Collectors::PlainString.new
end
end
def test_named_function
func = Nodes::NamedFunction.new 'omg', 'omg'
- @visitor.accept func
+ @visitor.accept func, Collectors::PlainString.new
end
# unary ops
@@ -41,7 +41,7 @@ module Arel
].each do |klass|
define_method("test_#{klass.name.gsub('::', '_')}") do
op = klass.new(:a)
- @visitor.accept op
+ @visitor.accept op, Collectors::PlainString.new
end
end
@@ -68,7 +68,7 @@ module Arel
].each do |klass|
define_method("test_#{klass.name.gsub('::', '_')}") do
binary = klass.new(:a, :b)
- @visitor.accept binary
+ @visitor.accept binary, Collectors::PlainString.new
end
end
end
diff --git a/test/visitors/test_ibm_db.rb b/test/visitors/test_ibm_db.rb
index b055e883d6..f1aa7612be 100644
--- a/test/visitors/test_ibm_db.rb
+++ b/test/visitors/test_ibm_db.rb
@@ -7,19 +7,25 @@ module Arel
@visitor = IBM_DB.new Table.engine.connection
end
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
it 'uses FETCH FIRST n ROWS to limit results' do
stmt = Nodes::SelectStatement.new
stmt.limit = Nodes::Limit.new(1)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT FETCH FIRST 1 ROWS ONLY"
end
it 'uses FETCH FIRST n ROWS in updates with a limit' do
+ table = Table.new(:users)
stmt = Nodes::UpdateStatement.new
- stmt.limit = Nodes::Limit.new(1)
- stmt.key = 'id'
- sql = @visitor.accept(stmt)
- sql.must_be_like "UPDATE NULL WHERE 'id' IN (SELECT 'id' FETCH FIRST 1 ROWS ONLY)"
+ stmt.relation = table
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1))
+ stmt.key = table[:id]
+ sql = compile(stmt)
+ sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT \"users\".\"id\" FROM \"users\" FETCH FIRST 1 ROWS ONLY)"
end
end
diff --git a/test/visitors/test_informix.rb b/test/visitors/test_informix.rb
index 90bbf5c104..6d94282b77 100644
--- a/test/visitors/test_informix.rb
+++ b/test/visitors/test_informix.rb
@@ -7,25 +7,31 @@ module Arel
@visitor = Informix.new Table.engine.connection
end
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
it 'uses LIMIT n to limit results' do
stmt = Nodes::SelectStatement.new
stmt.limit = Nodes::Limit.new(1)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT LIMIT 1"
end
it 'uses LIMIT n in updates with a limit' do
+ table = Table.new(:users)
stmt = Nodes::UpdateStatement.new
- stmt.limit = Nodes::Limit.new(1)
- stmt.key = 'id'
- sql = @visitor.accept(stmt)
- sql.must_be_like "UPDATE NULL WHERE 'id' IN (SELECT LIMIT 1 'id')"
+ stmt.relation = table
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1))
+ stmt.key = table[:id]
+ sql = compile(stmt)
+ sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT LIMIT 1 \"users\".\"id\" FROM \"users\")"
end
it 'uses SKIP n to jump results' do
stmt = Nodes::SelectStatement.new
stmt.offset = Nodes::Offset.new(10)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT SKIP 10"
end
@@ -33,7 +39,7 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.limit = Nodes::Limit.new(1)
stmt.offset = Nodes::Offset.new(1)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT SKIP 1 LIMIT 1"
end
@@ -43,7 +49,7 @@ module Arel
core.source = Nodes::JoinSource.new(table, [table.create_join(Table.new(:comments))])
stmt = Nodes::SelectStatement.new([core])
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like 'SELECT FROM "posts" INNER JOIN "comments"'
end
diff --git a/test/visitors/test_join_sql.rb b/test/visitors/test_join_sql.rb
deleted file mode 100644
index b3fc7661aa..0000000000
--- a/test/visitors/test_join_sql.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'helper'
-
-module Arel
- module Visitors
- describe 'the join_sql visitor' do
- before do
- @visitor = ToSql.new Table.engine.connection
- @visitor.extend(JoinSql)
- end
-
- it 'should visit string join' do
- sql = @visitor.accept Nodes::StringJoin.new('omg')
- sql.must_be_like "'omg'"
- end
-
- describe 'inner join' do
- it 'should visit left if left is a join' do
- t = Table.new :users
- sm = t.select_manager
- sm.join(t).on(t[:id]).join(t).on(t[:id])
- sm.join_sql.must_be_like %{
- INNER JOIN "users" ON "users"."id"
- INNER JOIN "users" ON "users"."id"
- }
- end
- end
-
- describe 'outer join' do
- it 'should visit left if left is a join' do
- t = Table.new :users
- sm = t.select_manager
- sm.join(t, Nodes::OuterJoin).on(t[:id]).join(
- t, Nodes::OuterJoin).on(t[:id])
- sm.join_sql.must_be_like %{
- LEFT OUTER JOIN "users" ON "users"."id"
- LEFT OUTER JOIN "users" ON "users"."id"
- }
- end
- end
- end
- end
-end
diff --git a/test/visitors/test_mssql.rb b/test/visitors/test_mssql.rb
index d62d4b8d1f..a3efcb8b27 100644
--- a/test/visitors/test_mssql.rb
+++ b/test/visitors/test_mssql.rb
@@ -8,9 +8,13 @@ module Arel
@table = Arel::Table.new "users"
end
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
it 'should not modify query if no offset or limit' do
stmt = Nodes::SelectStatement.new
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT"
end
@@ -18,15 +22,15 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.cores.first.from = @table
stmt.limit = Nodes::Limit.new(10)
- sql = @visitor.accept(stmt)
- sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY \"users\".\"id\") as _row_num FROM \"users\" ) as _t WHERE _row_num BETWEEN 1 AND 10"
+ sql = compile(stmt)
+ sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY \"users\".\"id\") as _row_num FROM \"users\") as _t WHERE _row_num BETWEEN 1 AND 10"
end
it 'should go over query ORDER BY if .order()' do
stmt = Nodes::SelectStatement.new
stmt.limit = Nodes::Limit.new(10)
stmt.orders << Nodes::SqlLiteral.new('order_by')
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY order_by) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10"
end
@@ -34,7 +38,7 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.cores.first.groups << Nodes::SqlLiteral.new('group_by')
stmt.limit = Nodes::Limit.new(10)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY group_by) as _row_num GROUP BY group_by) as _t WHERE _row_num BETWEEN 1 AND 10"
end
@@ -42,14 +46,14 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.limit = Nodes::Limit.new(10)
stmt.offset = Nodes::Offset.new(20)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 21 AND 30"
end
it 'should use >= if only .offset' do
stmt = Nodes::SelectStatement.new
stmt.offset = Nodes::Offset.new(20)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num >= 21"
end
@@ -57,7 +61,7 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.limit = Nodes::Limit.new(10)
stmt.cores.first.projections << Nodes::Count.new('*')
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT COUNT(1) as count_id FROM (SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10) AS subquery"
end
diff --git a/test/visitors/test_mysql.rb b/test/visitors/test_mysql.rb
index 6330112229..8e4b9e6861 100644
--- a/test/visitors/test_mysql.rb
+++ b/test/visitors/test_mysql.rb
@@ -7,14 +7,18 @@ module Arel
@visitor = MySQL.new Table.engine.connection
end
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
it 'squashes parenthesis on multiple unions' do
- subnode = Nodes::Union.new 'left', 'right'
- node = Nodes::Union.new subnode, 'topright'
- assert_equal 1, @visitor.accept(node).scan('(').length
+ subnode = Nodes::Union.new Arel.sql('left'), Arel.sql('right')
+ node = Nodes::Union.new subnode, Arel.sql('topright')
+ assert_equal 1, compile(node).scan('(').length
- subnode = Nodes::Union.new 'left', 'right'
- node = Nodes::Union.new 'topleft', subnode
- assert_equal 1, @visitor.accept(node).scan('(').length
+ subnode = Nodes::Union.new Arel.sql('left'), Arel.sql('right')
+ node = Nodes::Union.new Arel.sql('topleft'), subnode
+ assert_equal 1, compile(node).scan('(').length
end
###
@@ -23,31 +27,32 @@ module Arel
it 'defaults limit to 18446744073709551615' do
stmt = Nodes::SelectStatement.new
stmt.offset = Nodes::Offset.new(1)
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT FROM DUAL LIMIT 18446744073709551615 OFFSET 1"
end
it "should escape LIMIT" do
sc = Arel::Nodes::UpdateStatement.new
- sc.limit = Nodes::Limit.new("omg")
- assert_equal("UPDATE NULL LIMIT 'omg'", @visitor.accept(sc))
+ sc.relation = Table.new(:users)
+ sc.limit = Nodes::Limit.new(Nodes.build_quoted("omg"))
+ assert_equal("UPDATE \"users\" LIMIT 'omg'", compile(sc))
end
it 'uses DUAL for empty from' do
stmt = Nodes::SelectStatement.new
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like "SELECT FROM DUAL"
end
describe 'locking' do
it 'defaults to FOR UPDATE when locking' do
node = Nodes::Lock.new(Arel.sql('FOR UPDATE'))
- @visitor.accept(node).must_be_like "FOR UPDATE"
+ compile(node).must_be_like "FOR UPDATE"
end
it 'allows a custom string to be used as a lock' do
node = Nodes::Lock.new(Arel.sql('LOCK IN SHARE MODE'))
- @visitor.accept(node).must_be_like "LOCK IN SHARE MODE"
+ compile(node).must_be_like "LOCK IN SHARE MODE"
end
end
end
diff --git a/test/visitors/test_oracle.rb b/test/visitors/test_oracle.rb
index af81f2058b..29d7042084 100644
--- a/test/visitors/test_oracle.rb
+++ b/test/visitors/test_oracle.rb
@@ -7,13 +7,17 @@ module Arel
@visitor = Oracle.new Table.engine.connection_pool
end
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
it 'modifies order when there is distinct and first value' do
# *sigh*
select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
stmt = Nodes::SelectStatement.new
stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
stmt.orders << Nodes::SqlLiteral.new('foo')
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like %{
SELECT #{select} ORDER BY alias_0__
}
@@ -26,8 +30,8 @@ module Arel
stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
stmt.orders << Nodes::SqlLiteral.new('foo')
- sql = @visitor.accept(stmt)
- sql2 = @visitor.accept(stmt)
+ sql = compile(stmt)
+ sql2 = compile(stmt)
sql.must_equal sql2
end
@@ -37,7 +41,7 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
stmt.orders << Nodes::SqlLiteral.new('foo, bar')
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like %{
SELECT #{select} ORDER BY alias_0__, alias_1__
}
@@ -49,7 +53,7 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
stmt.orders << Nodes::SqlLiteral.new('NVL(LOWER(bar, foo), foo) DESC, UPPER(baz)')
- sql = @visitor.accept(stmt)
+ sql = compile(stmt)
sql.must_be_like %{
SELECT #{select} ORDER BY alias_0__ DESC, alias_1__
}
@@ -60,7 +64,7 @@ module Arel
it 'adds a rownum clause' do
stmt = Nodes::SelectStatement.new
stmt.limit = Nodes::Limit.new(10)
- sql = @visitor.accept stmt
+ sql = compile stmt
sql.must_be_like %{ SELECT WHERE ROWNUM <= 10 }
end
@@ -68,8 +72,8 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.orders << Nodes::SqlLiteral.new('foo')
stmt.limit = Nodes::Limit.new(10)
- sql = @visitor.accept stmt
- sql2 = @visitor.accept stmt
+ sql = compile stmt
+ sql2 = compile stmt
sql.must_equal sql2
end
@@ -77,42 +81,54 @@ module Arel
stmt = Nodes::SelectStatement.new
stmt.orders << Nodes::SqlLiteral.new('foo')
stmt.limit = Nodes::Limit.new(10)
- sql = @visitor.accept stmt
+ sql = compile stmt
+ sql.must_be_like %{
+ SELECT * FROM (SELECT ORDER BY foo ) WHERE ROWNUM <= 10
+ }
+ end
+
+ it 'creates a subquery when there is group by' do
+ stmt = Nodes::SelectStatement.new
+ stmt.cores.first.groups << Nodes::SqlLiteral.new('foo')
+ stmt.limit = Nodes::Limit.new(10)
+ sql = compile stmt
sql.must_be_like %{
- SELECT * FROM (SELECT ORDER BY foo) WHERE ROWNUM <= 10
+ SELECT * FROM (SELECT GROUP BY foo ) WHERE ROWNUM <= 10
}
end
it 'creates a subquery when there is DISTINCT' do
stmt = Nodes::SelectStatement.new
- stmt.cores.first.projections << Nodes::SqlLiteral.new('DISTINCT id')
+ stmt.cores.first.set_quantifier = Arel::Nodes::Distinct.new
+ stmt.cores.first.projections << Nodes::SqlLiteral.new('id')
stmt.limit = Arel::Nodes::Limit.new(10)
- sql = @visitor.accept stmt
+ sql = compile stmt
sql.must_be_like %{
- SELECT * FROM (SELECT DISTINCT id) WHERE ROWNUM <= 10
+ SELECT * FROM (SELECT DISTINCT id ) WHERE ROWNUM <= 10
}
end
it 'creates a different subquery when there is an offset' do
stmt = Nodes::SelectStatement.new
- stmt.limit = Nodes::Limit.new(10)
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(10))
stmt.offset = Nodes::Offset.new(10)
- sql = @visitor.accept stmt
+ sql = compile stmt
sql.must_be_like %{
SELECT * FROM (
SELECT raw_sql_.*, rownum raw_rnum_
- FROM (SELECT) raw_sql_
+ FROM (SELECT ) raw_sql_
+ WHERE rownum <= 20
)
- WHERE raw_rnum_ between 11 and 20
+ WHERE raw_rnum_ > 10
}
end
it 'is idempotent with different subquery' do
stmt = Nodes::SelectStatement.new
- stmt.limit = Nodes::Limit.new(10)
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(10))
stmt.offset = Nodes::Offset.new(10)
- sql = @visitor.accept stmt
- sql2 = @visitor.accept stmt
+ sql = compile stmt
+ sql2 = compile stmt
sql.must_equal sql2
end
end
@@ -121,7 +137,7 @@ module Arel
it 'creates a select from subquery with rownum condition' do
stmt = Nodes::SelectStatement.new
stmt.offset = Nodes::Offset.new(10)
- sql = @visitor.accept stmt
+ sql = compile stmt
sql.must_be_like %{
SELECT * FROM (
SELECT raw_sql_.*, rownum raw_rnum_
@@ -137,7 +153,7 @@ module Arel
it 'modified except to be minus' do
left = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 10")
right = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 20")
- sql = @visitor.accept Nodes::Except.new(left, right)
+ sql = compile Nodes::Except.new(left, right)
sql.must_be_like %{
( SELECT * FROM users WHERE age > 10 MINUS SELECT * FROM users WHERE age > 20 )
}
@@ -146,7 +162,7 @@ module Arel
describe 'locking' do
it 'defaults to FOR UPDATE when locking' do
node = Nodes::Lock.new(Arel.sql('FOR UPDATE'))
- @visitor.accept(node).must_be_like "FOR UPDATE"
+ compile(node).must_be_like "FOR UPDATE"
end
end
end
diff --git a/test/visitors/test_postgres.rb b/test/visitors/test_postgres.rb
index 921bd96c1a..3d646a7324 100644
--- a/test/visitors/test_postgres.rb
+++ b/test/visitors/test_postgres.rb
@@ -5,18 +5,24 @@ module Arel
describe 'the postgres visitor' do
before do
@visitor = PostgreSQL.new Table.engine.connection
+ @table = Table.new(:users)
+ @attr = @table[:id]
+ end
+
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
end
describe 'locking' do
it 'defaults to FOR UPDATE' do
- @visitor.accept(Nodes::Lock.new(Arel.sql('FOR UPDATE'))).must_be_like %{
+ compile(Nodes::Lock.new(Arel.sql('FOR UPDATE'))).must_be_like %{
FOR UPDATE
}
end
it 'allows a custom string to be used as a lock' do
node = Nodes::Lock.new(Arel.sql('FOR SHARE'))
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
FOR SHARE
}
end
@@ -24,10 +30,10 @@ module Arel
it "should escape LIMIT" do
sc = Arel::Nodes::SelectStatement.new
- sc.limit = Nodes::Limit.new("omg")
- sc.cores.first.projections << 'DISTINCT ON'
- sc.orders << "xyz"
- sql = @visitor.accept(sc)
+ sc.limit = Nodes::Limit.new(Nodes.build_quoted("omg"))
+ sc.cores.first.projections << Arel.sql('DISTINCT ON')
+ sc.orders << Arel.sql("xyz")
+ sql = compile(sc)
assert_match(/LIMIT 'omg'/, sql)
assert_equal 1, sql.scan(/LIMIT/).length, 'should have one limit'
end
@@ -35,13 +41,81 @@ module Arel
it 'should support DISTINCT ON' do
core = Arel::Nodes::SelectCore.new
core.set_quantifier = Arel::Nodes::DistinctOn.new(Arel.sql('aaron'))
- assert_match 'DISTINCT ON ( aaron )', @visitor.accept(core)
+ assert_match 'DISTINCT ON ( aaron )', compile(core)
end
it 'should support DISTINCT' do
core = Arel::Nodes::SelectCore.new
core.set_quantifier = Arel::Nodes::Distinct.new
- assert_equal 'SELECT DISTINCT', @visitor.accept(core)
+ assert_equal 'SELECT DISTINCT', compile(core)
+ end
+
+ describe "Nodes::Matches" do
+ it "should know how to visit" do
+ node = @table[:name].matches('foo%')
+ compile(node).must_be_like %{
+ "users"."name" ILIKE 'foo%'
+ }
+ end
+
+ it 'can handle subqueries' do
+ subquery = @table.project(:id).where(@table[:name].matches('foo%'))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" ILIKE 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::DoesNotMatch" do
+ it "should know how to visit" do
+ node = @table[:name].does_not_match('foo%')
+ compile(node).must_be_like %{
+ "users"."name" NOT ILIKE 'foo%'
+ }
+ end
+
+ it 'can handle subqueries' do
+ subquery = @table.project(:id).where(@table[:name].does_not_match('foo%'))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" NOT ILIKE 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::Regexp" do
+ it "should know how to visit" do
+ node = Arel::Nodes::Regexp.new(@table[:name], Nodes.build_quoted('foo%'))
+ compile(node).must_be_like %{
+ "users"."name" ~ 'foo%'
+ }
+ end
+
+ it 'can handle subqueries' do
+ subquery = @table.project(:id).where(Arel::Nodes::Regexp.new(@table[:name], Nodes.build_quoted('foo%')))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" ~ 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::NotRegexp" do
+ it "should know how to visit" do
+ node = Arel::Nodes::NotRegexp.new(@table[:name], Nodes.build_quoted('foo%'))
+ compile(node).must_be_like %{
+ "users"."name" !~ 'foo%'
+ }
+ end
+
+ it 'can handle subqueries' do
+ subquery = @table.project(:id).where(Arel::Nodes::NotRegexp.new(@table[:name], Nodes.build_quoted('foo%')))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" !~ 'foo%')
+ }
+ end
end
end
end
diff --git a/test/visitors/test_sqlite.rb b/test/visitors/test_sqlite.rb
index c06f554ea4..8fb8e76095 100644
--- a/test/visitors/test_sqlite.rb
+++ b/test/visitors/test_sqlite.rb
@@ -10,13 +10,13 @@ module Arel
it 'defaults limit to -1' do
stmt = Nodes::SelectStatement.new
stmt.offset = Nodes::Offset.new(1)
- sql = @visitor.accept(stmt)
+ sql = @visitor.accept(stmt, Collectors::SQLString.new).value
sql.must_be_like "SELECT LIMIT -1 OFFSET 1"
end
it 'does not support locking' do
node = Nodes::Lock.new(Arel.sql('FOR UPDATE'))
- @visitor.accept(node).must_be_nil
+ assert_equal '', @visitor.accept(node, Collectors::SQLString.new).value
end
end
end
diff --git a/test/visitors/test_to_sql.rb b/test/visitors/test_to_sql.rb
index 2164e68cb7..d84142e27a 100644
--- a/test/visitors/test_to_sql.rb
+++ b/test/visitors/test_to_sql.rb
@@ -5,21 +5,26 @@ module Arel
module Visitors
describe 'the to_sql visitor' do
before do
- @visitor = ToSql.new Table.engine.connection
+ @conn = FakeRecord::Base.new
+ @visitor = ToSql.new @conn.connection
@table = Table.new(:users)
@attr = @table[:id]
end
+ def compile node
+ @visitor.accept(node, Collectors::SQLString.new).value
+ end
+
it 'works with BindParams' do
node = Nodes::BindParam.new 'omg'
- sql = @visitor.accept node
+ sql = compile node
sql.must_be_like 'omg'
end
it 'can define a dispatch method' do
visited = false
- viz = Class.new(Arel::Visitors::Visitor) {
- define_method(:hello) do |node|
+ viz = Class.new(Arel::Visitors::Reduce) {
+ define_method(:hello) do |node, c|
visited = true
end
@@ -28,91 +33,200 @@ module Arel
end
}.new
- viz.accept(@table)
+ viz.accept(@table, Collectors::SQLString.new)
assert visited, 'hello method was called'
end
it 'should not quote sql literals' do
node = @table[Arel.star]
- sql = @visitor.accept node
+ sql = compile node
sql.must_be_like '"users".*'
end
it 'should visit named functions' do
function = Nodes::NamedFunction.new('omg', [Arel.star])
- assert_equal 'omg(*)', @visitor.accept(function)
+ assert_equal 'omg(*)', compile(function)
end
it 'should chain predications on named functions' do
function = Nodes::NamedFunction.new('omg', [Arel.star])
- sql = @visitor.accept(function.eq(2))
+ sql = compile(function.eq(2))
sql.must_be_like %{ omg(*) = 2 }
end
+ it 'should visit built-in functions' do
+ function = Nodes::Count.new([Arel.star])
+ assert_equal 'COUNT(*)', compile(function)
+
+ function = Nodes::Sum.new([Arel.star])
+ assert_equal 'SUM(*)', compile(function)
+
+ function = Nodes::Max.new([Arel.star])
+ assert_equal 'MAX(*)', compile(function)
+
+ function = Nodes::Min.new([Arel.star])
+ assert_equal 'MIN(*)', compile(function)
+
+ function = Nodes::Avg.new([Arel.star])
+ assert_equal 'AVG(*)', compile(function)
+ end
+
+ it 'should visit built-in functions operating on distinct values' do
+ function = Nodes::Count.new([Arel.star])
+ function.distinct = true
+ assert_equal 'COUNT(DISTINCT *)', compile(function)
+
+ function = Nodes::Sum.new([Arel.star])
+ function.distinct = true
+ assert_equal 'SUM(DISTINCT *)', compile(function)
+
+ function = Nodes::Max.new([Arel.star])
+ function.distinct = true
+ assert_equal 'MAX(DISTINCT *)', compile(function)
+
+ function = Nodes::Min.new([Arel.star])
+ function.distinct = true
+ assert_equal 'MIN(DISTINCT *)', compile(function)
+
+ function = Nodes::Avg.new([Arel.star])
+ function.distinct = true
+ assert_equal 'AVG(DISTINCT *)', compile(function)
+ end
+
it 'works with lists' do
function = Nodes::NamedFunction.new('omg', [Arel.star, Arel.star])
- assert_equal 'omg(*, *)', @visitor.accept(function)
+ assert_equal 'omg(*, *)', compile(function)
end
- describe 'equality' do
+ describe 'Nodes::Equality' do
+ it "should escape strings" do
+ test = Table.new(:users)[:name].eq 'Aaron Patterson'
+ compile(test).must_be_like %{
+ "users"."name" = 'Aaron Patterson'
+ }
+ end
+
it 'should handle false' do
- sql = @visitor.accept Nodes::Equality.new(false, false)
+ table = Table.new(:users)
+ val = Nodes.build_quoted(false, table[:active])
+ sql = compile Nodes::Equality.new(val, val)
sql.must_be_like %{ 'f' = 'f' }
end
it 'should use the column to quote' do
table = Table.new(:users)
- sql = @visitor.accept Nodes::Equality.new(table[:id], '1-fooo')
+ val = Nodes.build_quoted('1-fooo', table[:id])
+ sql = compile Nodes::Equality.new(table[:id], val)
sql.must_be_like %{ "users"."id" = 1 }
end
+
+ it 'should use the column to quote integers' do
+ table = Table.new(:users)
+ sql = compile table[:name].eq(0)
+ sql.must_be_like %{ "users"."name" = '0' }
+ end
+
+ it 'should handle nil' do
+ sql = compile Nodes::Equality.new(@table[:name], nil)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe 'Nodes::Grouping' do
+ it 'wraps nested groupings in brackets only once' do
+ sql = compile Nodes::Grouping.new(Nodes::Grouping.new(Nodes.build_quoted('foo')))
+ sql.must_equal "('foo')"
+ end
+ end
+
+ describe 'Nodes::NotEqual' do
+ it 'should handle false' do
+ val = Nodes.build_quoted(false, @table[:active])
+ sql = compile Nodes::NotEqual.new(@table[:active], val)
+ sql.must_be_like %{ "users"."active" != 'f' }
+ end
+
+ it 'should handle nil' do
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::NotEqual.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
end
it "should visit string subclass" do
- @visitor.accept(Class.new(String).new(":'("))
- @visitor.accept(Class.new(Class.new(String)).new(":'("))
+ [
+ Class.new(String).new(":'("),
+ Class.new(Class.new(String)).new(":'("),
+ ].each do |obj|
+ val = Nodes.build_quoted(obj, @table[:active])
+ sql = compile Nodes::NotEqual.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" != ':\\'(' }
+ end
end
it "should visit_Class" do
- @visitor.accept(DateTime).must_equal "'DateTime'"
+ compile(Nodes.build_quoted(DateTime)).must_equal "'DateTime'"
end
it "should escape LIMIT" do
sc = Arel::Nodes::SelectStatement.new
- sc.limit = Arel::Nodes::Limit.new("omg")
- assert_match(/LIMIT 'omg'/, @visitor.accept(sc))
+ sc.limit = Arel::Nodes::Limit.new(Nodes.build_quoted("omg"))
+ assert_match(/LIMIT 'omg'/, compile(sc))
+ end
+
+ it "should quote LIMIT without column type coercion" do
+ table = Table.new(:users)
+ sc = table.where(table[:name].eq(0)).take(1).ast
+ assert_match(/WHERE "users"."name" = '0' LIMIT 1/, compile(sc))
end
it "should visit_DateTime" do
- @visitor.accept DateTime.now
+ called_with = nil
+ @conn.connection.extend(Module.new {
+ define_method(:quote) do |thing, column|
+ called_with = column
+ super(thing, column)
+ end
+ })
+
+ dt = DateTime.now
+ table = Table.new(:users)
+ test = table[:created_at].eq dt
+ sql = compile test
+
+ assert_equal "created_at", called_with.name
+ sql.must_be_like %{"users"."created_at" = '#{dt.strftime("%Y-%m-%d %H:%M:%S")}'}
end
it "should visit_Float" do
- @visitor.accept 2.14
+ test = Table.new(:products)[:price].eq 2.14
+ sql = compile test
+ sql.must_be_like %{"products"."price" = 2.14}
end
it "should visit_Not" do
- sql = @visitor.accept Nodes::Not.new(Arel.sql("foo"))
+ sql = compile Nodes::Not.new(Arel.sql("foo"))
sql.must_be_like "NOT (foo)"
end
it "should apply Not to the whole expression" do
node = Nodes::And.new [@attr.eq(10), @attr.eq(11)]
- sql = @visitor.accept Nodes::Not.new(node)
+ sql = compile Nodes::Not.new(node)
sql.must_be_like %{NOT ("users"."id" = 10 AND "users"."id" = 11)}
end
it "should visit_As" do
as = Nodes::As.new(Arel.sql("foo"), Arel.sql("bar"))
- sql = @visitor.accept as
+ sql = compile as
sql.must_be_like "foo AS bar"
end
it "should visit_Bignum" do
- @visitor.accept 8787878092
+ compile 8787878092
end
it "should visit_Hash" do
- @visitor.accept({:a => 1})
+ compile(Nodes.build_quoted({:a => 1}))
end
it "should visit_Set" do
@@ -120,45 +234,114 @@ module Arel
end
it "should visit_BigDecimal" do
- @visitor.accept BigDecimal.new('2.14')
+ compile Nodes.build_quoted(BigDecimal.new('2.14'))
end
it "should visit_Date" do
- @visitor.accept Date.today
+ called_with = nil
+ @conn.connection.extend(Module.new {
+ define_method(:quote) do |thing, column|
+ called_with = column
+ super(thing, column)
+ end
+ })
+
+ dt = Date.today
+ table = Table.new(:users)
+ test = table[:created_at].eq dt
+ sql = compile test
+
+ assert_equal "created_at", called_with.name
+ sql.must_be_like %{"users"."created_at" = '#{dt.strftime("%Y-%m-%d")}'}
end
it "should visit_NilClass" do
- @visitor.accept(nil).must_be_like "NULL"
+ compile(Nodes.build_quoted(nil)).must_be_like "NULL"
+ end
+
+ it "unsupported input should not raise ArgumentError" do
+ error = assert_raises(RuntimeError) { compile(nil) }
+ assert_match(/\Aunsupported/, error.message)
+ end
+
+ it "should visit_Arel_SelectManager, which is a subquery" do
+ mgr = Table.new(:foo).project(:bar)
+ compile(mgr).must_be_like '(SELECT bar FROM "foo")'
end
it "should visit_Arel_Nodes_And" do
node = Nodes::And.new [@attr.eq(10), @attr.eq(11)]
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" = 10 AND "users"."id" = 11
}
end
it "should visit_Arel_Nodes_Or" do
node = Nodes::Or.new @attr.eq(10), @attr.eq(11)
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" = 10 OR "users"."id" = 11
}
end
+ it "should visit_Arel_Nodes_Assignment" do
+ column = @table["id"]
+ node = Nodes::Assignment.new(
+ Nodes::UnqualifiedColumn.new(column),
+ Nodes::UnqualifiedColumn.new(column)
+ )
+ compile(node).must_be_like %{
+ "id" = "id"
+ }
+ end
+
it "should visit visit_Arel_Attributes_Time" do
attr = Attributes::Time.new(@attr.relation, @attr.name)
- @visitor.accept attr
+ compile attr
end
it "should visit_TrueClass" do
test = Table.new(:users)[:bool].eq(true)
- @visitor.accept(test).must_be_like %{ "users"."bool" = 't' }
+ compile(test).must_be_like %{ "users"."bool" = 't' }
+ end
+
+ describe "Nodes::Matches" do
+ it "should know how to visit" do
+ node = @table[:name].matches('foo%')
+ compile(node).must_be_like %{
+ "users"."name" LIKE 'foo%'
+ }
+ end
+
+ it 'can handle subqueries' do
+ subquery = @table.project(:id).where(@table[:name].matches('foo%'))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" LIKE 'foo%')
+ }
+ end
+ end
+
+ describe "Nodes::DoesNotMatch" do
+ it "should know how to visit" do
+ node = @table[:name].does_not_match('foo%')
+ compile(node).must_be_like %{
+ "users"."name" NOT LIKE 'foo%'
+ }
+ end
+
+ it 'can handle subqueries' do
+ subquery = @table.project(:id).where(@table[:name].does_not_match('foo%'))
+ node = @attr.in subquery
+ compile(node).must_be_like %{
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" NOT LIKE 'foo%')
+ }
+ end
end
describe "Nodes::Ordering" do
it "should know how to visit" do
node = @attr.desc
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" DESC
}
end
@@ -167,35 +350,52 @@ module Arel
describe "Nodes::In" do
it "should know how to visit" do
node = @attr.in [1, 2, 3]
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" IN (1, 2, 3)
}
end
it "should return 1=0 when empty right which is always false" do
node = @attr.in []
- @visitor.accept(node).must_equal '1=0'
+ compile(node).must_equal '1=0'
end
it 'can handle two dot ranges' do
node = @attr.in 1..3
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" BETWEEN 1 AND 3
}
end
it 'can handle three dot ranges' do
node = @attr.in 1...3
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" >= 1 AND "users"."id" < 3
}
end
+ it 'can handle ranges bounded by infinity' do
+ node = @attr.in 1..Float::INFINITY
+ compile(node).must_be_like %{
+ "users"."id" >= 1
+ }
+ node = @attr.in(-Float::INFINITY..3)
+ compile(node).must_be_like %{
+ "users"."id" <= 3
+ }
+ node = @attr.in(-Float::INFINITY...3)
+ compile(node).must_be_like %{
+ "users"."id" < 3
+ }
+ node = @attr.in(-Float::INFINITY..Float::INFINITY)
+ compile(node).must_be_like %{1=1}
+ end
+
it 'can handle subqueries' do
table = Table.new(:users)
subquery = table.project(:id).where(table[:name].eq('Aaron'))
node = @attr.in subquery
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" IN (SELECT id FROM "users" WHERE "users"."name" = 'Aaron')
}
end
@@ -210,34 +410,35 @@ module Arel
super
end
end
- in_node = Nodes::In.new @attr, %w{ a b c }
+ vals = %w{ a b c }.map { |x| Nodes.build_quoted(x, @attr) }
+ in_node = Nodes::In.new @attr, vals
visitor = visitor.new(Table.engine.connection)
visitor.expected = Table.engine.connection.columns(:users).find { |x|
x.name == 'name'
}
- visitor.accept(in_node).must_equal %("users"."name" IN ('a', 'b', 'c'))
+ visitor.accept(in_node, Collectors::SQLString.new).value.must_equal %("users"."name" IN ('a', 'b', 'c'))
end
end
describe "Nodes::InfixOperation" do
it "should handle Multiplication" do
node = Arel::Attributes::Decimal.new(Table.new(:products), :price) * Arel::Attributes::Decimal.new(Table.new(:currency_rates), :rate)
- @visitor.accept(node).must_equal %("products"."price" * "currency_rates"."rate")
+ compile(node).must_equal %("products"."price" * "currency_rates"."rate")
end
it "should handle Division" do
node = Arel::Attributes::Decimal.new(Table.new(:products), :price) / 5
- @visitor.accept(node).must_equal %("products"."price" / 5)
+ compile(node).must_equal %("products"."price" / 5)
end
it "should handle Addition" do
node = Arel::Attributes::Decimal.new(Table.new(:products), :price) + 6
- @visitor.accept(node).must_equal %(("products"."price" + 6))
+ compile(node).must_equal %(("products"."price" + 6))
end
it "should handle Subtraction" do
node = Arel::Attributes::Decimal.new(Table.new(:products), :price) - 7
- @visitor.accept(node).must_equal %(("products"."price" - 7))
+ compile(node).must_equal %(("products"."price" - 7))
end
it "should handle arbitrary operators" do
@@ -246,42 +447,59 @@ module Arel
Arel::Attributes::String.new(Table.new(:products), :name),
Arel::Attributes::String.new(Table.new(:products), :name)
)
- @visitor.accept(node).must_equal %("products"."name" || "products"."name")
+ compile(node).must_equal %("products"."name" || "products"."name")
end
end
describe "Nodes::NotIn" do
it "should know how to visit" do
node = @attr.not_in [1, 2, 3]
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" NOT IN (1, 2, 3)
}
end
it "should return 1=1 when empty right which is always true" do
node = @attr.not_in []
- @visitor.accept(node).must_equal '1=1'
+ compile(node).must_equal '1=1'
end
it 'can handle two dot ranges' do
node = @attr.not_in 1..3
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" < 1 OR "users"."id" > 3
}
end
it 'can handle three dot ranges' do
node = @attr.not_in 1...3
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" < 1 OR "users"."id" >= 3
}
end
+ it 'can handle ranges bounded by infinity' do
+ node = @attr.not_in 1..Float::INFINITY
+ compile(node).must_be_like %{
+ "users"."id" < 1
+ }
+ node = @attr.not_in(-Float::INFINITY..3)
+ compile(node).must_be_like %{
+ "users"."id" > 3
+ }
+ node = @attr.not_in(-Float::INFINITY...3)
+ compile(node).must_be_like %{
+ "users"."id" >= 3
+ }
+ node = @attr.not_in(-Float::INFINITY..Float::INFINITY)
+ compile(node).must_be_like %{1=0}
+ end
+
it 'can handle subqueries' do
table = Table.new(:users)
subquery = table.project(:id).where(table[:name].eq('Aaron'))
node = @attr.not_in subquery
- @visitor.accept(node).must_be_like %{
+ compile(node).must_be_like %{
"users"."id" NOT IN (SELECT id FROM "users" WHERE "users"."name" = 'Aaron')
}
end
@@ -296,35 +514,27 @@ module Arel
super
end
end
- in_node = Nodes::NotIn.new @attr, %w{ a b c }
+ vals = %w{ a b c }.map { |x| Nodes.build_quoted(x, @attr) }
+ in_node = Nodes::NotIn.new @attr, vals
visitor = visitor.new(Table.engine.connection)
visitor.expected = Table.engine.connection.columns(:users).find { |x|
x.name == 'name'
}
- visitor.accept(in_node).must_equal %("users"."name" NOT IN ('a', 'b', 'c'))
- end
- end
-
- describe 'Equality' do
- it "should escape strings" do
- test = Table.new(:users)[:name].eq 'Aaron Patterson'
- @visitor.accept(test).must_be_like %{
- "users"."name" = 'Aaron Patterson'
- }
+ compile(in_node).must_equal %("users"."name" NOT IN ('a', 'b', 'c'))
end
end
describe 'Constants' do
it "should handle true" do
test = Table.new(:users).create_true
- @visitor.accept(test).must_be_like %{
+ compile(test).must_be_like %{
TRUE
}
end
it "should handle false" do
test = Table.new(:users).create_false
- @visitor.accept(test).must_be_like %{
+ compile(test).must_be_like %{
FALSE
}
end
@@ -333,7 +543,7 @@ module Arel
describe 'TableAlias' do
it "should use the underlying table for checking columns" do
test = Table.new(:users).alias('zomgusers')[:id].eq '3'
- @visitor.accept(test).must_be_like %{
+ compile(test).must_be_like %{
"zomgusers"."id" = 3
}
end
@@ -345,7 +555,27 @@ module Arel
core.set_quantifier = Arel::Nodes::DistinctOn.new(Arel.sql('aaron'))
assert_raises(NotImplementedError) do
- @visitor.accept(core)
+ compile(core)
+ end
+ end
+ end
+
+ describe 'Nodes::Regexp' do
+ it 'raises not implemented error' do
+ node = Arel::Nodes::Regexp.new(@table[:name], Nodes.build_quoted('foo%'))
+
+ assert_raises(NotImplementedError) do
+ compile(node)
+ end
+ end
+ end
+
+ describe 'Nodes::NotRegexp' do
+ it 'raises not implemented error' do
+ node = Arel::Nodes::NotRegexp.new(@table[:name], Nodes.build_quoted('foo%'))
+
+ assert_raises(NotImplementedError) do
+ compile(node)
end
end
end