views:

177

answers:

3

I'm playing around with prepared statements in PHP/PDO. The basic queries work fine, passing a value to the WHERE clause:

$stmt = $db->prepare( 'SELECT title FROM episode WHERE id=:id' );
$stmt->bindParam( ':id', $id, PDO::PARAM_INT );
$id = 5;
$stmt->execute();

However I have a situation where I need to pass variables for the field names. This query (with appropriate binding) works fine:

SELECT :field FROM episode WHERE id=:id

This one gives an error:

SELECT title FROM :field WHERE id=:id

This one doesn't give an error, but returns no rows:

SELECT title FROM episode WHERE :field=:id

So, what things should work in prepared statements? Can I 'parameterize' field names, table names and so on?

+3  A: 

You cannot parameterize table names, column names, or anything in an IN clause (thanks to c0r0ner for pointing out the IN clause restriction).

See this question, and subsequently this comment in the PHP manual.

Josh Leitzel
Thanks for the response. It turns out the first query I posted doesn't work - if you bind, say, `'title'` to `:field` then it just selects the string 'title' and not the value of the field. Strange that there isn't a method to bind columns/tables, since now I gotta add some extra security, which PDO was supposed to be handling for me :/
DisgruntledGoat
The thinking behind this is probably that you really shouldn't be letting your users directly choose which fields/tables your query is invoking. But I agree that it does add a little extra work on your end.
Josh Leitzel
I see your point. However, I'm using it in a situation where only I specify the tables, but in an abstract way, e.g. `displayTable('episode')`. I guess I don't really need to worry about parameters/security in this case.
DisgruntledGoat
But in your situation, there is no need for parameterization. I understand why you're inclined to try it at first. (I was too, just a week or so ago before I discovered the restrictions.) The point of parameterization is to prevent injection from the user, something you shouldn't need to worry about with your own private table/column names.
Josh Leitzel
A: 

@ Josh Leitzel

That thinking is very restrictive (and is in my opinion just an excuse for being too lazy to implement a robust solution), especially for dynamic tree structures expressed in a database.

Consider the following example:

My project has a logical structure:

A company hierarchy is expressed in terms of entities. Each entity can treated in the general case of being a member of the hierarchy or as a member of a specific level of the hierarchy. The hierarchy itself is defined in a table as a single tree branch as follows:

entity_structure (
   id
   name
   parent_entity_structure_id
);

and the entities themselves are expressed as:

entities (
   id
   name
   entity_structure_id
   parent_id
);

For ease of use I've built an algorithm that creates a flat view of the tree. The following concrete example illustrates what I mean:

SELECT * FROM entity_structure;

id      | name               | entity_structure_parent_id
-----------------------------------------------------------
1       | Company            | null    (special one that always exists)
2       | Division           | 1
3       | Area               | 2
4       | Store              | 3

This would result in the following flat representation being produced:

entity_tree (
   entity_id
   division_id
   area_id
   store_id
)

Entities that are at the division level would have division_id, area_id and store_id as NULL, An area area_id and store_id as NULL, etc.

The nice thing about this is it lets you query all the children of a division using a statement similar to the follow:

SELECT * FROM entity_tree WHERE division_id = :division_id;

However this assumes that I know the structure level of the entity I'm querying. It would be nice to do:

SELECT * FROM entity_tree WHERE :structure = :entity_id;

I know it's not hard to figure out the structure level of a single entity, but assume I'm looping through a collection of entities that may not all be at the same level. As it is now I have to build a separate query for each level of the hierarchy, but if I could parameterize fields I could do the following:

$children = array();
$stmt = $pdo->prepare('SELECT entity_id FROM entity_tree WHERE :structure = :entityId');
foreach ($entities AS $entity) {
   $stmt->execute(array(
      ':structure' = $entity->getEntityStructureId(),
      ':entityId'  = $entity->getId()
   ));

   $children[$entity->getId()] = $stmt->fetchAll(PDO::FETCH_COLUMN);
}

resulting in cleaner code and only one prepared statement.

The entire example uses no user input whatsoever.

Just something to consider.

Philip
What's the point, though? You could just as easily use regular variables in this instance since there is no user input. There is not any chance of injection here. Also, this should have been posted as a comment to my answer since this is not an answer to the OP's question. (I realize you wouldn't have been able to fit it, just noting this.)
Josh Leitzel
A: 

You can't parameterize anything withing IN clause as well.

c0r0ner
Thanks, I forgot about that. I'll add it to my answer.
Josh Leitzel