# The mssql_emulate_lateral_with_apply extension converts
# queries that use LATERAL into queries that use CROSS/OUTER
# APPLY, allowing code that works on databases that support
# LATERAL via Dataset#lateral to run on Microsoft SQL Server
# and Sybase SQLAnywhere.
#
# This is available as a separate extension instead of
# integrated into the Microsoft SQL Server and Sybase
# SQLAnywhere support because few people need it and there
# is a performance hit to code that doesn't use it.
#
# It is possible there are cases where this emulation does
# not work.  Users should probably verify that correct
# results are returned when using this extension.
#
# You can load this extension into specific datasets:
#
#   ds = DB[:table]
#   ds = ds.extension(:mssql_emulate_lateral_with_apply)
#
# Or you can load it into all of a database's datasets:
#
#   DB.extension(:mssql_emulate_lateral_with_apply)

module Sequel
  module MSSQL
    module EmulateLateralWithApply
      # If the table is a dataset that uses LATERAL,
      # convert it to a CROSS APPLY if it is a INNER
      # or CROSS JOIN, and an OUTER APPLY if it is a
      # LEFT JOIN.
      def join_table(type, table, expr=nil, *)
        if table.is_a?(Dataset) && table.opts[:lateral]
          table = table.clone(:lateral=>nil)
          case type
          when :inner
            type = :cross_apply
            table = table.where(expr)
            expr = nil
          when :cross
            type = :cross_apply
          when :left, :left_outer
            type = :outer_apply
            table = table.where(expr)
            expr = nil
          end
        end
        super
      end

      # When a FROM entry uses a LATERAL subquery,
      # convert that entry into a CROSS APPLY.
      def from(*source, &block)
        virtual_row_columns(source, block)
        lateral, source = source.partition{|t| t.is_a?(Sequel::Dataset) && t.opts[:lateral] || (t.is_a?(Sequel::SQL::AliasedExpression) && t.expression.is_a?(Sequel::Dataset) && t.expression.opts[:lateral])} unless source.empty?
        return super(*source, &nil) if !lateral || lateral.empty?

        ds = from(*source)
        lateral.each do |l|
          l = if l.is_a?(Sequel::SQL::AliasedExpression)
            l.expression.clone(:lateral=>nil).as(l.aliaz)
          else
            l.clone(:lateral=>nil)
          end
          ds = ds.cross_apply(l)
        end
        ds
      end
      
      # MSSQL can emulate lateral subqueries via CROSS/OUTER APPLY
      # when using this extension.
      def supports_lateral_subqueries?
        true
      end
    end
  end

  Dataset.register_extension(:mssql_emulate_lateral_with_apply, MSSQL::EmulateLateralWithApply)
end
