3

This is a really weird error that only started appearing today. When I use a prepared statement with ? for parameters, I get an error, but when I use it without parameters, it works just fine. Here is the error-causing code:

    String table = "files";
    Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    PreparedStatement prep = conn.prepareStatement("SELECT * FROM ?");
    prep.setString(1, table);
    ResultSet rs = prep.executeQuery();

    while(rs.next()) {
        System.out.println(rs.getString("file_name"));
    }

This produces the following error:

Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 ''files'' at line 1

Also, changing it to the following works just fine:

    String table = "files";
    Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    PreparedStatement prep = conn.prepareStatement("SELECT * FROM " + table);
    ResultSet rs = prep.executeQuery();

    while(rs.next()) {
        System.out.println(rs.getString("file_name"));
    }

This doesn't seem to be making a whole lot of sense. Any ideas?


Tried it on another table and got more weired results. This works and logs the admin in correctly:

    String sql = "SELECT * FROM " + ADMIN_AUTH_TABLE + " WHERE " + column + " = '" + hashedPassword + "'";
    PreparedStatement prepared = connection.prepareStatement(sql);

The following doesn't cause errors, but returns a message saying that the password entered is incorrect (it's correct - I double triple checked).

    String sql = "SELECT * FROM " + ADMIN_AUTH_TABLE + " WHERE ? = ?";
    PreparedStatement prepared = connection.prepareStatement(sql);
    prepared.setString(1, column);
    prepared.setString(2, hashedPassword);


Got it: use ? for values. Also, the answer here helped.

2
  • 1
    I don't think you can pass table name as parameter using prepared statement because prepared statement is pre-compiled and knowing the table(s) and/or view(s) at compile time. Commented Mar 24, 2016 at 2:36
  • And your change is just doing a string concatenation by add the table name to your query so it works just fine. Commented Mar 24, 2016 at 2:36

2 Answers 2

3

Bind parameters cannot be used for identifiers in the SQL statement. Only values can supplied through bind placeholders.

This will work:

SELECT foo FROM bar WHERE id = ? 

This will not work, because the table name is an identifier

SELECT foo FROM ? WHERE id = 2

You can't supply a column name, because column names are also identifiers. A statement like this will run, but it may not do what you think it does.

SELECT ? AS foo FROM bar WHERE ? = 0

If we supply values of 'foo' for both placeholders, the query will actually be equivalent to a query containing two string literals:

SELECT 'foo' AS foo FROM bar WHERE 'foo' = 0

MySQL will run that statement, because it's a valid statement (if the table bar exists and we have privileges on it.) That query will return every row in bar (because the predicate in the WHERE clause evaluates to TRUE, independent of the contents of the table.. And we get returned the constant string foo.

It doesn't matter one whit that the string foo happens to match the name of column in our table.


This restriction has to do with how the SQL optimizer operates. We don't need to delve into all the details of the steps (briefly: parsing tokens, performing syntax check, performing semantics check, determining query plan, and then the actual execution of the query plan.)

So here's the short story: The values for bind parameters are supplied too late in that process. They are not supplied until that final step, the execution of the query plan.

The optimizer needs to know which tables and columns are being referenced at earlier stages... for the semantics check, and for developing a query plan. The tables and columns have to be identified to the optimizer. Bind placeholders are "unknowns" at the time the table names and column names are needed.

(That short story isn't entirely accurate; don't take all of that as gospel. But it does explain the reason that bind parameters can't be used for identifiers, like table names and column names.)

tl;dr

Given the particular statement you're running, the only value that can be passed in as a bind parameter would be the "hashedPassword" value. Everything else in that statement has to be in the SQL string.

For example, something like this would work:

String sqltext = "SELECT * FROM mytable WHERE mycolumn = ?";
PreparedStatement prepared = connection.prepareStatement(sqltext);
prepared.setString(1, hashedPassword);

To make other parts of the SQL statement "dynamic" (like the table name and column name) you'd have to handle that in the Java code (using string concatenation.) The contents of that string would need to end up like the contents of the sqltext string (in my example) when it's passed to the prepareStatement method.

Sign up to request clarification or add additional context in comments.

Comments

1

The parameters of PreparedStatement should be applied only in parameters that can be used in conditional clauses. The table name is not the case here.

If you have a select where the table name can be applied in the conditional clause you can do it, otherwise you can not.

3 Comments

Can also be used in select clause, on clause, having clause, offset clause, ...; basically anywhere a value is allowed.
You're right @Andreas. Conditional clause sounds better. Thanks
This answer isn't entirely accurate. Bind placeholders can actually appear in a lot of different places. The restriction is actually that keywords and identifiers cannot be supplied as bind parameters. And the reason for that is that the values for the bind parameters are supplied too late in the statement processing. The values of those are unknown during the syntax check, when the keywords have to be known. And they are unknown at the time the semantics check is performed, when the tables, columns, functions, etc. must be identified.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.