Sqlite3 value in Ruby Script not working as string - ruby

I am trying to create a user-auth system and have run into a problem. In this code, I am trying to check if a certain value matches another value in the same row. It prints but does not work as a string when I set it equal to another string.
db.execute("CREATE TABLE IF NOT EXISTS auth(id int, name text, password text)")
db.execute("INSERT INTO auth(id, name, password) VALUES(?, ?, ?)", [1, 'Bugs', 'Carrots'])
t = db.execute("SELECT password FROM auth WHERE name = 'Bugs'")
if t == 'Carrots'
puts "yes"
end
#i am trying to print yes to the console

I think you're getting back an array representing the list of matches, although in this case there's only one row returned. Furthermore, the row itself is an array of values, although in this case the row will be an array of only one value. So you should be comparing t[0][0] rather than just t
You could use the function db.get_first_value instead to simplify this.
(This assumes that name is unique; you probably want to use a UNIQUE constraint in the database schema for both id and name.)
(And, yes, you should take #tadman's warning to heart.)
So here's a minimally altered version of the code you posted above (without the suggested improvements) that gives the desired result:
#!/usr/bin/env ruby
require 'sqlite3'
db = SQLite3::Database.new(':memory:')
db.execute("CREATE TABLE IF NOT EXISTS auth(id int, name text, password text)")
db.execute("INSERT INTO auth(id, name, password) VALUES(?, ?, ?)",
[1, 'Bugs', 'Carrots'])
t = db.execute("SELECT password FROM auth WHERE name = 'Bugs'")
if t[0][0] == 'Carrots'
puts "yes"
end

Related

How to get auto-generated row primary key via Ruby PostgreSQL gem?

My table in PostgreSQL looks like this:
CREATE TABLE user (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
)
I'm using Ruby pg gem to add a record to the table:
c = PG.connect(dbname: 'foo')
id = c.exec_params('INSERT INTO user (name) VALUES ($1)', ['Jeff']).oid_value
I'm getting nil back, instead of the auto-generated id of the new record. What is the right way to get it back?
According to the docs, if the object id you're retrieving isn't set, thus nil.
You either have to set the object id yourself, or use returning like this:
res = conn.exec("INSERT INTO users (name) VALUES ('john') returning id")
res[0]['id']
#=> 1

writing basic Ruby code to register user in SQLite database

I need some help writing basic Ruby code to register a user in a SQLite database. I'm very new to Ruby, I checked lots of good examples online but my code still doesn't work.
This is my 1st test project using Ruby, so appreciate any help and apologise for making any bad mistakes.
require 'sqlite3'
def register_user(l)
user = l[1]
pass = l[2]
db = SQLite3::Database.new "database.db"
db.execute("INSERT INTO users (user, pass)
VALUES (#{user}, #{pass})")
end
def cmd_register(l)
if register_user(#{#nick}, l[1])
sv_send 'NOTICE', 'REGISTER', ':*** User created'
else
sv_send 'NOTICE', 'REGISTER', ':*** User not created'
end
end
There are a few problems with your code. First, here:
db.execute("INSERT INTO users (user, pass)
VALUES (#{user}, #{pass})")
You're trying to generate a query that looks like this (supposing the variable user contains "Jordan" and pass contains "xyz"):
INSERT INTO users (user, pass) VALUES('Jordan', 'xyz')
...but your code generates a query that looks like this:
INSERT INTO users (user, pass) VALUES(Jordan, xyz)
Do you see the difference? Values in SQL queries need to be surrounded by quotation marks. Your query will fail because SQLite doesn't know what Jordan is; it only knows what 'Jordan' is.
You could just add quotation marks to your query, but then you would have another problem: SQL injection attacks. Because you're just blindly putting the values of user and pass into your query, an attacker could manipulate those values to perform a different query than you intended. Never use string interpolation (#{var}) or concatenation (+ or <<) when creating an SQL query. (For a brief description of how SQL injection attacks work, read the "How to get hacked" section on this page: http://ruby.bastardsbook.com/chapters/sql/.)
The correct way to use variables in a query is with prepared statements and parameter binding. It looks like this:
statement = db.prepare("INSERT INTO users (user, pass) VALUES (?, ?)")
statement.bind_params(user, pass)
result = statement.execute
What this does is automatically escapes the values of user and pass to make sure they don't do anything you don't expect, wraps them in quotation marks, and substitutes them for the question marks in the query. Another way to do the same thing is this:
result = db.execute("INSERT INTO users (user, pass) VALUES (?, ?)", user, pass)
The other obvious problem with your code is this:
if register_user(#{#nick}, l[1])
This is a syntax error. You can only use the #{var} syntax in a string, like "hello #{var}". In this case you just want to do this:
if register_user(#nick, l[1])
require "sqlite3"
my_db = SQLite3::Database.new "my_db1.db"
my_db.execute <<END_OF_CREATE #The <<END_OF_CREATE .... END_OF_CREATE thing is called HEREDOC syntax, which is one way to create a String that spans multiple lines
CREATE TABLE IF NOT EXISTS users( #A useful SQL command to be aware of.
name varchar(30),
password varchar(30)
);
END_OF_CREATE
def register_user(target_db, user_info)
user_name, user_pass = user_info #A neat trick for "unpacking" an Array
target_db.execute(
"INSERT INTO users (name, password)
VALUES (?, ?)", user_name, user_pass #For security reasons, inserts into a db should use this "question mark" format.
)
end
register_user(my_db, ['John', 'abc123'])
register_user(my_db, ['Jane', 'xyz456'])
my_db.execute("SELECT * FROM users") do |row|
p row #Use the 'p' method instead of puts to print an Array or Hash.
end
--output:--
["John", "abc123"]
["Jane", "xyz456"]
Also, don't ever name a variable l. You absolutely, no exceptions, have to use descriptive variable names. See the code above for an example.
Even though the code above unpacks the user_info array into separate variables, that is actually not required because execute() will take an
Array as an argument:
target_db.execute(
"INSERT INTO users (name, password)
VALUES (?, ?)", user_info
)
In other words, all the values for the question marks can be gathered into an Array and provided as the last argument for execute().
One problem you can run into when writing and testing database programs is when you change one of the column names in your table. The code above will cause an error: the table will not be re-created because the table already exists, but your new code will use the new column name, which won't exist in the table.
So, you might consider using this combination of sql statements:
my_db.execute <<END_OF_DROP
DROP TABLE IF EXISTS users
END_OF_DROP
my_db.execute <<END_OF_CREATE
CREATE TABLE users(
name varchar(30),
password varchar(30)
);
END_OF_CREATE
With those sql statements, if you change one of the column names (or add a column), then your new code won't throw an error because the table is destroyed and recreated with the new column names every time you run your program.

How to use Sequel to select one field from database

I am using Sinatra and Sequel with PostgreSQL.
After authentication, I want to welcome the user by printing their name but I cannot get only the value of the user's name from the database, it comes out as a hash.
The query is:
current_user = DB[:users].select(:username).where('password = ?', password).first
and the resulting piece of data is:
Welcome, {:username=>"Rich"}
which looks rather weird, I would prefer it to read "Welcome, Rich".
What am I doing wrong here? I tried the same query without 'first" at the end and that does not work either.
You can either pull the (single) column you selected out of the Hash you are given:
current_user = DB[:users].select(:username).where('password=?', password).first[:username]
Or you can map your results to an array of usernames and pull the first:
# Using a hash in the filter method is simpler than SQL placeholders.
current_user = DB[:users].filter(password:password).select_map(:username).first
But the best way is to get only the user you care about, and then get the name:
# Using [] on a dataset returns the first row matching the criteria
current_user = DB[:users][password:password][:username]
Try Sequel::Dataset#get. Also, as Phrogz points out, Sequel::Dataset#where can take a hash (it will securely escape values to prevent injection attacks).
current_username = DB[:users].where(password: password).get(:username)
There's also Sequel::Dataset#where_single_value, which is optimized for this exact situation:
current_username = DB[:users].select(:username).where_single_value(password: password)

SQLite query fails without error in Ruby

I have a function that finds an oid from a field in a table. According to the docs, the oid field is automatically created and auto incremented. Sounds great.
def teddy_bear_id_by_url(url)
query = "select oid from teddy_bears where url = \"#{url}\""
res = $db.execute(query)
return res
end
Unfortunately this code returns a [] (empty array), when running the query in the sqlite shell gives a 'good' value (e.g. 4).
def teddy_bear_id_by_url(url)
return $db.execute("select oid from teddy_bears where url = '?'", url)
end
The above doesn't work either.
I did indeed check that urlcontains what I think it does.
What might be happening?
There's probably something funny going on in your url that, combined with your string interpolation, is messing up your SQL. Your best bet is to use placeholders with your execute call:
$db.execute("select oid from teddy_bears where url = ?", url)

Optional parameters in SQL stored procedure

I am trying to create a stored procedure that has optional parameters. I followed the instructions listed here. I also referenced this SO question. However I keep receiving the following error:
Error converting data type varchar to int.
It works when I execute it as
EXEC sp_get_user {id#}
or
EXEC sp_get_user NULL, {username}
but fails with
EXEC sp_get_user {username}
Stored Procedure
#id int = NULL,
#username nvarchar(50) = NULL
SELECT
username = COALESCE(a.Username, b.Username),
password = COALESCE(a.Password, b.Password),
signup_date = COALESCE(a.SignedUpOn, b.Signup_Date)
FROM table1 a
FULL OUTER JOIN table 2 b
ON a.ID = b.ID
WHERE ((a.ID = #id OR #id IS NULL)
AND (a.Username = #username OR #username IS NULL)
OR (b.ID = #id OR #id IS NULL)
AND (b.Username = #username OR #username IS NULL))
I have tried adding the OPTION(RECOMPILE) and had no success. I want to make this dynamic so other developers can call this SP without having to specify all parameters each time. They will be connecting via LINQ if that makes a difference.
use named parameters in that case
EXEC sp_get_user #username = {username}
if both parameters are optional, SQL server will go by position, so the first one you are passing in will map to the first one in the proc
When executing stored procedures you have to conform for the parameters order as defined respectively, that is why the first and second statements works fine, in the first one EXEC sp_get_user {id#} you passed the id and ignored the user name, then it takes the defined default value. Moreover, in your second statement EXEC sp_get_user NULL, {username} you specified NULL for the id and you passed a value for the username parameter that is why it also works.
On the other hand, the third one EXEC sp_get_user {username} doesn't work because SQL Server treated your parameter {username} as the id value that is why it tries to convert it to integer and of course it will fail. Instead, you have to specify the paramer name while you are passing its value, see the following code:
EXEC sp_get_user #username = {username}
Well, yes, obviously your last attempt will fail.
Your stored proc expects two parameters, in that order:
#id INT
#username NVARCHAR(50)
If you just simply call your stored procedure with a single parameter, then that parameter will be mapped to #id - and thus it needs to be an INT.
If you want to call your stored proc with just a single value for user name, you will need to used a named parameter - you cannot rely on the position (since the first and only parameter will always match to #id)
EXEC sp_get_user #username = {username}

Resources