4

This query returns 5 results in phpMyAdmin:

SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT 0,5

And this returns count=12 in phpMyAdmin (which is fine, because there are 12 records):

SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT 0,5

This function was working fine before I added the two variables (offset, display), however now it doesn't work, and printing the variables out gives me offset=0, display=5 (so still LIMIT 0,5).

function getProducts($offset, $display) {
    $sql = "SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT ?,?;";
    $data = array((int)$offset, (int)$display);
    $rows = dbRowsCount($sql, $data);
logErrors("getProducts(".$offset.",".$display.") returned ".$rows." rows.");
    if ($rows > 0) {
        dbQuery($sql, $data);
        return dbFetchAll();
    } else {
        return null;
    }
}

It's not working because my dbRowsCount(...) method was returning empty string (stupid PDOStatement::fetchColumn), so I changed it to return the count with PDO::FETCH_ASSOC and it returns count=0.

Here is the function which does the row-count:

function dbRowsCount($sql, $data) {
    global $db, $query;
    $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/is';
    if (preg_match($regex, $sql, $output) > 0) {
        $query = $db->prepare("SELECT COUNT(*) AS count FROM {$output[1]}");
logErrors("Regex output: "."SELECT COUNT(*) AS count FROM {$output[1]}");
        $query->setFetchMode(PDO::FETCH_ASSOC);
        if ($data != null) $query->execute($data); else $query->execute();
        if (!$query) {
            echo "Oops! There was an error: PDOStatement returned false.";
            exit;
        }
        $result = $query->fetch();
        return (int)$result["count"];
    } else {
logErrors("Regex did not match: ".$sql);
    }
    return -1;
}

My error log gives me this output from the program:

Regex output: SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT ?,?;
getProducts(0,5) returned 0 rows.

As you can see, the SQL has not been malformed, and the method input variables were 0 and 5 as expected.

Does anyone know what has gone wrong?

Update

Following a suggestion, I did try to execute the query directly, and it returned the correct result:

function dbDebugTest() {
    global $db;
    $stmt = $db->query("SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update LIMIT 0,5;");
    $result = $stmt->fetch();
    $rows = (int)$result["count"];
    logErrors("dbDebugTest() returned rows=".$rows);
}

Output:

> dbDebugTest() returned rows=12

Following another suggestion, I changed !=null to !==null, and I also printed out the $data array:

logErrors("Data: ".implode(",",$data));
if ($data !== null) $query->execute($data); else $query->execute();

Output:

> Data: 0,5

However, the dbRowsCount($sql, $data) still returns 0 rows for this query!

Update 2

Following advice to implement a custom PDOStatement class which would allow me to output the query after the values have been binded, I found that the function would stop after $query->execute($data) and so the output would not be printed, although the custom class works for every other query in my program.

Updated code:

function dbRowsCount($sql, $data) {
    global $db, $query;
    $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/is';
    if (preg_match($regex, $sql, $output) > 0) {
        $query = $db->prepare("SELECT COUNT(*) AS count FROM {$output[1]}");
logErrors("Regex output: "."SELECT COUNT(*) AS count FROM {$output[1]}");
        $query->setFetchMode(PDO::FETCH_ASSOC);
logErrors("Data: ".implode(",",$data));
        $query->execute($data);
logErrors("queryString:".$query->queryString);
logErrors("_debugQuery():".$query->_debugQuery());
        if (!$query) {
            echo "Oops! There was an error: PDOStatement returned false.";
            exit;
        }
        $result = $query->fetch();
        return (int)$result["count"];
    } else {
logErrors("Regex did not match: ".$sql);
    }
    return -1;
}

Output:

Regex output: SELECT COUNT() AS count FROM tbl_product_category WHERE id=?;
Data: 5
queryString:SELECT COUNT(
) AS count FROM tbl_product_category WHERE id=?;
_debugQuery():SELECT COUNT(*) AS count FROM tbl_product_category WHERE id=?;

Regex output: SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT ?,?;
Data: 0,5
// function stopped and _debugQuery couldn't be output

Update 3

Since I couldn't get the custom PDOStatement class to give me some output, I thought I'd rewrite the getProducts(...) class to bind the params with named placeholders instead.

function getProducts($offset, $display) {
    $sql = "SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT :offset, :display;";
    $data = array(':offset'=>$offset, ':display'=>$display);
    $rows = dbRowsCount($sql, $data);
logErrors("getProducts(".$offset.",".$display.") returned ".$rows." rows.");
    if ($rows > 0) {
        dbQuery($sql, $data);
        return dbFetchAll();
    } else {
        return null;
    }
}

Output:

Regex output: SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT :offset, :display;
Data: 0,5
// Still crashes after $query->execute($data) and so logErrors("getProducts(".$offset."...)) wasn't printed out

Update 4

This dbDebugTest previously worked with declaring the limit values 0,5 directly in the SQL string. Now I've updated it to bind the parameters properly:

function dbDebugTest($offset, $display) {
    logErrors("Beginning dbDebugTest()");
    global $db;
    $stmt = $db->prepare("SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update LIMIT :offset,:display;");
    $stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
    $stmt->bindParam(':display', $display, PDO::PARAM_INT);
    if ($stmt->execute()) {
      $result = $stmt->fetch();
      $rows = (int)$result["count"];
      logErrors("dbDebugTest() returned rows=".$rows);
    } else {
      logErrors("dbDebugTest() failed!");
    }
}

The function crashes, and only this is output:

Beginning dbDebugTest()

Update 5

Following a suggestion to turn errors on (they are off by default), I did this:

$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

That itself, made the dbDebugTest() from Update 4 work!

Beginning dbDebugTest() dbDebugTest() returned rows=12

And now an error is generated in my webserver logs:

[warn] mod_fcgid: stderr: PHP Fatal error:
Uncaught exception 'PDOException' with message 'SQLSTATE[42000]:
Syntax error or access violation: 1064 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 ''0', '5'' at line 1'
in /home/linweb09/b/example.com-1050548206/user/my_program/database/dal.php:36

Line 36 refers to dbRowsCount(...) method and the line is $query->execute($data).

So the other method getProducts(...) still doesn't work because it uses this method of binding the data and the params turn into ''0' and '5'' (is this a bug?). A bit annoying but I'll have to create a new method in my dal.php to allow myself to bind the params in the stricter way - with bindParam.

Thanks especially to @Travesty3 and @eggyal for their help!! Much, much appreciated.

Community
  • 1
  • 1
Ozzy
  • 8,244
  • 7
  • 55
  • 95
  • did you try to run the query manually? like using `$db->query(xxx)` without any parameters or wrapper functions? does it work then? – kuba May 01 '12 at 17:10
  • I don't understand why this is being downvoted? @kuba I didn't because I tried the SQL directly in phpMyAdmin and so I assumed it would work, but I will try that now. – Ozzy May 01 '12 at 17:16
  • @Ozzy: Nor do I understand the downvotes (you have my +1). Have you tested the `if ($data != null)` to see which path the execution takes? Shouldn't one use [`!is_null()`](http://php.net/manual/en/function.is-null.php) or `!==` in PHP? – eggyal May 01 '12 at 17:19
  • @eggyal: Should probably be using [`!empty()`](http://php.net/manual/en/function.empty.php). – Travesty3 May 01 '12 at 17:22
  • kuba, yes that returns rows=12. I have a feeling the limit params aren't binding through the associative array. @eggyal I actually did try that and $data is not null p.s thanks for the upvote – Ozzy May 01 '12 at 17:25
  • @Ozzy: Perhaps try a wrapper class like [the one mentioned here](http://stackoverflow.com/a/7716896/259457) in order to see the actual query that was run. – Travesty3 May 01 '12 at 17:28
  • @Travesty3 is that the same as me printing the $sql statement I'm sending to the function which executes it? – Ozzy May 01 '12 at 17:40
  • @tereško did you have an answer? I can't seem to find whats wrong. – Ozzy May 01 '12 at 17:49
  • @Ozzy: The $sql statement still has the placeholder values `?,?`. Using the wrapper class, the aim is to get the actual query that is sent to MySQL, in order to verify that the placeholder values have been properly replaced. – Travesty3 May 01 '12 at 18:04
  • @Travesty3 Ohh okay, so I guess I'll use that then let you know. Cheers. – Ozzy May 01 '12 at 18:08
  • @Travesty3 If I do it after $query->prepare it prints out the SQL with placeholders still in there, if I do it after $query->execute($data) it doesn't work/doesn't run for this query, but runs for all of my other queries... I've updated my question to add this info. – Ozzy May 01 '12 at 18:35
  • @Ozzy: I just posted an answer. Based on the fact that the script stops immediately after the `execute`, that hints that you are getting an error on the query, but are not actually seeing the error. See my answer for details. – Travesty3 May 01 '12 at 18:46

2 Answers2

2

Based on Update 2 in the question, where execution stops after the execute statement, it looks like the query is failing. After looking at some PDO documentation, it looks like the default error handling setting is PDO::ERRMODE_SILENT, which would result in the behavior you are seeing.

This is most likely due to the numbers in your LIMIT clause being put into single-quotes when passed in as parameters, as was happening in this post.

The solution to that post was to specify the parameters as integers, using the bindValue method. So you will probably have to do something similar.

And it looks like you should also be executing your queries with try-catch blocks in order to catch the MySQL error.


bindValue method:

if ($data !== null)
{
    for ($i=0; $i<count($data); $i++)
        $query->bindValue($i+1, $data[$i], PDO::PARAM_INT);
    $query->execute($data);
}
else
    $query->execute();
Community
  • 1
  • 1
Travesty3
  • 14,351
  • 6
  • 61
  • 98
  • I'm type-casting to (int) in my `getProducts(...)` method and besides which I'm doing it the same way for many other queries with integer values, and those work fine. I have to see `bindParam` if that works... – Ozzy May 01 '12 at 18:47
  • Yes, you are type-casting to int, but the trick is to tell *PDO* that the value is an int. To do that, you need to use PDO::PARAM_INT. To use that, you need to use the `bindValue` method. – Travesty3 May 01 '12 at 18:55
  • Okay so I've tried it with the bindParam, type INT, and it still crashes. Please see my update. – Ozzy May 01 '12 at 19:06
  • @Ozzy: Did you try changing your error handling setting? Try `$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);`. – Travesty3 May 01 '12 at 19:13
  • wow, that made the dbDebugTest() work! But the other method doesn't work, and an error is generated in my webserver log files, with a weird param input (it added an apostrophe) – Ozzy May 01 '12 at 19:25
  • @Ozzy: Did you switch to `bindValue` in your other method? I believe using `bindValue` with PDO::PARAM_INT will prevent PDO from surrounding the values with apostrophes, which would cause a MySQL error in the LIMIT clause. – Travesty3 May 01 '12 at 19:29
  • No I haven't but I know, I'll have to change it. I'm a bit annoyed this doesn't work, because it works whenever I use it for other colums, its just with the LIMIT params that it fails (possibly because there are no columns, and it can't tell what the column type is). Thanks a LOT for your help!! – Ozzy May 01 '12 at 19:35
1

You're testing to see if $data is NULL with the equality, rather than the identity, operator (see the PHP manual for more information on how NULL values are handled by the various comparison operators). You need to either use the identity test === / !==, or else call is_null().

As @Travesty3 mentioned above, to test whether an array is empty, use empty().

eggyal
  • 122,705
  • 18
  • 212
  • 237
  • So thats a mistake, which I will now ammend. However, I did at one point print the the $data and it gave me "Array" so it is not null. – Ozzy May 01 '12 at 17:27
  • you are right, but I printed it outside of the IF clause, and just did it again now, its an array with values 0,5. I have a hunch this is to do with execute($data) not binding properly? – Ozzy May 01 '12 at 17:35
  • @eggyal: How did you determine that the test was failing? If it did take the `$query->execute();` path, then wouldn't MySQL throw an error if you tried to use `LIMIT ?,?`? (I'm actually asking, as I don't have much experience with PDO) – Travesty3 May 01 '12 at 17:36
  • @eggyal Oh I agreed with you and changed it back then (when I said 'you are right'). I just updated my post to include the info. – Ozzy May 01 '12 at 17:46
  • @Travesty3: [The documentation](http://www.php.net/manual/en/types.comparisons.php) is clear that loose equality comparison of an array with `NULL` is always `true` (and therefore `!=` is always `false`). QED. – eggyal May 01 '12 at 17:47
  • @eggyal I'm sorry if you misunderstood me, but when I said I updated my post to include the info, I actually meant, the info about changing that and it still not working! I'm still getting 0 rows returned – Ozzy May 01 '12 at 17:50
  • @eggyal I am using AS count!! And the dbDebugTest() worked! It's just when I'm binding the $data in `PDOStatement->execute($data)` that its not working. Please see the section in my question about `dbDebugTest()` – Ozzy May 01 '12 at 17:55
  • @eggyal the if ($data !== null) that one? – Ozzy May 01 '12 at 17:59
  • @eggyal I took out the clause completely, I did this: `$query->execute($data)` without any clause, and it doesn't crash, it gives me 0 rows. Is there a chance PHP is binding them in the wrong order? So for example it would bind `LIMIT 5,0` and return 0 results? Nevermind just tested (1,5) and still got 0 rows. – Ozzy May 01 '12 at 18:03
  • Good call @eggyal. I didn't know that. Take a look at [this post](http://stackoverflow.com/a/2269931/259457). – Travesty3 May 01 '12 at 18:13
  • @Travesty3: That wasn't actually what I was thinking, but hey, if that solves it.. I don't think it should make any difference though, given that Ozzy has cast the array values to `int` on assigning them. – eggyal May 01 '12 at 18:14
  • @eggyal: But perhaps the trick is to specify the value as PDO::PARAM_INT. For that, it looks like you may have to switch to the [`bindValue`](http://php.net/manual/en/pdostatement.bindvalue.php) method and use `PDO::PARAM_INT` as your third parameter. – Travesty3 May 01 '12 at 18:27