ERROR:SQLSTATE[42000]: Syntax error or access violation: 1064

SpaceBuzz picture SpaceBuzz · Jun 8, 2013 · Viewed 17.2k times · Source

I was looking for answer for last 3 hours, and i don't know what to do. Here is the code:

    function get_data($tablename)
    {
        try
        {
            $conn = $this->conn();
            $stmt = $conn->prepare("SELECT * FROM :tablename ORDER BY id");
            $stmt->bindParam(':tablename', $tablename, PDO::PARAM_STR);
            $stmt->execute();
            return $stmt;
        }
        catch (Exception $e)
        {
            echo "ERROR:" . $e->getMessage();
        }
    }  

And here is the error:

ERROR: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 ''products' ORDER BY id' at line 1

What I've done wrong?...

Answer

michaelb958--GoFundMonica picture michaelb958--GoFundMonica · Jun 8, 2013

As noted here (thanks @YourCommonSense), you can't use a parameter as a table name; and if you do, one of two things will happen:

  1. With proper prepared statements, the prepared-statement module will throw an exception (and quite rightly so, as you've asked it to do the impossible).
  2. With emulated prepared statements, the parameter will be blindly escaped, single-quoted, and substituted in, causing an SQL syntax error. This is what's happened here.

That's the problem. As for solutions:

  • Reevaluate your database design. Do you really need to split data across different tables like that? If not, combine the relevant data into a single table, and query accordingly.
  • If you're happy with the design (or can't change it), you'll need an ugly insecure hack like the following:

    function get_data($tablename, $acceptable_tablenames = array()) {
      /* $acceptable_tablenames is an array of strings, containing
       *  table names that you'll accept. It's your job to make sure
       *  these are safe; this is a much easier task than arbitrary
       *  sanitization.
       */
      if (array_search($tablename, $acceptable_tablenames, true) === FALSE) {
        throw new Exception("Invalid table name"); /* Improve me! */
      } else {
        /* your try/catch block with PDO stuff in it goes here
         * make sure to actually return the data
         */
      }
    }
    

    Call it as get_data($table, array("my_datatable_1", "my_datatable_2")). Credit to the post linked to at the start of my answer for inspiration.