How to sanitize raw SQL in a Ruby script - ruby

I'm trying to write a script that connects with a database using Sequel.
If I have a SQL query like this:
record_values = csv_row.to_h.values.join(', ')
sql_query = "INSERT INTO table (#{ COLUMN_NAMES.join(', ') }) VALUES (#{ record_values })"
and the array record_values is dangerous.
How can I sanitize it?
I tried to sanitize using
ActiveRecord.sanitize_sql_array(sql_query)
but I kept getting the error
NoMethodError: undefined method 'sanitize_sql_array' for ActiveRecord:Module

I don't know Sequel, but did you try standard insert method?
connection = Sequel.connect('...')
table_name = connection.from(:table_name)
# OR
# table_name = DB.from(:table_name)
# table_name = DB[:table_name]
table_name.insert(csv_row.to_h)
It's more reliable I believe, because you avoid difference between COLUMN_NAMES and record_values.

Related

adding array as parameter to sql query in ruby

I have an array
ziparray = ["95626", "95645", "95837"]
I want to pass this to my sql query ,
sql = "SELECT * from table_name WHERE code in ($1);"
res1 = conn.exec(sql, [ziparray])
It does work for single values.
I am using pg gem and connecting to database using
conn = PG.connect()
I am using postgres and it doesn't take double quotes . I am assuming that to be the problem.
How to achieve this.
Update
I could convert to desired string using
str = "'"
str << ziparray.join("','")
str << "'"
#print str
But I guess the problem is passing of multiple parameters.
this works -
res1 = conn.exec(fipscodesql, ['95626'])
But not this
res1 = conn.exec(fipscodesql, ['95626', '95625'])
and this is exactly what I did when I converted the array to string. I guess this is not the right way to use parameters.
is there any other way.
As others said, you can't parametrise a whole array. Use this instead:
ziparray = ["95626", "95645", "95837"]
zip_placeholders = ziparray.map.with_index(1) { |_, i| "$#{i}" }.join(', ')
sql = "SELECT * from table_name WHERE code in (#{zip_placeholders});"
# => "SELECT * from table_name WHERE code in ($1, $2, $3)"
Then you can use the normal parameter binding.
[ziparray].map { |zip| "'#{zip}'" }.join(',')
SQL method "IN" doesn't use array.
So:
IN(234) - correct
IN(234,543) - correct
IN([234,543]) - wrong
You can try to convert array to string and pass variable $1 like '95626, 95645, 95837'
If x is an array and x = ['a','b','c']
quotes = (select * from quote in (?), x)
We cannot use this in a SQL parameter
x.join(',') returns "a,b,c". If it is int this is not a problem.
Instead, save this array in a variable and use it in a SQL parameter.
a = x and then use quotes = (select * from quote in (?),a)

Sequel SELECT count(id) FROM tablename WHERE username = "alpha"

I can't seem to get this query right. Sequel implementation of:
SELECT count(id) FROM users WHERE username = "alpha" AND status = "new"
Here's what I have so far:
db = Sequel.connect('postgres://me:pw#0.0.0.0:5432/dbname)
u = db[:users]
puts "Total users: #{u.count}" # this is correct
puts db.where(:username => "alpha", :status => "new").count
I've tried various direct SQL and that doesn't seem to work either. It smells like this is remedial, but the connectivity is fine, and I can replicate the exact SQL which doesn't come back the same.
You forgot to select the table. You want this:
db[:users].where(username:'alpha', status:'new').count
For SQLite3, this produces the query:
SELECT count(*) AS 'count'
FROM `users`
WHERE ((`username` = 'alpha')
AND (`status` = 'new'))
LIMIT 1
The error message you should have been getting was:
NoMethodError: undefined method `where' for #<Sequel::SQLite::Database: ...>
If you saw this error and read it, it should have let you know that calling db.where was not right.
In addition to making this work with the native Sequel DSL, you can also always run raw sql commands in Sequel.

Using Rails Update to Append to a Text Column in Postgresql

Thanks in advance for any help on this one.
I have a model in rails that includes a postgresql text column.
I want to append (i.e. mycolumn = mycolumn || newdata) data to the existing column. The sql I want to generate would look like:
update MyOjbs set mycolumn = mycolumn || newdata where id = 12;
I would rather not select the data, update the attribute and then write the new data back to the database. The text column could grow relatively large and I'd rather not read that data if I don't need to.
I DO NOT want to do this:
#myinstvar = MyObj.select(:mycolumn).find(12)
newdata = #myinstvar.mycolumn.to_s + newdata
#myinstvar.update_attribute(:mycolumn, newdata)
Do I need to do a raw sql transaction to accomplish this?
I think you could solve this problem directly writing your query using the arel gem, that's already provided with rails.
Given that you have these values:
column_id = 12
newdata = "a custom string"
you can update the table this way:
# Initialize the Table and UpdateManager objects
table = MyOjbs.arel_table
update_manager = Arel::UpdateManager.new Arel::Table.engine
update_manager.table(table)
# Compose the concat() function
concat = Arel::Nodes::NamedFunction.new 'concat', [table[:mycolumn], new_data]
concat_sql = Arel::Nodes::SqlLiteral.new concat.to_sql
# Set up the update manager
update_manager.set(
[[table[:mycolumn], concat_sql]]
).where(
table[:id].eq(column_id)
)
# Execute the update
ActiveRecord::Base.connection.execute update_manager.to_sql
This will generate a SQL string like this one:
UPDATE "MyObjs" SET "mycolumn" = concat("MyObjs"."mycolumn", 'a custom string') WHERE "MyObjs"."id" = 12"

Is there a way to see the raw SQL that a Sequel expression will generate?

Say I have a Sequel expression like:
db.select(:id).from(:some_table).where(:foo => 5)
Is there a way to get the SQL string that this will generate (i.e. "SELECT id FROM some_table WHERE foo = 5")? I notice that calling inspect or to_s on the result of the above expression includes that generated SQL, but not sure how to access it directly.
And how about Sequel expressions that do not return a dataset, like:
db.from(:some_table).update(:foo => 5)
Is it possible to see the SQL from this before it's executed?
You can call sql on dataset:
db.select(:id).from(:some_table).where(:foo => 5).sql # => "SELECT `id` FROM `some_table` WHERE (`foo` = 5)"
For update queries you can do this:
db.from(:some_table).update_sql(:foo => 5) # => "UPDATE `some_table` SET `foo` = 5"
Some similar useful methods:
insert_sql
delete_sql
truncate_sql

Using ruby sqlite to insert to table within a loop which is part of a method

I'm struggling to loop through an "INSERT INTO" sqlite statement within a Ruby method. Please advice. My code is shown below. The error messages seem to suggest that Ruby doesn't recognise the db object within the method?? Apologies for being a bit of a noob but I've trawled through the internet and I couldn't find the answer :(
require "sqlite3"
require "nokogiri"
begin
db = SQLite3::Database.new('RM.db')
db.execute "CREATE TABLE IF NOT EXISTS Properties(address TEXT, askingPrice TEXT)"
def get_property_list(newpage, dbname)
resultspage = Nokogiri::HTML(open(newpage))
details = resultspage.css("div.details.clearfix")
count_items = details.count
puts "there are #{count_items} items on this page"
for i in 0..count_items-1
address = resultspage.css("span.displayaddress")[i]
asking_price = resultspage.css("p.price")[i]
puts address.text
puts asking_price.text
dbname.execute "INSERT INTO Properties VALUES(#{address}, #{asking_price})"
end
end
get_property_list("someurl.com", db)
rows = db.execute("select * from Properties")
p rows
ensure
db.close if db
end
You're not quoting either value in your INSERT. You shouldn't be using string interpolation for SQL at all, use bound parameters and always specify the column names too:
dbname.execute 'INSERT INTO Properties (address, askingPrice) VALUES (?, ?)', address, asking_price
Or you could prepare a statement and use it over and over again:
insert = dbname.prepare('INSERT INTO Properties (address, askingPrice) VALUES (?, ?)')
for i in 0..count_items-1
#...
insert.execute(address, asking_price)
end

Resources