diff options
69 files changed, 1412 insertions, 746 deletions
diff --git a/.autotest b/.autotest deleted file mode 100644 index 19ea6ecbe6..0000000000 --- a/.autotest +++ /dev/null @@ -1,26 +0,0 @@ -# -*- ruby -*- - -# require 'autotest/restart' - -ENV['GEM_PATH'] = "tmp/isolate/ruby-1.8" - -module Autotest::Restart - Autotest.add_hook :updated do |at, *args| - if args.flatten.include? ".autotest" then - warn "Detected change to .autotest, restarting" - cmd = %w(autotest) - cmd << " -v" if $v - cmd += ARGV - - exec(*cmd) - end - end -end - -Autotest.add_hook :initialize do |at| - at.add_exception 'tmp' - at.testlib = "minitest/autorun" - - at.find_directories = ARGV unless ARGV.empty? -end - diff --git a/.gemtest b/.gemtest deleted file mode 100644 index e69de29bb2..0000000000 --- a/.gemtest +++ /dev/null diff --git a/.travis.yml b/.travis.yml index 8e5462c440..b8a2f0019e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,28 @@ language: ruby -script: - - "rake test" +sudo: false +cache: bundler +script: + - "bundle exec rake test" - "gem build arel.gemspec" +env: + global: + - JRUBY_OPTS='--dev -J-Xmx1024M' rvm: - - rbx - - jruby - - 1.9.3 + - rbx-2 + - jruby-head - 2.0.0 - 2.1 + - 2.2 - ruby-head matrix: - allow_failures: - - rvm: rbx + fast_finish: true +bundler_args: --jobs 3 --retry 3 notifications: email: false irc: - "irc.freenode.org#rails-contrib" + campfire: + on_success: change + on_failure: always + rooms: + - secure: "sD4VEjsiNHTb+9LYUy0ZiGu86nH1voUANpmiO62fvO1VPg80ReKv8vNiq285MZVjcPBx9lt1iNx1nNfVlBmDzRI1tWjoyEV5LTuCAt3yQMB5Qpjy3HH1FNgyY2TGjDhIDwgTD6d8+DOCQjj/vgqzWWu1jEJVdac1K5d+dc/uElI=" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..104c05490f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,100 @@ +Contributing to Arel +===================== + +[](http://travis-ci.org/rails/arel) +[](https://gemnasium.com/rails/arel) + +Arel is work of [many contributors](https://github.com/rails/arel/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rails/arel/pulls), [propose features and discuss issues](https://github.com/rails/arel/issues). + +#### Fork the Project + +Fork the [project on Github](https://github.com/rails/arel) and check out your copy. + +``` +git clone https://github.com/contributor/arel.git +cd arel +git remote add upstream https://github.com/rails/arel.git +``` + +#### Create a Topic Branch + +Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. + +``` +git checkout master +git pull upstream master +git checkout -b my-feature-branch +``` + +#### Bundle Install and Test + +Ensure that you can build the project and run tests. + +``` +bundle install +bundle exec rake test +``` + +#### Write Tests + +Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [test](test). + +We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. + +#### Write Code + +Implement your feature or bug fix. + +Make sure that `bundle exec rake test` completes without errors. + +#### Write Documentation + +Document any external behavior in the [README](README.md). + +#### Commit Changes + +Make sure git knows your name and email address: + +``` +git config --global user.name "Your Name" +git config --global user.email "contributor@example.com" +``` + +Writing good commit logs is important. A commit log should describe what changed and why. + +``` +git add ... +git commit +``` + +#### Push + +``` +git push origin my-feature-branch +``` + +#### Make a Pull Request + +Go to https://github.com/contributor/arel and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. + +#### Rebase + +If you've been working on a change for a while, rebase with upstream/master. + +``` +git fetch upstream +git rebase upstream/master +git push origin my-feature-branch -f +``` + +#### Check on Your Pull Request + +Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. + +#### Be Patient + +It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! + +#### Thank You + +Please do know that we really appreciate and value your time and work. We love you, really. @@ -1,12 +1,3 @@ -# -*- ruby -*- - -# DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`. - source "https://rubygems.org/" - -gem "minitest", "~>5.1", :group => [:development, :test] -gem "rdoc", "~>4.0", :group => [:development, :test] -gem "hoe", "~>3.5", :group => [:development, :test] - -# vim: syntax=ruby +gemspec diff --git a/History.txt b/History.txt index 4fc06816a3..b161f5ca19 100644 --- a/History.txt +++ b/History.txt @@ -1,4 +1,4 @@ -=== NEXT / 2014-02-10 +=== 6.0.0 / 2014-11-25 * Enhancements diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt index 52f1ff7a17..001426b5b0 100644 --- a/MIT-LICENSE.txt +++ b/MIT-LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2007-2010 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson +Copyright (c) 2007-2015 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -18,3 +18,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/Manifest.txt b/Manifest.txt deleted file mode 100644 index 32e1dd43a6..0000000000 --- a/Manifest.txt +++ /dev/null @@ -1,135 +0,0 @@ -.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 diff --git a/README.markdown b/README.markdown index 76efb2b34a..d570f7855c 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -# Arel [](http://travis-ci.org/rails/arel) [](https://gemnasium.com/rails/arel) +# Arel * http://github.com/rails/arel @@ -222,6 +222,12 @@ photos.project(photo_clicks.as("photo_clicks")) FROM "photos" ``` -### License +## Contributing to Arel -Arel is released under the [MIT License](http://opensource.org/licenses/MIT). +Arel is work of many contributors. You're encouraged to submit pull requests, propose +features and discuss issues. + +See [CONTRIBUTING](CONTRIBUTING.md). + +## License +Arel is released under the [MIT License](http://www.opensource.org/licenses/MIT). @@ -1,20 +1,27 @@ -require "rubygems" -gem 'hoe', '>= 3.3.1' -require 'hoe' +require 'bundler' +Bundler::GemHelper.install_tasks -Hoe.plugins.delete :rubyforge -Hoe.plugin :minitest -Hoe.plugin :gemspec # `gem install hoe-gemspec` -Hoe.plugin :git # `gem install hoe-git` -Hoe.plugin :bundler # `gem install hoe-bundler` +require 'rake/testtask' -Hoe.spec 'arel' do - developer('Aaron Patterson', 'aaron@tenderlovemaking.com') - developer('Bryan Helmkamp', 'bryan@brynary.com') - developer('Emilio Tagua', 'miloops@gmail.com') - developer('Nick Kallen', 'nick@example.org') # FIXME: need Nick's email +desc "Default Task" +task default: [ :test ] - self.licenses = ['MIT'] - self.readme_file = 'README.markdown' - self.extra_rdoc_files = FileList['README.markdown'] +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/test_*.rb' + t.verbose = true +end + +specname = "arel.gemspec" +deps = `git ls-files`.split("\n") - [specname] + +file specname => deps do + files = ["History.txt", "MIT-LICENSE.txt", "README.markdown"] + `git ls-files -- lib`.split("\n") + + require 'erb' + + File.open specname, 'w:utf-8' do |f| + f.write ERB.new(File.read("#{specname}.erb")).result(binding) + end end diff --git a/arel.gemspec b/arel.gemspec index 832eb12f49..dd52c89e22 100644 --- a/arel.gemspec +++ b/arel.gemspec @@ -1,40 +1,25 @@ -# -*- encoding: utf-8 -*- -# stub: arel 6.0.0.beta1.20140817224534 ruby lib +# # -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "arel" Gem::Specification.new do |s| - s.name = "arel" - s.version = "6.0.0.beta1.20140817224534" - - 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.name = "arel" + s.version = Arel::VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Aaron Patterson", "Bryan Helmkamp", "Emilio Tagua", "Nick Kallen"] + s.email = ["aaron@tenderlovemaking.com", "bryan@brynary.com", "miloops@gmail.com"] + s.homepage = "https://github.com/rails/arel" 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/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.summary = "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby" + s.license = %q{MIT} + s.rdoc_options = ["--main", "README.markdown"] - 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"] + s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "README.markdown"] - if s.respond_to? :specification_version then - s.specification_version = 4 + s.files = ["History.txt","MIT-LICENSE.txt","README.markdown","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/bind_param.rb","lib/arel/nodes/casted.rb","lib/arel/nodes/count.rb","lib/arel/nodes/delete_statement.rb","lib/arel/nodes/descending.rb","lib/arel/nodes/equality.rb","lib/arel/nodes/extract.rb","lib/arel/nodes/false.rb","lib/arel/nodes/full_outer_join.rb","lib/arel/nodes/function.rb","lib/arel/nodes/grouping.rb","lib/arel/nodes/in.rb","lib/arel/nodes/infix_operation.rb","lib/arel/nodes/inner_join.rb","lib/arel/nodes/insert_statement.rb","lib/arel/nodes/join_source.rb","lib/arel/nodes/matches.rb","lib/arel/nodes/named_function.rb","lib/arel/nodes/node.rb","lib/arel/nodes/outer_join.rb","lib/arel/nodes/over.rb","lib/arel/nodes/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/oracle12.rb","lib/arel/visitors/postgresql.rb","lib/arel/visitors/reduce.rb","lib/arel/visitors/sqlite.rb","lib/arel/visitors/to_sql.rb","lib/arel/visitors/visitor.rb","lib/arel/visitors/where_sql.rb","lib/arel/window_predications.rb"] + s.require_paths = ["lib"] - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - 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>, ["~> 5.4"]) - s.add_dependency(%q<rdoc>, ["~> 4.0"]) - s.add_dependency(%q<hoe>, ["~> 3.12"]) - end - else - s.add_dependency(%q<minitest>, ["~> 5.4"]) - s.add_dependency(%q<rdoc>, ["~> 4.0"]) - s.add_dependency(%q<hoe>, ["~> 3.12"]) - end + s.add_development_dependency('minitest', '~> 5.4') + s.add_development_dependency('rdoc', '~> 4.0') + s.add_development_dependency('rake') end diff --git a/arel.gemspec.erb b/arel.gemspec.erb new file mode 100644 index 0000000000..7ed75226ca --- /dev/null +++ b/arel.gemspec.erb @@ -0,0 +1,25 @@ +# # -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "arel" + +Gem::Specification.new do |s| + s.name = "arel" + s.version = Arel::VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Aaron Patterson", "Bryan Helmkamp", "Emilio Tagua", "Nick Kallen"] + s.email = ["aaron@tenderlovemaking.com", "bryan@brynary.com", "miloops@gmail.com"] + s.homepage = "https://github.com/rails/arel" + s.description = "Arel Really Exasperates Logicians\n\nArel is a SQL AST manager for Ruby. It\n\n1. Simplifies the generation of complex SQL queries\n2. Adapts to various RDBMSes\n\nIt is intended to be a framework framework; that is, you can build your own ORM\nwith it, focusing on innovative object and collection modeling as opposed to\ndatabase compatibility and query generation." + s.summary = "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby" + s.license = %q{MIT} + + s.rdoc_options = ["--main", "README.markdown"] + s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "README.markdown"] + + s.files = [<%= files.map(&:inspect).join ',' %>] + s.require_paths = ["lib"] + + s.add_development_dependency('minitest', '~> 5.4') + s.add_development_dependency('rdoc', '~> 4.0') + s.add_development_dependency('rake') +end diff --git a/lib/arel.rb b/lib/arel.rb index 80677953df..aab93913d5 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.beta1' + VERSION = '7.0.0.alpha' def self.sql raw_sql Arel::Nodes::SqlLiteral.new raw_sql diff --git a/lib/arel/attributes/attribute.rb b/lib/arel/attributes/attribute.rb index 0906fa4f1d..cda5a5a3db 100644 --- a/lib/arel/attributes/attribute.rb +++ b/lib/arel/attributes/attribute.rb @@ -12,6 +12,14 @@ module Arel def lower relation.lower self end + + def type_cast_for_database(value) + relation.type_cast_for_database(name, value) + end + + def able_to_type_cast? + relation.able_to_type_cast? + end end class String < Attribute; end diff --git a/lib/arel/collectors/sql_string.rb b/lib/arel/collectors/sql_string.rb index 8ca89ca7bd..fd2faaef3a 100644 --- a/lib/arel/collectors/sql_string.rb +++ b/lib/arel/collectors/sql_string.rb @@ -5,8 +5,14 @@ require 'arel/collectors/plain_string' module Arel module Collectors class SQLString < PlainString + def initialize(*) + super + @bind_index = 1 + end + def add_bind bind - self << bind.to_s + self << yield(@bind_index) + @bind_index += 1 self end diff --git a/lib/arel/crud.rb b/lib/arel/crud.rb index 6f4962cbfe..d310c7381f 100644 --- a/lib/arel/crud.rb +++ b/lib/arel/crud.rb @@ -3,7 +3,7 @@ module Arel # FIXME hopefully we can remove this module Crud def compile_update values, pk - um = UpdateManager.new @engine + um = UpdateManager.new if Nodes::SqlLiteral === values relation = @ctx.from @@ -26,11 +26,12 @@ module Arel end def create_insert - InsertManager.new @engine + InsertManager.new end def compile_delete - dm = DeleteManager.new @engine + dm = DeleteManager.new + dm.take @ast.limit.expr if @ast.limit dm.wheres = @ctx.wheres dm.from @ctx.froms dm diff --git a/lib/arel/delete_manager.rb b/lib/arel/delete_manager.rb index b4c61f708f..20e988e01f 100644 --- a/lib/arel/delete_manager.rb +++ b/lib/arel/delete_manager.rb @@ -1,6 +1,6 @@ module Arel class DeleteManager < Arel::TreeManager - def initialize engine + def initialize super @ast = Nodes::DeleteStatement.new @ctx = @ast @@ -11,6 +11,11 @@ module Arel self end + def take limit + @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit + self + end + def wheres= list @ast.wheres = list end diff --git a/lib/arel/insert_manager.rb b/lib/arel/insert_manager.rb index 8839dd8181..7829c3f4f9 100644 --- a/lib/arel/insert_manager.rb +++ b/lib/arel/insert_manager.rb @@ -1,6 +1,6 @@ module Arel class InsertManager < Arel::TreeManager - def initialize engine + def initialize super @ast = Nodes::InsertStatement.new end diff --git a/lib/arel/nodes.rb b/lib/arel/nodes.rb index ccccd471e2..0e66d2dd0c 100644 --- a/lib/arel/nodes.rb +++ b/lib/arel/nodes.rb @@ -4,6 +4,7 @@ require 'arel/nodes/select_statement' require 'arel/nodes/select_core' require 'arel/nodes/insert_statement' require 'arel/nodes/update_statement' +require 'arel/nodes/bind_param' # terminal @@ -29,6 +30,7 @@ require 'arel/nodes/table_alias' require 'arel/nodes/infix_operation' require 'arel/nodes/over' require 'arel/nodes/matches' +require 'arel/nodes/regexp' # nary require 'arel/nodes/and' @@ -54,41 +56,4 @@ 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 +require 'arel/nodes/casted' diff --git a/lib/arel/nodes/binary.rb b/lib/arel/nodes/binary.rb index e35d2fd2e7..763091c267 100644 --- a/lib/arel/nodes/binary.rb +++ b/lib/arel/nodes/binary.rb @@ -16,7 +16,7 @@ module Arel end def hash - [@left, @right].hash + [self.class, @left, @right].hash end def eql? other @@ -38,9 +38,7 @@ module Arel LessThanOrEqual NotEqual NotIn - NotRegexp Or - Regexp Union UnionAll Intersect diff --git a/lib/arel/nodes/bind_param.rb b/lib/arel/nodes/bind_param.rb new file mode 100644 index 0000000000..3a4aedc4ba --- /dev/null +++ b/lib/arel/nodes/bind_param.rb @@ -0,0 +1,9 @@ +module Arel + module Nodes + class BindParam < Node + def ==(other) + other.is_a?(BindParam) + end + end + end +end diff --git a/lib/arel/nodes/casted.rb b/lib/arel/nodes/casted.rb new file mode 100644 index 0000000000..9fa02955ef --- /dev/null +++ b/lib/arel/nodes/casted.rb @@ -0,0 +1,40 @@ +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: + alias :val :value + def nil?; val.nil?; end + end + + def self.build_quoted other, attribute = nil + case other + when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted + 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/delete_statement.rb b/lib/arel/nodes/delete_statement.rb index 3bac8225ec..8aaf8ca0b6 100644 --- a/lib/arel/nodes/delete_statement.rb +++ b/lib/arel/nodes/delete_statement.rb @@ -1,6 +1,8 @@ module Arel module Nodes class DeleteStatement < Arel::Nodes::Binary + attr_accessor :limit + alias :relation :left alias :relation= :left= alias :wheres :right diff --git a/lib/arel/nodes/function.rb b/lib/arel/nodes/function.rb index 733a00df46..182dfa7329 100644 --- a/lib/arel/nodes/function.rb +++ b/lib/arel/nodes/function.rb @@ -3,6 +3,7 @@ module Arel class Function < Arel::Nodes::Node include Arel::Predications include Arel::WindowPredications + include Arel::OrderPredications attr_accessor :expressions, :alias, :distinct def initialize expr, aliaz = nil diff --git a/lib/arel/nodes/matches.rb b/lib/arel/nodes/matches.rb index 583fb97c9b..0d9c1925dc 100644 --- a/lib/arel/nodes/matches.rb +++ b/lib/arel/nodes/matches.rb @@ -2,10 +2,12 @@ module Arel module Nodes class Matches < Binary attr_reader :escape + attr_accessor :case_sensitive - def initialize(left, right, escape = nil) + def initialize(left, right, escape = nil, case_sensitive = false) super(left, right) @escape = escape && Nodes.build_quoted(escape) + @case_sensitive = case_sensitive end end diff --git a/lib/arel/nodes/regexp.rb b/lib/arel/nodes/regexp.rb new file mode 100644 index 0000000000..784368f5bf --- /dev/null +++ b/lib/arel/nodes/regexp.rb @@ -0,0 +1,14 @@ +module Arel + module Nodes + class Regexp < Binary + attr_accessor :case_sensitive + + def initialize(left, right, case_sensitive = true) + super(left, right) + @case_sensitive = case_sensitive + end + end + + class NotRegexp < Regexp; end + end +end diff --git a/lib/arel/nodes/select_core.rb b/lib/arel/nodes/select_core.rb index 09ae420aa1..3696dd20af 100644 --- a/lib/arel/nodes/select_core.rb +++ b/lib/arel/nodes/select_core.rb @@ -2,7 +2,7 @@ module Arel module Nodes class SelectCore < Arel::Nodes::Node attr_accessor :top, :projections, :wheres, :groups, :windows - attr_accessor :having, :source, :set_quantifier + attr_accessor :havings, :source, :set_quantifier def initialize super() @@ -14,7 +14,7 @@ module Arel @projections = [] @wheres = [] @groups = [] - @having = nil + @havings = [] @windows = [] end @@ -35,14 +35,14 @@ module Arel @projections = @projections.clone @wheres = @wheres.clone @groups = @groups.clone - @having = @having.clone if @having + @havings = @havings.clone @windows = @windows.clone end def hash [ @source, @top, @set_quantifier, @projections, - @wheres, @groups, @having, @windows + @wheres, @groups, @havings, @windows ].hash end @@ -54,7 +54,7 @@ module Arel self.projections == other.projections && self.wheres == other.wheres && self.groups == other.groups && - self.having == other.having && + self.havings == other.havings && self.windows == other.windows end alias :== :eql? diff --git a/lib/arel/nodes/sql_literal.rb b/lib/arel/nodes/sql_literal.rb index b43288b29c..2c56644b99 100644 --- a/lib/arel/nodes/sql_literal.rb +++ b/lib/arel/nodes/sql_literal.rb @@ -10,8 +10,5 @@ module Arel coder.scalar = self.to_s end end - - class BindParam < SqlLiteral - end end end diff --git a/lib/arel/nodes/table_alias.rb b/lib/arel/nodes/table_alias.rb index ebfcb58e64..a5adc0766a 100644 --- a/lib/arel/nodes/table_alias.rb +++ b/lib/arel/nodes/table_alias.rb @@ -13,8 +13,12 @@ module Arel relation.respond_to?(:name) ? relation.name : name end - def engine - relation.engine + def type_cast_for_database(*args) + relation.type_cast_for_database(*args) + end + + def able_to_type_cast? + relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast? end end end diff --git a/lib/arel/nodes/unary.rb b/lib/arel/nodes/unary.rb index 3d4a4b014a..a0062ff5be 100644 --- a/lib/arel/nodes/unary.rb +++ b/lib/arel/nodes/unary.rb @@ -23,7 +23,6 @@ module Arel %w{ Bin Group - Having Limit Not Offset diff --git a/lib/arel/predications.rb b/lib/arel/predications.rb index 3050526a43..1d2b0de235 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, Nodes.build_quoted(other, self) + Nodes::NotEqual.new self, quoted_node(other) end def not_eq_any others @@ -13,7 +13,7 @@ module Arel end def eq other - Nodes::Equality.new self, Nodes.build_quoted(other, self) + Nodes::Equality.new self, quoted_node(other) end def eq_any others @@ -21,7 +21,27 @@ module Arel end def eq_all others - grouping_all :eq, others.map { |x| Nodes.build_quoted(x, self) } + grouping_all :eq, quoted_array(others) + end + + def between other + if equals_quoted?(other.begin, -Float::INFINITY) + if equals_quoted?(other.end, Float::INFINITY) + not_in([]) + elsif other.exclude_end? + lt(other.end) + else + lteq(other.end) + end + elsif equals_quoted?(other.end, Float::INFINITY) + gteq(other.begin) + elsif other.exclude_end? + gteq(other.begin).and(lt(other.end)) + else + left = quoted_node(other.begin) + right = quoted_node(other.end) + Nodes::Between.new(self, left.and(right)) + end end def in other @@ -29,27 +49,16 @@ module Arel when Arel::SelectManager Arel::Nodes::In.new(self, other.ast) when Range - 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([Nodes.build_quoted(other.begin, self), Nodes.build_quoted(other.end, self)])) + if $VERBOSE + warn <<-eowarn +Passing a range to `#in` is deprecated. Call `#between`, instead. + eowarn end - when Array - Nodes::In.new self, other.map { |x| Nodes.build_quoted(x, self) } + between(other) + when Enumerable + Nodes::In.new self, quoted_array(other) else - Nodes::In.new self, Nodes.build_quoted(other, self) + Nodes::In.new self, quoted_node(other) end end @@ -61,34 +70,43 @@ module Arel grouping_all :in, others end + def not_between other + if equals_quoted?(other.begin, -Float::INFINITY) + if equals_quoted?(other.end, Float::INFINITY) + self.in([]) + elsif other.exclude_end? + gteq(other.end) + else + gt(other.end) + end + elsif equals_quoted?(other.end, Float::INFINITY) + lt(other.begin) + else + left = lt(other.begin) + right = if other.exclude_end? + gteq(other.end) + else + gt(other.end) + end + left.or(right) + end + end + def not_in other case other when Arel::SelectManager Arel::Nodes::NotIn.new(self, other.ast) when Range - if 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, 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 + if $VERBOSE + warn <<-eowarn +Passing a range to `#not_in` is deprecated. Call `#not_between`, instead. + eowarn end - when Array - Nodes::NotIn.new self, other.map { |x| Nodes.build_quoted(x, self) } + not_between(other) + when Enumerable + Nodes::NotIn.new self, quoted_array(other) else - Nodes::NotIn.new self, Nodes.build_quoted(other, self) + Nodes::NotIn.new self, quoted_node(other) end end @@ -100,20 +118,28 @@ module Arel grouping_all :not_in, others end - def matches other, escape = nil - Nodes::Matches.new self, Nodes.build_quoted(other, self), escape + def matches other, escape = nil, case_sensitive = false + Nodes::Matches.new self, quoted_node(other), escape, case_sensitive + end + + def matches_regexp other, case_sensitive = true + Nodes::Regexp.new self, quoted_node(other), case_sensitive + end + + def matches_any others, escape = nil, case_sensitive = false + grouping_any :matches, others, escape, case_sensitive end - def matches_any others, escape = nil - grouping_any :matches, others, escape + def matches_all others, escape = nil, case_sensitive = false + grouping_all :matches, others, escape, case_sensitive end - def matches_all others, escape = nil - grouping_all :matches, others, escape + def does_not_match other, escape = nil, case_sensitive = false + Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive end - def does_not_match other, escape = nil - Nodes::DoesNotMatch.new self, Nodes.build_quoted(other, self), escape + def does_not_match_regexp other, case_sensitive = true + Nodes::NotRegexp.new self, quoted_node(other), case_sensitive end def does_not_match_any others, escape = nil @@ -125,7 +151,7 @@ module Arel end def gteq right - Nodes::GreaterThanOrEqual.new self, Nodes.build_quoted(right, self) + Nodes::GreaterThanOrEqual.new self, quoted_node(right) end def gteq_any others @@ -137,7 +163,7 @@ module Arel end def gt right - Nodes::GreaterThan.new self, Nodes.build_quoted(right, self) + Nodes::GreaterThan.new self, quoted_node(right) end def gt_any others @@ -149,7 +175,7 @@ module Arel end def lt right - Nodes::LessThan.new self, Nodes.build_quoted(right, self) + Nodes::LessThan.new self, quoted_node(right) end def lt_any others @@ -161,7 +187,7 @@ module Arel end def lteq right - Nodes::LessThanOrEqual.new self, Nodes.build_quoted(right, self) + Nodes::LessThanOrEqual.new self, quoted_node(right) end def lteq_any others @@ -185,5 +211,21 @@ module Arel nodes = others.map {|expr| send(method_id, expr, *extras)} Nodes::Grouping.new Nodes::And.new(nodes) end + + def quoted_node(other) + Nodes.build_quoted(other, self) + end + + def quoted_array(others) + others.map { |v| quoted_node(v) } + end + + def equals_quoted?(maybe_quoted, value) + if maybe_quoted.is_a?(Nodes::Quoted) + maybe_quoted.val == value + else + maybe_quoted == value + end + end end end diff --git a/lib/arel/select_manager.rb b/lib/arel/select_manager.rb index 5a05e7e181..f7dec87ca3 100644 --- a/lib/arel/select_manager.rb +++ b/lib/arel/select_manager.rb @@ -6,8 +6,8 @@ module Arel STRING_OR_SYMBOL_CLASS = [Symbol, String] - def initialize engine, table = nil - super(engine) + def initialize table = nil + super() @ast = Nodes::SelectStatement.new @ctx = @ast.cores.last from table @@ -19,7 +19,7 @@ module Arel end def limit - @ast.limit && @ast.limit.expr.expr + @ast.limit && @ast.limit.expr end alias :taken :limit @@ -118,8 +118,8 @@ module Arel join(relation, Nodes::OuterJoin) end - def having *exprs - @ctx.having = Nodes::Having.new(collapse(exprs, @ctx.having)) + def having expr + @ctx.havings << expr self end @@ -176,10 +176,10 @@ module Arel @ast.orders end - def where_sql + def where_sql engine = Table.engine return if @ctx.wheres.empty? - viz = Visitors::WhereSql.new @engine.connection + viz = Visitors::WhereSql.new engine.connection Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value end @@ -216,8 +216,8 @@ module Arel def take limit if limit - @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) - @ctx.top = Nodes::Top.new(Nodes.build_quoted(limit)) + @ast.limit = Nodes::Limit.new(limit) + @ctx.top = Nodes::Top.new(limit) else @ast.limit = nil @ctx.top = nil diff --git a/lib/arel/table.rb b/lib/arel/table.rb index 01d4561ff1..b4b4a861b8 100644 --- a/lib/arel/table.rb +++ b/lib/arel/table.rb @@ -6,40 +6,24 @@ module Arel @engine = nil class << self; attr_accessor :engine; end - attr_accessor :name, :engine, :aliases, :table_alias + attr_accessor :name, :aliases, :table_alias # TableAlias and Table both have a #table_name which is the name of the underlying table alias :table_name :name - def initialize name, engine = Table.engine + def initialize(name, as: nil, type_caster: nil) @name = name.to_s - @engine = engine @columns = nil @aliases = [] - @table_alias = nil - @primary_key = nil + @type_caster = type_caster - if Hash === engine - @engine = engine[:engine] || Table.engine - - # Sometime AR sends an :as parameter to table, to let the table know - # that it is an Alias. We may want to override new, and return a - # TableAlias node? - @table_alias = engine[:as] unless engine[:as].to_s == @name - end - end - - def primary_key - if $VERBOSE - warn <<-eowarn -primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0 - eowarn - end - @primary_key ||= begin - primary_key_name = @engine.connection.primary_key(name) - # some tables might be without primary key - primary_key_name && self[primary_key_name] + # Sometime AR sends an :as parameter to table, to let the table know + # that it is an Alias. We may want to override new, and return a + # TableAlias node? + if as.to_s == @name + as = nil end + @table_alias = as end def alias name = "#{self.name}_2" @@ -48,12 +32,12 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0 end end - def from table - SelectManager.new(@engine, table) + def from + SelectManager.new(self) end def join relation, klass = Nodes::InnerJoin - return from(self) unless relation + return from unless relation case relation when String, Nodes::SqlLiteral @@ -61,7 +45,7 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0 klass = Nodes::StringJoin end - from(self).join(relation, klass) + from.join(relation, klass) end def outer_join relation @@ -69,55 +53,39 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0 end def group *columns - from(self).group(*columns) + from.group(*columns) end def order *expr - from(self).order(*expr) + from.order(*expr) end def where condition - from(self).where condition + from.where condition end def project *things - from(self).project(*things) + from.project(*things) end def take amount - from(self).take amount + from.take amount end def skip amount - from(self).skip amount + from.skip amount end def having expr - from(self).having expr + from.having expr end def [] name ::Arel::Attribute.new self, name end - def select_manager - SelectManager.new(@engine) - end - - def insert_manager - InsertManager.new(@engine) - end - - def update_manager - UpdateManager.new(@engine) - end - - def delete_manager - DeleteManager.new(@engine) - end - def hash - # Perf note: aliases, table alias and engine is excluded from the hash + # Perf note: aliases and table alias is excluded from the hash # aliases can have a loop back to this table breaking hashes in parent # relations, for the vast majority of cases @name is unique to a query @name.hash @@ -126,12 +94,23 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0 def eql? other self.class == other.class && self.name == other.name && - self.engine == other.engine && self.aliases == other.aliases && self.table_alias == other.table_alias end alias :== :eql? + def type_cast_for_database(attribute_name, value) + type_caster.type_cast_for_database(attribute_name, value) + end + + def able_to_type_cast? + !type_caster.nil? + end + + protected + + attr_reader :type_caster + private def attributes_for columns diff --git a/lib/arel/tree_manager.rb b/lib/arel/tree_manager.rb index 8bff97af78..5278ab06a1 100644 --- a/lib/arel/tree_manager.rb +++ b/lib/arel/tree_manager.rb @@ -8,8 +8,7 @@ module Arel attr_accessor :bind_values - def initialize engine - @engine = engine + def initialize @ctx = nil @bind_values = [] end @@ -20,13 +19,9 @@ module Arel collector.value end - def visitor - engine.connection.visitor - end - - def to_sql + def to_sql engine = Table.engine collector = Arel::Collectors::SQLString.new - collector = visitor.accept @ast, collector + collector = engine.connection.visitor.accept @ast, collector collector.value end diff --git a/lib/arel/update_manager.rb b/lib/arel/update_manager.rb index db8cf05f76..36fb74fe7c 100644 --- a/lib/arel/update_manager.rb +++ b/lib/arel/update_manager.rb @@ -1,6 +1,6 @@ module Arel class UpdateManager < Arel::TreeManager - def initialize engine + def initialize super @ast = Nodes::UpdateStatement.new @ctx = @ast diff --git a/lib/arel/visitors.rb b/lib/arel/visitors.rb index 4a8d254ba7..f492ca2d9d 100644 --- a/lib/arel/visitors.rb +++ b/lib/arel/visitors.rb @@ -6,6 +6,7 @@ require 'arel/visitors/postgresql' require 'arel/visitors/mysql' require 'arel/visitors/mssql' require 'arel/visitors/oracle' +require 'arel/visitors/oracle12' require 'arel/visitors/where_sql' require 'arel/visitors/dot' require 'arel/visitors/ibm_db' diff --git a/lib/arel/visitors/depth_first.rb b/lib/arel/visitors/depth_first.rb index a434f404c7..22704dd038 100644 --- a/lib/arel/visitors/depth_first.rb +++ b/lib/arel/visitors/depth_first.rb @@ -146,7 +146,7 @@ module Arel visit o.wheres visit o.groups visit o.windows - visit o.having + visit o.havings end def visit_Arel_Nodes_SelectStatement o @@ -173,6 +173,12 @@ module Arel def visit_Hash o o.each { |k,v| visit(k); visit(v) } end + + DISPATCH = dispatch_cache + + def get_dispatch_cache + DISPATCH + end end end end diff --git a/lib/arel/visitors/informix.rb b/lib/arel/visitors/informix.rb index 7e8a3ea458..c33ef50554 100644 --- a/lib/arel/visitors/informix.rb +++ b/lib/arel/visitors/informix.rb @@ -34,8 +34,13 @@ module Arel collector = inject_join o.groups, collector, ", " end - maybe_visit o.having, collector + if o.havings.any? + collector << " HAVING " + collector = inject_join o.havings, collector, " AND " + end + collector end + def visit_Arel_Nodes_Offset o, collector collector << "SKIP " visit o.expr, collector diff --git a/lib/arel/visitors/mssql.rb b/lib/arel/visitors/mssql.rb index 0e5b75ec59..92362a0c5f 100644 --- a/lib/arel/visitors/mssql.rb +++ b/lib/arel/visitors/mssql.rb @@ -3,6 +3,11 @@ module Arel class MSSQL < Arel::Visitors::ToSql RowNumber = Struct.new :children + def initialize(*) + @primary_keys = {} + super + end + private # `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate @@ -61,6 +66,23 @@ module Arel end end + def visit_Arel_Nodes_DeleteStatement o, collector + collector << 'DELETE ' + if o.limit + collector << 'TOP (' + visit o.limit.expr, collector + collector << ') ' + end + collector << 'FROM ' + collector = visit o.relation, collector + if o.wheres.any? + collector << ' WHERE ' + inject_join o.wheres, collector, AND + else + collector + end + end + def determine_order_by orders, x if orders.any? orders @@ -81,10 +103,20 @@ module Arel end # 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 o.primary_key if o.instance_of? Arel::Table - find_left_table_pk o.left if o.kind_of? Arel::Nodes::Join + if o.kind_of?(Arel::Nodes::Join) + find_left_table_pk(o.left) + elsif o.instance_of?(Arel::Table) + find_primary_key(o) + end + end + + def find_primary_key(o) + @primary_keys[o.name] ||= begin + primary_key_name = @connection.primary_key(o.name) + # some tables might be without primary key + primary_key_name && o[primary_key_name] + end end end end diff --git a/lib/arel/visitors/mysql.rb b/lib/arel/visitors/mysql.rb index f989b8ddef..724e0fc43e 100644 --- a/lib/arel/visitors/mysql.rb +++ b/lib/arel/visitors/mysql.rb @@ -40,7 +40,7 @@ module Arel # http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214 def visit_Arel_Nodes_SelectStatement o, collector if o.offset && !o.limit - o.limit = Arel::Nodes::Limit.new(Nodes.build_quoted(18446744073709551615)) + o.limit = Arel::Nodes::Limit.new(18446744073709551615) end super end diff --git a/lib/arel/visitors/oracle.rb b/lib/arel/visitors/oracle.rb index 91f6e0223e..875b0e5b6a 100644 --- a/lib/arel/visitors/oracle.rb +++ b/lib/arel/visitors/oracle.rb @@ -17,7 +17,7 @@ module Arel if o.limit && o.offset o = o.dup - limit = o.limit.expr.expr + limit = o.limit.expr offset = o.offset o.offset = nil collector << " @@ -132,6 +132,10 @@ module Arel array end + def visit_Arel_Nodes_BindParam o, collector + collector.add_bind(o) { |i| ":a#{i}" } + end + end end end diff --git a/lib/arel/visitors/oracle12.rb b/lib/arel/visitors/oracle12.rb new file mode 100644 index 0000000000..4a42343c9b --- /dev/null +++ b/lib/arel/visitors/oracle12.rb @@ -0,0 +1,53 @@ +module Arel + module Visitors + class Oracle12 < Arel::Visitors::ToSql + private + + def visit_Arel_Nodes_SelectStatement o, collector + # Oracle does not allow LIMIT clause with select for update + if o.limit && o.lock + o = o.dup + o.limit = [] + end + + super + end + + def visit_Arel_Nodes_SelectOptions o, collector + collector = maybe_visit o.offset, collector + collector = maybe_visit o.limit, collector + collector = maybe_visit o.lock, collector + end + + def visit_Arel_Nodes_Limit o, collector + collector << "FETCH FIRST " + collector = visit o.expr, collector + collector << " ROWS ONLY" + end + + def visit_Arel_Nodes_Offset o, collector + collector << "OFFSET " + visit o.expr, collector + collector << " ROWS" + end + + def visit_Arel_Nodes_Except o, collector + collector << "( " + collector = infix_value o, collector, " MINUS " + collector << " )" + end + + def visit_Arel_Nodes_UpdateStatement o, collector + # Oracle does not allow ORDER BY/LIMIT in UPDATEs. + if o.orders.any? && o.limit.nil? + # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided, + # otherwise let the user deal with the error + o = o.dup + o.orders = [] + end + + super + end + end + end +end diff --git a/lib/arel/visitors/postgresql.rb b/lib/arel/visitors/postgresql.rb index 60878ddd20..1ef0261bdd 100644 --- a/lib/arel/visitors/postgresql.rb +++ b/lib/arel/visitors/postgresql.rb @@ -4,25 +4,45 @@ module Arel private def visit_Arel_Nodes_Matches o, collector - infix_value o, collector, ' ILIKE ' + op = o.case_sensitive ? ' LIKE ' : ' ILIKE ' + collector = infix_value o, collector, op + if o.escape + collector << ' ESCAPE ' + visit o.escape, collector + else + collector + end end def visit_Arel_Nodes_DoesNotMatch o, collector - infix_value o, collector, ' NOT ILIKE ' + op = o.case_sensitive ? ' NOT LIKE ' : ' NOT ILIKE ' + collector = infix_value o, collector, op + if o.escape + collector << ' ESCAPE ' + visit o.escape, collector + else + collector + end end def visit_Arel_Nodes_Regexp o, collector - infix_value o, collector, ' ~ ' + op = o.case_sensitive ? ' ~ ' : ' ~* ' + infix_value o, collector, op end def visit_Arel_Nodes_NotRegexp o, collector - infix_value o, collector, ' !~ ' + op = o.case_sensitive ? ' !~ ' : ' !~* ' + infix_value o, collector, op end def visit_Arel_Nodes_DistinctOn o, collector collector << "DISTINCT ON ( " visit(o.expr, collector) << " )" end + + def visit_Arel_Nodes_BindParam o, collector + collector.add_bind(o) { |i| "$#{i}" } + end end end end diff --git a/lib/arel/visitors/reduce.rb b/lib/arel/visitors/reduce.rb index 1d74934fe5..9670cad27c 100644 --- a/lib/arel/visitors/reduce.rb +++ b/lib/arel/visitors/reduce.rb @@ -10,14 +10,14 @@ module Arel private def visit object, collector - send dispatch[object.class.name], object, collector + send dispatch[object.class], object, collector rescue NoMethodError => e - raise e if respond_to?(dispatch[object.class.name], true) + raise e if respond_to?(dispatch[object.class], true) superklass = object.class.ancestors.find { |klass| - respond_to?(dispatch[klass.name], true) + respond_to?(dispatch[klass], true) } raise(TypeError, "Cannot visit #{object.class}") unless superklass - dispatch[object.class.name] = dispatch[superklass.name] + dispatch[object.class] = dispatch[superklass] retry end end diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb index a3f8cb565d..ce1fdf80ce 100644 --- a/lib/arel/visitors/to_sql.rb +++ b/lib/arel/visitors/to_sql.rb @@ -4,6 +4,12 @@ require 'arel/visitors/reduce' module Arel module Visitors + class UnsupportedVisitError < StandardError + def initialize(object) + super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead." + end + end + class ToSql < Arel::Visitors::Reduce ## # This is some roflscale crazy stuff. I'm roflscaling this because @@ -74,14 +80,14 @@ module Arel end def visit_Arel_Nodes_DeleteStatement o, collector - collector << "DELETE FROM " + collector << 'DELETE FROM ' collector = visit o.relation, collector if o.wheres.any? - collector << " WHERE " - inject_join o.wheres, collector, AND - else - collector + collector << ' WHERE ' + collector = inject_join o.wheres, collector, AND end + + maybe_visit o.limit, collector end # FIXME: we should probably have a 2-pass visitor for this @@ -186,7 +192,8 @@ module Arel len = o.expressions.length - 1 o.expressions.zip(o.columns).each_with_index { |(value, attr), i| - if Nodes::SqlLiteral === value + case value + when Nodes::SqlLiteral, Nodes::BindParam collector = visit value, collector else collector << quote(value, attr && column_for(attr)).to_s @@ -210,7 +217,6 @@ module Arel } unless o.orders.empty? - collector << SPACE collector << ORDER_BY len = o.orders.length - 1 o.orders.each_with_index { |x, i| @@ -219,11 +225,15 @@ module Arel } end + visit_Arel_Nodes_SelectOptions(o, collector) + + collector + end + + def visit_Arel_Nodes_SelectOptions o, collector collector = maybe_visit o.limit, collector collector = maybe_visit o.offset, collector collector = maybe_visit o.lock, collector - - collector end def visit_Arel_Nodes_SelectCore o, collector @@ -265,7 +275,10 @@ module Arel end end - collector = maybe_visit o.having, collector + unless o.havings.empty? + collector << " HAVING " + inject_join o.havings, collector, AND + end unless o.windows.empty? collector << WINDOW @@ -404,11 +417,6 @@ module Arel end end - def visit_Arel_Nodes_Having o, collector - collector << "HAVING " - visit o.expr, collector - end - def visit_Arel_Nodes_Offset o, collector collector << "OFFSET " visit o.expr, collector @@ -574,8 +582,11 @@ module Arel visit o.left, collector end - def visit_Arel_Nodes_FullOuterJoin o - "FULL OUTER JOIN #{visit o.left} #{visit o.right}" + def visit_Arel_Nodes_FullOuterJoin o, collector + collector << "FULL OUTER JOIN " + collector = visit o.left, collector + collector << SPACE + visit o.right, collector end def visit_Arel_Nodes_OuterJoin o, collector @@ -585,8 +596,11 @@ module Arel visit o.right, collector end - def visit_Arel_Nodes_RightOuterJoin o - "RIGHT OUTER JOIN #{visit o.left} #{visit o.right}" + def visit_Arel_Nodes_RightOuterJoin o, collector + collector << "RIGHT OUTER JOIN " + collector = visit o.left, collector + collector << SPACE + visit o.right, collector end def visit_Arel_Nodes_InnerJoin o, collector @@ -713,7 +727,7 @@ module Arel def literal o, collector; collector << o.to_s; end def visit_Arel_Nodes_BindParam o, collector - collector.add_bind o + collector.add_bind(o) { "?" } end alias :visit_Arel_Nodes_SqlLiteral :literal @@ -721,11 +735,15 @@ module Arel alias :visit_Fixnum :literal def quoted o, a - quote(o, column_for(a)) + if a && a.able_to_type_cast? + quote(a.type_cast_for_database(o)) + else + quote(o, column_for(a)) + end end def unsupported o, collector - raise "unsupported: #{o.class.name}" + raise UnsupportedVisitError.new(o) end alias :visit_ActiveSupport_Multibyte_Chars :unsupported @@ -761,6 +779,9 @@ module Arel def quote value, column = nil return value if Arel::Nodes::SqlLiteral === value + if column + print_type_cast_deprecation + end @connection.quote value, column end @@ -810,6 +831,20 @@ module Arel collector end end + + def print_type_cast_deprecation + unless defined?($arel_silence_type_casting_deprecation) && $arel_silence_type_casting_deprecation + warn <<-eowarn +Arel performing automatic type casting is deprecated, and will be removed in Arel 8.0. If you are seeing this, it is because you are manually passing a value to an Arel predicate, and the `Arel::Table` object was constructed manually. The easiest way to remove this warning is to use an `Arel::Table` object returned from calling `arel_table` on an ActiveRecord::Base subclass. + +If you're certain the value is already of the right type, change `attribute.eq(value)` to `attribute.eq(Arel::Nodes::Quoted.new(value))` (you will be able to remove that in Arel 8.0, it is only required to silence this deprecation warning). + +You can also silence this warning globally by setting `$arel_silence_type_casting_deprecation` to `true`. (Do NOT do this if you are a library author) + +If you are passing user input to a predicate, you must either give an appropriate type caster object to the `Arel::Table`, or manually cast the value before passing it to Arel. + eowarn + end + end end end end diff --git a/lib/arel/visitors/visitor.rb b/lib/arel/visitors/visitor.rb index 2317d0c95f..bfe7342f04 100644 --- a/lib/arel/visitors/visitor.rb +++ b/lib/arel/visitors/visitor.rb @@ -2,17 +2,7 @@ module Arel module Visitors class Visitor def initialize - @dispatch = Hash.new do |hash, class_name| - raise if class_name == 'Arel::Nodes::Union' - hash[class_name] = "visit_#{(class_name || '').gsub('::', '_')}" - end - - # pre-populate cache. FIXME: this should be passed in to each - # instance, but we can do that later. - self.class.private_instance_methods.sort.each do |name| - next unless name =~ /^visit_(.*)$/ - @dispatch[$1.gsub('_', '::')] = name - end + @dispatch = get_dispatch_cache end def accept object @@ -21,19 +11,29 @@ module Arel private + def self.dispatch_cache + Hash.new do |hash, klass| + hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}" + end + end + + def get_dispatch_cache + self.class.dispatch_cache + end + def dispatch @dispatch end def visit object - send dispatch[object.class.name], object + send dispatch[object.class], object rescue NoMethodError => e - raise e if respond_to?(dispatch[object.class.name], true) + raise e if respond_to?(dispatch[object.class], true) superklass = object.class.ancestors.find { |klass| - respond_to?(dispatch[klass.name], true) + respond_to?(dispatch[klass], true) } raise(TypeError, "Cannot visit #{object.class}") unless superklass - dispatch[object.class.name] = dispatch[superklass.name] + dispatch[object.class] = dispatch[superklass] retry end end diff --git a/test/attributes/test_attribute.rb b/test/attributes/test_attribute.rb index 768df23d4d..4e55069df4 100644 --- a/test/attributes/test_attribute.rb +++ b/test/attributes/test_attribute.rb @@ -1,4 +1,5 @@ require 'helper' +require 'ostruct' module Arel module Attributes @@ -557,15 +558,155 @@ module Arel end end + describe 'with a range' do + it 'can be constructed with a standard range' do + attribute = Attribute.new nil, nil + node = attribute.between(1..3) + + node.must_equal Nodes::Between.new( + attribute, + Nodes::And.new([ + Nodes::Casted.new(1, attribute), + Nodes::Casted.new(3, attribute) + ]) + ) + end + + it 'can be constructed with a range starting from -Infinity' do + attribute = Attribute.new nil, nil + node = attribute.between(-::Float::INFINITY..3) + + node.must_equal Nodes::LessThanOrEqual.new( + attribute, + Nodes::Casted.new(3, attribute) + ) + end + + it 'can be constructed with a quoted range starting from -Infinity' do + attribute = Attribute.new nil, nil + node = attribute.between(quoted_range(-::Float::INFINITY, 3, false)) + + node.must_equal Nodes::LessThanOrEqual.new( + attribute, + Nodes::Quoted.new(3) + ) + end + + it 'can be constructed with an exclusive range starting from -Infinity' do + attribute = Attribute.new nil, nil + node = attribute.between(-::Float::INFINITY...3) + + node.must_equal Nodes::LessThan.new( + attribute, + Nodes::Casted.new(3, attribute) + ) + end + + it 'can be constructed with a quoted exclusive range starting from -Infinity' do + attribute = Attribute.new nil, nil + node = attribute.between(quoted_range(-::Float::INFINITY, 3, true)) + + node.must_equal Nodes::LessThan.new( + attribute, + Nodes::Quoted.new(3) + ) + end + + it 'can be constructed with an infinite range' do + attribute = Attribute.new nil, nil + node = attribute.between(-::Float::INFINITY..::Float::INFINITY) + + node.must_equal Nodes::NotIn.new(attribute, []) + end + + it 'can be constructed with a quoted infinite range' do + attribute = Attribute.new nil, nil + node = attribute.between(quoted_range(-::Float::INFINITY, ::Float::INFINITY, false)) + + node.must_equal Nodes::NotIn.new(attribute, []) + end + + + it 'can be constructed with a range ending at Infinity' do + attribute = Attribute.new nil, nil + node = attribute.between(0..::Float::INFINITY) + + node.must_equal Nodes::GreaterThanOrEqual.new( + attribute, + Nodes::Casted.new(0, attribute) + ) + end + + it 'can be constructed with a quoted range ending at Infinity' do + attribute = Attribute.new nil, nil + node = attribute.between(quoted_range(0, ::Float::INFINITY, false)) + + node.must_equal Nodes::GreaterThanOrEqual.new( + attribute, + Nodes::Quoted.new(0) + ) + end + + it 'can be constructed with an exclusive range' do + attribute = Attribute.new nil, nil + node = attribute.between(0...3) + + node.must_equal Nodes::And.new([ + Nodes::GreaterThanOrEqual.new( + attribute, + Nodes::Casted.new(0, attribute) + ), + Nodes::LessThan.new( + attribute, + Nodes::Casted.new(3, attribute) + ) + ]) + end + + def quoted_range(begin_val, end_val, exclude) + OpenStruct.new( + begin: Nodes::Quoted.new(begin_val), + end: Nodes::Quoted.new(end_val), + exclude_end?: exclude, + ) + end + end + describe '#in' do + it 'can be constructed with a subquery' do + relation = Table.new(:users) + mgr = relation.project relation[:id] + mgr.where relation[:name].does_not_match_all(['%chunky%','%bacon%']) + attribute = Attribute.new nil, nil + + node = attribute.in(mgr) + + node.must_equal Nodes::In.new(attribute, mgr.ast) + end + it 'can be constructed with a list' do + attribute = Attribute.new nil, nil + node = attribute.in([1, 2, 3]) + + node.must_equal Nodes::In.new( + attribute, + [ + Nodes::Casted.new(1, attribute), + Nodes::Casted.new(2, attribute), + Nodes::Casted.new(3, attribute), + ] + ) end - it 'should return an in node' do + it 'can be constructed with a random object' do attribute = Attribute.new nil, nil - node = Nodes::In.new attribute, [1,2,3] - node.left.must_equal attribute - node.right.must_equal [1, 2, 3] + random_object = Object.new + node = attribute.in(random_object) + + node.must_equal Nodes::In.new( + attribute, + Nodes::Casted.new(random_object, attribute) + ) end it 'should generate IN in sql' do @@ -610,13 +751,112 @@ module Arel end end + describe 'with a range' do + it 'can be constructed with a standard range' do + attribute = Attribute.new nil, nil + node = attribute.not_between(1..3) + + node.must_equal Nodes::Grouping.new(Nodes::Or.new( + Nodes::LessThan.new( + attribute, + Nodes::Casted.new(1, attribute) + ), + Nodes::GreaterThan.new( + attribute, + Nodes::Casted.new(3, attribute) + ) + )) + end + + it 'can be constructed with a range starting from -Infinity' do + attribute = Attribute.new nil, nil + node = attribute.not_between(-::Float::INFINITY..3) + + node.must_equal Nodes::GreaterThan.new( + attribute, + Nodes::Casted.new(3, attribute) + ) + end + + it 'can be constructed with an exclusive range starting from -Infinity' do + attribute = Attribute.new nil, nil + node = attribute.not_between(-::Float::INFINITY...3) + + node.must_equal Nodes::GreaterThanOrEqual.new( + attribute, + Nodes::Casted.new(3, attribute) + ) + end + + it 'can be constructed with an infinite range' do + attribute = Attribute.new nil, nil + node = attribute.not_between(-::Float::INFINITY..::Float::INFINITY) + + node.must_equal Nodes::In.new(attribute, []) + end + + it 'can be constructed with a range ending at Infinity' do + attribute = Attribute.new nil, nil + node = attribute.not_between(0..::Float::INFINITY) + + node.must_equal Nodes::LessThan.new( + attribute, + Nodes::Casted.new(0, attribute) + ) + end + + it 'can be constructed with an exclusive range' do + attribute = Attribute.new nil, nil + node = attribute.not_between(0...3) + + node.must_equal Nodes::Grouping.new(Nodes::Or.new( + Nodes::LessThan.new( + attribute, + Nodes::Casted.new(0, attribute) + ), + Nodes::GreaterThanOrEqual.new( + attribute, + Nodes::Casted.new(3, attribute) + ) + )) + end + end + describe '#not_in' do + it 'can be constructed with a subquery' do + relation = Table.new(:users) + mgr = relation.project relation[:id] + mgr.where relation[:name].does_not_match_all(['%chunky%','%bacon%']) + attribute = Attribute.new nil, nil + + node = attribute.not_in(mgr) + + node.must_equal Nodes::NotIn.new(attribute, mgr.ast) + end + + it 'can be constructed with a list' do + attribute = Attribute.new nil, nil + node = attribute.not_in([1, 2, 3]) + + node.must_equal Nodes::NotIn.new( + attribute, + [ + Nodes::Casted.new(1, attribute), + Nodes::Casted.new(2, attribute), + Nodes::Casted.new(3, attribute), + ] + ) + end - it 'should return a NotIn node' do + it 'can be constructed with a random object' do attribute = Attribute.new nil, nil - node = Nodes::NotIn.new attribute, [1,2,3] - node.left.must_equal attribute - node.right.must_equal [1, 2, 3] + random_object = Object.new + node = attribute.not_in(random_object) + + node.must_equal Nodes::NotIn.new( + attribute, + Nodes::Casted.new(random_object, attribute) + ) end it 'should generate NOT IN in sql' do @@ -719,5 +959,39 @@ module Arel end end end + + describe 'type casting' do + it 'does not type cast by default' do + table = Table.new(:foo) + condition = table["id"].eq("1") + + refute table.able_to_type_cast? + condition.to_sql.must_equal %("foo"."id" = '1') + end + + it 'type casts when given an explicit caster' do + fake_caster = Object.new + def fake_caster.type_cast_for_database(attr_name, value) + if attr_name == "id" + value.to_i + else + value + end + end + table = Table.new(:foo, type_caster: fake_caster) + condition = table["id"].eq("1").and(table["other_id"].eq("2")) + + assert table.able_to_type_cast? + condition.to_sql.must_equal %("foo"."id" = 1 AND "foo"."other_id" = '2') + end + + it 'falls back to using the connection adapter for type casting' do + table = Table.new(:users) + condition = table["id"].eq("1") + + refute table.able_to_type_cast? + condition.to_sql.must_equal %("users"."id" = 1) + end + end end end diff --git a/test/collectors/test_bind_collector.rb b/test/collectors/test_bind_collector.rb index 60532f061c..fc7df2fc45 100644 --- a/test/collectors/test_bind_collector.rb +++ b/test/collectors/test_bind_collector.rb @@ -20,21 +20,21 @@ module Arel def ast_with_binds bv table = Table.new(:users) - manager = Arel::SelectManager.new Table.engine, table + manager = Arel::SelectManager.new table manager.where(table[:age].eq(bv)) manager.where(table[:name].eq(bv)) manager.ast end def test_leaves_binds - node = Nodes::BindParam.new 'omg' + node = Nodes::BindParam.new list = compile node assert_equal node, list.first assert_equal node.class, list.first.class end def test_adds_strings - bv = Nodes::BindParam.new('?') + bv = Nodes::BindParam.new list = compile ast_with_binds bv assert_operator list.length, :>, 0 assert_equal bv, list.grep(Nodes::BindParam).first @@ -42,7 +42,7 @@ module Arel end def test_substitute_binds - bv = Nodes::BindParam.new('?') + bv = Nodes::BindParam.new collector = collect ast_with_binds bv values = collector.value @@ -59,7 +59,7 @@ module Arel end def test_compile - bv = Nodes::BindParam.new('?') + bv = Nodes::BindParam.new collector = collect ast_with_binds bv sql = collector.compile ["hello", "world"] diff --git a/test/collectors/test_sql_string.rb b/test/collectors/test_sql_string.rb index 6d2e23151b..37a9e41f71 100644 --- a/test/collectors/test_sql_string.rb +++ b/test/collectors/test_sql_string.rb @@ -20,14 +20,14 @@ module Arel def ast_with_binds bv table = Table.new(:users) - manager = Arel::SelectManager.new Table.engine, table + manager = Arel::SelectManager.new table manager.where(table[:age].eq(bv)) manager.where(table[:name].eq(bv)) manager.ast end def test_compile - bv = Nodes::BindParam.new('?') + bv = Nodes::BindParam.new collector = collect ast_with_binds bv sql = collector.compile ["hello", "world"] diff --git a/test/helper.rb b/test/helper.rb index 6e8ac836fc..87f5756d24 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -6,6 +6,8 @@ require 'arel' require 'support/fake_record' Arel::Table.engine = FakeRecord::Base.new +$arel_silence_type_casting_deprecation = true + class Object def must_be_like other gsub(/\s+/, ' ').strip.must_equal other.gsub(/\s+/, ' ').strip diff --git a/test/nodes/test_binary.rb b/test/nodes/test_binary.rb new file mode 100644 index 0000000000..7e25a21151 --- /dev/null +++ b/test/nodes/test_binary.rb @@ -0,0 +1,26 @@ +require 'helper' +require 'set' + +module Arel + module Nodes + describe 'Binary' do + describe '#hash' do + it 'generates a hash based on its value' do + eq = Equality.new('foo', 'bar') + eq2 = Equality.new('foo', 'bar') + eq3 = Equality.new('bar', 'baz') + + assert_equal eq.hash, eq2.hash + refute_equal eq.hash, eq3.hash + end + + it 'generates a hash specific to its class' do + eq = Equality.new('foo', 'bar') + neq = NotEqual.new('foo', 'bar') + + refute_equal eq.hash, neq.hash + end + end + end + end +end diff --git a/test/nodes/test_bind_param.rb b/test/nodes/test_bind_param.rb new file mode 100644 index 0000000000..ea008f4c99 --- /dev/null +++ b/test/nodes/test_bind_param.rb @@ -0,0 +1,15 @@ +require 'helper' + +module Arel + module Nodes + describe 'BindParam' do + it 'is equal to other bind params' do + BindParam.new.must_equal(BindParam.new) + end + + it 'is not equal to other nodes' do + BindParam.new.wont_equal(Node.new) + end + end + end +end diff --git a/test/nodes/test_select_core.rb b/test/nodes/test_select_core.rb index ca4f070444..4114bcf4ff 100644 --- a/test/nodes/test_select_core.rb +++ b/test/nodes/test_select_core.rb @@ -34,14 +34,14 @@ module Arel core1.wheres = %w[g h i] core1.groups = %w[j k l] core1.windows = %w[m n o] - core1.having = %w[p q r] + core1.havings = %w[p q r] core2 = SelectCore.new core2.froms = %w[a b c] core2.projections = %w[d e f] core2.wheres = %w[g h i] core2.groups = %w[j k l] core2.windows = %w[m n o] - core2.having = %w[p q r] + core2.havings = %w[p q r] array = [core1, core2] assert_equal 1, array.uniq.size end @@ -53,14 +53,14 @@ module Arel core1.wheres = %w[g h i] core1.groups = %w[j k l] core1.windows = %w[m n o] - core1.having = %w[p q r] + core1.havings = %w[p q r] core2 = SelectCore.new core2.froms = %w[a b c] core2.projections = %w[d e f] core2.wheres = %w[g h i] core2.groups = %w[j k l] core2.windows = %w[m n o] - core2.having = %w[l o l] + core2.havings = %w[l o l] array = [core1, core2] assert_equal 2, array.uniq.size end diff --git a/test/nodes/test_sql_literal.rb b/test/nodes/test_sql_literal.rb index ed602cc47d..c09e5882d5 100644 --- a/test/nodes/test_sql_literal.rb +++ b/test/nodes/test_sql_literal.rb @@ -56,7 +56,7 @@ module Arel end describe 'grouped "and" equality' do - it 'makes a grouping node with an or node' do + it 'makes a grouping node with an and node' do node = SqlLiteral.new('foo').eq_all([1,2]) compile(node).must_be_like %{ (foo = 1 AND foo = 2) } end diff --git a/test/nodes/test_sum.rb b/test/nodes/test_sum.rb index d65cd31d4b..d387e7f9ef 100644 --- a/test/nodes/test_sum.rb +++ b/test/nodes/test_sum.rb @@ -21,4 +21,13 @@ describe Arel::Nodes::Sum do assert_equal 2, array.uniq.size end end + + describe 'order' do + it 'should order the sum' do + table = Arel::Table.new :users + table[:id].sum.desc.to_sql.must_be_like %{ + SUM("users"."id") DESC + } + end + end end diff --git a/test/nodes/test_table_alias.rb b/test/nodes/test_table_alias.rb index 4aafd12b79..57c9a42fc6 100644 --- a/test/nodes/test_table_alias.rb +++ b/test/nodes/test_table_alias.rb @@ -4,28 +4,20 @@ require 'ostruct' module Arel module Nodes describe 'table alias' do - it 'has an #engine which delegates to the relation' do - engine = 'vroom' - relation = Table.new(:users, engine) - - node = TableAlias.new relation, :foo - node.engine.must_equal engine - end - describe 'equality' do it 'is equal with equal ivars' do - relation1 = Table.new(:users, 'vroom') + relation1 = Table.new(:users) node1 = TableAlias.new relation1, :foo - relation2 = Table.new(:users, 'vroom') + relation2 = Table.new(:users) node2 = TableAlias.new relation2, :foo array = [node1, node2] assert_equal 1, array.uniq.size end it 'is not equal with different ivars' do - relation1 = Table.new(:users, 'vroom') + relation1 = Table.new(:users) node1 = TableAlias.new relation1, :foo - relation2 = Table.new(:users, 'vroom') + relation2 = Table.new(:users) node2 = TableAlias.new relation2, :bar array = [node1, node2] assert_equal 2, array.uniq.size diff --git a/test/test_delete_manager.rb b/test/test_delete_manager.rb index fd12c5acd2..ece2389d88 100644 --- a/test/test_delete_manager.rb +++ b/test/test_delete_manager.rb @@ -4,21 +4,29 @@ module Arel describe 'delete manager' do describe 'new' do it 'takes an engine' do - Arel::DeleteManager.new Table.engine + Arel::DeleteManager.new end end + it 'handles limit properly' do + table = Table.new(:users) + dm = Arel::DeleteManager.new + dm.take 10 + dm.from table + assert_match(/LIMIT 10/, dm.to_sql) + end + describe 'from' do it 'uses from' do table = Table.new(:users) - dm = Arel::DeleteManager.new Table.engine + dm = Arel::DeleteManager.new dm.from table dm.to_sql.must_be_like %{ DELETE FROM "users" } end it 'chains' do table = Table.new(:users) - dm = Arel::DeleteManager.new Table.engine + dm = Arel::DeleteManager.new dm.from(table).must_equal dm end end @@ -26,7 +34,7 @@ module Arel describe 'where' do it 'uses where values' do table = Table.new(:users) - dm = Arel::DeleteManager.new Table.engine + dm = Arel::DeleteManager.new dm.from table dm.where table[:id].eq(10) dm.to_sql.must_be_like %{ DELETE FROM "users" WHERE "users"."id" = 10} @@ -34,7 +42,7 @@ module Arel it 'chains' do table = Table.new(:users) - dm = Arel::DeleteManager.new Table.engine + dm = Arel::DeleteManager.new dm.where(table[:id].eq(10)).must_equal dm end end diff --git a/test/test_insert_manager.rb b/test/test_insert_manager.rb index 9cfd01262b..4289b0fa8c 100644 --- a/test/test_insert_manager.rb +++ b/test/test_insert_manager.rb @@ -4,13 +4,13 @@ module Arel describe 'insert manager' do describe 'new' do it 'takes an engine' do - Arel::InsertManager.new Table.engine + Arel::InsertManager.new end end describe 'insert' do it 'can create a Values node' do - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new values = manager.create_values %w{ a b }, %w{ c d } assert_kind_of Arel::Nodes::Values, values @@ -19,7 +19,7 @@ module Arel end it 'allows sql literals' do - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into Table.new(:users) manager.values = manager.create_values [Arel.sql('*')], %w{ a } manager.to_sql.must_be_like %{ @@ -29,7 +29,7 @@ module Arel it "inserts false" do table = Table.new(:users) - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.insert [[table[:bool], false]] manager.to_sql.must_be_like %{ @@ -39,7 +39,7 @@ module Arel it "inserts null" do table = Table.new(:users) - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.insert [[table[:id], nil]] manager.to_sql.must_be_like %{ INSERT INTO "users" ("id") VALUES (NULL) @@ -48,7 +48,7 @@ module Arel it "inserts time" do table = Table.new(:users) - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new time = Time.now attribute = table[:created_at] @@ -61,7 +61,7 @@ module Arel it 'takes a list of lists' do table = Table.new(:users) - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into table manager.insert [[table[:id], 1], [table[:name], 'aaron']] manager.to_sql.must_be_like %{ @@ -71,7 +71,7 @@ module Arel it 'defaults the table' do table = Table.new(:users) - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.insert [[table[:id], 1], [table[:name], 'aaron']] manager.to_sql.must_be_like %{ INSERT INTO "users" ("id", "name") VALUES (1, 'aaron') @@ -80,7 +80,7 @@ module Arel it 'noop for empty list' do table = Table.new(:users) - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.insert [[table[:id], 1]] manager.insert [] manager.to_sql.must_be_like %{ @@ -91,13 +91,13 @@ module Arel describe 'into' do it 'takes a Table and chains' do - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into(Table.new(:users)).must_equal manager end it 'converts to sql' do table = Table.new :users - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into table manager.to_sql.must_be_like %{ INSERT INTO "users" @@ -108,7 +108,7 @@ module Arel describe 'columns' do it "converts to sql" do table = Table.new :users - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into table manager.columns << table[:id] manager.to_sql.must_be_like %{ @@ -120,7 +120,7 @@ module Arel describe "values" do it "converts to sql" do table = Table.new :users - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into table manager.values = Nodes::Values.new [1] @@ -133,7 +133,7 @@ module Arel describe "combo" do it "combines columns and values list in order" do table = Table.new :users - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into table manager.values = Nodes::Values.new [1, 'aaron'] @@ -150,10 +150,10 @@ module Arel it "accepts a select query in place of a VALUES clause" do table = Table.new :users - manager = Arel::InsertManager.new Table.engine + manager = Arel::InsertManager.new manager.into table - select = Arel::SelectManager.new Table.engine + select = Arel::SelectManager.new select.project Arel.sql('1') select.project Arel.sql('"aaron"') diff --git a/test/test_select_manager.rb b/test/test_select_manager.rb index 1ffb56fd9f..8425cee031 100644 --- a/test/test_select_manager.rb +++ b/test/test_select_manager.rb @@ -4,13 +4,13 @@ module Arel describe 'select manager' do def test_join_sources - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new 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 + manager = Arel::SelectManager.new assert_equal [], manager.bind_values manager.bind_values = [1] assert_equal [1], manager.bind_values @@ -20,7 +20,7 @@ module Arel describe 'project' do it 'accepts symbols as sql literals' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project :id manager.from table manager.to_sql.must_be_like %{ @@ -32,7 +32,7 @@ module Arel describe 'order' do it 'accepts symbols' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Nodes::SqlLiteral.new '*' manager.from table manager.order :foo @@ -43,7 +43,7 @@ module Arel describe 'group' do it 'takes a symbol' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.group :foo manager.to_sql.must_be_like %{ SELECT FROM "users" GROUP BY foo } @@ -52,7 +52,7 @@ module Arel describe 'as' do it 'makes an AS node by grouping the AST' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new as = manager.as(Arel.sql('foo')) assert_kind_of Arel::Nodes::Grouping, as.left assert_equal manager.ast, as.left.expr @@ -60,18 +60,18 @@ module Arel end it 'converts right to SqlLiteral if a string' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new as = manager.as('foo') assert_kind_of Arel::Nodes::SqlLiteral, as.right end it 'can make a subselect' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Arel.star manager.from Arel.sql('zomg') as = manager.as(Arel.sql('foo')) - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Arel.sql('name') manager.from as manager.to_sql.must_be_like "SELECT name FROM (SELECT * FROM zomg) foo" @@ -81,7 +81,7 @@ module Arel describe 'from' do it 'ignores strings when table of same name exists' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.from 'users' @@ -91,9 +91,9 @@ module Arel it 'should support any ast' do table = Table.new :users - manager1 = Arel::SelectManager.new Table.engine + manager1 = Arel::SelectManager.new - manager2 = Arel::SelectManager.new Table.engine + manager2 = Arel::SelectManager.new manager2.project(Arel.sql('*')) manager2.from table @@ -110,23 +110,23 @@ module Arel describe 'having' do it 'converts strings to SQLLiterals' do table = Table.new :users - mgr = table.from table - mgr.having 'foo' + mgr = table.from + mgr.having Arel.sql('foo') mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo } end it 'can have multiple items specified separately' do table = Table.new :users - mgr = table.from table - mgr.having 'foo' - mgr.having 'bar' + mgr = table.from + mgr.having Arel.sql('foo') + mgr.having Arel.sql('bar') mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo AND bar } end - it 'can have multiple items specified together' do + it 'can receive any node' do table = Table.new :users - mgr = table.from table - mgr.having 'foo', 'bar' + mgr = table.from + mgr.having Arel::Nodes::And.new([Arel.sql('foo'), Arel.sql('bar')]) mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo AND bar } end end @@ -135,7 +135,7 @@ module Arel it 'converts to sqlliterals' do table = Table.new :users right = table.alias - mgr = table.from table + mgr = table.from mgr.join(right).on("omg") mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg } end @@ -143,7 +143,7 @@ module Arel it 'converts to sqlliterals with multiple items' do table = Table.new :users right = table.alias - mgr = table.from table + mgr = table.from mgr.join(right).on("omg", "123") mgr.to_sql.must_be_like %{ SELECT FROM "users" INNER JOIN "users" "users_2" ON omg AND 123 } end @@ -153,7 +153,7 @@ module Arel describe 'clone' do it 'creates new cores' do table = Table.new :users, :as => 'foo' - mgr = table.from table + mgr = table.from m2 = mgr.clone m2.project "foo" mgr.to_sql.wont_equal m2.to_sql @@ -161,7 +161,7 @@ module Arel it 'makes updates to the correct copy' do table = Table.new :users, :as => 'foo' - mgr = table.from table + mgr = table.from m2 = mgr.clone m3 = m2.clone m2.project "foo" @@ -173,7 +173,7 @@ module Arel describe 'initialize' do it 'uses alias in sql' do table = Table.new :users, :as => 'foo' - mgr = table.from table + mgr = table.from mgr.skip 10 mgr.to_sql.must_be_like %{ SELECT FROM "users" "foo" OFFSET 10 } end @@ -182,14 +182,14 @@ module Arel describe 'skip' do it 'should add an offset' do table = Table.new :users - mgr = table.from table + mgr = table.from mgr.skip 10 mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 } end it 'should chain' do table = Table.new :users - mgr = table.from table + mgr = table.from mgr.skip(10).to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 } end end @@ -197,14 +197,14 @@ module Arel describe 'offset' do it 'should add an offset' do table = Table.new :users - mgr = table.from table + mgr = table.from mgr.offset = 10 mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 } end it 'should remove an offset' do table = Table.new :users - mgr = table.from table + mgr = table.from mgr.offset = 10 mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 } @@ -214,7 +214,7 @@ module Arel it 'should return the offset' do table = Table.new :users - mgr = table.from table + mgr = table.from mgr.offset = 10 assert_equal 10, mgr.offset end @@ -223,7 +223,7 @@ module Arel describe 'exists' do it 'should create an exists clause' do table = Table.new(:users) - manager = Arel::SelectManager.new Table.engine, table + manager = Arel::SelectManager.new table manager.project Nodes::SqlLiteral.new '*' m2 = Arel::SelectManager.new(manager.engine) m2.project manager.exists @@ -232,7 +232,7 @@ module Arel it 'can be aliased' do table = Table.new(:users) - manager = Arel::SelectManager.new Table.engine, table + manager = Arel::SelectManager.new table manager.project Nodes::SqlLiteral.new '*' m2 = Arel::SelectManager.new(manager.engine) m2.project manager.exists.as('foo') @@ -243,11 +243,11 @@ module Arel describe 'union' do before do table = Table.new :users - @m1 = Arel::SelectManager.new Table.engine, table + @m1 = Arel::SelectManager.new table @m1.project Arel.star @m1.where(table[:age].lt(18)) - @m2 = Arel::SelectManager.new Table.engine, table + @m2 = Arel::SelectManager.new table @m2.project Arel.star @m2.where(table[:age].gt(99)) @@ -278,11 +278,11 @@ module Arel describe 'intersect' do before do table = Table.new :users - @m1 = Arel::SelectManager.new Table.engine, table + @m1 = Arel::SelectManager.new table @m1.project Arel.star @m1.where(table[:age].gt(18)) - @m2 = Arel::SelectManager.new Table.engine, table + @m2 = Arel::SelectManager.new table @m2.project Arel.star @m2.where(table[:age].lt(99)) @@ -305,15 +305,13 @@ module Arel describe 'except' do before do table = Table.new :users - @m1 = Arel::SelectManager.new Table.engine, table + @m1 = Arel::SelectManager.new table @m1.project Arel.star - @m1.where(table[:age].in(18..60)) + @m1.where(table[:age].between(18..60)) - @m2 = Arel::SelectManager.new Table.engine, table + @m2 = Arel::SelectManager.new table @m2.project Arel.star - @m2.where(table[:age].in(40..99)) - - + @m2.where(table[:age].between(40..99)) end it 'should except two managers' do @@ -353,17 +351,17 @@ module Arel replies = Table.new(:replies) replies_id = replies[:id] - recursive_term = Arel::SelectManager.new Table.engine + recursive_term = Arel::SelectManager.new recursive_term.from(comments).project(comments_id, comments_parent_id).where(comments_id.eq 42) - non_recursive_term = Arel::SelectManager.new Table.engine + non_recursive_term = Arel::SelectManager.new non_recursive_term.from(comments).project(comments_id, comments_parent_id).join(replies).on(comments_parent_id.eq replies_id) union = recursive_term.union(non_recursive_term) as_statement = Arel::Nodes::As.new replies, union - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.with(:recursive, as_statement).from(replies).project(Arel.star) sql = manager.to_sql @@ -381,13 +379,13 @@ module Arel describe 'ast' do it 'should return the ast' do table = Table.new :users - mgr = table.from table + mgr = table.from assert mgr.ast end it 'should allow orders to work when the ast is grepped' do table = Table.new :users - mgr = table.from table + mgr = table.from mgr.project Arel.sql '*' mgr.from table mgr.orders << Arel::Nodes::Ascending.new(Arel.sql('foo')) @@ -398,7 +396,7 @@ module Arel describe 'taken' do it 'should return limit' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.take 10 manager.taken.must_equal 10 end @@ -408,7 +406,7 @@ module Arel # This should fail on other databases it 'adds a lock node' do table = Table.new :users - mgr = table.from table + mgr = table.from mgr.lock.to_sql.must_be_like %{ SELECT FROM "users" FOR UPDATE } end end @@ -416,7 +414,7 @@ module Arel describe 'orders' do it 'returns order clauses' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new order = table[:id] manager.order table[:id] manager.orders.must_equal [order] @@ -426,7 +424,7 @@ module Arel describe 'order' do it 'generates order clauses' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Nodes::SqlLiteral.new '*' manager.from table manager.order table[:id] @@ -438,7 +436,7 @@ module Arel # FIXME: I would like to deprecate this it 'takes *args' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Nodes::SqlLiteral.new '*' manager.from table manager.order table[:id], table[:name] @@ -449,13 +447,13 @@ module Arel it 'chains' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.order(table[:id]).must_equal manager end it 'has order attributes' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Nodes::SqlLiteral.new '*' manager.from table manager.order table[:id].desc @@ -470,7 +468,7 @@ module Arel left = Table.new :users right = left.alias predicate = left[:id].eq(right[:id]) - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from left manager.join(right).on(predicate, predicate) @@ -486,7 +484,7 @@ module Arel left = Table.new :users right = left.alias predicate = left[:id].eq(right[:id]) - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from left manager.join(right).on( @@ -505,12 +503,12 @@ module Arel end it 'should hand back froms' do - relation = Arel::SelectManager.new Table.engine + relation = Arel::SelectManager.new assert_equal [], relation.froms end it 'should create and nodes' do - relation = Arel::SelectManager.new Table.engine + relation = Arel::SelectManager.new children = ['foo', 'bar', 'baz'] clause = relation.create_and children assert_kind_of Arel::Nodes::And, clause @@ -518,13 +516,13 @@ module Arel end it 'should create insert managers' do - relation = Arel::SelectManager.new Table.engine + relation = Arel::SelectManager.new insert = relation.create_insert assert_kind_of Arel::InsertManager, insert end it 'should create join nodes' do - relation = Arel::SelectManager.new Table.engine + relation = Arel::SelectManager.new join = relation.create_join 'foo', 'bar' assert_kind_of Arel::Nodes::InnerJoin, join assert_equal 'foo', join.left @@ -532,7 +530,7 @@ module Arel end it 'should create join nodes with a full outer join klass' do - relation = Arel::SelectManager.new Table.engine + relation = Arel::SelectManager.new join = relation.create_join 'foo', 'bar', Arel::Nodes::FullOuterJoin assert_kind_of Arel::Nodes::FullOuterJoin, join assert_equal 'foo', join.left @@ -540,7 +538,7 @@ module Arel end it 'should create join nodes with a outer join klass' do - relation = Arel::SelectManager.new Table.engine + relation = Arel::SelectManager.new join = relation.create_join 'foo', 'bar', Arel::Nodes::OuterJoin assert_kind_of Arel::Nodes::OuterJoin, join assert_equal 'foo', join.left @@ -548,7 +546,7 @@ module Arel end it 'should create join nodes with a right outer join klass' do - relation = Arel::SelectManager.new Table.engine + relation = Arel::SelectManager.new join = relation.create_join 'foo', 'bar', Arel::Nodes::RightOuterJoin assert_kind_of Arel::Nodes::RightOuterJoin, join assert_equal 'foo', join.left @@ -560,7 +558,7 @@ module Arel left = Table.new :users right = left.alias predicate = left[:id].eq(right[:id]) - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from left manager.join(right).on(predicate) @@ -575,7 +573,7 @@ module Arel left = Table.new :users right = left.alias predicate = left[:id].eq(right[:id]) - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from left manager.join(right, Nodes::OuterJoin).on(predicate) @@ -586,8 +584,38 @@ module Arel } end + it 'takes the full outer join class' do + left = Table.new :users + right = left.alias + predicate = left[:id].eq(right[:id]) + manager = Arel::SelectManager.new + + manager.from left + manager.join(right, Nodes::FullOuterJoin).on(predicate) + manager.to_sql.must_be_like %{ + SELECT FROM "users" + FULL OUTER JOIN "users" "users_2" + ON "users"."id" = "users_2"."id" + } + end + + it 'takes the right outer join class' do + left = Table.new :users + right = left.alias + predicate = left[:id].eq(right[:id]) + manager = Arel::SelectManager.new + + manager.from left + manager.join(right, Nodes::RightOuterJoin).on(predicate) + manager.to_sql.must_be_like %{ + SELECT FROM "users" + RIGHT OUTER JOIN "users" "users_2" + ON "users"."id" = "users_2"."id" + } + end + it 'noops on nil' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.join(nil).must_equal manager end end @@ -597,7 +625,7 @@ module Arel left = Table.new :users right = left.alias predicate = left[:id].eq(right[:id]) - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from left manager.outer_join(right).on(predicate) @@ -609,7 +637,7 @@ module Arel end it 'noops on nil' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.outer_join(nil).must_equal manager end end @@ -619,7 +647,7 @@ module Arel it 'returns inner join sql' do table = Table.new :users aliaz = table.alias - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from Nodes::InnerJoin.new(aliaz, table[:id].eq(aliaz[:id])) assert_match 'INNER JOIN "users" "users_2" "users"."id" = "users_2"."id"', manager.to_sql @@ -628,7 +656,7 @@ module Arel it 'returns outer join sql' do table = Table.new :users aliaz = table.alias - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from Nodes::OuterJoin.new(aliaz, table[:id].eq(aliaz[:id])) assert_match 'LEFT OUTER JOIN "users" "users_2" "users"."id" = "users_2"."id"', manager.to_sql @@ -638,7 +666,7 @@ module Arel users = Table.new :users comments = Table.new :comments - counts = comments.from(comments). + counts = comments.from. group(comments[:user_id]). project( comments[:user_id].as("user_id"), @@ -668,7 +696,7 @@ module Arel end it 'returns string join sql' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from Nodes::StringJoin.new(Nodes.build_quoted('hello')) assert_match "'hello'", manager.to_sql end @@ -677,7 +705,7 @@ module Arel describe 'group' do it 'takes an attribute' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.group table[:id] manager.to_sql.must_be_like %{ @@ -687,13 +715,13 @@ module Arel it 'chains' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.group(table[:id]).must_equal manager end it 'takes multiple args' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.group table[:id], table[:name] manager.to_sql.must_be_like %{ @@ -704,7 +732,7 @@ module Arel # FIXME: backwards compat it 'makes strings literals' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.group 'foo' manager.to_sql.must_be_like %{ SELECT FROM "users" GROUP BY foo } @@ -714,7 +742,7 @@ module Arel describe 'window definition' do it 'can be empty' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window') manager.to_sql.must_be_like %{ @@ -724,7 +752,7 @@ module Arel it 'takes an order' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').order(table['foo'].asc) manager.to_sql.must_be_like %{ @@ -734,7 +762,7 @@ module Arel it 'takes an order with multiple columns' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').order(table['foo'].asc, table['bar'].desc) manager.to_sql.must_be_like %{ @@ -744,7 +772,7 @@ module Arel it 'takes a partition' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').partition(table['bar']) manager.to_sql.must_be_like %{ @@ -754,7 +782,7 @@ module Arel it 'takes a partition and an order' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').partition(table['foo']).order(table['foo'].asc) manager.to_sql.must_be_like %{ @@ -765,7 +793,7 @@ module Arel it 'takes a partition with multiple columns' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').partition(table['bar'], table['baz']) manager.to_sql.must_be_like %{ @@ -775,7 +803,7 @@ module Arel it 'takes a rows frame, unbounded preceding' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').rows(Arel::Nodes::Preceding.new) manager.to_sql.must_be_like %{ @@ -785,7 +813,7 @@ module Arel it 'takes a rows frame, bounded preceding' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').rows(Arel::Nodes::Preceding.new(5)) manager.to_sql.must_be_like %{ @@ -795,7 +823,7 @@ module Arel it 'takes a rows frame, unbounded following' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').rows(Arel::Nodes::Following.new) manager.to_sql.must_be_like %{ @@ -805,7 +833,7 @@ module Arel it 'takes a rows frame, bounded following' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').rows(Arel::Nodes::Following.new(5)) manager.to_sql.must_be_like %{ @@ -815,7 +843,7 @@ module Arel it 'takes a rows frame, current row' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').rows(Arel::Nodes::CurrentRow.new) manager.to_sql.must_be_like %{ @@ -825,7 +853,7 @@ module Arel it 'takes a rows frame, between two delimiters' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table window = manager.window('a_window') window.frame( @@ -842,7 +870,7 @@ module Arel it 'takes a range frame, unbounded preceding' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').range(Arel::Nodes::Preceding.new) manager.to_sql.must_be_like %{ @@ -852,7 +880,7 @@ module Arel it 'takes a range frame, bounded preceding' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').range(Arel::Nodes::Preceding.new(5)) manager.to_sql.must_be_like %{ @@ -862,7 +890,7 @@ module Arel it 'takes a range frame, unbounded following' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').range(Arel::Nodes::Following.new) manager.to_sql.must_be_like %{ @@ -872,7 +900,7 @@ module Arel it 'takes a range frame, bounded following' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').range(Arel::Nodes::Following.new(5)) manager.to_sql.must_be_like %{ @@ -882,7 +910,7 @@ module Arel it 'takes a range frame, current row' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.window('a_window').range(Arel::Nodes::CurrentRow.new) manager.to_sql.must_be_like %{ @@ -892,7 +920,7 @@ module Arel it 'takes a range frame, between two delimiters' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table window = manager.window('a_window') window.frame( @@ -911,7 +939,7 @@ module Arel describe 'delete' do it "copies from" do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table stmt = manager.compile_delete @@ -920,7 +948,7 @@ module Arel it "copies where" do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.where table[:id].eq 10 stmt = manager.compile_delete @@ -934,7 +962,7 @@ module Arel describe 'where_sql' do it 'gives me back the where sql' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.where table[:id].eq 10 manager.where_sql.must_be_like %{ WHERE "users"."id" = 10 } @@ -942,7 +970,7 @@ module Arel it 'returns nil when there are no wheres' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.where_sql.must_be_nil end @@ -952,7 +980,7 @@ module Arel it 'creates an update statement' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table stmt = manager.compile_update({table[:id] => 1}, Arel::Attributes::Attribute.new(table, 'id')) @@ -963,7 +991,7 @@ module Arel it 'takes a string' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table stmt = manager.compile_update(Nodes::SqlLiteral.new('foo = bar'), Arel::Attributes::Attribute.new(table, 'id')) @@ -972,7 +1000,7 @@ module Arel it 'copies limits' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.take 1 stmt = manager.compile_update(Nodes::SqlLiteral.new('foo = bar'), Arel::Attributes::Attribute.new(table, 'id')) @@ -986,7 +1014,7 @@ module Arel it 'copies order' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.order :foo stmt = manager.compile_update(Nodes::SqlLiteral.new('foo = bar'), Arel::Attributes::Attribute.new(table, 'id')) @@ -1000,7 +1028,7 @@ module Arel it 'copies where clauses' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.where table[:id].eq 10 manager.from table stmt = manager.compile_update({table[:id] => 1}, Arel::Attributes::Attribute.new(table, 'id')) @@ -1012,7 +1040,7 @@ module Arel it 'copies where clauses when nesting is triggered' do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.where table[:foo].eq 10 manager.take 42 manager.from table @@ -1027,20 +1055,20 @@ module Arel describe 'project' do it "takes sql literals" do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new 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 = Arel::SelectManager.new manager.project Nodes::SqlLiteral.new('foo'), Nodes::SqlLiteral.new('bar') manager.to_sql.must_be_like %{ SELECT foo, bar } end it 'takes strings' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project '*' manager.to_sql.must_be_like %{ SELECT * } end @@ -1049,7 +1077,7 @@ module Arel describe 'projections' do it 'reads projections' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Arel.sql('foo'), Arel.sql('bar') manager.projections.must_equal [Arel.sql('foo'), Arel.sql('bar')] end @@ -1057,7 +1085,7 @@ module Arel describe 'projections=' do it 'overwrites projections' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.project Arel.sql('foo') manager.projections = [Arel.sql('bar')] manager.to_sql.must_be_like %{ SELECT bar } @@ -1067,7 +1095,7 @@ module Arel describe 'take' do it "knows take" do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from(table).project(table['id']) manager.where(table['id'].eq(1)) manager.take 1 @@ -1081,12 +1109,12 @@ module Arel end it "chains" do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.take(1).must_equal manager end it 'removes LIMIT when nil is passed' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.limit = 10 assert_match('LIMIT', manager.to_sql) @@ -1098,7 +1126,7 @@ module Arel describe 'where' do it "knows where" do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from(table).project(table['id']) manager.where(table['id'].eq(1)) manager.to_sql.must_be_like %{ @@ -1110,7 +1138,7 @@ module Arel it "chains" do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from(table) manager.project(table['id']).where(table['id'].eq 1).must_equal manager end @@ -1119,7 +1147,7 @@ module Arel describe 'from' do it "makes sql" do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from table manager.project table['id'] @@ -1128,7 +1156,7 @@ module Arel it "chains" do table = Table.new :users - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.from(table).project(table['id']).must_equal manager manager.to_sql.must_be_like 'SELECT "users"."id" FROM "users"' end @@ -1136,14 +1164,14 @@ module Arel describe 'source' do it 'returns the join source of the select core' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.source.must_equal manager.ast.cores.last.source end end describe 'distinct' do it 'sets the quantifier' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.distinct manager.ast.cores.last.set_quantifier.class.must_equal Arel::Nodes::Distinct @@ -1153,7 +1181,7 @@ module Arel end it "chains" do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new manager.distinct.must_equal manager manager.distinct(false).must_equal manager end @@ -1161,7 +1189,7 @@ module Arel describe 'distinct_on' do it 'sets the quantifier' do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new table = Table.new :users manager.distinct_on(table['id']) @@ -1172,7 +1200,7 @@ module Arel end it "chains" do - manager = Arel::SelectManager.new Table.engine + manager = Arel::SelectManager.new table = Table.new :users manager.distinct_on(table['id']).must_equal manager diff --git a/test/test_table.rb b/test/test_table.rb index 14256475ec..e8eaf901cc 100644 --- a/test/test_table.rb +++ b/test/test_table.rb @@ -47,12 +47,6 @@ module Arel assert_equal "INSERT INTO \"users\" VALUES(NULL)", im.to_sql end - it 'should return IM from insert_manager' do - im = @relation.insert_manager - assert_kind_of Arel::InsertManager, im - assert_equal im.engine, @relation.engine - end - describe 'skip' do it 'should add an offset' do sm = @relation.skip 2 @@ -60,29 +54,6 @@ module Arel end end - describe 'select_manager' do - it 'should return an empty select manager' do - sm = @relation.select_manager - sm.to_sql.must_be_like 'SELECT' - 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) @@ -149,14 +120,9 @@ module Arel end describe 'new' do - it 'should accept an engine' do - rel = Table.new :users, 'foo' - rel.engine.must_equal 'foo' - end - it 'should accept a hash' do - rel = Table.new :users, :engine => 'foo' - rel.engine.must_equal 'foo' + rel = Table.new :users, :as => 'foo' + rel.table_alias.must_equal 'foo' end it 'ignores as if it equals name' do @@ -213,10 +179,6 @@ module Arel @relation.table_name.must_equal 'users' end - it "should have an engine" do - @relation.engine.must_equal Table.engine - end - describe '[]' do describe 'when given a Symbol' do it "manufactures an attribute if the symbol names an attribute within the relation" do @@ -228,10 +190,10 @@ module Arel describe 'equality' do it 'is equal with equal ivars' do - relation1 = Table.new(:users, 'vroom') + relation1 = Table.new(:users) relation1.aliases = %w[a b c] relation1.table_alias = 'zomg' - relation2 = Table.new(:users, 'vroom') + relation2 = Table.new(:users) relation2.aliases = %w[a b c] relation2.table_alias = 'zomg' array = [relation1, relation2] @@ -239,10 +201,10 @@ module Arel end it 'is not equal with different ivars' do - relation1 = Table.new(:users, 'vroom') + relation1 = Table.new(:users) relation1.aliases = %w[a b c] relation1.table_alias = 'zomg' - relation2 = Table.new(:users, 'vroom') + relation2 = Table.new(:users) relation2.aliases = %w[x y z] relation2.table_alias = 'zomg' array = [relation1, relation2] diff --git a/test/test_update_manager.rb b/test/test_update_manager.rb index f1a019970d..f41dc46e7d 100644 --- a/test/test_update_manager.rb +++ b/test/test_update_manager.rb @@ -4,21 +4,21 @@ module Arel describe 'update manager' do describe 'new' do it 'takes an engine' do - Arel::UpdateManager.new Table.engine + Arel::UpdateManager.new end end it "should not quote sql literals" do table = Table.new(:users) - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table table - um.set [[table[:name], (Arel::Nodes::BindParam.new '?')]] + 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 = Arel::UpdateManager.new um.key = 'id' um.take 10 um.table table @@ -29,7 +29,7 @@ module Arel describe 'set' do it "updates with null" do table = Table.new(:users) - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table table um.set [[table[:name], nil]] um.to_sql.must_be_like %{ UPDATE "users" SET "name" = NULL } @@ -37,7 +37,7 @@ module Arel it 'takes a string' do table = Table.new(:users) - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table table um.set Nodes::SqlLiteral.new "foo = bar" um.to_sql.must_be_like %{ UPDATE "users" SET foo = bar } @@ -45,7 +45,7 @@ module Arel it 'takes a list of lists' do table = Table.new(:users) - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table table um.set [[table[:id], 1], [table[:name], 'hello']] um.to_sql.must_be_like %{ @@ -55,25 +55,25 @@ module Arel it 'chains' do table = Table.new(:users) - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.set([[table[:id], 1], [table[:name], 'hello']]).must_equal um end end describe 'table' do it 'generates an update statement' do - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table Table.new(:users) um.to_sql.must_be_like %{ UPDATE "users" } end it 'chains' do - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table(Table.new(:users)).must_equal um end it 'generates an update statement with joins' do - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new table = Table.new(:users) join_source = Arel::Nodes::JoinSource.new( @@ -89,7 +89,7 @@ module Arel describe 'where' do it 'generates a where clause' do table = Table.new :users - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table table um.where table[:id].eq(1) um.to_sql.must_be_like %{ @@ -99,7 +99,7 @@ module Arel it 'chains' do table = Table.new :users - um = Arel::UpdateManager.new Table.engine + um = Arel::UpdateManager.new um.table table um.where(table[:id].eq(1)).must_equal um end @@ -108,7 +108,7 @@ module Arel describe 'key' do before do @table = Table.new :users - @um = Arel::UpdateManager.new Table.engine + @um = Arel::UpdateManager.new @um.key = @table[:foo] end diff --git a/test/visitors/test_bind_visitor.rb b/test/visitors/test_bind_visitor.rb index 333636ed51..f0007cabbf 100644 --- a/test/visitors/test_bind_visitor.rb +++ b/test/visitors/test_bind_visitor.rb @@ -17,8 +17,8 @@ module Arel # 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 = Arel::UpdateManager.new + bp = Nodes::BindParam.new um.set [[table[:name], bp]] visitor = Class.new(Arel::Visitors::ToSql) { include Arel::Visitors::BindVisitor @@ -38,7 +38,7 @@ module Arel include Arel::Visitors::BindVisitor }.new nil - bp = Nodes::BindParam.new 'omg' + bp = Nodes::BindParam.new called = false visitor.accept(bp, collector) { called = true } assert called diff --git a/test/visitors/test_depth_first.rb b/test/visitors/test_depth_first.rb index d50ea3e59a..3356759b7d 100644 --- a/test/visitors/test_depth_first.rb +++ b/test/visitors/test_depth_first.rb @@ -30,7 +30,6 @@ module Arel Arel::Nodes::Grouping, Arel::Nodes::Offset, Arel::Nodes::Ordering, - Arel::Nodes::Having, Arel::Nodes::StringJoin, Arel::Nodes::UnqualifiedColumn, Arel::Nodes::Top, @@ -206,7 +205,7 @@ module Arel core.wheres << :c core.groups << :d core.windows << :e - core.having = :f + core.havings << :f @visitor.accept core assert_equal [ @@ -216,7 +215,7 @@ module Arel :c, core.wheres, :d, core.groups, :e, core.windows, - :f, + :f, core.havings, core], @collector.calls end diff --git a/test/visitors/test_dot.rb b/test/visitors/test_dot.rb index 7763350f5c..4dc3c9c6c5 100644 --- a/test/visitors/test_dot.rb +++ b/test/visitors/test_dot.rb @@ -34,7 +34,6 @@ module Arel Arel::Nodes::Grouping, Arel::Nodes::Offset, Arel::Nodes::Ordering, - Arel::Nodes::Having, Arel::Nodes::UnqualifiedColumn, Arel::Nodes::Top, Arel::Nodes::Limit, diff --git a/test/visitors/test_mssql.rb b/test/visitors/test_mssql.rb index a3efcb8b27..fe228bce4b 100644 --- a/test/visitors/test_mssql.rb +++ b/test/visitors/test_mssql.rb @@ -26,6 +26,34 @@ module Arel 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 'caches the PK lookup for order' do + connection = MiniTest::Mock.new + connection.expect(:primary_key, ["id"], ["users"]) + + # We don't care how many times these methods are called + def connection.quote_table_name(*); ""; end + def connection.quote_column_name(*); ""; end + + @visitor = MSSQL.new(connection) + stmt = Nodes::SelectStatement.new + stmt.cores.first.from = @table + stmt.limit = Nodes::Limit.new(10) + + compile(stmt) + compile(stmt) + + connection.verify + end + + it 'should use TOP for limited deletes' do + stmt = Nodes::DeleteStatement.new + stmt.relation = @table + stmt.limit = Nodes::Limit.new(10) + sql = compile(stmt) + + sql.must_be_like "DELETE TOP (10) FROM \"users\"" + end + it 'should go over query ORDER BY if .order()' do stmt = Nodes::SelectStatement.new stmt.limit = Nodes::Limit.new(10) diff --git a/test/visitors/test_oracle.rb b/test/visitors/test_oracle.rb index 29d7042084..e9ed9d76b3 100644 --- a/test/visitors/test_oracle.rb +++ b/test/visitors/test_oracle.rb @@ -4,7 +4,8 @@ module Arel module Visitors describe 'the oracle visitor' do before do - @visitor = Oracle.new Table.engine.connection_pool + @visitor = Oracle.new Table.engine.connection + @table = Table.new(:users) end def compile node @@ -110,7 +111,7 @@ module Arel it 'creates a different subquery when there is an offset' do stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(Nodes.build_quoted(10)) + stmt.limit = Nodes::Limit.new(10) stmt.offset = Nodes::Offset.new(10) sql = compile stmt sql.must_be_like %{ @@ -125,7 +126,7 @@ module Arel it 'is idempotent with different subquery' do stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(Nodes.build_quoted(10)) + stmt.limit = Nodes::Limit.new(10) stmt.offset = Nodes::Offset.new(10) sql = compile stmt sql2 = compile stmt @@ -165,6 +166,16 @@ module Arel compile(node).must_be_like "FOR UPDATE" end end + + describe "Nodes::BindParam" do + it "increments each bind param" do + query = @table[:name].eq(Arel::Nodes::BindParam.new) + .and(@table[:id].eq(Arel::Nodes::BindParam.new)) + compile(query).must_be_like %{ + "users"."name" = :a1 AND "users"."id" = :a2 + } + end + end end end end diff --git a/test/visitors/test_oracle12.rb b/test/visitors/test_oracle12.rb new file mode 100644 index 0000000000..df0f01b30b --- /dev/null +++ b/test/visitors/test_oracle12.rb @@ -0,0 +1,47 @@ +require 'helper' + +module Arel + module Visitors + describe 'the oracle visitor' do + before do + @visitor = Oracle12.new Table.engine.connection_pool + end + + def compile node + @visitor.accept(node, Collectors::SQLString.new).value + end + + 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 = compile Nodes::Except.new(left, right) + sql.must_be_like %{ + ( SELECT * FROM users WHERE age > 10 MINUS SELECT * FROM users WHERE age > 20 ) + } + end + + it 'generates select options offset then limit' do + stmt = Nodes::SelectStatement.new + stmt.offset = Nodes::Offset.new(1) + stmt.limit = Nodes::Limit.new(10) + sql = compile(stmt) + sql.must_be_like "SELECT OFFSET 1 ROWS FETCH FIRST 10 ROWS ONLY" + end + + describe 'locking' do + it 'removes limit when locking' do + stmt = Nodes::SelectStatement.new + stmt.limit = Nodes::Limit.new(10) + stmt.lock = Nodes::Lock.new(Arel.sql('FOR UPDATE')) + sql = compile(stmt) + sql.must_be_like "SELECT FOR UPDATE" + end + + it 'defaults to FOR UPDATE when locking' do + node = Nodes::Lock.new(Arel.sql('FOR UPDATE')) + compile(node).must_be_like "FOR UPDATE" + end + end + end + end +end diff --git a/test/visitors/test_postgres.rb b/test/visitors/test_postgres.rb index 3d646a7324..4b7dbe367f 100644 --- a/test/visitors/test_postgres.rb +++ b/test/visitors/test_postgres.rb @@ -53,11 +53,28 @@ module Arel describe "Nodes::Matches" do it "should know how to visit" do node = @table[:name].matches('foo%') + node.must_be_kind_of Nodes::Matches + node.case_sensitive.must_equal(false) compile(node).must_be_like %{ "users"."name" ILIKE 'foo%' } end + it "should know how to visit case sensitive" do + node = @table[:name].matches('foo%', nil, true) + node.case_sensitive.must_equal(true) + compile(node).must_be_like %{ + "users"."name" LIKE 'foo%' + } + end + + it "can handle ESCAPE" do + node = @table[:name].matches('foo!%', '!') + compile(node).must_be_like %{ + "users"."name" ILIKE 'foo!%' ESCAPE '!' + } + end + it 'can handle subqueries' do subquery = @table.project(:id).where(@table[:name].matches('foo%')) node = @attr.in subquery @@ -70,11 +87,28 @@ module Arel describe "Nodes::DoesNotMatch" do it "should know how to visit" do node = @table[:name].does_not_match('foo%') + node.must_be_kind_of Nodes::DoesNotMatch + node.case_sensitive.must_equal(false) compile(node).must_be_like %{ "users"."name" NOT ILIKE 'foo%' } end + it "should know how to visit case sensitive" do + node = @table[:name].does_not_match('foo%', nil, true) + node.case_sensitive.must_equal(true) + compile(node).must_be_like %{ + "users"."name" NOT LIKE 'foo%' + } + end + + it "can handle ESCAPE" do + node = @table[:name].does_not_match('foo!%', '!') + compile(node).must_be_like %{ + "users"."name" NOT ILIKE 'foo!%' ESCAPE '!' + } + end + it 'can handle subqueries' do subquery = @table.project(:id).where(@table[:name].does_not_match('foo%')) node = @attr.in subquery @@ -86,34 +120,65 @@ module Arel describe "Nodes::Regexp" do it "should know how to visit" do - node = Arel::Nodes::Regexp.new(@table[:name], Nodes.build_quoted('foo%')) + node = @table[:name].matches_regexp('foo.*') + node.must_be_kind_of Nodes::Regexp + node.case_sensitive.must_equal(true) compile(node).must_be_like %{ - "users"."name" ~ 'foo%' + "users"."name" ~ 'foo.*' + } + end + + it "can handle case insensitive" do + node = @table[:name].matches_regexp('foo.*', false) + node.must_be_kind_of Nodes::Regexp + node.case_sensitive.must_equal(false) + 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%'))) + subquery = @table.project(:id).where(@table[:name].matches_regexp('foo.*')) node = @attr.in subquery compile(node).must_be_like %{ - "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" ~ 'foo%') + "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%')) + node = @table[:name].does_not_match_regexp('foo.*') + node.must_be_kind_of Nodes::NotRegexp + node.case_sensitive.must_equal(true) + compile(node).must_be_like %{ + "users"."name" !~ 'foo.*' + } + end + + it "can handle case insensitive" do + node = @table[:name].does_not_match_regexp('foo.*', false) + node.case_sensitive.must_equal(false) compile(node).must_be_like %{ - "users"."name" !~ 'foo%' + "users"."name" !~* 'foo.*' } end it 'can handle subqueries' do - subquery = @table.project(:id).where(Arel::Nodes::NotRegexp.new(@table[:name], Nodes.build_quoted('foo%'))) + subquery = @table.project(:id).where(@table[:name].does_not_match_regexp('foo.*')) node = @attr.in subquery compile(node).must_be_like %{ - "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" !~ 'foo%') + "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" !~ 'foo.*') + } + end + end + + describe "Nodes::BindParam" do + it "increments each bind param" do + query = @table[:name].eq(Arel::Nodes::BindParam.new) + .and(@table[:id].eq(Arel::Nodes::BindParam.new)) + compile(query).must_be_like %{ + "users"."name" = $1 AND "users"."id" = $2 } end end diff --git a/test/visitors/test_to_sql.rb b/test/visitors/test_to_sql.rb index abd8cfe356..7ae5d5b3af 100644 --- a/test/visitors/test_to_sql.rb +++ b/test/visitors/test_to_sql.rb @@ -16,9 +16,16 @@ module Arel end it 'works with BindParams' do - node = Nodes::BindParam.new 'omg' + node = Nodes::BindParam.new sql = compile node - sql.must_be_like 'omg' + sql.must_be_like '?' + end + + it 'does not quote BindParams used as part of a Values' do + bp = Nodes::BindParam.new + values = Nodes::Values.new([bp]) + sql = compile values + sql.must_be_like 'VALUES (?)' end it 'can define a dispatch method' do @@ -29,7 +36,7 @@ module Arel end def dispatch - { Arel::Table.name => 'hello' } + { Arel::Table => 'hello' } end }.new @@ -174,6 +181,13 @@ module Arel assert_match(/LIMIT 'omg'/, compile(sc)) end + it "should contain a single space before ORDER BY" do + table = Table.new(:users) + test = table.order(table[:name]) + sql = compile test + assert_match(/"users" ORDER BY/, sql) + end + it "should quote LIMIT without column type coercion" do table = Table.new(:users) sc = table.where(table[:name].eq(0)).take(1).ast @@ -259,9 +273,9 @@ 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) + it "unsupported input should raise UnsupportedVisitError" do + error = assert_raises(UnsupportedVisitError) { compile(nil) } + assert_match(/\AUnsupported/, error.message) end it "should visit_Arel_SelectManager, which is a subquery" do @@ -284,7 +298,7 @@ module Arel end it "should visit_Arel_Nodes_Assignment" do - column = @table["id"] + column = @table["id"] node = Nodes::Assignment.new( Nodes::UnqualifiedColumn.new(column), Nodes::UnqualifiedColumn.new(column) @@ -375,33 +389,33 @@ module Arel end it 'can handle two dot ranges' do - node = @attr.in 1..3 + node = @attr.between 1..3 compile(node).must_be_like %{ "users"."id" BETWEEN 1 AND 3 } end it 'can handle three dot ranges' do - node = @attr.in 1...3 + node = @attr.between 1...3 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 + node = @attr.between 1..Float::INFINITY compile(node).must_be_like %{ "users"."id" >= 1 } - node = @attr.in(-Float::INFINITY..3) + node = @attr.between(-Float::INFINITY..3) compile(node).must_be_like %{ "users"."id" <= 3 } - node = @attr.in(-Float::INFINITY...3) + node = @attr.between(-Float::INFINITY...3) compile(node).must_be_like %{ "users"."id" < 3 } - node = @attr.in(-Float::INFINITY..Float::INFINITY) + node = @attr.between(-Float::INFINITY..Float::INFINITY) compile(node).must_be_like %{1=1} end @@ -479,33 +493,33 @@ module Arel end it 'can handle two dot ranges' do - node = @attr.not_in 1..3 - compile(node).must_be_like %{ - "users"."id" < 1 OR "users"."id" > 3 - } + node = @attr.not_between 1..3 + compile(node).must_equal( + %{("users"."id" < 1 OR "users"."id" > 3)} + ) end it 'can handle three dot ranges' do - node = @attr.not_in 1...3 - compile(node).must_be_like %{ - "users"."id" < 1 OR "users"."id" >= 3 - } + node = @attr.not_between 1...3 + compile(node).must_equal( + %{("users"."id" < 1 OR "users"."id" >= 3)} + ) end it 'can handle ranges bounded by infinity' do - node = @attr.not_in 1..Float::INFINITY + node = @attr.not_between 1..Float::INFINITY compile(node).must_be_like %{ "users"."id" < 1 } - node = @attr.not_in(-Float::INFINITY..3) + node = @attr.not_between(-Float::INFINITY..3) compile(node).must_be_like %{ "users"."id" > 3 } - node = @attr.not_in(-Float::INFINITY...3) + node = @attr.not_between(-Float::INFINITY...3) compile(node).must_be_like %{ "users"."id" >= 3 } - node = @attr.not_in(-Float::INFINITY..Float::INFINITY) + node = @attr.not_between(-Float::INFINITY..Float::INFINITY) compile(node).must_be_like %{1=0} end |