QuoteSQL

Duncan Booth duncan.booth at invalid.invalid
Wed Sep 27 12:54:48 CEST 2006


Lawrence D'Oliveiro <ldo at geek-central.gen.new_zealand> wrote:

>> You are still missing the point. I'm not talking about generating a
>> MySQL string literal, I'm talking about preventing wildcards
>> characters having their special meaning when using the string as a
>> parameter in cursor.execute.
> 
> But that's what cursor.execute will do if you use its
> parameter-substitution mechanism--generate a string literal.

The current implementation of the MySQL database adapter will do that. 
Other database adaptors may handle parameters without generating string 
literals.

> 
>> You still have to escape the escape character...
> 
> Which will be done by cursor.execute if you use its
> parameter-substitution mechanism.

Too late and not enough. Too late, because if you want to search for the 
literal "\\%" (single backslash percent) you need to escape the backslash 
before you escape the percent. Not enough because at the point MySQLdb 
finally converts it to a string literal a literal backslash to be used in a 
context where wildcards are allowed needs to be spelled with 4 backslashes. 
i.e. it needs to be escaped twice, once for the string literal and once to 
stop it being interpreted as an escape within the wildcard string.

> 
>> Calling the SQLString routine in this situation would be wrong
>> because it would escape characters such as newline which must not be
>> escaped. 
> 
> SQLString will convert newlines into the \n sequence in the generated
> string literal, which MySQL will interpret as a newline.
> cursor.execute's parameter-substitution mechanism would do exactly the
> same thing. 
> 
Correct: they both do the same thing. So you have to use either SQLString 
or the parameter substitution. You cannot use both. Calling SQLString on a 
string to be passed in to the parameter substitution mechanism will not 
work correctly.

May I suggest that the way for you to progress would be if you wrote some 
unit tests? So, create a simple table containing a few strings with special 
characters and do a few wildcard searches looking for %, newline etc. That 
way you can post not just a function, but some runnable code which either 
demonstrates that your function does what you say, or lets people suggest a 
new test which demonstrates that it fails to handle some particular edge 
case.

Here, I'll even give you a start. Run the code below (you might need to 
create a database called 'test' if you don't already have one), and then 
explain why test_escapebackslashwild fails, and either why you think the 
test is broken or how you would fix your code? All the other tests should 
pass.


---------------- mysqltest.py ---------------
import unittest
import MySQLdb

def EscapeSQLWild(Str) :
    """escapes MySQL pattern wildcards in Str."""
    Result = []
    for Ch in str(Str) :
        if Ch == "%" or Ch == "_" :
            Result.append("\\")
        #end if
        Result.append(Ch)
    #end for
    return "".join(Result)
#end EscapeSQLWild

class Tests(unittest.TestCase):
    values = "x%x", "xnx", "x\nx", "x\\nx", "x\\%x"
    def setUp(self):
        db = self.db = MySQLdb.connect("", "", "", "test")
        cursor = self.cursor = db.cursor()
        cursor.execute('''create temporary table pythontest
            (id INT NOT NULL AUTO_INCREMENT,
            PRIMARY KEY(id),
            name VARCHAR(30))''')
        cursor.executemany(
            "insert into pythontest(name) values(%s)",
            self.values)

    def tearDown(self):
        self.cursor.execute("drop table pythontest")

    def test_wildcard(self):
        n = self.cursor.execute(
            "select name from pythontest where name like %s",
            "x%x")
        self.assertEqual(n, 5)

    def test_nonwildcard(self):
        self.cursor.execute(
            "select name from pythontest where name like %s",
            "x\\%x")
        expected = (('x%x',),)
        self.assertEqual(expected, self.cursor.fetchall())

    def test_newline(self):
        self.cursor.execute(
            "select name from pythontest where name like %s",
            "x\nx")
        expected = (('x\nx',),)
        self.assertEqual(expected, self.cursor.fetchall())

    def test_backslashn(self):
        self.cursor.execute(
            "select name from pythontest where name like %s",
            "x\\\\nx")
        expected = (('x\\nx',),)
        self.assertEqual(expected, self.cursor.fetchall())

    def test_backslashpercent(self):
        self.cursor.execute(
            "select name from pythontest where name like %s",
            "x\\\\\\%x")
        expected = (('x\\%x',),)
        self.assertEqual(expected, self.cursor.fetchall())

    def test_escapewild(self):
        self.cursor.execute(
            "select name from pythontest where name like %s",
            EscapeSQLWild("x%x"))
        expected = (('x%x',),)
        self.assertEqual(expected, self.cursor.fetchall())

    def test_escapebackslashwild(self):
        self.cursor.execute(
            "select name from pythontest where name like %s",
            EscapeSQLWild("x\\%x"))
        expected = (('x\\%x',),)
        self.assertEqual(expected, self.cursor.fetchall())

if __name__=='__main__':
    unittest.main()
---------------------------------------------



More information about the Python-list mailing list