How to prepare SQL statement without using where() method - sql

Is there a way to prepare an SQL statement like that built by ActiveRecord where() method (using named placeholders):
Client.where("created_at >= :start_date AND created_at <= :end_date",
{start_date: params[:start_date], end_date: params[:end_date]})
I have to use this in CASE .. END statement after ORDER BY clause (or in SELECT to create a computed column) to protect it from SQL injection.
EDIT:
I have to retrieve all the ActiveRecord models by this query too. So can I use find_by_sql()? (trying...).
EDIT2:
find_by_sql() can't use named placeholders (:start_date in the above code example).
It's Rails 3.2.11
EDIT3:
Sorry, it can use with an Array as one parameter (find_by_sql with array format in Rails 3).

Yes, you can write / prepare anything that ActiveRecord does. Do you mean to use a direct DBI query?
I recommend you trace the SQL that ActiveRecord is generating.
Retrieving sql queries from Active-record queries in Rails 3
Tracing Rails 3 SQL queries
http://guides.rubyonrails.org/v2.3.11/debugging_rails_applications.html
See query_trace for one approach.
A tutorial on how to use Ruby DBI and prepared statements:
http://www.tutorialspoint.com/ruby/ruby_database_access.htm
Quoted directly from the tutorial:
#!/usr/bin/ruby -w
require "dbi"
begin
# connect to the MySQL server
dbh = DBI.connect("DBI:Mysql:TESTDB:localhost",
"testuser", "test123")
sth = dbh.prepare( "INSERT INTO EMPLOYEE(FIRST_NAME,
LAST_NAME,
AGE,
SEX,
INCOME)
VALUES (?, ?, ?, ?, ?)" )
sth.execute('John', 'Poul', 25, 'M', 2300)
sth.execute('Zara', 'Ali', 17, 'F', 1000)
sth.finish
dbh.commit
puts "Record has been created"
rescue DBI::DatabaseError => e
puts "An error occurred"
puts "Error code: #{e.err}"
puts "Error message: #{e.errstr}"
dbh.rollback
ensure
# disconnect from server
dbh.disconnect if dbh
end

Using find_by_sql() solved this.
But this can't be use as scoped query. (And thus cannot be chained).

Related

Create PostgreSQL table comment using a prepared statement

Is it possible to prepare a Postgres 'COMMENT ON' statement?
I have a program that allows users to create tables. I'd like to give them the option to add a description of the table's contents. As this data is coming from users, I'd like to use prepared statements.
Using Ruby and the 'pg' gem, I use the below to setup a Postgres connection and example data:
table_name = "test_shakespeare"
description = "Shakespeare's sonnets"
con = PG.connect(
:dbname => "poefy",
:user => "poefy",
:password => "poefy"
)
sql = "CREATE TABLE #{table_name} (example TEXT);"
con.exec(sql)
Here are my failed approaches, together with the errors they throw.
# ERROR: syntax error at or near "$1" (PG::SyntaxError)
sql = "COMMENT ON TABLE #{table_name} IS $1;"
con.exec(sql, [*description])
# ERROR: syntax error at or near "$1" (PG::SyntaxError)
sql = "COMMENT ON TABLE #{table_name} IS $1;"
con.prepare("comment", sql)
con.exec_prepared("comment", [*description])
# ERROR: could not determine data type of parameter $1 (PG::IndeterminateDatatype)
sql = "COMMENT ON TABLE #{table_name} IS '$1';"
con.exec(sql, [*description])
# ERROR: bind message supplies 1 parameters, but prepared statement "comment" requires 0 (PG::ProtocolViolation)
sql = "COMMENT ON TABLE #{table_name} IS '$1';"
con.prepare("comment", sql)
con.exec_prepared("comment", [*description])
It seems that preparation is not possible for this type of statement, and I should resort to SQL string manipulation. That being the case, what is the best way to go about this? The data is not sensitive or critical, and I am only really concerned with correctly represented quote marks and apostrophes.
Thanks in advance.
I assume ruby supports same statements as postgres, which does it for
Any SELECT, INSERT, UPDATE, DELETE, or VALUES statement
not COMMENT IS
It does appear that this is not possible.
So I went with the old "double all the single quotes" method.
safe_desc = description.gsub("'", "''")
con.exec "COMMENT ON TABLE #{table_name} IS '#{safe_desc}';"
This feels really hacky. But for now I'm marking it as the answer.
If there's a safer way, please let me know.

PostgreSQL - incorrect syntax, when in condition is "false" value

I have this scope in Rails:
scope :by_default_checks, {:conditions => ['cars.sold IS ? AND cars.deactivated IS ?', nil, false]}
#cars = Car.by_title(params[:search][:title_like]).by_greater(params[:search][:amount_gte]).by_smaller(params[:search][:amount_lte]).by_default_checks
and on Heroku I am getting this error:
Completed 500 Internal Server Error in 6ms
... AND cars.sold IS NULL AND cars.deactivated IS 'f')
SELECT "cars".* FROM "cars" WHERE (cars.title LIKE '%iphoe%') AND (cars.sold IS NULL AND cars.deactivated IS 'f')
PG::SyntaxError: ERROR: syntax error at or near "'f'"
This code is working on SQLite, but doesn't on PostgreSQL. How to replace it?
Thanks
You should use = to check for equality with non-null values:
['cars.sold IS ? AND cars.deactivated = ?', nil, false]
You usually use is in is null, is not null, is distinct from, and is not distinct from when you're faced with NULLs and a simple = comparison will not work the way you want it to. You can use is for booleans if you're using the true or false values but not the 't' and 'f' strings that ActiveRecord uses to represent PostgreSQL booleans.
See Comparison Operators in the manual for details.
Alternatively, you could let ActiveRecord build the whole thing instead of using the old-school :conditions stuff:
scope :by_default_checks, where(:sold => nil).where(:deactivated => false)
That way ActiveRecord is responsible for all the native-to-PostgreSQL type conversions and it will choose the correct comparison operators for you.
Also, developing on top of one database (SQLite) and deploying on another (PostgreSQL) is a really bad idea that will just lead to pain, suffering, and hair loss. There are all sorts of differences between databases that no ORM can insulate you from. Please fix this bug and then immediately switch your development environment to PostgreSQL.

Syntax error in WHERE clause near '?) AND (Date = ?)'

I am trying to send a SQL prepared statement to MySQL DB. This is what I have:
String sql1 = "SELECT idReimbursed_Funds As idReimFunds FROM reimbursedfunds Where ReimFundsName = ? AND Date = ?";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.setString(1, reimfund.getReimFundsName());
pstmt1.setDate(2, (Date) reimfund.getDate());
ResultSet rs1 = pstmt1.executeQuery(sql1);
while(rs1.next()){
idReimFunds = rs1.getInt("idReimFunds");
}
After googling this problem, I found solutions to use parenthesis around the question marks or the whole where clause such as:
String sql1 = "SELECT idReimbursed_Funds As idReimFunds FROM reimbursedfunds Where (ReimFundsName = ?) AND (Date = ?)";
This didn't work though. I get the same error message that is generated by my original code:
"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 '?) AND (Date = ?)' at line 1.
When I try the SQL statement in MySQL Workbench is works fine. Is there a way to use 2 where clauses with JDBC? I know in other posts people have answered that it has to be sent as two different queries, but I thought I would ask just in case someone else reads this posts and knows of a way. Thank you!
The problem (apart from the Date issue as mentioned by bgp), is the line:
ResultSet rs1 = pstmt1.executeQuery(sql1);
You are trying to execute a query string on a prepared statement, which is not allowed by the JDBC standard (MySQL should actually throw an exception instead of sending it to the server as it currently does, but the end result is the same). The documentation of Statement.executeQuery(String sql) says:
Throws:
SQLException - if a database access error occurs, this method is called on a closed Statement, the given SQL statement produces anything other than a single ResultSet object, the method is called on a PreparedStatement or CallableStatement
(emphasis mine)
The reason is that you want to execute the prepared statement, not any other query. You should call PreparedStatement.executeQuery() (so without a parameter):
ResultSet rs1 = pstmt1.executeQuery();
Pretty sure this is because "Date" is a MySQL keyword (reserved). Call the field something else or escape it with backticks, i.e. `Date`

Database-independent SQL String Concatenation in Rails

I want to do a database-side string concatenation in a Rails query, and do it in database-independent way.
SQL-92 specifies double-bar (||) as the concatenation operator. Unfortunately it looks like MS SQL Server doesn't support it; it uses + instead.
I'm guessing that Rails' SQL grammar abstraction has solved the db-specific operator problem already. If it does exist, how do I use it?
I had the same problem and never came up with anything that was built into Rails. So I wrote this little method.
# Symbols should be used for field names, everything else will be quoted as a string
def db_concat(*args)
adapter = configurations[RAILS_ENV]['adapter'].to_sym
args.map!{ |arg| arg.class==Symbol ? arg.to_s : "'#{arg}'" }
case adapter
when :mysql
"CONCAT(#{args.join(',')})"
when :sqlserver
args.join('+')
else
args.join('||')
end
end
I'm thinking somebody should really write some sort of SQL helper plugin that could automatically format simple SQL expressions based using the correct functions or operators for the current adapter. Maybe I'll write one myself.
It hasn't received much usage yet but I wrote the following code which seems to solve the problem. This monkey-patches the adapters to have a method to support it:
module ActiveRecord
module ConnectionAdapters
class AbstractAdapter
# Will return the given strings as a SQL concationation. By default
# uses the SQL-92 syntax:
#
# concat('foo', 'bar') -> "foo || bar"
def concat(*args)
args * " || "
end
end
class AbstractMysqlAdapter < AbstractAdapter
# Will return the given strings as a SQL concationation.
# Uses MySQL format:
#
# concat('foo', 'bar') -> "CONCAT(foo, bar)"
def concat(*args)
"CONCAT(#{args * ', '})"
end
end
class SQLServerAdapter < AbstractAdapter
# Will return the given strings as a SQL concationation.
# Uses MS-SQL format:
#
# concat('foo', 'bar') -> foo + bar
def concat(*args)
args * ' + '
end
end
end
end
With this you should be able to do the following in your code:
class User < ActiveRecord::Base
def self.find_by_name(name)
where("#{connection.concat('first_name', 'last_name')} = ?", name)
end
end
This outputs the following SQL query on a SQL-92 database (Oracle, SQLite, PostgreSQL):
SELECT * FROM users WHERE first_name || last_name = ?
For MySQL it outputs:
SELECT * FROM users WHERE CONCAT(first_name, last_name) = ?
For SQL Server it outputs
SELECT * FROM users WHERE first_name + last_name = ?
Obviously you could extend this concept to other database adapters.
If you want something Rails neutral, you're going to need to return the values you want concatenated and do that once the data has been delivered to rails (or do it in rails before you give it to the database).
It looks like Mysql uses CONCAT(), Postgres ||, Oracle CONCAT() or ||, T-SQL +.
Any rails abstraction of the same would have to take place at a point where you could just be doing concatenation using regular Ruby, or I've completely misunderstood the question.

How do you talk SQL directly to MySQL from Ruby?

I want to write a script in Ruby to clean up some messed up keys in several copies of the same MySQL schema. I'd like to do something like SHOW CREATE TABLE, then look at what comes back and delete keys if they exist.
I know in the Rails environment you can do this...
ActiveRecord::Base.connection.execute( some sql )
But what you get back is a "Result" object. For this task I need a String so I can analyze it and act accordingly.
This should help you:
>> result = ActiveRecord::Base.connection.execute("SHOW TABLES")
=> #<Mysql::Result:0x37ecb30>
>> result.class.instance_methods - Object.instance_methods
=> ["all_hashes", "field_seek", "row_tell", "fetch_field_direct", "free", "field_tell", "fetch_lengths", "num_fields", "data_seek", "fetch_row", "num_rows", "fetch_field", "each", "each_hash", "fetch_hash", "row_seek", "fetch_fields"]
Look at #all_hashes on the MySql::Result instance
I would use the mysql-ruby gem and you would do something like this:
require 'mysql'
m = MySQL.new("localhost", "username", "password", "database")
r = m.query("SELECT * FROM people ORDER BY name")
r.each_hash do |f|
print "#{f['name']} - #{f['email']}"
end
You could check the mysql-ruby gem.
Here is a write-up on how to use it: Using the Ruby MySQL Module
More can be found via google
If you don't want to use ActiveRecord an ORM may be a bit complicated for your usage right now), you can still use the ruby-mysql library or even better IMHO is to use the Ruby DBI/DBD library (here) which has DBD drivers for mysql & postgresql out-of-the-box.
That way, you can issue straight SQL statements like this
require "dbi"
require "dbi/dbrc"
# == Configuration
DB = "sympa"
HOST = "saphir"
cnt = 0
dup = 0
# == Crude option processing
#
list_name = ARGV.shift.to_s
file = ARGV.shift.to_s
db = DBI::DBRC.new(DB)
DBI.connect(db.dsn + ":#{HOST}", db.user, db.password) do |dbh|
date = Time.now.asctime
if not list_name or list_name == "" then
puts "List name is mandatory"
exit 1
end
req1 = <<-"EOR"
insert into user_table (email_user,lang_user)
values (?, ?)
EOR
...
req2 = <<-"EOR"
insert into subscriber_table
(user_subscriber, list_subscriber, visibility_subscriber,
date_subscriber, reception_subscriber) values (?, ?, ?, NOW(), ?)
EOR
sth1 = dbh.prepare(req1)
sth2 = dbh.prepare(req2)
...
#
# Insertion in user_table
#
begin
sth1.execute(line, "en")
cnt += 1
rescue DBI::DatabaseError => err
$stderr.puts("DBI: #{err}")
end
dbi/dbrc is a useful module that enables you to avoid putting login&password directly in the script. See there.
There is probably a better way to do that programmatically, however if you really want to drive the interactive commands and parse the results, then expect may be more suitable. You could still kick off expect from your ruby script.
Use Mysql2
To update this thread: I'd suggest Mysql2 now: http://rubygems.org/gems/mysql2