I started a PHP side project a while back, and recently started cleaning up all the code and updating it. Part of that is moving things to start using PDO for DB queries. In one particular query I have a WHERE clause statement that uses the BETWEEN clause along with the INET_ATON() function. So far I have not been able to get this to work properly and would appreciate if anyone can point out whether or not it will even work.
I found this question which leads me to believe that the BETWEEN statement is not having any issues, but I fear it might be the combination with the function causing the issue.
Here is what that portion of my query looks like:
SELECT foo, bar FROM table JOIN more_table
WHERE item1 = :value AND ip_address BETWEEN INET_ATON(:ipstart) AND INET_ATON(:ipend)
The actual query is quite a bit larger than that, but for basics this works. To replace the tokens with the actual values I have the following foreach loop set up (the var_dump is just there so i can verify the correct values are getting passed in, which they are):
foreach($p as $k=>$v) {
echo var_dump($k, $v);
if(is_int($v)){
$querySubmit->bindValue($k, $v, PDO::PARAM_INT);
} else {
$querySubmit->bindValue($k, $v, PDO::PARAM_STR);
}
}
If anyone knows whether PDO::Prepare and PDO::bindValue have any limitations with a query set up to use BETWEEN and INET_ATON(), I would greatly appreciate knowing!
Your parameter for bindValue() is $k it should be :value
foreach($p as $k=>$v) {
echo var_dump($k, $v);
if(is_int($v)){
$querySubmit->bindValue(:value, $v, PDO::PARAM_INT);
} else {
$querySubmit->bindValue(:value, $v, PDO::PARAM_STR);
}
}
Also are you binding :ipstart and :ipend?
The issue ended up being with a separate section of code. The values being passed into the SQL query were correct, and the bindValue operation was working successfully.
To anyone who reviews this question in the future, who believes they are having a similar problem, if set up correctly there should be no issues with using the INET_ATON/NTOA() functions with BETWEEN.
I believe the first comment, left by #MarcB, was probably the best answer to this actually, "PDO has absolutely NO knowledge of MySQL functions. as long as the MySQL parser doesn't reject the query string, PDO will happily shove in whatever you want, wherever you want it."
Related
I have checked everywhere thoroughly, and have gone through everything possible to find an answer to this. Besides saying "the code doesn't work" which obviously is not enough, I have yet to find anything that will even come close to this. I'm probably going to get downvotes, but let's see how this goes.
I am learning how to do prepared statements for a search query from the user end, and I have to do it for multiple queries. I have to bind parameters to these multiple queries, and then execute them and use them and receive multiple rows. This is most of my code, and what I currently have is not reporting any errors whatsoever. It just returns a blank white page.
I am doing this from a simple test.php file, and those are the results I'm getting.
Now for the code.
$prep1 = $test->prepare("SELECT * FROM sb__bans WHERE sb__bans.authid=? ORDER BY sb__bans.bid DESC");
$prep2 = $test->prepare("SELECT * FROM sb__bans AS bans INNER JOIN sb__admins AS admins ON bans.aid = admins.aid WHERE bans.authid=? ORDER BY bans.bid DESC");
$prep3 = $test->prepare("SELECT * FROM sb__bans AS bans INNER JOIN sb__servers AS servers ON bans.sid = servers.sid WHERE bans.authid=? ORDER BY bans.bid DESC");
$search = "steam";
$prep1->bind_param("s", $search);
$prep2->bind_param("s", $search);
$prep3->bind_param("s", $search);
$prep1->execute();
$prep2->execute();
$prep3->execute();
while($row = $prep1->fetch() && $admin = $prep2->fetch() && $sv = $prep3->fetch()) {
echo $row['test'];
echo $admin['test'];
echo $sv['test'];
}
The database is initialized above this as $test = new mysqli("localhost", "test", "test", "test");
$search = "steam" steam would be replaced with the the post variable of course, but for testing reasons I've removed that for now and am testing with just a simple variable.
What seems to be the problem here?
Thanks in advance.
Regarding the general question you asked.
There is not a single problem with having multiple queries prepared. While speaking of getting results from a prepared query, there is indeed a problem caused by the result buffering. In order to be able to execute another query, you have to call store_result()/get_result() right after execute.
Regarding the particular problem you have.
To get errors you have to ask PHP for them.
There is absolutely no point in making three queries, you have to make just one. If you have a trouble making one, ask another question marking it with mysql tag and bringing your 3 queries along.
Even for multiple queries it's just wrong idea to do multiple fetches in a single loop. Fetch your query results one by one.
Your mysqli syntax even for a single query is incomplete. You need to re-read your tutorial and practice on a single query first.
Two points:
Based on personal experience, you can only have one prepared statement in existence at a time. I suspect this is because the db requires each PS to have a session-unique name, and the PHP layer is passing some common default name rather than generating a unique name for each PS. By comparison, the PostgreSQL driver allows an optional name for each PS, but still allows only one unnamed PS to exist. Essentially this means that you must prepare, bind, execute and fetch one PS completely before you can prepare the next PS.
You're misusing mysqli_stmt::fetch(). fetch() returns only true or false, and is used to update variables which have previously been bound with mysqli_stmt::bind_result(). To retrieve values into a $row array, you must first call mysqli_stmt::get_result() to return a mysqli_result, and then call mysqli_result::fetch_array().
So honestly, this is the first time I am working with PDO and Error Exceptions. I have gone thru manuals as well as different Q/As resolved here in past and came up with code that I am pretty satisfied with. But I really really need your opinion on this. I have built few functions that I normally use often in my projects.
Please note that right now I am doing a test project just intended to learn these 2 new things.
First of all, I am not a fan of OOP and have always preferred procedural programming type.
function _database_row($_table, $_id = 0, $_key = "id") {
global $Database;
if(is_object($Database)) {
$Query = $Database->prepare("SELECT * FROM {$_table} WHERE {$_key} = :id");
if($Query->execute(Array(":id" => $_id))) {
$Query_Data = $Query->fetchAll(PDO::FETCH_ASSOC);
if(count($Query_Data) >= 1) {
if(count($Query_Data) == 1) {
return $Query_Data[0];
}
return $Query_Data;
}
} else {
throw new Exception("Database Query Failure: ".$Query->errorInfo()[2]);
}
}
return false;
}
the above function is intended to fetch a row from $_table table with $_id (not necessarily an integer value).
Please note that $_id may (sometimes) be the only thing (for this function) that is fetched from $_REQUEST. By simply preparing the statement, am I totally secure from any SQL injection threat?
I couldn't find an alternative to mysql_num_rows() (I indeed found few PDO methods to use fetchColumn while using COUNT() in the query. But I didn't prefer it that way, so I want to know if I did it right?
also before people ask, let me explain that in above function I designed it so it returns the row directly whenever I am looking for a single one (it will always be the case when I use "id" as $_key because its PRIMARY auto_increment in database), while in rare cases I will also need multiple results :) this seems to be working fine, just need your opinions.
example of use:
_database_row("user", 14); // fetch me the user having id # 14
_database_row("products", 2); // fetch me the user having id # 14
_database_row("products", "enabled", "status"); // fetch me all products with status enabled
...sometimes during procedural programming, I wouldn't like those nasty "uncaught exception" errors, instead I will simply prefer a bool(false). So I did it like this:
function __database_row($_table, $_id = 0, $_key = "id") {
try {
return _database_row($_table, $_id, $_key);
} catch (Exception $e) {
return false;
}
}
(don't miss the use of another leading "_"). This also seems to be working perfectly fine, so what's your opinion here?
IMPORTANT:
what is the use of "PDOStatement::closeCursor" exactly? I did read the manual but I am quite confused as I can call my functions as many times as I want and still get the desired/expected results but never "closed the cursor"
now... ENOUGH WITH SELECTS AND FETCHINGS :) lets talk about INSERTS
so I made this function to add multiple products in a single script execution and quickly.
function _add_product($_name, $_price = 0) {
global $Database;
if(is_object($Database)) {
$Query = $Database->prepare("INSERT INTO products (name, price) VALUES (:name, :price)");
$Query->execute(Array(":name" => $_name, ":price" => $_price));
if($Query->rowCount() >= 1) {
return $Database->lastInsertId();
} else {
throw new Exception("Database Query Failure: ".$Query->errorInfo()[2]);
}
}
return false;
}
This also seems to be working perfectly fine, but can I really rely on the method I used to get the ID of latest insert?
Thank you all!
There is a lot going on here, so I will try to answer specific questions and address some issues.
I am not a fan of OOP
Note that just because code has objects doesn't mean that it is object oriented. You can use PDO in a purely procedural style, and the presence of -> does not make it OOP. I wouldn't be scared of using PDO for this reason. If it makes you feel any better, you could use the procedural style mysqli instead, but I personally prefer PDO.
By simply preparing the statement, am I totally secure from any SQL injection threat?
No.
Consider $pdo->prepare("SELECT * FROM t1 WHERE col1 = $_POST[rightFromUser]"). This is a prepared statement, but it is still vulnerable to injection. Injection vulnerability has more to do with the queries themselves. If the statement is properly parameterized (e.g. you were using ? instead of $_POST), you would know longer be vulnerable. Your query:
SELECT * FROM {$_table} WHERE {$_key} = :id
actually is vulnerable because it has variables in it that can be injected. Although the query is vulnerable, it doesn't necessarily mean that the code is. Perhaps you have a whitelist on the table and column names and they are checked before the function is called. However the query is not portable by itself. I would suggest avoiding variables in queries at all -- even for table/column names. It's just a suggestion, though.
I couldn't find an alternative to mysql_num_rows()
There isn't one. Looking at the count of fetched results, using SELECT COUNT or looking at the table stats (for some engines) are surefire way to get the column count for SELECT statements. Note that PDOStatement::rowCount does work for SELECT with MySQL. However, it is not guaranteed to work with any database in particular according to the documentation. I will say that I've never had a problem using it to get the selected row count with MySQL.
There are similar comments regarding PDO::lastInsertId. I've never had a problem with that and MySQL either.
let me explain that in above function I designed it so it returns the row directly whenever I am looking for a single one
I would advise against this because you have to know about this functionality when using the function. It can be convenient at times, but I think it would be easier to handle the result of the function transparently. That is to say, you should not have to inspect the return value to discover its type and figure out how to handle it.
I wouldn't like those nasty "uncaught exception" errors
Exception swallowing is bad. You should allow exceptions to propagate and appropriately handle them.
Generally exceptions should not occur unless something catastrophic happens (MySQL error, unable to connect to the database, etc.) These errors should be very rare in production unless something legitimately happens to the server. You can display an error page to users, but at least make sure the exceptions are logged. During development, you probably want the exceptions to be as loud as possible so you can figure out exactly what to debug.
I also think that names should be reasonably descriptive, so two functions named __database_row and _database_row are really confusing.
IMPORTANT: what is the use of "PDOStatement::closeCursor" exactly?
I doubt you will have to use this, so don't worry too much about it. Essentially it allows you to fetch from separate prepared statements in parallel. For example:
$stmt1 = $pdo->prepare($query1);
$stmt2 = $pdo->prepare($query2);
$stmt1->execute();
$stmt1->fetch();
// You may need to do this before $stmt2->execute()
$stmt1->closeCursor();
$stmt2->fetch();
I could be wrong, but I don't think you need to do this for MySQL (i.e. you could call execute on both statements without calling closeCursor.
can I really rely on the method I used to get the ID of latest insert?
PDO's documentation on this (above) seems to be more forgiving about it than it is about rowCount and SELECT. I would use it with confidence for MySQL, but you can always just SELECT LAST_INSERT_ID().
Am I doing it right?
This is a difficult question to answer because there are so many possible definitions of right. Apparently your code is working and you are using PDO, so in a way you are. I do have some criticisms:
global $Database;
This creates a reliance on the declaration of a global $Database variable earlier in the script. Instead you should pass the database as an argument to the function. If you are an OOP fan, you could also make the database a property of the class that had this function as a method. In general you should avoid global state since it makes code harder to reuse and harder to test.
the above function is intended to fetch a row from $_table table with $_id
Rather than create a generic function for querying like this, it is better to design your application in a way that will allow you to run queries that serve specific purposes. I don't really see why you would want to select all columns for a table for a given ID. This is not as useful as it seems. Instead, you probably want to get specific columns from these tables, perhaps joined with other tables, to serve specific functions.
Well, your main problem is not OOP, but SQL.
To tell you truth, SQL is by no means a silly key-value storage you are taking it for. So, it makes your first function, that can be used only on too limited SQL subset, is totally useless.
Moreover, you are making a gibberish out of almost natural English of SQL. Compare these 2 sentences
SELECT * FROM products WHERE status = ?
quite comprehensible - isn't it?
products! enabled! status!
HUH?
Not to mention that this function is prone to SQL injection. So, you just have to get rid of it. If you want a one-liner, you can make something like this
function db_row($sql, $data = array(), $mode = PDO::FETCH_ASSOC) {
global $Database;
$stmt = $Database->prepare($sql);
$stmt->execute($data);
return $stmt->fetch($mode);
}
to be called this way:
$row = db_row("SELECT * FROM products WHERE id = ?",[14]);
Note that PDO is intelligent enough to report you errors without any intervention. All you need is set it into exception mode.
Speaking of second function, can be reviewed.
function _add_product($_name, $_price = 0)
{
global $Database;
$sql = "INSERT INTO products (name, price) VALUES (?,?)";
$Database->prepare($sql)->execute(func_get_args());
return $Database->lastInsertId();
}
is all you actually need.
I couldn't find an alternative to mysql_num_rows()
You actually never needed it with mysql and would never need with PDO either
Here's the code i am using withing a for loop which run 10 times:
$query = "UPDATE fblikes SET likes = '$var[$i]' WHERE link = '$web[$i]'";
if(mysql_query($query))
{
echo $query;
}
else
{
echo mysql_error();
}
The code runs, I do get ok! printed 10 times but nothing happens in the table. I also checked the 2 arrays i.e. $var and $web, they contain the correct values.
The query looks okay to me. Here's what i got (one of the 10 outputs) : UPDATE fblikes SET likes = '5' WHERE link = 'xxxxxxx.com/xxxx/iet.php';
I don't know what the problem exactly is, and to figure out you should print the value of $query, and show us what you get. More, please tell us the value of mysql_affected_rows() after the call to mysql_query().
However, your code implements some wrong patterns.
First of all, you are not escaping $var[$i] and $web[$i] with two potential effects:
You can produce wrong queries
You don't sanitize the input to the database, thus exposing your application to security issues
Moreover, you perform several similar queries that differ only on the inputs provided.
The solution, for both issues, is the use of prepared statements that will give you more control, security and performance. Consider abandoning mysql_* functions and switching to mysqli_* or PDO, and read about prepared statements.
I'm trying to loop data from a api and then post these values to a MySQL db.
something like this:
$values = json_decode(file_get_contents("my-json-file"));
$SQL = new mysqli(SQL_HOST, SQL_USER, SQL_PASS, DB_NAME);
$SQL->autocommit(FALSE);
foreach($values As $item)
{
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2.";)";
$SQL->query($query);
if(!$SQL->commit())
{
echo "ERROR ON INSERT: [" . $query . "]<hr/>";
}
}
$SQL->close();
Since the loop is too fast, the SQL can't catch up. (Yea!)
I would then need something like this:
foreach($values As $item)
{
/**** STOP/PAUSE LOOP ****/
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2.";");
$SQL->query($query);
if($SQL->commit())
{
/**** START THE LOOP AGAIN ****/
}
else
{
echo "ERROR ON INSERT: [" . $query . "]<hr/>";
}
}
Or how should I do this the right way?
EDIT: It inserts random posts every time.
EDIT 2: This is just example code. It does escape and all that, and yes the semi colon is wrong here but since so many commented on it i will not change it. This was not the problem in the real case.
I tried to run it on another server and there it worked. The problem was fixed by restarting MAMP.
Firstly, your idea that the loop runs too fast for MySQL to keep up is completely totally wrong. The $SQL->query() call will wait for the MySQL to return a response before proceeding, so the loop won't run any faster than MySQL is responding.
Now onto the actual problem.... your query:
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2.";)";
There's a semi-colon in there at the end, after value2 which is invalid. I guess you intended to type a quote mark there? The semi-colon will be causing all your queries to fail and throw errors.
This may be the cause of your problem but you haven't got any error checking in there, so you won't know. Add some error checking to your code after calling the query; even if the query is right, it's still possible to get errors, and your code should check for them. See the examples on this manual page: http://www.php.net/manual/en/mysqli-stmt.error.php
Finally, since you're using the mysqli API, it's worth mentioning that your code would be a lot better and probably more secure if you used prepared statements. See the examples in PHP manual here: http://www.php.net/manual/en/mysqli-stmt.bind-param.php
[EDIT]
Another possible reason your query is failing is that you're not escaping the input values. If any of the input values contains a quote character (or any other character that is illegal in SQL) then the query will fail. In addition, this problem makes your code vulnerable to a SQL injection hacking attack.
You need to escape your input using $SQL->real_escape_string() OR by changing your query to use prepared statements (as recommended above).
Your query is inside the loop, which means that the loop will wait until your query finished executing before it continue, php code is processed in order...
Has #phpalix said, PHP goes in order, and waits for the previous action to finish.
I think you SQL is wrong. Try replacing your INSERT with this:
$query = "INSERT INTO my_table VALUES ('".$item->value1."', '".$item->value2."');";
And don't forget to run at least mysql_real_escape_string for each variable, for security measures.
As many of the answers and comments say, it does not continue until the SQL is done. The problem was in my local apache/mysql server. It was fixed by restarting it. Yes, stupid post.
Hopefully someone will be able to help me because I've been banging my head against the wall all night trying to solve this little problem.
I want to insert data into a database using PDO (which I am admittedly not the most knowledgeable about). I am using a statement that I have used many times in the past, but for some reason this time it's not working. The statement is as follows:
$userID = "Johnny5";
$sql = "INSERT INTO user_info(user_id) VALUES(:user-id)";
if($stmt = $this->_db->prepare($sql))
{
$stmt->bindParam(":user-id", $userID, PDO::PARAM_STR);
$stmt->execute();
$stmt->closeCursor();
return TRUE;
} else {
return FALSE;
}
But unfortunately this is always returning TRUE without ever entering anything into my database. I have tried every combination changes to the statement that I could think of, but I am still at a loss.
I hope someone out there can point out a really simple error that I have made.
Also, placing single quote marks around the parameter :user-id in the $sql string is the only way that I can get anything to appear into the database, but that obviously doesn't enter in any actual data into the database.
EDIT
I have also changed the PDO parameter types from PDO::PARAM_STR to PDO::PARAM_INT but have still had no luck.
After further investigation, execute() is returning FALSE.
Solution
Thanks to everyone for their guidance. #Nabeel was correct in saying not to use placeholders in PDO parameters.
Don't use dashes in your SQL statements.
Make this:
:user-id
as this:
:userid
$userId is a string in this code, yet you say it's in int by defining PDO::PARAM_INT. Try replacing it with PDO::PARAM_STRING or try setting $userId to a number(1 for example)
If something fails, then there most certainly was an error. First you need to find out which statement fails by checking their return values (always read about every command you use in the PHP Manual first).
If you've located which command failed, use PDOStatement->errorInfo to find out more about the concrete error.
That information should help you to solve your issue.
Update: And if you want to deal with Exceptions (as Pekka suggested): How to squeeze error message out of PDO?