Skip to content

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

Closed
@jmlevesque333

Description

@jmlevesque333

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions