{"id":426,"date":"2012-03-09T10:20:56","date_gmt":"2012-03-09T15:20:56","guid":{"rendered":"http:\/\/www.jetmore.org\/john\/blog\/?p=426"},"modified":"2012-03-30T14:09:33","modified_gmt":"2012-03-30T18:09:33","slug":"commented-sql-named-parametes-and-phps-pdo-module","status":"publish","type":"post","link":"https:\/\/www.jetmore.org\/john\/blog\/2012\/03\/commented-sql-named-parametes-and-phps-pdo-module\/","title":{"rendered":"Commented SQL, Named Parameters, and PHP&#8217;s PDO module"},"content":{"rendered":"<p>I can&#8217;t include code in this post because I don&#8217;t own it, but I think I can discuss the concept, which I found really interesting.<\/p>\n<p>A coworker  had the idea to extend the <a href=\"http:\/\/php.net\/manual\/en\/book.pdo.php\">PDO<\/a> <a href=\"http:\/\/www.php.net\/manual\/en\/pdo.prepare.php\"><code>prepare()<\/code><\/a> method to aid in debugging.  I&#8217;m not sure if he implemented something he found on the net or it was an original idea, but I really like the results.  It prepends, to every query executed, an SQL comment (ala &#8220;<code>\/* comment *\/<\/code>&#8220;) including the username of the logged in user and the URL of the current request.  This usually makes no difference, but if you&#8217;re in a really tough spot in debugging you can turn on mysql general_log, run the session that is having problems, and easily see every query that the was executed on behalf of the user having problems.<\/p>\n<p><!--more--><\/p>\n<p>This has been working for over a year, and through three software releases.<\/p>\n<p>Yesterday a support engineer brought me a problem involving a little-used corner of our software. This particular function would ultimately make a GET with a piece of (properly encoded) arbitrary text (something along the lines of &#8220;append this string to this log&#8221;, something so simple there wasn&#8217;t any reason not to make it a GET).<\/p>\n<p>The engineer found that on some strings we would get PHP errors.  Specifically &#8220;<code>SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters<\/code>&#8220;.  I dug in and found two things.  First, the text in question that was causing this looked something like &#8220;<code>foo_:_bar<\/code>&#8220;.  The colon was the key.  Second, the actual query that was blowing up did not have the URL in it, it was a very innocent query looking up session data.  It did have one positional parameter in it (&#8216;?&#8217;).  I temporarily rewrote the query to embed the single param instead, and the error changed to &#8220;<code>SQLSTATE[HY093]: Invalid parameter number: no parameters were bound<\/code>&#8220;.<\/p>\n<p>Clearly the PDO parser thought there was a named param in my query, and just as clearly I could see that there wasn&#8217;t.  I then found the <a href=\"http:\/\/www.php.net\/manual\/en\/pdostatement.debugdumpparams.php\"><code>debugDumpParams()<\/code><\/a> function, and prepare thought my query looked something like this:<\/p>\n<pre class=\"brush: plain; light: true; title: ; notranslate\" title=\"\">\r\n\/* https:\/\/server\/path\/to\/code\/arbitrary_foo_:_bar *\/\r\nSELECT count(*)\r\n  FROM Foo\r\n<\/pre>\n<p>Ah.  That&#8217;d explain it then.<\/p>\n<p>The solution was very simple, just wrap the URL in <code>parent::quote()<\/code> in the extended <code>prepare()<\/code> function.  It was very interesting to me though that PDO still scans SQL comments and at first glance I thought it was a bug.  I decided that this was actually a feature though.  It allows you to comment out portions of your SQL without having to alter the <code>execute()<\/code> to match.<\/p>\n<p>One open question I have through all of this is &#8220;what is the canonical definition of what a named parameter looks like and where it can  be used?&#8221;  I can&#8217;t find this documented anywhere.  If you know please let me know.  It is not as simple as saying &#8220;a colon causes this problem&#8221;, because it doesn&#8217;t.  Here&#8217;s my empirical findings:<\/p>\n<ul>\n<li>foo_:_bar &#8211; IS considered a named parameter<\/li>\n<li>foo:bar &#8211; IS NOT considered a named parameter<\/li>\n<li>foo_:bar &#8211; IS<\/li>\n<li>foo:_bar &#8211; IS NOT<\/li>\n<li>foo):bar &#8211; IS<\/li>\n<li>foo:(bar &#8211; IS NOT<\/li>\n<\/ul>\n<p>I took a look at the pdo source (ext\/pdo\/pdo_sql_parser.c), but the actual scanner is a state machine that made my head hurt.  I do think I got far enough to decide that a colon following alphanum is appended to the preceeding string by the machine, which is why we get the variability above.  In other words:<\/p>\n<ul>\n<li>foo:bar -> scans to (&#8220;foo:&#8221;, &#8220;bar&#8221;)<\/li>\n<li>foo_:bar -> scans to (&#8220;foo_&#8221;, &#8220;:bar&#8221;)<\/li>\n<\/ul>\n<p>I don&#8217;t have a moral to this story, I just found it to be an interesting example of an action-at-a-distance bug (both time and code-spatial) and I wanted to remember the details.  Let&#8217;s be careful out there!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I can&#8217;t include code in this post because I don&#8217;t own it, but I think I can discuss the concept, which I found really interesting. A coworker had the idea to extend the PDO prepare() method to aid in debugging. &hellip; <a href=\"https:\/\/www.jetmore.org\/john\/blog\/2012\/03\/commented-sql-named-parametes-and-phps-pdo-module\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[5],"tags":[8],"_links":{"self":[{"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/posts\/426"}],"collection":[{"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/comments?post=426"}],"version-history":[{"count":5,"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/posts\/426\/revisions"}],"predecessor-version":[{"id":563,"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/posts\/426\/revisions\/563"}],"wp:attachment":[{"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/media?parent=426"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/categories?post=426"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.jetmore.org\/john\/blog\/wp-json\/wp\/v2\/tags?post=426"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}