Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails - Mailing list pgsql-jdbc

From Dave Cramer
Subject Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails
Date
Msg-id 014CC1FC-95DA-413F-976B-3C753B448EB2@fastcrypt.com
Whole thread Raw
In response to Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails  (Michael Paesold <mpaesold@gmx.at>)
Responses Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails
List pgsql-jdbc
Michael,

I'm only looking at this from a performance standpoint.

1) testing for array.length in a loop is fairly expensive compared to
testing for 0, not a big deal but it adds up.
2) I'm wondering what the cost of a switch is for two cases
( candidly I don't know the answer to this,and it's quite likely that
modern compilers will turn it into an if else anyway.)

Dave

On 1-Nov-06, at 4:47 PM, Michael Paesold wrote:

> Michael Paesold wrote:
>> Kris Jurka wrote:
>>> On Fri, 6 Oct 2006, Michael Paesold wrote:
>>>
>>>> I thought I'd keep you posted on my progress. Here is an updated
>>>> work-in-progress patch implementing dollar-quotes, -- and /* */
>>>> quotes (also with SQL compliant nesting, i.e. /* /* */ */). It
>>>> replaces my last one.
>>>
>>> This looks good to me.  Have you done anything further?
>> Yeah, I have started extracting the code for parsing specific
>> parts of the query (single-quotes, dollar-quotes, comments, etc.)
>> into a separate class so the code can be reused in the V2Query
>> code. I was also looking at supporting standard_conforming_strings
>> as a patch on top of that work.
>
> Attached is a new version of the patch. From my side it looks
> pretty complete now, but I am of course willing to improve it based
> on further comments.
>
> The parsing code is now split into several methods in a new class
> org.postgresql.core.Parser. These methods are used in the v3
> QueryExecutorImpl as well as the V2Query class. Therefore, both
> support dollar-quotes and comments, now.
>
> If you are OK with the approach, I will start coding the support
> for standard_conforming_strings.
>
> Best Regards,
> Michael Paesold
> diff -crN pgjdbc.862ca68b9ea5/org/postgresql/core/Parser.java
> pgjdbc.323e7d139a7b/org/postgresql/core/Parser.java
> *** pgjdbc.862ca68b9ea5/org/postgresql/core/Parser.java    1970-01-01
> 01:00:00.000000000 +0100
> --- pgjdbc.323e7d139a7b/org/postgresql/core/Parser.java    2006-11-01
> 22:41:41.000000000 +0100
> ***************
> *** 0 ****
> --- 1,199 ----
> + /
> *---------------------------------------------------------------------
> ----
> + *
> + * Copyright (c) 2006, PostgreSQL Global Development Group
> + *
> + * IDENTIFICATION
> + *   $PostgreSQL$
> + *
> +
> *---------------------------------------------------------------------
> ----
> + */
> + package org.postgresql.core;
> +
> + /**
> +  * Basic query parser infrastructure.
> +  *
> +  * @author Michael Paesold (mpaesold@gmx.at)
> +  */
> + public class Parser {
> +
> +     /**
> +      * Find the end of the single-quoted string starting at the
> given offset.
> +      */
> +     public static int parseSingleQuotes(final char[] query, int
> offset) {
> +         while (++offset < query.length)
> +         {
> +             switch (query[offset])
> +             {
> +             case '\\':
> +                 ++offset;
> +                 break;
> +             case '\'':
> +                 return offset;
> +             }
> +         }
> +         return query.length;
> +     }
> +
> +     /**
> +      * Find the end of the double-quoted string starting at the
> given offset.
> +      */
> +     public static int parseDoubleQuotes(final char[] query, int
> offset) {
> +         while (++offset < query.length && query[offset] != '"') ;
> +         return offset;
> +     }
> +
> +     /**
> +      * Test if the dollar character (<tt>$</tt>) at the given
> offset starts
> +      * a dollar-quoted string and return the offset of the ending
> dollar
> +      * character.
> +      */
> +     public static int parseDollarQuotes(final char[] query, int
> offset) {
> +         if (offset + 1 < query.length)
> +         {
> +             int endIdx = -1;
> +             if (query[offset + 1] == '$')
> +                 endIdx = offset + 1;
> +             else if (isDollarQuoteStartChar(query[offset + 1]))
> +             {
> +                 for (int d = offset + 2; d < query.length; ++d)
> +                 {
> +                     if (query[d] == '$')
> +                     {
> +                         endIdx = d;
> +                         break;
> +                     }
> +                     else if (!isDollarQuoteContChar(query[d]))
> +                         break;
> +                 }
> +             }
> +             if (endIdx > 0)
> +             {
> +                 // found; note: tag includes start and end $
> character
> +                 int tagIdx = offset, tagLen = endIdx - offset + 1;
> +                 offset = endIdx; // loop continues at endIdx + 1
> +                 for (++offset; offset < query.length; ++offset)
> +                 {
> +                     if (query[offset] == '$' &&
> +                         subArraysEqual(query, tagIdx, offset,
> tagLen))
> +                     {
> +                         offset += tagLen - 1;
> +                         break;
> +                     }
> +                 }
> +             }
> +         }
> +         return offset;
> +     }
> +
> +     /**
> +      * Test if the <tt>-</tt> character at <tt>offset</tt> starts a
> +      * <tt>--</tt> style line comment, and return the position of
> the first
> +      * <tt>\r</tt> or <tt>\n</tt> character.
> +      */
> +     public static int parseLineComment(final char[] query, int
> offset) {
> +         if (offset + 1 < query.length && query[offset + 1] == '-')
> +         {
> +             while (++offset < query.length)
> +             {
> +                 if (query[offset] == '\r' || query[offset] == '\n')
> +                     break;
> +             }
> +         }
> +         return offset;
> +     }
> +
> +     /**
> +      * Test if the <tt>/</tt> character at <tt>offset</tt> starts
> a block
> +      * comment, and return the position of the last <tt>/</tt>
> character.
> +      */
> +     public static int parseBlockComment(final char[] query, int
> offset) {
> +         if (offset + 1 < query.length && query[offset + 1] == '*')
> +         {
> +             // /* /* */ */ nest, according to SQL spec
> +             int level = 1;
> +             for (offset += 2; offset < query.length; ++offset)
> +             {
> +                 switch (query[offset-1]) {
> +                 case '*':
> +                     if (query[offset] == '/')
> +                     {
> +                         --level;
> +                         ++offset; // don't parse / in */* twice
> +                     }
> +                     break;
> +                 case '/':
> +                     if (query[offset] == '*')
> +                     {
> +                         ++level;
> +                         ++offset; // don't parse * in /*/ twice
> +                     }
> +                     break;
> +                 }
> +                 if (level == 0)
> +                 {
> +                     --offset; // reset position to last '/' char
> +                     break;
> +                 }
> +             }
> +         }
> +         return offset;
> +     }
> +
> +     /**
> +      * Checks if a character is valid as the start of a dollar
> quoting tag.
> +      *
> +      * @param c the character to check
> +      * @return true if valid as first character of a dollar
> quoting tag; false if not
> +      */
> +     public static boolean isDollarQuoteStartChar(char c) {
> +         /*
> +          * The allowed dollar quote start and continuation
> characters
> +          * must stay in sync with what the backend defines in
> +          * pgsql/src/backend/parser/scan.l
> +          */
> +         return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
> +                 || c == '_' || c > 127;
> +     }
> +
> +     /**
> +      * Checks if a character is valid as the second or latter
> character of a
> +      * dollar quoting tag.
> +      *
> +      * @param c the character to check
> +      * @return true if valid as second or later character of a
> dollar quoting tag;
> +      *         false if not
> +      */
> +     public static boolean isDollarQuoteContChar(char c) {
> +         return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
> +                 || c == '_' || c > 127
> +                 || (c >= '0' && c <= '9');
> +     }
> +
> +     /**
> +      * Compares two sub-arrays of the given character array for
> equalness.
> +      * If the length is zero, the result is true, if and only if
> the offsets
> +      * are within the bounds of the array.
> +      *
> +      * @param arr  a char array
> +      * @param offA first sub-array start offset
> +      * @param offB second sub-array start offset
> +      * @param len  length of the sub arrays to compare
> +      * @return     true if the sub-arrays are equal; false if not
> +      */
> +     private static boolean subArraysEqual(final char[] arr,
> +                                           final int offA, final
> int offB,
> +                                           final int len) {
> +         if (offA < 0 || offB < 0
> +                 || offA >= arr.length || offB >= arr.length
> +                 || offA + len > arr.length || offB + len >
> arr.length)
> +             return false;
> +
> +         for (int i = 0; i < len; ++i)
> +         {
> +             if (arr[offA + i] != arr[offB + i])
> +                 return false;
> +         }
> +
> +         return true;
> +     }
> + }
> diff -crN pgjdbc.862ca68b9ea5/org/postgresql/core/v2/V2Query.java
> pgjdbc.323e7d139a7b/org/postgresql/core/v2/V2Query.java
> *** pgjdbc.862ca68b9ea5/org/postgresql/core/v2/V2Query.java
> 2006-11-01 22:41:41.000000000 +0100
> --- pgjdbc.323e7d139a7b/org/postgresql/core/v2/V2Query.java
> 2006-11-01 22:41:41.000000000 +0100
> ***************
> *** 29,62 ****
>           Vector v = new Vector();
>           int lastParmEnd = 0;
>
> !         boolean inSingleQuotes = false;
> !         boolean inDoubleQuotes = false;
>
> !         for (int i = 0; i < query.length(); ++i)
>           {
> !             char c = query.charAt(i);
> !
> !             switch (c)
>               {
> !             case '\\':
> !                 if (inSingleQuotes)
> !                     ++i; // Skip one character.
>                   break;
>
> !             case '\'':
> !                 inSingleQuotes = !inDoubleQuotes && !inSingleQuotes;
>                   break;
>
> !             case '"':
> !                 inDoubleQuotes = !inSingleQuotes && !inDoubleQuotes;
>                   break;
>
>               case '?':
> !                 if (!inSingleQuotes && !inDoubleQuotes)
> !                 {
> !                     v.addElement(query.substring (lastParmEnd, i));
> !                     lastParmEnd = i + 1;
> !                 }
>                   break;
>
>               default:
> --- 29,63 ----
>           Vector v = new Vector();
>           int lastParmEnd = 0;
>
> !         char []aChars = query.toCharArray();
>
> !         for (int i = 0; i < aChars.length; ++i)
>           {
> !             switch (aChars[i])
>               {
> !             case '\'': // single-quotes
> !                 i = Parser.parseSingleQuotes(aChars, i);
> !                 break;
> !
> !             case '"': // double-quotes
> !                 i = Parser.parseDoubleQuotes(aChars, i);
>                   break;
>
> !             case '-': // possibly -- style comment
> !                 i = Parser.parseLineComment(aChars, i);
>                   break;
>
> !             case '/': // possibly /* */ style comment
> !                 i = Parser.parseBlockComment(aChars, i);
> !                 break;
> !
> !             case '$': // possibly dollar quote start
> !                 i = Parser.parseDollarQuotes(aChars, i);
>                   break;
>
>               case '?':
> !                 v.addElement(query.substring (lastParmEnd, i));
> !                 lastParmEnd = i + 1;
>                   break;
>
>               default:
> diff -crN pgjdbc.862ca68b9ea5/org/postgresql/core/v3/
> QueryExecutorImpl.java pgjdbc.323e7d139a7b/org/postgresql/core/v3/
> QueryExecutorImpl.java
> *** pgjdbc.862ca68b9ea5/org/postgresql/core/v3/
> QueryExecutorImpl.java    2006-11-01 22:41:41.000000000 +0100
> --- pgjdbc.323e7d139a7b/org/postgresql/core/v3/
> QueryExecutorImpl.java    2006-11-01 22:41:41.000000000 +0100
> ***************
> *** 63,116 ****
>           ArrayList statementList = new ArrayList();
>           ArrayList fragmentList = new ArrayList(15);
>
> -         boolean inQuotes = false;
>           int fragmentStart = 0;
> -
> -         boolean inSingleQuotes = false;
> -         boolean inDoubleQuotes = false;
>           int inParen = 0;
> !
>           char []aChars = query.toCharArray();
> !
>           for (int i = 0; i < aChars.length; ++i)
>           {
> !             char c = aChars[i];
> !
> !             switch (c)
>               {
> !             case '\\':
> !                 if (inSingleQuotes)
> !                     ++i; // Skip one character.
>                   break;
>
> !             case '\'':
> !                 inSingleQuotes = !inDoubleQuotes && !inSingleQuotes;
>                   break;
>
> !             case '"':
> !                 inDoubleQuotes = !inSingleQuotes && !inDoubleQuotes;
>                   break;
>
> !             case '?':
> !                 if (withParameters && !inSingleQuotes && !
> inDoubleQuotes)
> !                 {
> !                     fragmentList.add(query.substring
> (fragmentStart, i));
> !                     fragmentStart = i + 1;
> !                 }
>                   break;
>
>               case '(':
> !                 if (!inSingleQuotes && !inDoubleQuotes)
> !                         inParen++;
>                   break;
>
>               case ')':
> !                 if (!inSingleQuotes && !inDoubleQuotes)
> !                         inParen--;
>                   break;
>
>               case ';':
> !                 if (!inSingleQuotes && !inDoubleQuotes && inParen
> == 0)
>                   {
>                       fragmentList.add(query.substring
> (fragmentStart, i));
>                       fragmentStart = i + 1;
> --- 63,115 ----
>           ArrayList statementList = new ArrayList();
>           ArrayList fragmentList = new ArrayList(15);
>
>           int fragmentStart = 0;
>           int inParen = 0;
> !
>           char []aChars = query.toCharArray();
> !
>           for (int i = 0; i < aChars.length; ++i)
>           {
> !             switch (aChars[i])
>               {
> !             case '\'': // single-quotes
> !                 i = Parser.parseSingleQuotes(aChars, i);
>                   break;
>
> !             case '"': // double-quotes
> !                 i = Parser.parseDoubleQuotes(aChars, i);
>                   break;
>
> !             case '-': // possibly -- style comment
> !                 i = Parser.parseLineComment(aChars, i);
>                   break;
>
> !             case '/': // possibly /* */ style comment
> !                 i = Parser.parseBlockComment(aChars, i);
> !                 break;
> !
> !             case '$': // possibly dollar quote start
> !                 i = Parser.parseDollarQuotes(aChars, i);
>                   break;
>
>               case '(':
> !                 inParen++;
>                   break;
>
>               case ')':
> !                 inParen--;
> !                 break;
> !
> !             case '?':
> !                 if (withParameters)
> !                 {
> !                     fragmentList.add(query.substring
> (fragmentStart, i));
> !                     fragmentStart = i + 1;
> !                 }
>                   break;
>
>               case ';':
> !                 if (inParen == 0)
>                   {
>                       fragmentList.add(query.substring
> (fragmentStart, i));
>                       fragmentStart = i + 1;
> diff -crN pgjdbc.862ca68b9ea5/org/postgresql/test/jdbc2/
> PreparedStatementTest.java pgjdbc.323e7d139a7b/org/postgresql/test/
> jdbc2/PreparedStatementTest.java
> *** pgjdbc.862ca68b9ea5/org/postgresql/test/jdbc2/
> PreparedStatementTest.java    2006-11-01 22:41:41.000000000 +0100
> --- pgjdbc.323e7d139a7b/org/postgresql/test/jdbc2/
> PreparedStatementTest.java    2006-11-01 22:41:41.000000000 +0100
> ***************
> *** 304,309 ****
> --- 304,374 ----
>               pstmt.close();
>           }
>       }
> +
> +     public void testDollarQuotes() throws SQLException {
> +         // dollar-quotes are supported in the backend since
> version 8.0
> +         if (!TestUtil.haveMinimumServerVersion(conn, "8.0"))
> +             return;
> +
> +         PreparedStatement st;
> +         ResultSet rs;
> +
> +         st = conn.prepareStatement("SELECT $$;$$ WHERE $x$?$x$=$_0
> $?$_0$ AND $$?$$=?");
> +         st.setString(1, "?");
> +         rs = st.executeQuery();
> +         assertTrue(rs.next());
> +         assertEquals(";", rs.getString(1));
> +         assertFalse(rs.next());
> +         st.close();
> +
> +         st = conn.prepareStatement(
> +                   "SELECT $__$;$__$ WHERE ''''=$q_1$'$q_1$ AND
> ';'=?;"
> +                 + "SELECT $x$$a$;$x $a$$x$ WHERE $$;$$=? OR ''=$c
> $c$;$c$;"
> +                 + "SELECT ?");
> +         st.setString(1, ";");
> +         st.setString(2, ";");
> +         st.setString(3, "$a$ $a$");
> +
> +         assertTrue(st.execute());
> +         rs = st.getResultSet();
> +         assertTrue(rs.next());
> +         assertEquals(";", rs.getString(1));
> +         assertFalse(rs.next());
> +
> +         assertTrue(st.getMoreResults());
> +         rs = st.getResultSet();
> +         assertTrue(rs.next());
> +         assertEquals("$a$;$x $a$", rs.getString(1));
> +         assertFalse(rs.next());
> +
> +         assertTrue(st.getMoreResults());
> +         rs = st.getResultSet();
> +         assertTrue(rs.next());
> +         assertEquals("$a$ $a$", rs.getString(1));
> +         assertFalse(rs.next());
> +         st.close();
> +     }
> +
> +     public void testComments() throws SQLException {
> +         PreparedStatement st;
> +         ResultSet rs;
> +
> +         st = conn.prepareStatement("SELECT /*?*/ /*/*/*/**/*/*/*/
> 1;SELECT ?;--SELECT ?");
> +         st.setString(1, "a");
> +         assertTrue(st.execute());
> +         assertTrue(st.getMoreResults());
> +         assertFalse(st.getMoreResults());
> +         st.close();
> +
> +         st = conn.prepareStatement("SELECT /**/'?'/*/**/*/ WHERE
> '?'=/*/*/*?*/*/*/--?\n?");
> +         st.setString(1, "?");
> +         rs = st.executeQuery();
> +         assertTrue(rs.next());
> +         assertEquals("?", rs.getString(1));
> +         assertFalse(rs.next());
> +         st.close();
> +     }
> +
>       public void testDouble() throws SQLException
>       {
>           PreparedStatement pstmt = conn.prepareStatement("CREATE
> TEMP TABLE double_tab (max_double float, min_double float,
> null_value float)");
> diff -crN pgjdbc.862ca68b9ea5/org/postgresql/test/jdbc2/
> StatementTest.java pgjdbc.323e7d139a7b/org/postgresql/test/jdbc2/
> StatementTest.java
> *** pgjdbc.862ca68b9ea5/org/postgresql/test/jdbc2/
> StatementTest.java    2006-11-01 22:41:41.000000000 +0100
> --- pgjdbc.323e7d139a7b/org/postgresql/test/jdbc2/
> StatementTest.java    2006-11-01 22:41:41.000000000 +0100
> ***************
> *** 390,395 ****
> --- 390,442 ----
>           assertTrue(!rs.next());
>       }
>
> +     public void testParsingDollarQuotes() throws SQLException
> +     {
> +         // dollar-quotes are supported in the backend since
> version 8.0
> +         if (!TestUtil.haveMinimumServerVersion(con, "8.0"))
> +             return;
> +
> +         Statement st = con.createStatement();
> +         ResultSet rs;
> +
> +         rs = st.executeQuery("SELECT '$a$ ; $a$'");
> +         assertTrue(rs.next());
> +         assertEquals("$a$ ; $a$", rs.getObject(1));
> +         rs.close();
> +
> +         rs = st.executeQuery("SELECT $$;$$");
> +         assertTrue(rs.next());
> +         assertEquals(";", rs.getObject(1));
> +         rs.close();
> +
> +         rs = st.executeQuery("SELECT $OR$$a$'$b$a$$OR$ WHERE '$a
> $''$b$a$'=$OR$$a$'$b$a$$OR$OR ';'=''");
> +         assertTrue(rs.next());
> +         assertEquals("$a$'$b$a$", rs.getObject(1));
> +         assertFalse(rs.next());
> +         rs.close();
> +
> +         rs = st.executeQuery("SELECT $B$;$b$B$");
> +         assertTrue(rs.next());
> +         assertEquals(";$b", rs.getObject(1));
> +         rs.close();
> +
> +         rs = st.executeQuery("SELECT $c$c$;$c$");
> +         assertTrue(rs.next());
> +         assertEquals("c$;", rs.getObject(1));
> +         rs.close();
> +
> +         rs = st.executeQuery("SELECT $A0$;$A0$ WHERE ''=$t$t$t$
> OR ';$t$'=';$t$'");
> +         assertTrue(rs.next());
> +         assertEquals(";", rs.getObject(1));
> +         assertFalse(rs.next());
> +         rs.close();
> +
> +         st.executeQuery("SELECT /* */$$;$$/**//*;*/").close();
> +         st.executeQuery("SELECT /* */--;\n$$a$$/**/--\n--;
> \n").close();
> +
> +         st.close();
> +     }
> +
>       public void testUnbalancedParensParseError() throws SQLException
>       {
>           Statement stmt = con.createStatement();


pgsql-jdbc by date:

Previous
From: Michael Paesold
Date:
Subject: Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails
Next
From: Michael Paesold
Date:
Subject: Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails