Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Statement nullified after loading results #204

Closed
SharkyKZ opened this issue Nov 1, 2019 · 15 comments
Closed

Statement nullified after loading results #204

SharkyKZ opened this issue Nov 1, 2019 · 15 comments

Comments

@SharkyKZ
Copy link
Contributor

SharkyKZ commented Nov 1, 2019

Steps to reproduce the issue

Run a prepared statement in a loop.

$ids = [1, 2, 3];

$query
	->select('*')
	->from('#__table')
	->where('id = :id')
	->bind(':id', $id, ParameterType::INTEGER);
$db->setQuery($query);

foreach ($ids as $id)
{
	$db->loadObject();
}

Expected result

Rows with specified IDs loaded.

Actual result

Call to a member function bindParam() on null

System information (as much as possible)

PDO and MySQLi.

Additional comments

I'm again not sure whether this is expected or not. One can just move $db->setQuery() inside the loop to workaround this. However, this is not required when performing queries that are executed with $db->execute() (e.g. insert/update). To my (limited) understanding, given the nature of prepared statements, DatabaseDriver::freeResult() should nullify only the result set and not the entire statement.

@mbabker
Copy link
Contributor

mbabker commented Nov 1, 2019 via email

@SharkyKZ
Copy link
Contributor Author

SharkyKZ commented Nov 1, 2019

Yes, I am. I know it's not needed for this specific query, but that's beside the point. It's just an example.

@SharkyKZ
Copy link
Contributor Author

SharkyKZ commented Nov 1, 2019

This should be clearer https://github.com/joomla/joomla-cms/blob/68f5004fefde38a09a3a70e7c42ec76f4be144fa/administrator/components/com_menus/Model/MenusModel.php#L79-L135

I would expect the query run in rapid succession without having to be re-set over and over again. That's how prepared statements are supposed to work, right?

And this does work when, for example, running update queries:

$query
	->update('#__table')
	->set('column = :data')
	->where('id = :id')
	->bind(':data', $data, ParameterType::STRING);
	->bind(':id', $id, ParameterType::INTEGER);
$db->setQuery($query);

foreach ($dataArray as $id => $data)
{
	$db->execute();
}

@mbabker
Copy link
Contributor

mbabker commented Nov 1, 2019 via email

@SharkyKZ
Copy link
Contributor Author

SharkyKZ commented Nov 1, 2019

PDO documentation says otherwise:

The query only needs to be parsed (or prepared) once, but can be executed multiple times with the same or different parameters.

@mbabker
Copy link
Contributor

mbabker commented Nov 1, 2019 via email

@HLeithner
Copy link
Contributor

That's one of the main features of prepared statements and we support it in the database drivers bind function with the reference value parameter.

I'm pretty sure we use this in the cms already but with an additional setQuery() before execute().

If we don't want to support this we should remove the reference in the bind function because it's a pain for alternative syntax like using arrays.

@SharkyKZ
Copy link
Contributor Author

SharkyKZ commented Nov 2, 2019

execute() works correctly without the need to run setQuery() every time. It's only load*() methods that have the issue. If we have to run setQuery() in the loop, we're not making use of performance improvements prepared statements have to offer.

@mbabker
Copy link
Contributor

mbabker commented Nov 2, 2019 via email

@mbabker
Copy link
Contributor

mbabker commented Nov 2, 2019 via email

@SharkyKZ
Copy link
Contributor Author

SharkyKZ commented Nov 3, 2019

Closing the cursor is fine and possibly required on some systems, according to PDO documentation. It's the statement itself that should only be discarded when no longer needed.

What would be the most correct way to do this? Adding a parameter to load*() methods that's passed to freeResult() to make nullifying the statement optional? And a public method for nullifying the statement manually? Or some other way?

@mbabker
Copy link
Contributor

mbabker commented Nov 3, 2019 via email

@SharkyKZ
Copy link
Contributor Author

SharkyKZ commented Nov 6, 2019

PR #205.

May I ask though, why are statements done in such a reclusive way? I noticed you mentioning Doctrine on multiple occasions. By comparison, it's a lot more public regarding this. Users could call $db->prepare() and get the prepared statements and then do whatever with it directly. Meanwhile here everything is done through the driver instance which holds at most a single statement at any given time. So while #205 allows to run statements in rapid succession, we still can't prepare multiple statements for later use.

@mbabker
Copy link
Contributor

mbabker commented Nov 6, 2019 via email

@SharkyKZ
Copy link
Contributor Author

SharkyKZ commented Nov 7, 2019

Exposing the statement allows to run both prepared and non-prepared statements and to maintain multiple prepared statements. The latter isn't very suitable for use in the CMS though, as picking which statements to keep globally is very project-specific. But for custom applications using the framework it would make sense.

Anyways, for now I'd just like to continue writing parameterized queries in the CMS. #205 is enough for that.

Thanks.

@SharkyKZ SharkyKZ closed this as completed Nov 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants