Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Arel + Rails 4.2 causing problems (bindings being lost)

Tags:

We recently upgraded to Rails 4.2 from Rails 4.1 and are seeing problems with using Arel + Activerecord because we're getting this type of error:

ActiveRecord::StatementInvalid: PG::ProtocolViolation: ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 8

Here's the code that is breaking:

customers = Customer.arel_table

      ne_subquery = ImportLog.where(
        importable_type: Customer.to_s,
        importable_id: customers['id'],
        remote_type: remote_type.to_s.singularize,
        destination: 'hello'
      ).exists.not

      first  = Customer.where(ne_subquery).where(company_id: @company.id)
      second = Customer.joins(:import_logs).merge(
        ImportLog.where(
          importable_type: Customer.to_s,
          importable_id: customers['id'],
          remote_type: remote_type.to_s.singularize,
          status: 'pending',
          destination: 'hello',
          remote_id: nil
        )
      ).where(company_id: @company.id)

      Customer.from(
        customers.create_table_alias(
          first.union(second),
          Customer.table_name
        )
      )

We figured out how to solve the first part of the query (running into the same rails bug of not having bindings) by moving the exists.not to be within Customer.where like so:

ne_subquery = ImportLog.where(
       importable_type: Customer.to_s,
       importable_id: customers['id'],
       destination: 'hello'
     )

     first  = Customer.where("NOT (EXISTS (#{ne_subquery.to_sql}))").where(company_id: @company.id)

This seemed to work but we ran into the same issue with this line of code:

first.union(second)

whenever we run this part of the query, the bindings get lost. first and second are both active record objects but as soon as we "union" them, they lose the bindings are become arel objects.

We tried cycling through the query and manually replacing the bindings but couldn't seem to get it working properly. What should we do instead?

EDIT:

We also tried extracting the bind values from first and second, and then manually replacing them in the arel object like so:

union.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
  bv = bind_values[i]
  bp.replace(Customer.connection.substitute_at(bv, i))
end

However, it fails because:

NoMethodError: undefined method `replace' for #<Arel::Nodes::BindParam:0x007f8aba6cc248>

This was a solution suggested in the rails github repo.

like image 359
Matthew Berman Avatar asked Nov 10 '15 23:11

Matthew Berman


1 Answers

I know this question is a bit old, but the error sounded familiar. I had some notes and our solution in a repository, so I thought I'd share.

The error we were receiving was:

PG::ProtocolViolation: ERROR: bind message supplies 0 parameters, but prepared statement "" requires 1

So as you can see, our situation is a bit different. We didn't have 8 bind values. However, our single bind value was still being clobbered. I changed the naming of things to keep it general.

first_level = Blog.all_comments
second_level = Comment.where(comment_id: first_level.select(:id))
third_level = Comment.where(comment_id: second_level.select(:id))

Blog.all_comments is where we have the single bind value. That's the piece we're losing.

union = first_level.union second_level
union2 = Comment.from(
  Comment.arel_table.create_table_alias union, :comments
).union third_level

relation = Comment.from(Comment.arel_table.create_table_alias union2, :comments)

We created a union much like you except that we needed to union three different queries.

To get the lost bind values at this point, we did a simple assignment. In the end, this is a little simpler of a case than yours. However, it may be helpful.

relation.bind_values = first_level.bind_values
relation

By the way, here's the GitHub issue we found while working on this. It doesn't appear to have any updates since this question was posted though.

like image 94
Derek Hopper Avatar answered Sep 19 '22 15:09

Derek Hopper