Permalink
Browse files

Find and fix uncorrelated joins. https://gist.github.com/780158

  • Loading branch information...
1 parent e8d09d7 commit f5e0c39d805f04a25ca0790eb5e35e85c4458706 @metaskills metaskills committed Jan 14, 2011
Showing with 62 additions and 40 deletions.
  1. +48 −18 lib/arel/visitors/sqlserver.rb
  2. +14 −22 test/cases/scratch_test_sqlserver.rb
@@ -78,30 +78,42 @@ def visit_Arel_Nodes_LockWithSQLServer o
# SQLServer ToSql/Visitor (Additions)
def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
+ find_and_fix_uncorrelated_joins_in_select_statement(o)
core = o.cores.first
projections = core.projections
+ groups = core.groups
+ orders = o.orders
if windowed && !function_select_statement?(o)
projections = projections.map { |x| projection_without_expression(x) }
- elsif eager_limiting_select?(o)
- # TODO visit_Arel_Nodes_SelectStatementWithOutOffset - eager_limiting_select
- raise 'visit_Arel_Nodes_SelectStatementWithOutOffset - eager_limiting_select'
+ elsif eager_limiting_select_statement?(o)
+ raise "visit_Arel_Nodes_SelectStatementWithOutOffset - eager_limiting_select_statement?"
+ groups = projections.map { |x| projection_without_expression(x) }
+ projections = projections.map { |x| projection_without_expression(x) }
+ # TODO: Let's alter objects vs strings and make new order objects
+ orders = orders.map do |x|
+ Arel::SqlLiteral.new(x.split(',').reject(&:blank?).map do |c|
+ max = c =~ /desc\s*/i
+ c = clause_without_expression(c).sub(/(asc|desc)/i,'').strip
+ max ? "MAX(#{c})" : "MIN(#{c})"
+ end.join(', '))
+ end
end
[ ("SELECT" if !windowed),
(visit(o.limit) if o.limit && !windowed),
(projections.map{ |x| visit(x) }.join(', ')),
visit(core.source),
(visit(o.lock) if o.lock),
("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
- ("GROUP BY #{core.groups.map { |x| visit x }.join ', ' }" unless core.groups.empty?),
+ ("GROUP BY #{groups.map { |x| visit x }.join ', ' }" unless groups.empty?),
(visit(core.having) if core.having),
- ("ORDER BY #{o.orders.map{ |x| visit(x) }.join(', ')}" if !o.orders.empty? && !windowed)
+ ("ORDER BY #{orders.map{ |x| visit(x) }.join(', ')}" if !orders.empty? && !windowed)
].compact.join ' '
end
def visit_Arel_Nodes_SelectStatementWithOffset(o)
orders = rowtable_orders(o)
[ "SELECT",
- (visit(o.limit) if o.limit && !single_distinct_select?(o)),
+ (visit(o.limit) if o.limit && !single_distinct_select_statement?(o)),
(rowtable_projections(o).map{ |x| visit(x) }.join(', ')),
"FROM (",
"SELECT ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.uniq.join(', ')}) AS [__rn],",
@@ -115,7 +127,7 @@ def visit_Arel_Nodes_SelectStatementForComplexCount(o)
# joins = correlated_safe_joins
core = o.cores.first
orders = rowtable_orders(o)
- o.limit.expr = o.limit.expr + o.offset.expr if o.limit
+ o.limit.expr = o.limit.expr + (o.offset ? o.offset.expr : 0) if o.limit
[ "SELECT COUNT([count]) AS [count_id]",
"FROM (",
"SELECT",
@@ -140,36 +152,54 @@ def table_name_from_select_statement(o)
o.cores.first.source.left.name
end
- def single_distinct_select?(o)
+ def single_distinct_select_statement?(o)
projections = o.cores.first.projections
- projections.size == 1 && projections.first.include?('DISTINCT')
+ first_prjn = projections.first
+ projections.size == 1 &&
+ ((first_prjn.respond_to?(:distinct) && first_prjn.distinct) || first_prjn.include?('DISTINCT'))
end
def function_select_statement?(o)
core = o.cores.first
core.projections.any? { |x| Arel::Nodes::Function === x }
end
- def eager_limiting_select?(o)
- false
- # single_distinct_select?(o) && taken_only? && relation.group_clauses.blank?
+ def eager_limiting_select_statement?(o)
+ core = o.cores.first
+ single_distinct_select_statement?(o) && (o.limit && !o.offset) && core.groups.empty?
+ end
+
+ def join_in_select_statement?(o)
+ core = o.cores.first
+ core.source.right.any? { |x| Arel::Nodes::Join === x }
end
def complex_count_sql?(o)
core = o.cores.first
core.projections.size == 1 &&
Arel::Nodes::Count === core.projections.first &&
(o.limit || !core.wheres.empty?) &&
- true # TODO: This was - relation.joins(self).blank?
- # Consider visit(core.source)
- # Consider core.from
- # Consider core.froms
+ !join_in_select_statement?(o)
+ end
+
+ def find_and_fix_uncorrelated_joins_in_select_statement(o)
+ core = o.cores.first
+ return if !join_in_select_statement?(o) || core.source.right.size != 2
+ j1 = core.source.right.first
+ j2 = core.source.right.second
+ return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::StringJoin === j2
+ j1_tn = j1.left.name
+ j2_tn = j2.left.match(/JOIN \[(.*)\].*ON/).try(:[],1)
+ return unless j1_tn == j2_tn
+ crltd_tn = "#{j1_tn}_crltd"
+ j1.left.table_alias = crltd_tn
+ j1.right.expr.left.relation.table_alias = crltd_tn
end
def rowtable_projections(o)
core = o.cores.first
- if single_distinct_select?(o)
- raise 'TODO: single_distinct_select'
+ if single_distinct_select_statement?(o)
+ raise 'TODO: single_distinct_select_statement'
# ::Array.wrap(relation.select_clauses.first.dup.tap do |sc|
# sc.sub! 'DISTINCT', "DISTINCT #{taken_clause if relation.taken.present?}".strip
# sc.sub! table_name_from_select_clause(sc), '__rnt'
@@ -1,36 +1,28 @@
require 'cases/sqlserver_helper'
+require 'models/tag'
+require 'models/tagging'
require 'models/post'
+require 'models/item'
+require 'models/comment'
require 'models/author'
-require 'models/project'
-require 'models/developer'
+require 'models/category'
+require 'models/categorization'
+require 'models/vertex'
+require 'models/edge'
+require 'models/book'
+require 'models/citation'
class ScratchTestSqlserver < ActiveRecord::TestCase
- fixtures :posts, :authors, :projects, :developers
+ self.use_transactional_fixtures = false
+ fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings,
+ :author_favorites, :vertices, :items, :books, :edges
should 'pass' do
- jack = Author.new :name => "Jack"
- post = jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep"
-
- callback_log = ["before_adding<new>", "after_adding#{jack.posts_with_callbacks.first.id}"]
- assert_equal callback_log, jack.post_log
- assert jack.save
-
- # SELECT COUNT([count]) AS [count_id]
- # FROM (
- # SELECT ROW_NUMBER() OVER (ORDER BY [posts].[id] ASC) AS [__rn],
- # 1 AS [count]
- # FROM [posts]
- # WHERE ([posts].author_id = 3)
- # ) AS [__rnt] NULL
-
- assert_equal 1, jack.posts_with_callbacks.count
- assert_equal callback_log, jack.post_log
+ assert_equal 1, posts(:welcome).tags.count
end
-
-
end

0 comments on commit f5e0c39

Please sign in to comment.