views:

124

answers:

3

I am using MySQL Connector/Net and I want to write a query against a table whose name will be specified at runtime.

This example is off the top of my head (not tested):

public class DataAccess
{
    public enum LookupTable
    {
        Table1,
        Table2,
        Table3
    }

    public int GetLookupTableRowCount(LookupTable table)
    {
        string tableName = string.Empty;

        switch (table)
        {
            case LookupTable.Table1: 
                tableName = "table_1";
                break;
            case LookupTable.Table2: 
                tableName = "table_2";
                break;
            case LookupTable.Table3: 
                tableName = "table_3";
                break;
            default:
                throw new ApplicationException("Invalid lookup table specified.");
        }

        string commandText = string.Concat("SELECT COUNT(*) FROM ", tableName);

    // Query gets executed and function returns a value here...
    }
}

Since I don't think you can parameterize a table name in a query, I used an enum rather than a string in the function parameter to limit the possibility of SQL injection.

Does that seem like a good approach? Is there a better way?

+1  A: 

Right, you can't use a query parameter for a table name, a column name, an SQL keyword or expression, etc. You can use a query parameter only for a single value.

I do agree that doing some kind of mapping from input to the literal table name is a good way to protect against SQL injection.

I don't program in .NET, I usually use dynamic languages like PHP, Python, or Perl. So I use a hash array. You can skip the switch() if you can simply use your enumeration variable to index into the hash array.

$tableName = $tableNameHash[ $table ];

Does .NET support a hash-map type of data structure? That's what I'd look for.


Looks like there is a hash_map class in Standard C++ library.

Bill Karwin
Yes, there are hashtables and dictionaries and lots of other collection-style classes in .NET. I like your suggestion.
Andy West
+3  A: 

You can't paramaterize an identifier (table name or field name) in MySQL, however, you can escape them using backticks.

The following query will run safely but produce an error because the table doesn't exist (unless by some weird chance you actually have a table named like this):

SELECT * FROM `users; DROP TABLE users;`;

Basically, you can use dynamic names or fields as long as they are enclosed in backticks. In order to prevent SQL injection this way, all you need to do is strip out any backticks first:

tableName = tableName.Replace("`", "");
string commandText = "SELECT COUNT(*) FROM `" + tableName + "`";
zombat
I see what you are saying, but I think I'm safer isolating my query text from any free-form inputs. That's why I didn't want to use a string parameter in my data access method.
Andy West
Well, I can't make you trust the semantics of the language. You either trust MySQL's backtick and escape methods or you don't. The alternative is coupling your database structure to your code via some kind of lookup table as others have suggested, which I personally find very distasteful.
zombat
I would actually suggest you read that page on identifiers thoroughly, as it lays out what a valid identifier is. If you wanted extra protection, you could always enforce alphanumeric chars only or even query to see if the table existed beforehand. However, MySQL will handle any invalid characters in an identifier by throwing an error on the query.
zombat
If you're worried about compatibility, see the ANSI_QUOTES server setting (http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_ansi_quotes). Your comment seems to have disappeared, but if you're truly developing an application that takes no advantage of MySQL-specific features or syntax anywhere, and you think you'll be using different databases with it, then you'll want to be developing with all the ANSI features turned on anyway.
zombat
I deleted my comment because I didn't think it was fair to mention multiple database support after the fact. I understand what you're saying about coupling. It also seems to violate the DRY principle somewhat. Still, it seems difficult to avoid "coupling" in some form when dealing with DA code, ORM, etc.
Andy West
Ok, you have convinced me. This is safe enough and simple enough for my purposes. Also, it looks like most databases have settings for quoted identifiers.
Andy West
+2  A: 

Dynamic table names are never a good approach (unless you are developing a PHPMyAdmin or something similar).

If you table names are limited, why don't you just make a stored procedure and call it with a parameter?

DECLARE _which INT
BEGIN
        SELECT  COUNT(*)
        FROM    table_1
        WHERE    _which = 1
        UNION ALL
        SELECT  COUNT(*)
        FROM    table_2
        WHERE    _which = 2
        UNION ALL
        SELECT  COUNT(*)
        FROM    table_3
        WHERE   _which = 3
END
Quassnoi
I disagree with "never a good approach". Large enterprise applications almost assuredly will use some kind of dynamic naming scheme to break data into manageable sets. Database sharding techniques will even use dynamic *database* names, not just tables.
zombat