QuoteSQL

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Duncan Booth

    #61
    Re: QuoteSQL

    Lawrence D'Oliveiro <ldo@geek-central.gen.new _zealandwrote:
    >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_escapeback slashwild 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(S tr) :
    """escapes MySQL pattern wildcards in Str."""
    Result = []
    for Ch in str(Str) :
    if Ch == "%" or Ch == "_" :
    Result.append(" \\")
    #end if
    Result.append(C h)
    #end for
    return "".join(Res ult)
    #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.executem any(
    "insert into pythontest(name ) values(%s)",
    self.values)

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

    def test_wildcard(s elf):
    n = self.cursor.exe cute(
    "select name from pythontest where name like %s",
    "x%x")
    self.assertEqua l(n, 5)

    def test_nonwildcar d(self):
    self.cursor.exe cute(
    "select name from pythontest where name like %s",
    "x\\%x")
    expected = (('x%x',),)
    self.assertEqua l(expected, self.cursor.fet chall())

    def test_newline(se lf):
    self.cursor.exe cute(
    "select name from pythontest where name like %s",
    "x\nx")
    expected = (('x\nx',),)
    self.assertEqua l(expected, self.cursor.fet chall())

    def test_backslashn (self):
    self.cursor.exe cute(
    "select name from pythontest where name like %s",
    "x\\\\nx")
    expected = (('x\\nx',),)
    self.assertEqua l(expected, self.cursor.fet chall())

    def test_backslashp ercent(self):
    self.cursor.exe cute(
    "select name from pythontest where name like %s",
    "x\\\\\\%x" )
    expected = (('x\\%x',),)
    self.assertEqua l(expected, self.cursor.fet chall())

    def test_escapewild (self):
    self.cursor.exe cute(
    "select name from pythontest where name like %s",
    EscapeSQLWild(" x%x"))
    expected = (('x%x',),)
    self.assertEqua l(expected, self.cursor.fet chall())

    def test_escapeback slashwild(self) :
    self.cursor.exe cute(
    "select name from pythontest where name like %s",
    EscapeSQLWild(" x\\%x"))
    expected = (('x\\%x',),)
    self.assertEqua l(expected, self.cursor.fet chall())

    if __name__=='__ma in__':
    unittest.main()
    ---------------------------------------------

    Comment

    • Lawrence D'Oliveiro

      #62
      Re: QuoteSQL

      In message <Xns984B793317A 15duncanbooth@1 27.0.0.1>, Duncan Booth wrote:
      Lawrence D'Oliveiro <ldo@geek-central.gen.new _zealandwrote:
      >
      >>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.
      Doesn't matter what other implementations of parametrization might or might
      not do. The syntax I generate is valid for MySQL, therefore it will work
      with the MySQL database adapter regardless of what else the adaptor might
      do.
      >>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.
      I'm assuming you mean, how would you get from a Python expression to a MySQL
      clause that looks like

      name like "%\\\\%%"

      (wildcard % followed by literal backslash \\ followed by literal percent \%
      followed by wildcard %.) That's easy:

      EscapeSQLWild(r "\%") =r"\\%"
      SQLString(r"\\% ") =r'"\\\\%"'

      So the Python expression

      "name like %s" % SQLString("%" + EscapeSQLWild(r "\%") + "%")

      gives you what you want.
      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.
      I thought I had made that clear already.

      Comment

      • Duncan Booth

        #63
        Re: QuoteSQL

        Lawrence D'Oliveiro <ldo@geek-central.gen.new _zealandwrote:
        I'm assuming you mean, how would you get from a Python expression to a
        MySQL clause that looks like
        >
        name like "%\\\\%%"
        >
        (wildcard % followed by literal backslash \\ followed by literal
        percent \% followed by wildcard %.) That's easy:
        >
        EscapeSQLWild(r "\%") =r"\\%"
        SQLString(r"\\% ") =r'"\\\\%"'
        >
        So the Python expression
        >
        "name like %s" % SQLString("%" + EscapeSQLWild(r "\%") + "%")
        >
        gives you what you want.
        >
        Deary me. Did you actually test out that bit of code before you posted it?
        No, I thought not. I even gave you a test harness to make it easy for you
        to check the quality of your code before posting.

        All you had to do was to add another test:

        def test_escapeback slashwild2(self ):
        self.cursor.exe cute(
        ("select name from pythontest where name like %s" %
        SQLString("%" + EscapeSQLWild(r "\%") + "%")))
        expected = (('x\\%x',),)
        self.assertEqua l(expected, self.cursor.fet chall())

        and the output is:
        =============== =============== =============== =============== ==========
        FAIL: test_escapeback slashwild2 (__main__.Tests )
        ----------------------------------------------------------------------
        Traceback (most recent call last):
        File "mysqltest. py", line 111, in test_escapeback slashwild2
        self.assertEqua l(expected, self.cursor.fet chall())
        AssertionError: (('x\\%x',),) != (('x\\nx',), ('x\\%x',))

        ----------------------------------------------------------------------

        as I said before, your escaping is too late and not enough. You've got a
        search for a literal backslash in there sure enough, but you haven't
        managed to escape the percent character.

        Try again.

        Comment

        • Lawrence D'Oliveiro

          #64
          Re: QuoteSQL

          In message <Xns984B8482155 F6duncanbooth@1 27.0.0.1>, Duncan Booth wrote:
          Lawrence D'Oliveiro <ldo@geek-central.gen.new _zealandwrote:
          >
          >I'm assuming you mean, how would you get from a Python expression to a
          >MySQL clause that looks like
          >>
          > name like "%\\\\%%"
          >>
          >(wildcard % followed by literal backslash \\ followed by literal
          >percent \% followed by wildcard %.) That's easy:
          >>
          > EscapeSQLWild(r "\%") =r"\\%"
          > SQLString(r"\\% ") =r'"\\\\%"'
          >>
          >So the Python expression
          >>
          > "name like %s" % SQLString("%" + EscapeSQLWild(r "\%") + "%")
          >>
          >gives you what you want.
          >>
          Deary me. Did you actually test out that bit of code before you posted it?
          >>execfile("Quo teSQL.py")
          >>EscapeSQLWild (r"\%")
          '\\\\%'
          >>SQLString(" %" + EscapeSQLWild(r "\%") + "%")
          '"%\\\\\\\\% %"'
          >>EscapeSQLWild (r"\%") == r"\\%"
          True
          >>SQLString(" %" + EscapeSQLWild(r "\%") + "%") == r'"%\\\\%%"'
          True

          Comment

          • Duncan Booth

            #65
            Re: QuoteSQL

            Lawrence D'Oliveiro <ldo@geek-central.gen.new _zealandwrote:
            In message <Xns984B8482155 F6duncanbooth@1 27.0.0.1>, Duncan Booth wrote:
            >Deary me. Did you actually test out that bit of code before you
            >posted it?
            >
            >>>execfile("Qu oteSQL.py")
            >>>EscapeSQLWil d(r"\%")
            '\\\\%'
            >>>SQLString("% " + EscapeSQLWild(r "\%") + "%")
            '"%\\\\\\\\% %"'
            >>>EscapeSQLWil d(r"\%") == r"\\%"
            True
            >>>SQLString("% " + EscapeSQLWild(r "\%") + "%") == r'"%\\\\%%"'
            True
            >
            Ah, so that's a 'no' then. I can't see any tests there. How do you know
            that those strings work correctly MySQL queries?

            Please, open your mind to what I'm saying. I'm not trying to criticise your
            aims, just trying to point out the simple fact that your EscapeSQLWild
            function has a bug. If nothing else, the fact that you are finding this so
            hard to understand shows that there is a need for a correctly written
            function to do this.

            The fix to EscapeSQLWild to get test_escapeback slashwild2 to work is a
            trivial change, and not suprisingly also makes the other failing test in my
            script (the one using parameterised queries and EscapeSQLWild) pass.

            Again, please, try running the script I posted, and in particular
            test_escapeback slashwild2. It uses the SQL query you yourself created, and
            it fails because it matches something it shouldn't.

            Comment

            • Lawrence D'Oliveiro

              #66
              Re: QuoteSQL

              In message <efc5b3$can$1@l ust.ihug.co.nz> , I wrote:
              def EscapeSQLWild(S tr) :
              """escapes MySQL pattern wildcards in Str."""
              Result = []
              for Ch in str(Str) :
              if Ch == "%" or Ch == "_" :
              Result.append(" \\")
              #end if
              Result.append(C h)
              #end for
              return "".join(Res ult)
              #end EscapeSQLWild
              Correction, backslashes need to be escaped at this level as well. So that
              should become

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

              Comment

              • Lawrence D'Oliveiro

                #67
                Re: QuoteSQL

                In message <efeqpd$8vh$1@l ust.ihug.co.nz> , LI wrote:
                >>>execfile("Qu oteSQL.py")
                >>>EscapeSQLWil d(r"\%")
                '\\\\%'
                >>>SQLString("% " + EscapeSQLWild(r "\%") + "%")
                '"%\\\\\\\\% %"'
                >>>EscapeSQLWil d(r"\%") == r"\\%"
                True
                >>>SQLString("% " + EscapeSQLWild(r "\%") + "%") == r'"%\\\\%%"'
                True
                With the correction to EscapeSQLWild, this becomes:
                >>execfile("Quo teSQL.py")
                >>EscapeSQLWild (r"\%")
                '\\\\\\%'
                >>SQLString(" %" + EscapeSQLWild(r "\%") + "%")
                '"%\\\\\\\\\\\\ %%"'
                >>EscapeSQLWild (r"\%") == r"\\\%"
                True
                >>SQLString(" %" + EscapeSQLWild(r "\%") + "%") == r'"%\\\\\\%%" '
                True

                Comment

                • Duncan Booth

                  #68
                  Re: QuoteSQL

                  Lawrence D'Oliveiro <ldo@geek-central.gen.new _zealandwrote:
                  In message <efeqpd$8vh$1@l ust.ihug.co.nz> , LI wrote:
                  >
                  >>>>execfile("Q uoteSQL.py")
                  >>>>EscapeSQLWi ld(r"\%")
                  >'\\\\%'
                  >>>>SQLString(" %" + EscapeSQLWild(r "\%") + "%")
                  >'"%\\\\\\\\%%" '
                  >>>>EscapeSQLWi ld(r"\%") == r"\\%"
                  >True
                  >>>>SQLString(" %" + EscapeSQLWild(r "\%") + "%") == r'"%\\\\%%"'
                  >True
                  >
                  With the correction to EscapeSQLWild, this becomes:
                  >
                  >>>execfile("Qu oteSQL.py")
                  >>>EscapeSQLWil d(r"\%")
                  '\\\\\\%'
                  >>>SQLString("% " + EscapeSQLWild(r "\%") + "%")
                  '"%\\\\\\\\\\\\ %%"'
                  >>>EscapeSQLWil d(r"\%") == r"\\\%"
                  True
                  >>>SQLString("% " + EscapeSQLWild(r "\%") + "%") == r'"%\\\\\\%%" '
                  True
                  >
                  True but irrelevant. The point is that it isn't relevant whether you are
                  seeing 4, 6, 8, or 12 backslashes, because you wrote the code to produce
                  the number you thought you wanted and you had misunderstood how MySQL
                  works. That's why it is important in a situation like this to test against
                  the code that actually uses the string. I had no idea how MySQL would
                  handle escapes in this situation, but I didn't need to know, I just wrote
                  some tests and figured out which strings would make them pass or fail.

                  Anyway, congratulations on finally getting the message.

                  Comment

                  Working...