diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index b6f9bf5fef..0beee4c7ef 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,9 +1,13 @@ +* Remove ibm_db, informix, mssql, oracle, and oracle12 Arel visitors which are not used in the code base. + + *Ryuta Kamizono* + * Prevent `build_association` from `touching` a parent record if the record isn't persisted for `has_one` associations. Fixes #38219 *Josh Brody* - + * Add support for `if_not_exists` option for adding index. The `add_index` method respects `if_not_exists` option. If it is set to true @@ -569,4 +573,5 @@ *Michael Duchemin* + Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/lib/arel/visitors.rb b/activerecord/lib/arel/visitors.rb index a1097f6750..954f01d3a5 100644 --- a/activerecord/lib/arel/visitors.rb +++ b/activerecord/lib/arel/visitors.rb @@ -5,13 +5,8 @@ require "arel/visitors/to_sql" require "arel/visitors/sqlite" require "arel/visitors/postgresql" require "arel/visitors/mysql" -require "arel/visitors/mssql" -require "arel/visitors/oracle" -require "arel/visitors/oracle12" require "arel/visitors/where_sql" require "arel/visitors/dot" -require "arel/visitors/ibm_db" -require "arel/visitors/informix" module Arel # :nodoc: all module Visitors diff --git a/activerecord/lib/arel/visitors/ibm_db.rb b/activerecord/lib/arel/visitors/ibm_db.rb deleted file mode 100644 index 5cf958f5f0..0000000000 --- a/activerecord/lib/arel/visitors/ibm_db.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Arel # :nodoc: all - module Visitors - class IBM_DB < Arel::Visitors::ToSql - private - def visit_Arel_Nodes_SelectCore(o, collector) - collector = super - maybe_visit o.optimizer_hints, collector - end - - def visit_Arel_Nodes_OptimizerHints(o, collector) - hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join - collector << "/* #{hints} */" - end - - def visit_Arel_Nodes_Limit(o, collector) - collector << "FETCH FIRST " - collector = visit o.expr, collector - collector << " ROWS ONLY" - end - - def is_distinct_from(o, collector) - collector << "DECODE(" - collector = visit [o.left, o.right, 0, 1], collector - collector << ")" - end - - def collect_optimizer_hints(o, collector) - collector - end - end - end -end diff --git a/activerecord/lib/arel/visitors/informix.rb b/activerecord/lib/arel/visitors/informix.rb deleted file mode 100644 index 1a4ad1c8d8..0000000000 --- a/activerecord/lib/arel/visitors/informix.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -module Arel # :nodoc: all - module Visitors - class Informix < Arel::Visitors::ToSql - private - def visit_Arel_Nodes_SelectStatement(o, collector) - collector << "SELECT " - collector = maybe_visit o.offset, collector - collector = maybe_visit o.limit, collector - collector = o.cores.inject(collector) { |c, x| - visit_Arel_Nodes_SelectCore x, c - } - if o.orders.any? - collector << "ORDER BY " - collector = inject_join o.orders, collector, ", " - end - maybe_visit o.lock, collector - end - - def visit_Arel_Nodes_SelectCore(o, collector) - collector = inject_join o.projections, collector, ", " - if o.source && !o.source.empty? - collector << " FROM " - collector = visit o.source, collector - end - - if o.wheres.any? - collector << " WHERE " - collector = inject_join o.wheres, collector, " AND " - end - - if o.groups.any? - collector << "GROUP BY " - collector = inject_join o.groups, collector, ", " - end - - if o.havings.any? - collector << " HAVING " - collector = inject_join o.havings, collector, " AND " - end - collector - end - - def visit_Arel_Nodes_OptimizerHints(o, collector) - hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ") - collector << "/*+ #{hints} */" - end - - def visit_Arel_Nodes_Offset(o, collector) - collector << "SKIP " - visit o.expr, collector - end - - def visit_Arel_Nodes_Limit(o, collector) - collector << "FIRST " - visit o.expr, collector - collector << " " - end - end - end -end diff --git a/activerecord/lib/arel/visitors/mssql.rb b/activerecord/lib/arel/visitors/mssql.rb deleted file mode 100644 index 92eb94f802..0000000000 --- a/activerecord/lib/arel/visitors/mssql.rb +++ /dev/null @@ -1,156 +0,0 @@ -# frozen_string_literal: true - -module Arel # :nodoc: all - module Visitors - class MSSQL < Arel::Visitors::ToSql - RowNumber = Struct.new :children - - def initialize(*) - @primary_keys = {} - super - end - - private - def visit_Arel_Nodes_IsNotDistinctFrom(o, collector) - right = o.right - - if right.nil? - collector = visit o.left, collector - collector << " IS NULL" - else - collector << "EXISTS (VALUES (" - collector = visit o.left, collector - collector << ") INTERSECT VALUES (" - collector = visit right, collector - collector << "))" - end - end - - def visit_Arel_Nodes_IsDistinctFrom(o, collector) - if o.right.nil? - collector = visit o.left, collector - collector << " IS NOT NULL" - else - collector << "NOT " - visit_Arel_Nodes_IsNotDistinctFrom o, collector - end - end - - def visit_Arel_Visitors_MSSQL_RowNumber(o, collector) - collector << "ROW_NUMBER() OVER (ORDER BY " - inject_join(o.children, collector, ", ") << ") as _row_num" - end - - def visit_Arel_Nodes_SelectStatement(o, collector) - if !o.limit && !o.offset - return super - end - - is_select_count = false - o.cores.each { |x| - core_order_by = row_num_literal determine_order_by(o.orders, x) - if select_count? x - x.projections = [core_order_by] - is_select_count = true - else - x.projections << core_order_by - end - } - - if is_select_count - # fixme count distinct wouldn't work with limit or offset - collector << "SELECT COUNT(1) as count_id FROM (" - end - - collector << "SELECT _t.* FROM (" - collector = o.cores.inject(collector) { |c, x| - visit_Arel_Nodes_SelectCore x, c - } - collector << ") as _t WHERE #{get_offset_limit_clause(o)}" - - if is_select_count - collector << ") AS subquery" - else - collector - end - end - - def visit_Arel_Nodes_SelectCore(o, collector) - collector = super - maybe_visit o.optimizer_hints, collector - end - - def visit_Arel_Nodes_OptimizerHints(o, collector) - hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ") - collector << "OPTION (#{hints})" - end - - def get_offset_limit_clause(o) - first_row = o.offset ? o.offset.expr.to_i + 1 : 1 - last_row = o.limit ? o.limit.expr.to_i - 1 + first_row : nil - if last_row - " _row_num BETWEEN #{first_row} AND #{last_row}" - else - " _row_num >= #{first_row}" - end - end - - def visit_Arel_Nodes_DeleteStatement(o, collector) - collector << "DELETE " - if o.limit - collector << "TOP (" - visit o.limit.expr, collector - collector << ") " - end - collector << "FROM " - collector = visit o.relation, collector - if o.wheres.any? - collector << " WHERE " - inject_join o.wheres, collector, " AND " - else - collector - end - end - - def collect_optimizer_hints(o, collector) - collector - end - - def determine_order_by(orders, x) - if orders.any? - orders - elsif x.groups.any? - x.groups - else - pk = find_left_table_pk(x.froms) - pk ? [pk] : [] - end - end - - def row_num_literal(order_by) - RowNumber.new order_by - end - - def select_count?(x) - x.projections.length == 1 && Arel::Nodes::Count === x.projections.first - end - - # FIXME raise exception of there is no pk? - def find_left_table_pk(o) - if o.kind_of?(Arel::Nodes::Join) - find_left_table_pk(o.left) - elsif o.instance_of?(Arel::Table) - find_primary_key(o) - end - end - - def find_primary_key(o) - @primary_keys[o.name] ||= begin - primary_key_name = @connection.primary_key(o.name) - # some tables might be without primary key - primary_key_name && o[primary_key_name] - end - end - end - end -end diff --git a/activerecord/lib/arel/visitors/oracle.rb b/activerecord/lib/arel/visitors/oracle.rb deleted file mode 100644 index a0a74d365c..0000000000 --- a/activerecord/lib/arel/visitors/oracle.rb +++ /dev/null @@ -1,158 +0,0 @@ -# frozen_string_literal: true - -module Arel # :nodoc: all - module Visitors - class Oracle < Arel::Visitors::ToSql - private - def visit_Arel_Nodes_SelectStatement(o, collector) - o = order_hacks(o) - - # if need to select first records without ORDER BY and GROUP BY and without DISTINCT - # then can use simple ROWNUM in WHERE clause - if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && !o.cores.first.set_quantifier.class.to_s.match?(/Distinct/) - o.cores.last.wheres.push Nodes::LessThanOrEqual.new( - Nodes::SqlLiteral.new("ROWNUM"), o.limit.expr - ) - return super - end - - if o.limit && o.offset - o = o.dup - limit = o.limit.expr - offset = o.offset - o.offset = nil - collector << " - SELECT * FROM ( - SELECT raw_sql_.*, rownum raw_rnum_ - FROM (" - - collector = super(o, collector) - - if offset.expr.is_a? Nodes::BindParam - collector << ") raw_sql_ WHERE rownum <= (" - collector = visit offset.expr, collector - collector << " + " - collector = visit limit, collector - collector << ") ) WHERE raw_rnum_ > " - collector = visit offset.expr, collector - return collector - else - collector << ") raw_sql_ - WHERE rownum <= #{offset.expr.to_i + limit} - ) - WHERE " - return visit(offset, collector) - end - end - - if o.limit - o = o.dup - limit = o.limit.expr - collector << "SELECT * FROM (" - collector = super(o, collector) - collector << ") WHERE ROWNUM <= " - return visit limit, collector - end - - if o.offset - o = o.dup - offset = o.offset - o.offset = nil - collector << "SELECT * FROM ( - SELECT raw_sql_.*, rownum raw_rnum_ - FROM (" - collector = super(o, collector) - collector << ") raw_sql_ - ) - WHERE " - return visit offset, collector - end - - super - end - - def visit_Arel_Nodes_Limit(o, collector) - collector - end - - def visit_Arel_Nodes_Offset(o, collector) - collector << "raw_rnum_ > " - visit o.expr, collector - end - - def visit_Arel_Nodes_Except(o, collector) - collector << "( " - collector = infix_value o, collector, " MINUS " - collector << " )" - end - - def visit_Arel_Nodes_UpdateStatement(o, collector) - # Oracle does not allow ORDER BY/LIMIT in UPDATEs. - if o.orders.any? && o.limit.nil? - # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided, - # otherwise let the user deal with the error - o = o.dup - o.orders = [] - end - - super - end - - ### - # Hacks for the order clauses specific to Oracle - def order_hacks(o) - return o if o.orders.empty? - return o unless o.cores.any? do |core| - core.projections.any? do |projection| - /FIRST_VALUE/ === projection - end - end - # Previous version with join and split broke ORDER BY clause - # if it contained functions with several arguments (separated by ','). - # - # orders = o.orders.map { |x| visit x }.join(', ').split(',') - orders = o.orders.map do |x| - string = visit(x, Arel::Collectors::SQLString.new).value - if string.include?(",") - split_order_string(string) - else - string - end - end.flatten - o.orders = [] - orders.each_with_index do |order, i| - o.orders << - Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i.match?(order)}") - end - o - end - - # Split string by commas but count opening and closing brackets - # and ignore commas inside brackets. - def split_order_string(string) - array = [] - i = 0 - string.split(",").each do |part| - if array[i] - array[i] << "," << part - else - # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral - array[i] = part.to_s - end - i += 1 if array[i].count("(") == array[i].count(")") - end - array - end - - def visit_Arel_Nodes_BindParam(o, collector) - collector.add_bind(o.value) { |i| ":a#{i}" } - end - - def is_distinct_from(o, collector) - collector << "DECODE(" - collector = visit [o.left, o.right, 0, 1], collector - collector << ")" - end - end - end -end diff --git a/activerecord/lib/arel/visitors/oracle12.rb b/activerecord/lib/arel/visitors/oracle12.rb deleted file mode 100644 index 36783243b5..0000000000 --- a/activerecord/lib/arel/visitors/oracle12.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -module Arel # :nodoc: all - module Visitors - class Oracle12 < Arel::Visitors::ToSql - private - def visit_Arel_Nodes_SelectStatement(o, collector) - # Oracle does not allow LIMIT clause with select for update - if o.limit && o.lock - raise ArgumentError, <<~MSG - Combination of limit and lock is not supported. Because generated SQL statements - `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014. - MSG - end - super - end - - def visit_Arel_Nodes_SelectOptions(o, collector) - collector = maybe_visit o.offset, collector - collector = maybe_visit o.limit, collector - maybe_visit o.lock, collector - end - - def visit_Arel_Nodes_Limit(o, collector) - collector << "FETCH FIRST " - collector = visit o.expr, collector - collector << " ROWS ONLY" - end - - def visit_Arel_Nodes_Offset(o, collector) - collector << "OFFSET " - visit o.expr, collector - collector << " ROWS" - end - - def visit_Arel_Nodes_Except(o, collector) - collector << "( " - collector = infix_value o, collector, " MINUS " - collector << " )" - end - - def visit_Arel_Nodes_UpdateStatement(o, collector) - # Oracle does not allow ORDER BY/LIMIT in UPDATEs. - if o.orders.any? && o.limit.nil? - # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided, - # otherwise let the user deal with the error - o = o.dup - o.orders = [] - end - - super - end - - def visit_Arel_Nodes_BindParam(o, collector) - collector.add_bind(o.value) { |i| ":a#{i}" } - end - - def is_distinct_from(o, collector) - collector << "DECODE(" - collector = visit [o.left, o.right, 0, 1], collector - collector << ")" - end - end - end -end diff --git a/activerecord/test/cases/arel/visitors/ibm_db_test.rb b/activerecord/test/cases/arel/visitors/ibm_db_test.rb deleted file mode 100644 index aece03eb7a..0000000000 --- a/activerecord/test/cases/arel/visitors/ibm_db_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -require_relative "../helper" - -module Arel - module Visitors - class IbmDbTest < Arel::Spec - before do - @visitor = IBM_DB.new Table.engine.connection - end - - def compile(node) - @visitor.accept(node, Collectors::SQLString.new).value - end - - it "uses FETCH FIRST n ROWS to limit results" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(1) - sql = compile(stmt) - _(sql).must_be_like "SELECT FETCH FIRST 1 ROWS ONLY" - end - - it "uses FETCH FIRST n ROWS in updates with a limit" do - table = Table.new(:users) - stmt = Nodes::UpdateStatement.new - stmt.relation = table - stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1)) - stmt.key = table[:id] - sql = compile(stmt) - _(sql).must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT \"users\".\"id\" FROM \"users\" FETCH FIRST 1 ROWS ONLY)" - end - - describe "Nodes::IsNotDistinctFrom" do - it "should construct a valid generic SQL statement" do - test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson" - _(compile(test)).must_be_like %{ - DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0 - } - end - - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - DECODE("users"."first_name", "users"."last_name", 0, 1) = 0 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NULL } - end - end - - describe "Nodes::IsDistinctFrom" do - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - DECODE("users"."first_name", "users"."last_name", 0, 1) = 1 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NOT NULL } - end - end - end - end -end diff --git a/activerecord/test/cases/arel/visitors/informix_test.rb b/activerecord/test/cases/arel/visitors/informix_test.rb deleted file mode 100644 index 37b91dec54..0000000000 --- a/activerecord/test/cases/arel/visitors/informix_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -# frozen_string_literal: true - -require_relative "../helper" - -module Arel - module Visitors - class InformixTest < Arel::Spec - before do - @visitor = Informix.new Table.engine.connection - end - - def compile(node) - @visitor.accept(node, Collectors::SQLString.new).value - end - - it "uses FIRST n to limit results" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(1) - sql = compile(stmt) - _(sql).must_be_like "SELECT FIRST 1" - end - - it "uses FIRST n in updates with a limit" do - table = Table.new(:users) - stmt = Nodes::UpdateStatement.new - stmt.relation = table - stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1)) - stmt.key = table[:id] - sql = compile(stmt) - _(sql).must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT FIRST 1 \"users\".\"id\" FROM \"users\")" - end - - it "uses SKIP n to jump results" do - stmt = Nodes::SelectStatement.new - stmt.offset = Nodes::Offset.new(10) - sql = compile(stmt) - _(sql).must_be_like "SELECT SKIP 10" - end - - it "uses SKIP before FIRST" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(1) - stmt.offset = Nodes::Offset.new(1) - sql = compile(stmt) - _(sql).must_be_like "SELECT SKIP 1 FIRST 1" - end - - it "uses INNER JOIN to perform joins" do - core = Nodes::SelectCore.new - table = Table.new(:posts) - core.source = Nodes::JoinSource.new(table, [table.create_join(Table.new(:comments))]) - - stmt = Nodes::SelectStatement.new([core]) - sql = compile(stmt) - _(sql).must_be_like 'SELECT FROM "posts" INNER JOIN "comments"' - end - - describe "Nodes::IsNotDistinctFrom" do - it "should construct a valid generic SQL statement" do - test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson" - _(compile(test)).must_be_like %{ - CASE WHEN "users"."name" = 'Aaron Patterson' OR ("users"."name" IS NULL AND 'Aaron Patterson' IS NULL) THEN 0 ELSE 1 END = 0 - } - end - - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 0 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NULL } - end - end - - describe "Nodes::IsDistinctFrom" do - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 1 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NOT NULL } - end - end - end - end -end diff --git a/activerecord/test/cases/arel/visitors/mssql_test.rb b/activerecord/test/cases/arel/visitors/mssql_test.rb deleted file mode 100644 index 70981d3717..0000000000 --- a/activerecord/test/cases/arel/visitors/mssql_test.rb +++ /dev/null @@ -1,138 +0,0 @@ -# frozen_string_literal: true - -require_relative "../helper" - -module Arel - module Visitors - class MssqlTest < Arel::Spec - before do - @visitor = MSSQL.new Table.engine.connection - @table = Arel::Table.new "users" - end - - def compile(node) - @visitor.accept(node, Collectors::SQLString.new).value - end - - it "should not modify query if no offset or limit" do - stmt = Nodes::SelectStatement.new - sql = compile(stmt) - _(sql).must_be_like "SELECT" - end - - it "should go over table PK if no .order() or .group()" do - stmt = Nodes::SelectStatement.new - stmt.cores.first.from = @table - stmt.limit = Nodes::Limit.new(10) - sql = compile(stmt) - _(sql).must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY \"users\".\"id\") as _row_num FROM \"users\") as _t WHERE _row_num BETWEEN 1 AND 10" - end - - it "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) - stmt.orders << Nodes::SqlLiteral.new("order_by") - sql = compile(stmt) - _(sql).must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY order_by) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10" - end - - it "should go over query GROUP BY if no .order() and there is .group()" do - stmt = Nodes::SelectStatement.new - stmt.cores.first.groups << Nodes::SqlLiteral.new("group_by") - stmt.limit = Nodes::Limit.new(10) - sql = compile(stmt) - _(sql).must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY group_by) as _row_num GROUP BY group_by) as _t WHERE _row_num BETWEEN 1 AND 10" - end - - it "should use BETWEEN if both .limit() and .offset" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(10) - stmt.offset = Nodes::Offset.new(20) - sql = compile(stmt) - _(sql).must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 21 AND 30" - end - - it "should use >= if only .offset" do - stmt = Nodes::SelectStatement.new - stmt.offset = Nodes::Offset.new(20) - sql = compile(stmt) - _(sql).must_be_like "SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num >= 21" - end - - it "should generate subquery for .count" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(10) - stmt.cores.first.projections << Nodes::Count.new("*") - sql = compile(stmt) - _(sql).must_be_like "SELECT COUNT(1) as count_id FROM (SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10) AS subquery" - end - - describe "Nodes::IsNotDistinctFrom" do - it "should construct a valid generic SQL statement" do - test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson" - _(compile(test)).must_be_like %{ - EXISTS (VALUES ("users"."name") INTERSECT VALUES ('Aaron Patterson')) - } - end - - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - EXISTS (VALUES ("users"."first_name") INTERSECT VALUES ("users"."last_name")) - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NULL } - end - end - - describe "Nodes::IsDistinctFrom" do - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - NOT EXISTS (VALUES ("users"."first_name") INTERSECT VALUES ("users"."last_name")) - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NOT NULL } - end - end - end - end -end diff --git a/activerecord/test/cases/arel/visitors/oracle12_test.rb b/activerecord/test/cases/arel/visitors/oracle12_test.rb deleted file mode 100644 index 7032cdd53e..0000000000 --- a/activerecord/test/cases/arel/visitors/oracle12_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -require_relative "../helper" - -module Arel - module Visitors - class Oracle12Test < Arel::Spec - before do - @visitor = Oracle12.new Table.engine.connection - @table = Table.new(:users) - 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 "generates ArgumentError if limit and lock are used" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(10) - stmt.lock = Nodes::Lock.new(Arel.sql("FOR UPDATE")) - assert_raises ArgumentError do - compile(stmt) - end - 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 - - describe "Nodes::BindParam" do - it "increments each bind param" do - query = @table[:name].eq(Arel::Nodes::BindParam.new(1)) - .and(@table[:id].eq(Arel::Nodes::BindParam.new(1))) - _(compile(query)).must_be_like %{ - "users"."name" = :a1 AND "users"."id" = :a2 - } - end - end - - describe "Nodes::IsNotDistinctFrom" do - it "should construct a valid generic SQL statement" do - test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson" - _(compile(test)).must_be_like %{ - DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0 - } - end - - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - DECODE("users"."first_name", "users"."last_name", 0, 1) = 0 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NULL } - end - end - - describe "Nodes::IsDistinctFrom" do - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - DECODE("users"."first_name", "users"."last_name", 0, 1) = 1 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NOT NULL } - end - end - end - end -end diff --git a/activerecord/test/cases/arel/visitors/oracle_test.rb b/activerecord/test/cases/arel/visitors/oracle_test.rb deleted file mode 100644 index 6fe8eaec64..0000000000 --- a/activerecord/test/cases/arel/visitors/oracle_test.rb +++ /dev/null @@ -1,236 +0,0 @@ -# frozen_string_literal: true - -require_relative "../helper" - -module Arel - module Visitors - class OracleTest < Arel::Spec - before do - @visitor = Oracle.new Table.engine.connection - @table = Table.new(:users) - end - - def compile(node) - @visitor.accept(node, Collectors::SQLString.new).value - end - - it "modifies order when there is distinct and first value" do - # *sigh* - select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__" - stmt = Nodes::SelectStatement.new - stmt.cores.first.projections << Nodes::SqlLiteral.new(select) - stmt.orders << Nodes::SqlLiteral.new("foo") - sql = compile(stmt) - _(sql).must_be_like %{ - SELECT #{select} ORDER BY alias_0__ - } - end - - it "is idempotent with crazy query" do - # *sigh* - select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__" - stmt = Nodes::SelectStatement.new - stmt.cores.first.projections << Nodes::SqlLiteral.new(select) - stmt.orders << Nodes::SqlLiteral.new("foo") - - sql = compile(stmt) - sql2 = compile(stmt) - _(sql).must_equal sql2 - end - - it "splits orders with commas" do - # *sigh* - select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__" - stmt = Nodes::SelectStatement.new - stmt.cores.first.projections << Nodes::SqlLiteral.new(select) - stmt.orders << Nodes::SqlLiteral.new("foo, bar") - sql = compile(stmt) - _(sql).must_be_like %{ - SELECT #{select} ORDER BY alias_0__, alias_1__ - } - end - - it "splits orders with commas and function calls" do - # *sigh* - select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__" - stmt = Nodes::SelectStatement.new - stmt.cores.first.projections << Nodes::SqlLiteral.new(select) - stmt.orders << Nodes::SqlLiteral.new("NVL(LOWER(bar, foo), foo) DESC, UPPER(baz)") - sql = compile(stmt) - _(sql).must_be_like %{ - SELECT #{select} ORDER BY alias_0__ DESC, alias_1__ - } - end - - describe "Nodes::SelectStatement" do - describe "limit" do - it "adds a rownum clause" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(10) - sql = compile stmt - _(sql).must_be_like %{ SELECT WHERE ROWNUM <= 10 } - end - - it "is idempotent" do - stmt = Nodes::SelectStatement.new - stmt.orders << Nodes::SqlLiteral.new("foo") - stmt.limit = Nodes::Limit.new(10) - sql = compile stmt - sql2 = compile stmt - _(sql).must_equal sql2 - end - - it "creates a subquery when there is order_by" do - stmt = Nodes::SelectStatement.new - stmt.orders << Nodes::SqlLiteral.new("foo") - stmt.limit = Nodes::Limit.new(10) - sql = compile stmt - _(sql).must_be_like %{ - SELECT * FROM (SELECT ORDER BY foo ) WHERE ROWNUM <= 10 - } - end - - it "creates a subquery when there is group by" do - stmt = Nodes::SelectStatement.new - stmt.cores.first.groups << Nodes::SqlLiteral.new("foo") - stmt.limit = Nodes::Limit.new(10) - sql = compile stmt - _(sql).must_be_like %{ - SELECT * FROM (SELECT GROUP BY foo ) WHERE ROWNUM <= 10 - } - end - - it "creates a subquery when there is DISTINCT" do - stmt = Nodes::SelectStatement.new - stmt.cores.first.set_quantifier = Arel::Nodes::Distinct.new - stmt.cores.first.projections << Nodes::SqlLiteral.new("id") - stmt.limit = Arel::Nodes::Limit.new(10) - sql = compile stmt - _(sql).must_be_like %{ - SELECT * FROM (SELECT DISTINCT id ) WHERE ROWNUM <= 10 - } - end - - it "creates a different subquery when there is an offset" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(10) - stmt.offset = Nodes::Offset.new(10) - sql = compile stmt - _(sql).must_be_like %{ - SELECT * FROM ( - SELECT raw_sql_.*, rownum raw_rnum_ - FROM (SELECT ) raw_sql_ - WHERE rownum <= 20 - ) - WHERE raw_rnum_ > 10 - } - end - - it "creates a subquery when there is limit and offset with BindParams" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(Nodes::BindParam.new(1)) - stmt.offset = Nodes::Offset.new(Nodes::BindParam.new(1)) - sql = compile stmt - _(sql).must_be_like %{ - SELECT * FROM ( - SELECT raw_sql_.*, rownum raw_rnum_ - FROM (SELECT ) raw_sql_ - WHERE rownum <= (:a1 + :a2) - ) - WHERE raw_rnum_ > :a3 - } - end - - it "is idempotent with different subquery" do - stmt = Nodes::SelectStatement.new - stmt.limit = Nodes::Limit.new(10) - stmt.offset = Nodes::Offset.new(10) - sql = compile stmt - sql2 = compile stmt - _(sql).must_equal sql2 - end - end - - describe "only offset" do - it "creates a select from subquery with rownum condition" do - stmt = Nodes::SelectStatement.new - stmt.offset = Nodes::Offset.new(10) - sql = compile stmt - _(sql).must_be_like %{ - SELECT * FROM ( - SELECT raw_sql_.*, rownum raw_rnum_ - FROM (SELECT) raw_sql_ - ) - WHERE raw_rnum_ > 10 - } - end - end - 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 - - describe "locking" do - 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 - - describe "Nodes::BindParam" do - it "increments each bind param" do - query = @table[:name].eq(Arel::Nodes::BindParam.new(1)) - .and(@table[:id].eq(Arel::Nodes::BindParam.new(1))) - _(compile(query)).must_be_like %{ - "users"."name" = :a1 AND "users"."id" = :a2 - } - end - end - - describe "Nodes::IsNotDistinctFrom" do - it "should construct a valid generic SQL statement" do - test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson" - _(compile(test)).must_be_like %{ - DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0 - } - end - - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - DECODE("users"."first_name", "users"."last_name", 0, 1) = 0 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NULL } - end - end - - describe "Nodes::IsDistinctFrom" do - it "should handle column names on both sides" do - test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name] - _(compile(test)).must_be_like %{ - DECODE("users"."first_name", "users"."last_name", 0, 1) = 1 - } - end - - it "should handle nil" do - @table = Table.new(:users) - val = Nodes.build_quoted(nil, @table[:active]) - sql = compile Nodes::IsDistinctFrom.new(@table[:name], val) - _(sql).must_be_like %{ "users"."name" IS NOT NULL } - end - end - end - end -end