You should avoid using string interpolation (#{...}
) when constructing SQL queries. In addition to issues like the one you're experiencing, it's a great way to open yourself up to SQL injection attacks.
Instead, you should use parameter binding. This lets the database itself take care of correctly and securely sanitizing and quoting your values so you don't have to worry about it.
In your case it would look like this:
vals = [ "1", "It happened!", "It did!" ]
query = <<SQL
INSERT INTO my_table (posted, title, body)
VALUES (?, ?, ?);
SQL
DB.execute(query, vals)
You can shorten it a bit to this:
DB.execute <<-SQL, vals
INSERT INTO my_table (posted, title, body)
VALUES (?, ?, ?);
SQL
When the query is executed, the ?
placeholders will be replaced with the corresponding values—sanitized and quoted—in the array you supply as the second argument.
You've probably already noticed that this isn't exactly equivalent to your code, because I've hard-coded the table name and column names. A limitation of SQLite is that you can't do parameter binding for identifiers like those. What to do, then? If you're getting column names from an untrusted source (like a web request from an end user) and so can't just hard-code them in your query, you'll have to compromise. Probably the best thing you can do is have a whitelist:
column_whitelist = %w[ posted title body ]
unless keys.all? {|key| column_whitelist.include?(key) }
raise "Invalid column name '#{key}'!"
end
You'll want to do something similar for table_name
if it's coming from an untrusted source, too.
Once you've screened your table and column names, you can safely use them in your query:
column_names = keys.join(", ")
placeholders = keys.map { "?" }.join(", ")
DB.execute <<-SQL, vals
INSERT INTO #{table_name} (#{column_names})
VALUES (#{placeholders});
SQL
P.S. If your table or column names have spaces, quotation marks, or any other special characters, you'll need to escape and quote them. That means escaping any double-quotes by preceding them with another double-quote ("
becomes ""
) and then surrounding the whole thing with double-quotes. A small helper method like this would do it:
def escape_and_quote_identifier(str)
sprintf('"%s"', str.gsub(/"/, '""'))
end
Then you would want to apply it to your table and column names:
table_name = escape_and_quote_identifier(table_name)
column_names = keys.map {|key| escape_and_quote_identifier(key) }
.join(", ")
placeholders = keys.map { "?" }.join(", ")
DB.execute <<-SQL, vals
INSERT INTO #{table_name} (#{column_names})
VALUES (#{placeholders});
SQL