Remove unused Arel visitors in the code base

This removes ibm_db, informix, mssql, oracle, and oracle12 Arel visitors
which are not used in the code base.

Actually oracle and oracle12 visitors are used at oracle-enhanced
adapter, but now I think that those visitors should be in the adapter's
repo like sqlserver adapter and the dedicated Arel visitor
(https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/master/lib/arel/visitors/sqlserver.rb),
otherwise it is hard to find a bug and review PRs for the oracle
visitors (e.g. #35838, #37646), since we don't have knowledge and
environment enough for Oracle.
This commit is contained in:
Ryuta Kamizono 2020-04-14 23:33:56 +09:00
parent a2152c0b6b
commit a2040ee329
12 changed files with 6 additions and 1126 deletions

View File

@ -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.

View File

@ -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

View File

@ -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 << "/* <OPTGUIDELINES>#{hints}</OPTGUIDELINES> */"
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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