Received: from localhost (unknown [200.46.204.183]) by postgresql.org (Postfix) with ESMTP id 9D5F66500CD for ; Fri, 29 Aug 2008 22:39:19 -0300 (ADT) Received: from postgresql.org ([200.46.204.86]) by localhost (mx1.hub.org [200.46.204.183]) (amavisd-maia, port 10024) with ESMTP id 76417-03 for ; Fri, 29 Aug 2008 22:39:03 -0300 (ADT) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from ti-out-0910.google.com (ti-out-0910.google.com [209.85.142.186]) by postgresql.org (Postfix) with ESMTP id 8A11564FD1B for ; Fri, 29 Aug 2008 22:39:07 -0300 (ADT) Received: by ti-out-0910.google.com with SMTP id y8so634556tia.5 for ; Fri, 29 Aug 2008 18:39:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:message-id:date:from:to :subject:mime-version:content-type; bh=m9s8IWRPWLEjJ03PXF3EZKBQeRew01bsxSIyV30N1x4=; b=dYnsb1wfhrrkBK3HyCNM0YYiLZtnjW9fG+SoMIgpSvys64lCVcAivbyWMbR06ynnIf EQR+t4TU+sCnUKMBkHDjqFX759yUMlEEUWyurzRlkw6567BdNuPNRiuWgj45SXumDZZO HRUgMe0TuqYy6XMdqNmK+K2DTXKuiaWK7OWQ8= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=message-id:date:from:to:subject:mime-version:content-type; b=D6fPBopDyGDQ7o6x2Zx5UqRd/iDieEo5mQ3WHWUAK2gPfpAHZzausWA2VRfg4RIYtj NacSvhFSxjEQFUetmZ9hTZwIn9PHUQIDpD2widhSpBX4ssMcXrgCOA4L2l0RfDonyv+2 R+OYJ8VOvU35a73Kp8NvXQiV/kbOvoh/URjd8= Received: by 10.110.84.2 with SMTP id h2mr4030447tib.7.1220060344586; Fri, 29 Aug 2008 18:39:04 -0700 (PDT) Received: by 10.110.41.1 with HTTP; Fri, 29 Aug 2008 18:39:04 -0700 (PDT) Message-ID: <37ed240d0808291839t21e19956mdfbdc80aeb1b3c19@mail.gmail.com> Date: Sat, 30 Aug 2008 11:39:04 +1000 From: "Brendan Jurd" To: pgsql-patches@postgresql.org Subject: to_date() validation MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_28558_2284308.1220060344581" X-Virus-Scanned: Maia Mailguard 1.0.1 X-Spam-Status: No, hits=0 tagged_above=0 required=5 tests=none X-Spam-Level: X-Archive-Number: 200808/34 X-Sequence-Number: 7010 ------=_Part_28558_2284308.1220060344581 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Content-Disposition: inline Hi all, Well, it's been a long time coming, but I've finally got a patch to improve the error handling of to_date() and to_timestamp(), as previously discussed on -hackers. [1][2] The patch is against HEAD, and compiles cleanly and passes all regression tests on gentoo amd64. I did some testing to see whether the extra error checks had an adverse effect on performance. It turns out that my patch actually performs materially *better*, somehow. About 14.5% better over 1M calls to to_date in a single statement. Go figure (C compiler optimisations are completely arcane to me). What the patch does ==== With this patch I've tried to make DCH_from_char a bit smarter with regard to parsing and validation of the user input. The goal here is that if the user makes a mistake and enters something invalid, to_date() will throw an meaningful error instead of just acting like everything is okay and then producing an absurd answer. I've modified the code to error out usefully in a few different scenarios: * Non-integer value for an integer field * Out-of-range value for an integer field * Not enough characters in the input string to provide for all the format fields * Two conflicting values given for a field * Illegal mixture of date conventions (ISO week date and Gregorian) I've included new regression tests to check that these errors are occurring when appropriate. How I went about it ==== In order to detect an illegal mixture of Gregorian and ISO week date conventions, the code needs to know which fomatting fields are Gregorian, which are ISO WD and which are date-convention-neutral. To get this working I added a new enum called FromCharDateMode, and added a field to both KeyWord and TmFromChar to hold the date mode. In KeyWord, it tells you which convention the keyword belongs to (YYYY is Gregorian, IYYY is ISO WD and HH24 has nothing to do with dates). In TmFromChar it keeps track of which convention is currently in use. Because TmFromChar now tracks the date mode independently, it is not necessary to maintain separate fields for the ISO WD values, so 'iyear' and 'iw' are removed. In order to get some consistent treatment on pulling integers out of a string and stuffing them into a TmFromChar, I had to refactor DCH_from_char somewhat. The function is basically a giant switch statement, with branches for each of the formatting fields. In HEAD these branches are highly duplicative. The integer fields all perform a sscanf() on the source string, putting the result straight into the TmFromChar struct, and then skipping over the number of characters consumed by sscanf(). I moved this logic into functions: * from_char_parse_int() uses strtol() rather than sscanf() to parse an integer from the string, and produces meaningful errors if something is wrong with the input, * from_char_set_int() saves an integer into a TmFromChar field, but throws an error if the field has already been set to some other non-zero value. What the patch doesn't do ==== It doesn't consider to_number() at all. I don't have any interest in the number formatting stuff. There is plenty of room to educate do_to_timestamp() further about dates; it's still very naive. Currently you can do something like to_date('2008-50', 'YYYY-MM') and it won't even bat an eyelid. It'll just advance the date by 50 months. I suppose you could consider that a bug or a feature, depending on your point of view. Personally, I think of it as a bug. If you're giving a month value outside of 1-12 to to_date(), chances are good that it's a user error in the query, not a deliberate attempt to perform some date arithmetic. do_to_timestamp() also doesn't try to detect logical mistakes like 'YYYY-MM-DD DDD' where the DDD part conflicts with the MM-DD part. I have some thoughts about how I would improve on that, but I think it's best left for a separate patch. The patch has been added to the September commitfest. Thanks for your time. Cheers, BJ [1] http://archives.postgresql.org/pgsql-hackers/2007-02/msg00915.php [2] http://archives.postgresql.org/message-id/37ed240d0707170747p4f5c26ffx63fff2b5750c62e5@mail.gmail.com ------=_Part_28558_2284308.1220060344581 Content-Type: application/x-gzip; name=to-date-validation-1.diff.gz Content-Transfer-Encoding: base64 X-Attachment-Id: f_fkhjsot90 Content-Disposition: attachment; filename=to-date-validation-1.diff.gz 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 oXI7zFAZaoXZFWMYk7B0i21pFltUtJKKllWyYlhaZ+fKNKtvZa2+VWj1Rw8lVn8UyzUel1r9sVax 3OqPx8VW3xa9L7L6dszE1vSQtfr2fbHVt2ezWZnVt2faMM1ZfVvTcNbq24VW/02x1X8Td8ixi62+ k9TS4Jy1+k7aLjs5dtnR7bIzZ5edoNjqT1OzcKrNQpF2jEWcaiJOszPuXbHNfhdT8DUKvpM1o35c DwOMEpsdaBWLbXb4ISkLP2SZhTGNSKORtdlRXOteg17WZt8ntQpt9swQ+Cy02bOYyCyF5HmbPdOR nFNabLNneTabsnMowcQMpFag7tn1y5cGZlXx9xPm82QeL51cXsKIREYcARt5Pju77j0/vmTvuR/Y EAvVVXwhnlmIRxYhczm3RtGEjbnPW61W4/NCG+CRDW0w2t/e3TZ29jaSaP97xUpGkuqsjJPgUJBs hoFfKICao4Ap79KoqoxnSbxVyqgoCKvqX0V0Vi1qXtxWynShgK6grwsFedUy54V/C7TKCwxLB6Ug WixvkxdClreY8zCq+5IbcRYqfbEwtGzMqmPT0i7mBaylDYqi2LlG8oGQmAzlwW1x2+Kgt7JNQTC8 QLscd6m61bwbVdlmzr2qajEfbBcDqzoCLwVleVhePQ1KAvaFGueF8gs0zAnyS9FcEvkvwC0/J1Dd sChbUDbPK1IIpb2czyuU46Yi2VC5CFUnIcp6Wp6ZKO1obrqiFOflKYzSpuV5jUollSU8qiFUnAop a7tQimQB5rnJk0XazbvoC7Sac96r2xSlYqqctuocTSn+chM3C0ibl9IpZVSU56nqX0UCqFrUvNRQ JdQrc0Zl5qAqj1Qtc16GaYFWebmn0kGZT0hVc8lNVZWaner8VZk2q5NapV3My3SVNihKf5W7i+VZ sSo3KS9bVtmmIIu2QLsvcBfzsm6VbT7bXZzP0pUv++Wpu1JQlufzqqdBSaZvocZ5OcAFGuZkB0vR XJQyLJtxFXnEUn7zycXyEazIOFYa6upMZFlPy9OTpR3NzVmWIq48j1natDy5WamksqxnNeaK86EV jlt1nnQB5rkZ1EXafYnjNp91LW2Tk4/9ZulYqiBq9cc8zmfSe9RJSlO+XY6vXGOu1ucT/t50Q5Gs /epMbd4rL3udLWNvo5N67XM4NmEe3vFwEI7r4sKNYArbIAm+FUPv+6TqBqE/gCpxdYsHocEqmuIL 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 ------=_Part_28558_2284308.1220060344581--