activerecord-sqlserver-adapter icon indicating copy to clipboard operation
activerecord-sqlserver-adapter copied to clipboard

Distinct with ArelExtensions on mssql returns TOP(1) 1 AS one

Open jmlevesque333 opened this issue 3 years ago • 0 comments

Issue

Doing the following query Model.distinct.to_sql returns the following query SELECT DISTINCT TOP(1) 1 AS [one] FROM [models] This happens when running with mssql on Windows with arel_extensions (https://github.com/Faveod/arel-extensions). The issue is caused by this piece of code:

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

interacting with the overriding of the == operator from ArelExtensions:

def ==(other)
  Arel::Nodes::Equality.new self, Arel::Nodes.build_quoted(other, self)
end

so when it's running the .all? block, x == ActiveRecord::FinderMethods::ONE_AS_ONE creates a query from the operator, which is truthy no matter what.

Expected behavior

Should return all distinct models

Actual behavior

Returns one model with its only attribute being 1

How to reproduce

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"
  gem "tiny_tds", "2.1.3.pre"
  gem "rails", "6.0.4.1"
  gem "activerecord", "6.0.4.1"
  gem "activerecord-sqlserver-adapter", "6.0.2"
  gem "arel_extensions", "2.1.0"
end

require "active_record"
require "minitest/autorun"
require "logger"


ENV["RAILS_ENV"] = "production"

ActiveRecord::Base.configurations = {
  "test": {
    adapter:  "sqlserver",
    timeout:  5000,
    pool:     100,
    encoding: "utf8",
    database: "test_database",
    username: "admin",
    password: "admin",
    host:     "localhost",
    port:     1433,
  }
}

ActiveRecord::Base.establish_connection

ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  drop_table :machines rescue nil
  drop_table :capabilities rescue nil

  create_table :machines, force: true do |t|
    t.string :sn

    t.timestamps
  end

  create_table :capabilities, force: true do |t|
    t.string :name
    t.belongs_to :machine

    t.timestamps
  end
end

class Machine < ActiveRecord::Base
  has_many :capabilities
end

class Capability < ActiveRecord::Base
  has_one :machine
end

class TestEerValueTest < Minitest::Test
  def setup
    capabilities = [
      {name: 'cap_1'},
      {name: 'cap_2'},
      {name: 'cap_3'},
      {name: 'cap_4'}
    ]

    machines = [
      {
        sn: 'test_1',
        capabilities: %w[cap_1 cap_2]
      },
      {
        name: 'test_2',
        capabilities: %w[cap_2 cap_3]
      },
      {
        name: 'test_3',
        capabilities: %w[cap_4]
      }
    ]

    Capability.create!(capabilities)

    machines.each do |machine|
      m = Machine.new(sn: machine[:sn])
      m.capabilities = Capability.where(name: machine[:capabilities])
      m.save!
    end
  end

  def test_count
    assert_equal 'SELECT DISTINCT TOP(1) 1 AS [one] FROM [machines]', Machine.distinct.to_sql
  end
end

Details

  • Rails version: 6.0.4.1
  • SQL Server adapter version: 6.0.2
  • TinyTDS version: 2.1.3.pre

jmlevesque333 avatar Feb 28 '22 17:02 jmlevesque333