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

Поиск
Список
Период
Сортировка
От Michael Paesold
Тема Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails
Дата
Msg-id 4549A8A8.1060106@gmx.at
обсуждение исходный текст
Ответ на Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails  (Dave Cramer <pg@fastcrypt.com>)
Ответы Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement  (Kris Jurka <books@ejurka.com>)
Список pgsql-jdbc
Dave Cramer wrote:
> On 1-Nov-06, at 8:19 PM, Michael Paesold wrote:
>
>> Hi Dave,
>>
>> first, I forgot to say that I did do performance tests using the
>> previously posted test case. The new version is definitely faster than
>> the original code, although only marginally. When doing my first
>> version of the patch, I tested different code constructs for speed. I
>> chose the one with the best performance.
>>
>> I ran the tests with both 1.4.2 and 1.5.0. It might be possible that
>> the performance will be worse in an earlier versions of the JDK with a
>> less sophisticated hot-spot compiler.
>
> Cool my comments were pretty minor anyway.

Nevertheless, attached is an updated patch that adds the two missing
"default: break;" cases to the switch statements for better readability.

Best Regards
Michael Paesold
diff -crN pgjdbc.862ca68b9ea5/org/postgresql/core/Parser.java pgjdbc.0af947a7aad5/org/postgresql/core/Parser.java
*** pgjdbc.862ca68b9ea5/org/postgresql/core/Parser.java    1970-01-01 01:00:00.000000000 +0100
--- pgjdbc.0af947a7aad5/org/postgresql/core/Parser.java    2006-11-02 09:08:26.000000000 +0100
***************
*** 0 ****
--- 1,215 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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.
+      *
+      * Note: for <tt>'single '' quote in string'</tt>, this method currently
+      * returns the offset of first <tt>'</tt> character after the initial
+      * one. The caller must call the method a second time for the second
+      * part of the quoted string.
+      */
+     public static int parseSingleQuotes(final char[] query, int offset) {
+         while (++offset < query.length)
+         {
+             switch (query[offset])
+             {
+             case '\\':
+                 ++offset;
+                 break;
+             case '\'':
+                 return offset;
+             default:
+                 break;
+             }
+         }
+         return query.length;
+     }
+
+     /**
+      * Find the end of the double-quoted string starting at the given offset.
+      *
+      * Note: for <tt>"double "" quote in string"</tt>,
+      * this method currently returns the offset of first <tt>"</tt>
+      * character after the initial one. The caller must call the method a
+      * second time for the second part of the quoted string.
+      */
+     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;
+                 default:
+                     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.0af947a7aad5/org/postgresql/core/v2/V2Query.java
*** pgjdbc.862ca68b9ea5/org/postgresql/core/v2/V2Query.java    2006-11-02 09:08:26.000000000 +0100
--- pgjdbc.0af947a7aad5/org/postgresql/core/v2/V2Query.java    2006-11-02 09:08:26.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.0af947a7aad5/org/postgresql/core/v3/QueryExecutorImpl.java
*** pgjdbc.862ca68b9ea5/org/postgresql/core/v3/QueryExecutorImpl.java    2006-11-02 09:08:26.000000000 +0100
--- pgjdbc.0af947a7aad5/org/postgresql/core/v3/QueryExecutorImpl.java    2006-11-02 09:08:26.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.0af947a7aad5/org/postgresql/test/jdbc2/PreparedStatementTest.java
*** pgjdbc.862ca68b9ea5/org/postgresql/test/jdbc2/PreparedStatementTest.java    2006-11-02 09:08:26.000000000 +0100
--- pgjdbc.0af947a7aad5/org/postgresql/test/jdbc2/PreparedStatementTest.java    2006-11-02 09:08:26.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.0af947a7aad5/org/postgresql/test/jdbc2/StatementTest.java
*** pgjdbc.862ca68b9ea5/org/postgresql/test/jdbc2/StatementTest.java    2006-11-02 09:08:26.000000000 +0100
--- pgjdbc.0af947a7aad5/org/postgresql/test/jdbc2/StatementTest.java    2006-11-02 09:08:26.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 по дате отправления:

Предыдущее
От: Alex Stienstra
Дата:
Сообщение: calling java stored functions through jdbc gives errors.
Следующее
От: Markus Schaber
Дата:
Сообщение: Re: calling java stored functions through jdbc gives errors.