class Arel::Visitors::SQLServer

Constants

BIND_BLOCK

SQLServer ToSql/Visitor (Overrides)

FETCH
FETCH0
OFFSET
ROWS
ROWS_ONLY

Private Instance Methods

bind_block() click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 19
def bind_block; BIND_BLOCK; end
collect_optimizer_hints(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 200
def collect_optimizer_hints(o, collector)
  collector
end
distinct_One_As_One_Is_So_Not_Fetch(o) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 275
def distinct_One_As_One_Is_So_Not_Fetch(o)
  core = o.cores.first
  distinct = Nodes::Distinct === core.set_quantifier
  oneasone = core.projections.all? { |x| x == ActiveRecord::FinderMethods::ONE_AS_ONE }
  limitone = [nil, 0, 1].include? node_value(o.limit)
  if distinct && oneasone && limitone && !o.offset
    core.projections = [Arel.sql("TOP(1) 1 AS [one]")]
    o.limit = nil
  end
end
make_Fetch_Possible_And_Deterministic(o) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 262
def make_Fetch_Possible_And_Deterministic(o)
  return if o.limit.nil? && o.offset.nil?

  t = table_From_Statement o
  pk = primary_Key_From_Table t
  return unless pk

  if o.orders.empty?
    # Prefer deterministic vs a simple `(SELECT NULL)` expr.
    o.orders = [pk.asc]
  end
end
node_value(node) click to toggle source

SQLServer Helpers

# File lib/arel/visitors/sqlserver.rb, line 248
def node_value(node)
  return nil unless node

  case node.expr
  when NilClass then nil
  when Numeric then node.expr
  when Arel::Nodes::Unary then node.expr.expr
  end
end
primary_Key_From_Table(t) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 297
def primary_Key_From_Table(t)
  return unless t

  primary_keys = @connection.schema_cache.primary_keys(t.name)
  column_name = nil

  case primary_keys
  when NilClass
    column_name = @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
  when String
    column_name = primary_keys
  when Array
    candidate_columns = @connection.schema_cache.columns_hash(t.name).slice(*primary_keys).values
    candidate_column = candidate_columns.find(&:is_identity?)
    candidate_column ||= candidate_columns.first
    column_name = candidate_column.try(:name)
  end

  column_name ? t[column_name] : nil
end
remote_server_table_name(o) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 318
def remote_server_table_name(o)
  ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
    "#{o.class.engine.connection.database_prefix}#{o.name}"
  ).quoted
end
remove_invalid_ordering_from_select_statement(node) click to toggle source

Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer returns error “The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.”

# File lib/arel/visitors/sqlserver.rb, line 327
def remove_invalid_ordering_from_select_statement(node)
  return unless Arel::Nodes::SelectStatement === node

  node.orders = [] unless node.offset || node.limit
end
sanitize_as_option_clause(value) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 333
def sanitize_as_option_clause(value)
  value.gsub(%r{OPTION \s* \( (.+) \)}xi, "\\1")
end
select_statement_lock?() click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 258
def select_statement_lock?
  @select_statement && @select_statement.lock
end
table_From_Statement(o) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 286
def table_From_Statement(o)
  core = o.cores.first
  if Arel::Table === core.from
    core.from
  elsif Arel::Nodes::SqlLiteral === core.from
    Arel::Table.new(core.from)
  elsif Arel::Nodes::JoinSource === core.source
    Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left.left
  end
end
visit_Arel_Nodes_Bin(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 21
def visit_Arel_Nodes_Bin(o, collector)
  visit o.expr, collector
  collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
end
visit_Arel_Nodes_Concat(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 26
def visit_Arel_Nodes_Concat(o, collector)
  visit o.left, collector
  collector << " + "
  visit o.right, collector
end
visit_Arel_Nodes_Grouping(o, collector) click to toggle source
Calls superclass method
# File lib/arel/visitors/sqlserver.rb, line 62
def visit_Arel_Nodes_Grouping(o, collector)
  remove_invalid_ordering_from_select_statement(o.expr)
  super
end
visit_Arel_Nodes_HomogeneousIn(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 67
def visit_Arel_Nodes_HomogeneousIn(o, collector)
  collector.preparable = false

  visit o.left, collector

  if o.type == :in
    collector << " IN ("
  else
    collector << " NOT IN ("
  end

  values = o.casted_values

  # Monkey-patch start.
  column_name = o.attribute.name
  column_type = o.attribute.relation.type_for_attribute(column_name)
  column_type = column_type.cast_type if column_type.is_a?(ActiveRecord::Encryption::EncryptedAttributeType) # Use cast_type on encrypted attributes. Don't encrypt them again

  if values.empty?
    collector << @connection.quote(nil)
  elsif @connection.prepared_statements && !column_type.serialized?
    # Add query attribute bindings rather than just values.
    attrs = values.map { |value| ActiveRecord::Relation::QueryAttribute.new(column_name, value, column_type) }
    collector.add_binds(attrs, &bind_block)
  else
    collector.add_binds(values, o.proc_for_binds, &bind_block)
  end
  # Monkey-patch end.

  collector << ")"
end
visit_Arel_Nodes_In(o, collector) click to toggle source
Calls superclass method
# File lib/arel/visitors/sqlserver.rb, line 190
def visit_Arel_Nodes_In(o, collector)
  if Array === o.right
    o.right.each { |node| remove_invalid_ordering_from_select_statement(node) }
  else
    remove_invalid_ordering_from_select_statement(o.right)
  end

  super
end
visit_Arel_Nodes_InnerJoin(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 160
def visit_Arel_Nodes_InnerJoin(o, collector)
  if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
    collector << "CROSS "
    visit o.left, collector
  else
    collector << "INNER JOIN "
    collector = visit o.left, collector
    collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
    if o.right
      collector << " "
      visit(o.right, collector)
    else
      collector
    end
  end
end
visit_Arel_Nodes_JoinSource(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 148
def visit_Arel_Nodes_JoinSource(o, collector)
  if o.left
    collector = visit o.left, collector
    collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
  end
  if o.right.any?
    collector << " " if o.left
    collector = inject_join o.right, collector, " "
  end
  collector
end
visit_Arel_Nodes_Lateral(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 234
def visit_Arel_Nodes_Lateral(o, collector)
  collector << "APPLY"
  collector << " "
  if o.expr.is_a?(Arel::Nodes::SelectStatement)
    collector << "("
    visit(o.expr, collector)
    collector << ")"
  else
    visit(o.expr, collector)
  end
end
visit_Arel_Nodes_Limit(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 51
def visit_Arel_Nodes_Limit(o, collector)
  if node_value(o) == 0
    collector << FETCH0
    collector << ROWS_ONLY
  else
    collector << FETCH
    visit o.expr, collector
    collector << ROWS_ONLY
  end
end
visit_Arel_Nodes_Lock(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 39
def visit_Arel_Nodes_Lock(o, collector)
  o.expr = Arel.sql("WITH(UPDLOCK)") if o.expr.to_s =~ /FOR UPDATE/
  collector << " "
  visit o.expr, collector
end
visit_Arel_Nodes_Offset(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 45
def visit_Arel_Nodes_Offset(o, collector)
  collector << OFFSET
  visit o.expr, collector
  collector << ROWS
end
visit_Arel_Nodes_OptimizerHints(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 121
def visit_Arel_Nodes_OptimizerHints(o, collector)
  hints = o.expr.map { |v| sanitize_as_option_clause(v) }.join(", ")
  collector << "OPTION (#{hints})"
end
visit_Arel_Nodes_OuterJoin(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 177
def visit_Arel_Nodes_OuterJoin(o, collector)
  if o.left.is_a?(Arel::Nodes::As) && o.left.left.is_a?(Arel::Nodes::Lateral)
    collector << "OUTER "
    visit o.left, collector
  else
    collector << "LEFT OUTER JOIN "
    collector = visit o.left, collector
    collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
    collector << " "
    visit o.right, collector
  end
end
visit_Arel_Nodes_SelectCore(o, collector) click to toggle source
Calls superclass method
# File lib/arel/visitors/sqlserver.rb, line 116
def visit_Arel_Nodes_SelectCore(o, collector)
  collector = super
  maybe_visit o.optimizer_hints, collector
end
visit_Arel_Nodes_SelectStatement(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 99
def visit_Arel_Nodes_SelectStatement(o, collector)
  @select_statement = o
  distinct_One_As_One_Is_So_Not_Fetch o
  if o.with
    collector = visit o.with, collector
    collector << " "
  end
  collector = o.cores.inject(collector) { |c, x|
    visit_Arel_Nodes_SelectCore(x, c)
  }
  collector = visit_Orders_And_Let_Fetch_Happen o, collector
  collector = visit_Make_Fetch_Happen o, collector
  collector
ensure
  @select_statement = nil
end
visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {}) click to toggle source

SQLServer ToSql/Visitor (Additions)

# File lib/arel/visitors/sqlserver.rb, line 206
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {})
  if select_statement_lock?
    collector = visit @select_statement.lock, collector
    collector << " " if options[:space]
  end
  collector
end
visit_Arel_Nodes_UpdateStatement(o, collector) click to toggle source
Calls superclass method
# File lib/arel/visitors/sqlserver.rb, line 32
def visit_Arel_Nodes_UpdateStatement(o, collector)
  if o.orders.any? && o.limit.nil?
    o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
  end
  super
end
visit_Arel_Table(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 126
def visit_Arel_Table(o, collector)
  # Apparently, o.engine.connection can actually be a different adapter
  # than sqlserver. Can be removed if fixed in ActiveRecord. See:
  # github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
  table_name =
    begin
      if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
        remote_server_table_name(o)
      else
        quote_table_name(o.name)
      end
    rescue Exception
      quote_table_name(o.name)
    end

  if o.table_alias
    collector << "#{table_name} #{quote_table_name o.table_alias}"
  else
    collector << table_name
  end
end
visit_Make_Fetch_Happen(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 227
def visit_Make_Fetch_Happen(o, collector)
  o.offset = Nodes::Offset.new(0) if o.limit && !o.offset
  collector = visit o.offset, collector if o.offset
  collector = visit o.limit, collector if o.limit
  collector
end
visit_Orders_And_Let_Fetch_Happen(o, collector) click to toggle source
# File lib/arel/visitors/sqlserver.rb, line 214
def visit_Orders_And_Let_Fetch_Happen(o, collector)
  make_Fetch_Possible_And_Deterministic o
  unless o.orders.empty?
    collector << " ORDER BY "
    len = o.orders.length - 1
    o.orders.each_with_index { |x, i|
      collector = visit(x, collector)
      collector << ", " unless len == i
    }
  end
  collector
end