Rails exec_query bindings ignored - ruby-on-rails

I'm trying to use exec_query to run an arbitrary query, with values brought in through bindings, and am getting unexpected errors.
Running this in the console
sql = 'SELECT * FROM foobars WHERE id IN (?)'
name = 'query_name_placeholder'
binds = [FooBar.first]
ActiveRecord::Base.connection.exec_query sql, name, binds
Yields this error:
Account Load (7.9ms) SELECT "foobars".* FROM "foobars" ORDER BY "foobars"."id" ASC LIMIT 1
PG::SyntaxError: ERROR: syntax error at or near ")"
LINE 1: SELECT * FROM foobars WHERE id IN (?)
^
: SELECT * FROM foobars WHERE id IN (?)
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near ")"
LINE 1: SELECT * FROM foobars WHERE id IN (?)
^
: SELECT * FROM accounts WHERE id IN (?)
from /Users/foo_user/.rvm/gems/ruby-2.2.4#foo_project/gems/activerecord-4.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb:641:in `prepare'
It appears the binding syntax is being ignored? I've tried ... WHERE id = ? as well, but to no avail.

mu is too short got you part of the way there. For reference, here is the method's documentation: https://apidock.com/rails/ActiveRecord/ConnectionAdapters/DatabaseStatements/exec_query
He's right in that you will need to use the underlying database's binds syntax to set bind variables in the SQL string. For Oracle this is :1, :2 for PostgreSQL this is $1, $2... so that's step one.
Step two is you need to build bind objects, which are QueryAttribute objects, not just values to be passed in. This is a bit clunky, but here's an example:
binds = [ ActiveRecord::Relation::QueryAttribute.new(
"id", 6, ActiveRecord::Type::Integer.new
)]
ApplicationRecord.connection.exec_query(
'SELECT * FROM users WHERE id = $1', 'sql', binds
)
I just spent a whole day going through unit tests and source code trying to figure that out.

Related

Executing a SQL query with an `IN` clause from Rails code

I know precious nothing abour Rails, so please excuse my naivete about this question.
I'm trying to modify a piece of code that I got from somewhere to make it execute it for a randomly selected bunch of users. Here it goes:
users = RedshiftRecord.connection.execute(<<~SQL
select distinct user_id
from tablename
order by random()
limit 1000
SQL
).to_a
sql = 'select user_id, count(*) from tablename where user_id in (?) group by user_id'
<Library>.on_replica(:something) do
Something::SomethingElse.
connection.
exec_query(sql, users.join(',')).to_h
end
This gives me the following error:
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near ")"
LINE 1: ...ount(*) from tablename where user_id in (?) group by...
^
Users is an array, I know this coz I executed the following and it resulted in true:
p users.instance_of? Array
Would someone please help me execute this code? I want to execute a simple SQL query that would look like this:
select user_id, count(*) from tablename where user_id in (user1,user2,...,user1000) group by user_id
The problem here is that IN takes a list of parameters. Using a single bind IN (?) and a comma separated string will not magically turn it into a list of arguments. Thats just not how SQL works.
What you want is:
where user_id in (?, ?, ?, ...)
Where the number of binds matches the length of the array you want to pass.
The simple but hacky way to do this would be just interpolate in n number of question marks into the SQL string:
binds = Array.new(users.length, '?').join(',')
sql = <<~SQL
select user_id, count(*)
from tablename
where user_id in (#{binds)})
group by user_id'
SQL
<Library>.on_replica(:something) do
Something::SomethingElse.
connection.
exec_query(sql, users).to_h
end
But you would typically do this in a Rails app by creating a model and using the ActiveRecord query interface or using Arel to programatically create the SQL query.

Postgres weird PG::UndefinedColumn: ERROR: on value

While executing a simple select - where operation using activerecord execute,
ActiveRecord::Base.connection.execute('select * from spree_variants where sku = "1SB-E4196-00";')
I got this error:
from /Users/abc/.rvm/gems/ruby-2.7.2#cboparts/gems/activerecord-6.0.3.5/lib/active_record/connection_adapters/postgresql/database_statements.rb:92:in `exec'
Caused by PG::UndefinedColumn: ERROR: column "1SB-E4196-00" does not exist
LINE 1: select * from spree_variants where sku = "1SB-E4196-00";
Why it is considering "1SB-E4196-00" as a column but not SKU? The error seems misleading.
Because PostgreSQL expects strings to be bounded in single quotes. While double quotes have a different meaning:
There is a second kind of identifier: the delimited identifier or quoted identifier. It is formed by enclosing an arbitrary sequence of characters in double-quotes ("). A delimited identifier is always an identifier, never a key word.
That means if the following query should work:
ActiveRecord::Base.connection.execute(
"select * from spree_variants where sku = '1SB-E4196-00';"
)
Btw you if you are using Rails and have a SpreeVariant model then you can see in the console how Rails formats and escapes the query like this:
puts SpreeVariant.where(sku: '1SB-E4196-00').to_sql

SQL statement working in Sqlite and not working in PostgreSQL why?

Showing /app/app/views/admin/business/_business_update_form.html.erb where line #31 raised:
PG::UndefinedFunction: ERROR: operator does not exist: character varying = integer
LINE 1: SELECT name, id FROM categories WHERE parent = 1
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT name, id FROM categories WHERE parent = 1
Extracted source (around line #10):
parent = object.category.parent
sql = "SELECT name, id FROM categories WHERE parent = " + parent.to_s
Category.find_by_sql(sql)
end
Any solution. Please help.
It's much better to use the Rails method of doing things with ActiveRecord. That way Rails creates db queries that run on any db (sqlite, pg or mysql at least)
parent = object.category.parent
Category.where(parent: parent).select(:id, :name)

RuntimeError: ERROR Mrelation "tablename" does not exist

Rails - 2.3.8
Database - Postgres(9.2)
Active record query is not able to generate tablename in double quotes ie
# this ran fine
Table.find_by_sql('Select * from "Table" Limit 1')
Sql generated - Select * from "Table" Limit 1
But issue comes in,
Table.find(:first)
Sql generated - Select * from Table Limit 1 (Clearly noticed that table not in double quotes)
Active record displaying error
ActiveRecord::StatementInvalid: RuntimeError: ERROR
C42P01 Mrelation "Table" does not exist
P15 Fparse_relation.c L864
RparserOpenTable: SELECT * FROM Table LIMIT 1
I feel that postgresql adapter is not able to generate tablename in double quotes.
I never get a chance to work on Postgres. But I have a workaround solution for this. Try as follows:
table_name = '"Table"'
table_name.find(:first)
I haven't try this in my machine since I do not have the required setup. I hope it should work.

Active record query select_all, prepared statement

Am trying to write a prepared statement like below :
#fofs = FileOrFolder.connection.select_all("select * from newtestdocB.file_or_folders where name like","%#{params[:search]}%")
It turns out to be a wrong query on mysql console. Does not take the variable %#{params[:search]}% value in to query
Please correct my query ...
console messages:
Parameters: {"search"=>"do", "cluster_id"=>"2", "datasetid"=>"1", "id"=>"1"}
%do% (0.2ms) select * from newtestdocB.file_or_folders where name like
ActiveRecord::StatementInvalid (Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1: select * from newtestdocB.file_or_folders where name like):
Try a placeholder ?
"select * from newtestdocB.file_or_folders where name like ?","%#{params[:search]}%"
See the question mark in the above statement which will be filled in by the params that follows the comma.
IF the above the statement doesnt work, this must work, using "+" to concatenate, beware of the space next to "like"
sql = "select * from newtestdocB.file_or_folders where name like <<space>>" + "%#{params[:search]}%"
FileOrFolder.connection.select_all(sql)

Resources