diff options
-rw-r--r-- | Manifest.txt | 1 | ||||
-rw-r--r-- | README.markdown | 21 | ||||
-rw-r--r-- | arel.gemspec | 23 | ||||
-rw-r--r-- | lib/arel.rb | 2 | ||||
-rw-r--r-- | lib/arel/collectors/plain_string.rb | 18 | ||||
-rw-r--r-- | lib/arel/collectors/sql_string.rb | 17 | ||||
-rw-r--r-- | lib/arel/expressions.rb | 9 | ||||
-rw-r--r-- | lib/arel/nodes.rb | 2 | ||||
-rw-r--r-- | lib/arel/nodes/and.rb | 6 | ||||
-rw-r--r-- | lib/arel/nodes/window.rb | 27 | ||||
-rw-r--r-- | lib/arel/predications.rb | 40 | ||||
-rw-r--r-- | lib/arel/select_manager.rb | 2 | ||||
-rw-r--r-- | lib/arel/table.rb | 2 | ||||
-rw-r--r-- | lib/arel/tree_manager.rb | 4 | ||||
-rw-r--r-- | lib/arel/visitors/bind_substitute.rb | 2 | ||||
-rw-r--r-- | lib/arel/visitors/dot.rb | 6 | ||||
-rw-r--r-- | lib/arel/visitors/to_sql.rb | 17 | ||||
-rw-r--r-- | test/attributes/test_attribute.rb | 30 | ||||
-rw-r--r-- | test/collectors/test_bind_collector.rb | 2 | ||||
-rw-r--r-- | test/nodes/test_infix_operation.rb | 2 | ||||
-rw-r--r-- | test/nodes/test_window.rb | 12 | ||||
-rw-r--r-- | test/test_insert_manager.rb | 11 | ||||
-rw-r--r-- | test/test_select_manager.rb | 160 | ||||
-rw-r--r-- | test/visitors/test_dot.rb | 8 | ||||
-rw-r--r-- | test/visitors/test_to_sql.rb | 5 |
25 files changed, 273 insertions, 156 deletions
diff --git a/Manifest.txt b/Manifest.txt index 3892cd3b2a..32e1dd43a6 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -13,6 +13,7 @@ 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 diff --git a/README.markdown b/README.markdown index 75b0b3ee0b..1776330da8 100644 --- a/README.markdown +++ b/README.markdown @@ -120,10 +120,10 @@ 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) AS sum_id FROM users -users.project(users[:age].average) # => SELECT AVG(users.age) AS avg_id FROM users -users.project(users[:age].maximum) # => SELECT MAX(users.age) AS max_id FROM users -users.project(users[:age].minimum) # => SELECT MIN(users.age) AS min_id FROM users +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 ``` @@ -160,7 +160,7 @@ products. #### 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: ```ruby comments = Arel::Table.new(:comments) @@ -172,16 +172,17 @@ And this table has the following attributes: # [:id, :body, :parent_id] ``` -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: +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])) -# => SELECT * FROM comments INNER JOIN comments AS comments_2 WHERE comments_2.parent_id = comments.id + 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 first comment's reply's body. +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: @@ -200,7 +201,7 @@ users. 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) AS sum_id FROM users INNER JOIN cte_table ON users.id = cte_table.user_id +# => 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`: diff --git a/arel.gemspec b/arel.gemspec index e6fe10a4bf..832eb12f49 100644 --- a/arel.gemspec +++ b/arel.gemspec @@ -1,22 +1,21 @@ # -*- encoding: utf-8 -*- -# stub: arel 6.0.0.20140505020427 ruby lib +# stub: arel 6.0.0.beta1.20140817224534 ruby lib Gem::Specification.new do |s| s.name = "arel" - s.version = "6.0.0.20140505020427" + s.version = "6.0.0.beta1.20140817224534" - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + 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-05-05" + 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/collectors/bind.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.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.rubyforge_project = "arel" 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"] @@ -25,17 +24,17 @@ Gem::Specification.new do |s| s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_development_dependency(%q<minitest>, ["~> 5.3"]) + s.add_development_dependency(%q<minitest>, ["~> 5.4"]) s.add_development_dependency(%q<rdoc>, ["~> 4.0"]) - s.add_development_dependency(%q<hoe>, ["~> 3.6"]) + s.add_development_dependency(%q<hoe>, ["~> 3.12"]) else - s.add_dependency(%q<minitest>, ["~> 5.3"]) + s.add_dependency(%q<minitest>, ["~> 5.4"]) s.add_dependency(%q<rdoc>, ["~> 4.0"]) - s.add_dependency(%q<hoe>, ["~> 3.6"]) + s.add_dependency(%q<hoe>, ["~> 3.12"]) end else - s.add_dependency(%q<minitest>, ["~> 5.3"]) + s.add_dependency(%q<minitest>, ["~> 5.4"]) s.add_dependency(%q<rdoc>, ["~> 4.0"]) - s.add_dependency(%q<hoe>, ["~> 3.6"]) + s.add_dependency(%q<hoe>, ["~> 3.12"]) end end diff --git a/lib/arel.rb b/lib/arel.rb index 1e3c51a254..80677953df 100644 --- a/lib/arel.rb +++ b/lib/arel.rb @@ -21,7 +21,7 @@ require 'arel/delete_manager' require 'arel/nodes' module Arel - VERSION = '6.0.0' + VERSION = '6.0.0.beta1' def self.sql raw_sql Arel::Nodes::SqlLiteral.new raw_sql 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 index 45001bb507..8ca89ca7bd 100644 --- a/lib/arel/collectors/sql_string.rb +++ b/lib/arel/collectors/sql_string.rb @@ -1,21 +1,10 @@ # encoding: utf-8 +require 'arel/collectors/plain_string' + module Arel module Collectors - class SQLString - def initialize - @str = '' - end - - def value - @str - end - - def << str - @str << str - self - end - + class SQLString < PlainString def add_bind bind self << bind.to_s self 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/nodes.rb b/lib/arel/nodes.rb index a68e327983..c6bde8c3cc 100644 --- a/lib/arel/nodes.rb +++ b/lib/arel/nodes.rb @@ -78,7 +78,7 @@ module Arel def self.build_quoted other, attribute = nil case other - when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Nodes::SelectStatement, Arel::Table, Arel::Nodes::BindParam + when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager other else case attribute diff --git a/lib/arel/nodes/and.rb b/lib/arel/nodes/and.rb index 62e8ef6f11..8e1afda709 100644 --- a/lib/arel/nodes/and.rb +++ b/lib/arel/nodes/and.rb @@ -3,12 +3,8 @@ module Arel class And < Arel::Nodes::Node attr_reader :children - def initialize children, right = nil + def initialize children super() - unless Array === children - warn "(#{caller.first}) AND nodes should be created with a list" - children = [children, right] - end @children = children end diff --git a/lib/arel/nodes/window.rb b/lib/arel/nodes/window.rb index 60259e8c05..fee8eeff7a 100644 --- a/lib/arel/nodes/window.rb +++ b/lib/arel/nodes/window.rb @@ -1,10 +1,12 @@ module Arel module Nodes class Window < Arel::Nodes::Node - attr_accessor :orders, :framing + attr_accessor :orders, :framing, :partitions def initialize @orders = [] + @partitions = [] + @framing = nil end def order *expr @@ -15,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 @@ -39,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 2ef4837548..78cd87d430 100644 --- a/lib/arel/predications.rb +++ b/lib/arel/predications.rb @@ -29,14 +29,16 @@ module Arel when Arel::SelectManager Arel::Nodes::In.new(self, other.ast) when Range - if other.begin == -Float::INFINITY && other.end == Float::INFINITY - Nodes::NotIn.new self, [] + 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.begin == -Float::INFINITY && other.exclude_end? - Nodes::LessThan.new(self, Nodes.build_quoted(other.end, self)) - elsif other.begin == -Float::INFINITY - Nodes::LessThanOrEqual.new(self, Nodes.build_quoted(other.end, 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)) @@ -64,21 +66,23 @@ module Arel when Arel::SelectManager Arel::Nodes::NotIn.new(self, other.ast) when Range - if other.begin == -Float::INFINITY && other.end == Float::INFINITY - Nodes::In.new self, [] - elsif other.end == Float::INFINITY + 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)) - elsif other.begin == -Float::INFINITY && other.exclude_end? - Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self)) - elsif other.begin == -Float::INFINITY - Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, self)) - elsif other.exclude_end? - left = Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self)) - right = Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self)) - Nodes::Or.new left, right else left = Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self)) - right = Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, 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 diff --git a/lib/arel/select_manager.rb b/lib/arel/select_manager.rb index f1dde6403a..4a652f2c9c 100644 --- a/lib/arel/select_manager.rb +++ b/lib/arel/select_manager.rb @@ -106,7 +106,7 @@ module Arel case relation when String, Nodes::SqlLiteral - raise if relation.blank? + raise if relation.empty? klass = Nodes::StringJoin end diff --git a/lib/arel/table.rb b/lib/arel/table.rb index 545e73e3ae..01d4561ff1 100644 --- a/lib/arel/table.rb +++ b/lib/arel/table.rb @@ -57,7 +57,7 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0 case relation when String, Nodes::SqlLiteral - raise if relation.blank? + raise if relation.empty? klass = Nodes::StringJoin end diff --git a/lib/arel/tree_manager.rb b/lib/arel/tree_manager.rb index 87887800d0..8bff97af78 100644 --- a/lib/arel/tree_manager.rb +++ b/lib/arel/tree_manager.rb @@ -15,7 +15,9 @@ module Arel 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 diff --git a/lib/arel/visitors/bind_substitute.rb b/lib/arel/visitors/bind_substitute.rb index 0503a9c986..ce0fb5c924 100644 --- a/lib/arel/visitors/bind_substitute.rb +++ b/lib/arel/visitors/bind_substitute.rb @@ -1,7 +1,7 @@ module Arel module Visitors class BindSubstitute - def initialize delegte + def initialize delegate @delegate = delegate end end diff --git a/lib/arel/visitors/dot.rb b/lib/arel/visitors/dot.rb index ba35223ac9..f0cefeabd7 100644 --- a/lib/arel/visitors/dot.rb +++ b/lib/arel/visitors/dot.rb @@ -22,9 +22,9 @@ module Arel @seen = {} end - def accept object + def accept object, collector visit object - to_dot + collector << to_dot end private @@ -82,12 +82,14 @@ 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" diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb index 264521f4c6..ae1b7930af 100644 --- a/lib/arel/visitors/to_sql.rb +++ b/lib/arel/visitors/to_sql.rb @@ -103,9 +103,7 @@ module Arel collector << "UPDATE " collector = visit o.relation, collector - values = false unless o.values.empty? - values = true collector << " SET " collector = inject_join o.values, collector, ", " end @@ -300,8 +298,9 @@ module Arel 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, collector @@ -337,12 +336,20 @@ module Arel 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 @@ -719,7 +726,7 @@ module Arel quote(o, column_for(a)) end - def unsupported o + def unsupported o, collector raise "unsupported: #{o.class.name}" end diff --git a/test/attributes/test_attribute.rb b/test/attributes/test_attribute.rb index e53b32d0ac..dd2d9d343e 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,17 @@ 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 end describe '#gt_any' do @@ -258,12 +269,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 +285,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 +308,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 @@ -550,8 +558,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 index 036d8f05fe..60532f061c 100644 --- a/test/collectors/test_bind_collector.rb +++ b/test/collectors/test_bind_collector.rb @@ -49,7 +49,7 @@ module Arel offsets = values.map.with_index { |v,i| [v,i] - }.find_all { |(v,i)| Nodes::BindParam === v }.map(&:last) + }.find_all { |(v,_)| Nodes::BindParam === v }.map(&:last) list = collector.substitute_binds ["hello", "world"] assert_equal "hello", list[offsets[0]] diff --git a/test/nodes/test_infix_operation.rb b/test/nodes/test_infix_operation.rb index bec226e395..40616024e5 100644 --- a/test/nodes/test_infix_operation.rb +++ b/test/nodes/test_infix_operation.rb @@ -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_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/test_insert_manager.rb b/test/test_insert_manager.rb index 4e82ca34c0..9cfd01262b 100644 --- a/test/test_insert_manager.rb +++ b/test/test_insert_manager.rb @@ -78,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 @@ -126,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 diff --git a/test/test_select_manager.rb b/test/test_select_manager.rb index 09608ea71e..3380bbec6f 100644 --- a/test/test_select_manager.rb +++ b/test/test_select_manager.rb @@ -140,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 @@ -152,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" @@ -160,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 @@ -172,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 } @@ -330,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) @@ -368,8 +382,7 @@ module Arel it 'should return the ast' do table = Table.new :users mgr = table.from table - ast = mgr.ast - assert ast + assert mgr.ast end it 'should allow orders to work when the ast is grepped' do @@ -518,7 +531,7 @@ 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 @@ -526,7 +539,7 @@ module Arel assert_equal 'bar', join.right end - it 'should create join nodes with a klass' do + 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 @@ -534,7 +547,7 @@ module Arel assert_equal 'bar', join.right end - it 'should create join nodes with a klass' do + 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 @@ -602,7 +615,8 @@ module Arel 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 @@ -637,6 +651,22 @@ module Arel } end + 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 + it 'returns string join sql' do manager = Arel::SelectManager.new Table.engine manager.from Nodes::StringJoin.new(Nodes.build_quoted('hello')) @@ -702,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 @@ -878,6 +949,27 @@ 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 table = Table.new :users manager = Arel::SelectManager.new Table.engine @@ -906,15 +998,6 @@ module Arel } 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 where clauses' do table = Table.new :users manager = Arel::SelectManager.new Table.engine @@ -940,19 +1023,15 @@ module Arel } end - it 'executes 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 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'), @@ -966,11 +1045,6 @@ module Arel manager.to_sql.must_be_like %{ SELECT * } end - it "takes sql literals" do - manager = Arel::SelectManager.new Table.engine - manager.project Nodes::SqlLiteral.new '*' - manager.to_sql.must_be_like %{ SELECT * } - end end describe 'projections' do @@ -1042,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 diff --git a/test/visitors/test_dot.rb b/test/visitors/test_dot.rb index ee7fc7886c..7763350f5c 100644 --- a/test/visitors/test_dot.rb +++ b/test/visitors/test_dot.rb @@ -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_to_sql.rb b/test/visitors/test_to_sql.rb index 738bbe9626..c03d89cfef 100644 --- a/test/visitors/test_to_sql.rb +++ b/test/visitors/test_to_sql.rb @@ -254,6 +254,11 @@ module Arel 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")' |