toxalot
toxalot

Reputation: 11820

MySQLi prepared statements are silentely mangling my data. Is this expected behaviour?

My code currently uses mysqli::query and checks mysqli::$warning_count. When trying to insert text into an integer column, 0 gets inserted and a warning is generated. However, I'm just starting to learn prepared statements and for the same query, no warning is generated.

Here's an excerpt from my code:

$db_err_msg = 'Database Error: Failed to update profile';
$sql = "UPDATE tblProfiles SET intBookId = ? WHERE lngProfileId = ?";
$stmt = $mysqli->prepare($sql) or output_error($db_err_msg);

$book = 'CA';
$id = 10773;
$stmt->bind_param('ii', $book, $id) or output_error($db_err_msg);
$stmt->execute() or output_error($db_err_msg);
echo '$stmt->affected_rows is ', $stmt->affected_rows, "\n";

if ($mysqli->warning_count) {
    $warnings = $stmt->get_warnings();
    do {
        trigger_error('Database Warning (' . $warnings->errno . '): '
            . $warnings->message, E_USER_WARNING);
    } while ( $warnings->next() );
}
else {
    echo 'no warnings', "\n";
}

which produces the following output:

$stmt->affected_rows is 1
no warnings

Note that the intBookId column has a TINYINT data type. The same query generates a warning when using mysqli::query, but not when using prepared statements.

Enabling strict mode does not help. It will turn the warning into an error when using mysqli::query, but when using prepared statements the column is silently updated with a 0;

For the record, my application already does extensive validation before it gets to this point. But I wanted this extra validation as way to catch anything I might miss by mistake.

Note: It's beyond the scope of the current project to switch to PDO.

Why is this happening with MySQLi prepared statements? Is this expected behaviour?

Upvotes: 0

Views: 151

Answers (1)

toxalot
toxalot

Reputation: 11820

Though it may not be immediately obvious, it is expected behaviour.

Because of this line of code:

$stmt->bind_param('ii', $book, $id) or output_error($db_err_msg);

PHP is casting the value to an integer before sending it to MySQL. MySQL receives the value 0 which is a valid value for the column and no warning is generated.

If you don't want PHP to do this, then you need to send it as a string. Like so:

$stmt->bind_param('si', $book, $id) or output_error($db_err_msg);

MySQL will receive the string 'CA' and generate a warning about it being an incorrect integer value.

Note: By sending everything as a string, MySQL will have to do a little more processing to convert the strings to integers, but it was already doing that anyway when the whole query was sent as a string using mysqli::query.

Note: A related problem occurs when using integers that are greater than PHP_INT_MAX. If you think an integer value will surpass that maximum (which is platform-dependent and only 2147483647 on 32-bit platforms) and precision is important, it's safer to send it as a string.

Upvotes: 1

Related Questions