Skip to content

Commit

Permalink
Use rails for exists clause
Browse files Browse the repository at this point in the history
rails suggests model.where(id: model.joins(:other_model).where('a=b'))

we did the same. this method expects arel coming back, that is the
reason we used the arel nodes.

using primary_attribute.in(...) botched up the in clause, so we
resorted to the longer version of declaring the in clause.

wish it was not joining to Vm, but that is the ruby produced.
  • Loading branch information
kbrock committed Apr 29, 2020
1 parent 6f099e9 commit edfe0ac
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 48 deletions.
44 changes: 20 additions & 24 deletions lib/miq_expression.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1376,13 +1376,7 @@ def to_arel(exp, tz)
ids = tag.model.find_tagged_with(:any => parsed_value, :ns => tag.namespace).pluck(:id)
tag.model.arel_attribute(:id).in(ids)
else
raise unless field.associations.one?
reflection = field.reflections.first
arel = arel_attribute.eq(parsed_value)
arel = arel.and(Arel::Nodes::SqlLiteral.new(extract_where_values(reflection.klass, reflection.scope))) if reflection.scope
field.model.arel_attribute(:id).in(
field.arel_table.where(arel).project(field.arel_table[reflection.foreign_key]).distinct
)
subquery_for_contains(field, arel_attribute.eq(parsed_value))
end
when "is"
value = parsed_value
Expand All @@ -1404,24 +1398,26 @@ def to_arel(exp, tz)
end
end

def extract_where_values(klass, scope)
relation = ActiveRecord::Relation.new(klass, klass.arel_table, klass.predicate_builder)
relation = relation.instance_eval(&scope)
def subquery_for_contains(field, limiter_query)
return limiter_query if field.reflections.empty?

begin
# This is basically ActiveRecord::Relation#to_sql, only using our
# custom visitor instance

connection = klass.connection
visitor = WhereExtractionVisitor.new(connection)

arel = relation.arel
binds = relation.bound_attributes
binds = binds.collect(&:value_for_database)
binds.map! { |value| connection.quote(value) }
collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
collect.substitute_binds(binds).join
end
# Remove the default scopes via `base_class`. The scope is already in the main query and not needed in the subquery
main_model = field.model.base_class
primary_attribute = main_model.arel_table[main_model.primary_key]

includes_associations = field.reflections.reverse.inject({}) { |i, k| {k.name => i} }
relation_query = main_model.select(primary_attribute)
.joins(includes_associations)
.where(limiter_query)

conn = main_model.connection
sql = if ActiveRecord.version.to_s >= "5.2"
conn.unprepared_statement { conn.to_sql(relation_query.arel) }
else
conn.unprepared_statement { conn.to_sql(relation_query.arel, relation_query.bound_attributes) }
end

Arel::Nodes::In.new(primary_attribute, Arel::Nodes::SqlLiteral.new(sql))
end

def self.determine_relat_path(ref)
Expand Down
21 changes: 0 additions & 21 deletions lib/miq_expression/where_extraction_visitor.rb

This file was deleted.

8 changes: 5 additions & 3 deletions spec/lib/miq_expression_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,9 @@

it "generates the SQL for a CONTAINS expression with has_many field" do
sql, * = MiqExpression.new("CONTAINS" => {"field" => "Vm.guest_applications-name", "value" => "foo"}).to_sql
expect(sql).to eq("\"vms\".\"id\" IN (SELECT DISTINCT \"guest_applications\".\"vm_or_template_id\" FROM \"guest_applications\" WHERE \"guest_applications\".\"name\" = 'foo')")
expected = "\"vms\".\"id\" IN (SELECT \"vms\".\"id\" FROM \"vms\" INNER JOIN \"guest_applications\" ON "\
"\"guest_applications\".\"vm_or_template_id\" = \"vms\".\"id\" WHERE \"guest_applications\".\"name\" = 'foo')"
expect(sql).to eq(expected)
end

it "can't generate the SQL for a CONTAINS expression with association.association-field" do
Expand Down Expand Up @@ -530,8 +532,8 @@

it "generates the SQL for a CONTAINS expression with field containing a scope" do
sql, * = MiqExpression.new("CONTAINS" => {"field" => "Vm.users-name", "value" => "foo"}).to_sql
expected = "\"vms\".\"id\" IN (SELECT DISTINCT \"accounts\".\"vm_or_template_id\" FROM \"accounts\" "\
"WHERE \"accounts\".\"name\" = 'foo' AND \"accounts\".\"accttype\" = 'user')"
expected = "\"vms\".\"id\" IN (SELECT \"vms\".\"id\" FROM \"vms\" INNER JOIN \"accounts\" ON \"accounts\".\"vm_or_template_id\" = "\
"\"vms\".\"id\" AND \"accounts\".\"accttype\" = 'user' WHERE \"accounts\".\"name\" = 'foo')"
expect(sql).to eq(expected)
end

Expand Down

0 comments on commit edfe0ac

Please sign in to comment.