Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails - Mailing list pgsql-jdbc
From | Michael Paesold |
---|---|
Subject | Re: [pgsql-jdbc] dollar-quoted CREATE FUNCTION statement fails |
Date | |
Msg-id | 451FC6CD.6060904@gmx.at 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 Paesold wrote: > Dave Cramer wrote: > >> I guess it's just a matter of coding, right? Would the JDBC > >> maintainers accept a patch for the 8.2 release of the driver? > > Absolutely! > > Ok, I am going to write support for this... [snip] Here is a first version of the patch. It works well for me so far, so I am asking for comments now. This patch only adds knowledge about dollar-quotes to v3.QueryExecutorImpl.parseQuery() for now, but there are other possible candidates: - V2Query: for people connecting to postgres >= 8.0 using ?protocolVersion=2 - AbstractJdbc2Statement.modifyJdbcCall() for prepareCall: I would not expect someone to use dollar-quoting here, but single-quotes are supported, so adding dollar-quoting does not seem completely off track. I have tried to make the patch rather non-invasive. I thought about improving parseQuery to be more efficient, but without having done any profiling, I did not want to considerably change the existing code paths. All JUnit tests still pass running against an 8.1 server. p1-remove-unused-var.patch: org/postgresql/core/v3/QueryExecutorImpl.java: - Remove an unused variable from parseQuery p2-v3protocol-dolquot.patch: org/postgresql/core/Utils.java: - Add static methods isDollarQuoteStartChar(char) and isDollarQuoteContChar(char) org/postgresql/core/v3/QueryExecutorImpl.java: - Add support for dollar-quoting in parseQuery org/postgresql/test/jdbc3/Jdbc3DollarQuotingTest.java: - New file with JUnit tests for new code org/postgresql/test/jdbc3/Jdbc3TestSuite.java: - Add the new JUnit TestCase to the test suite if server >= 8.0 and protocolVersion == 3 Best Regards Michael Paesold diff -cr pgjdbc-orig/org/postgresql/core/v3/QueryExecutorImpl.java pgjdbc/org/postgresql/core/v3/QueryExecutorImpl.java *** pgjdbc-orig/org/postgresql/core/v3/QueryExecutorImpl.java Sat Sep 30 19:45:14 2006 --- pgjdbc/org/postgresql/core/v3/QueryExecutorImpl.java Sat Sep 30 19:37:41 2006 *************** *** 63,69 **** ArrayList statementList = new ArrayList(); ArrayList fragmentList = new ArrayList(15); - boolean inQuotes = false; int fragmentStart = 0; boolean inSingleQuotes = false; --- 63,68 ---- diff -crN pgjdbc-orig/org/postgresql/core/Utils.java pgjdbc/org/postgresql/core/Utils.java *** pgjdbc-orig/org/postgresql/core/Utils.java Tue Jan 11 09:25:43 2005 --- pgjdbc/org/postgresql/core/Utils.java Sun Oct 1 14:59:57 2006 *************** *** 53,56 **** --- 53,81 ---- throw new RuntimeException("Unexpected exception: UTF-8 charset not supported: " + e); } } + + /** + * 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) { + 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'); + } } diff -crN pgjdbc-orig/org/postgresql/core/v3/QueryExecutorImpl.java pgjdbc/org/postgresql/core/v3/QueryExecutorImpl.java *** pgjdbc-orig/org/postgresql/core/v3/QueryExecutorImpl.java Sat Sep 30 21:30:26 2006 --- pgjdbc/org/postgresql/core/v3/QueryExecutorImpl.java Sun Oct 1 14:56:18 2006 *************** *** 67,72 **** --- 67,74 ---- boolean inSingleQuotes = false; boolean inDoubleQuotes = false; + boolean inDollarQuotes = false; + String dollarQuoteTag = null; int inParen = 0; char []aChars = query.toCharArray(); *************** *** 83,97 **** 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; --- 85,99 ---- break; case '\'': ! inSingleQuotes = !inDoubleQuotes && !inDollarQuotes && !inSingleQuotes; break; case '"': ! inDoubleQuotes = !inSingleQuotes && !inDollarQuotes && !inDoubleQuotes; break; case '?': ! if (withParameters && !inSingleQuotes && !inDoubleQuotes && !inDollarQuotes) { fragmentList.add(query.substring(fragmentStart, i)); fragmentStart = i + 1; *************** *** 99,115 **** 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; --- 101,164 ---- break; case '(': ! if (!inSingleQuotes && !inDoubleQuotes && !inDollarQuotes) inParen++; break; case ')': ! if (!inSingleQuotes && !inDoubleQuotes && !inDollarQuotes) inParen--; break; + case '$': + if (!inSingleQuotes && !inDoubleQuotes) { + if (!inDollarQuotes) + { + // possible dollar quote start + if (i + 1 < aChars.length) + { + int endIdx = -1; + if (aChars[i + 1] == '$') + endIdx = i + 1; + else if (Utils.isDollarQuoteStartChar(aChars[i + 1])) + { + for (int d = i + 2; d < aChars.length; ++d) + { + if (aChars[d] == '$') + { + endIdx = d; + break; + } + else if (!Utils.isDollarQuoteContChar(aChars[d])) + break; + } + } + if (endIdx > 0) + { + inDollarQuotes = true; + // dollarQuoteTag includes start and end $ character + dollarQuoteTag = query.substring(i, endIdx + 1); + i = endIdx; // main loop continues at endIdx + 1 + } + } + } + else + { + // possible dollar quote end + if (i + dollarQuoteTag.length() <= aChars.length && + query.substring(i, i + dollarQuoteTag.length()).equals( + dollarQuoteTag)) + { + inDollarQuotes = false; + i += dollarQuoteTag.length() - 1; + dollarQuoteTag = null; + } + } + } + break; + case ';': ! if (!inSingleQuotes && !inDoubleQuotes && !inDollarQuotes && inParen == 0) { fragmentList.add(query.substring(fragmentStart, i)); fragmentStart = i + 1; diff -crN pgjdbc-orig/org/postgresql/test/jdbc3/Jdbc3DollarQuotingTest.java pgjdbc/org/postgresql/test/jdbc3/Jdbc3DollarQuotingTest.java *** pgjdbc-orig/org/postgresql/test/jdbc3/Jdbc3DollarQuotingTest.java Thu Jan 1 01:00:00 1970 --- pgjdbc/org/postgresql/test/jdbc3/Jdbc3DollarQuotingTest.java Sun Oct 1 14:46:54 2006 *************** *** 0 **** --- 1,139 ---- + /*------------------------------------------------------------------------- + * + * Copyright (c) 2006, PostgreSQL Global Development Group + * + * IDENTIFICATION + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ + package org.postgresql.test.jdbc3; + + import org.postgresql.test.TestUtil; + + import java.sql.*; + + import junit.framework.TestCase; + + /* + * Test for dollar-quoting + */ + public class Jdbc3DollarQuotingTest extends TestCase { + + private Connection con; + + public Jdbc3DollarQuotingTest(String name) { + super(name); + } + + protected void setUp() throws Exception + { + con = TestUtil.openDB(); + TestUtil.createTable(con, "testdquot", "t text"); + Statement stmt = con.createStatement(); + + stmt.executeUpdate(TestUtil.insertSQL("testdquot", "';'")); + stmt.executeUpdate(TestUtil.insertSQL("testdquot", "'?'")); + stmt.executeUpdate(TestUtil.insertSQL("testdquot", "'$a$ $a$'")); + stmt.executeUpdate(TestUtil.insertSQL("testdquot", "'$a$''$b$a$'")); + stmt.executeUpdate(TestUtil.insertSQL("testdquot", "';$b'")); + stmt.executeUpdate(TestUtil.insertSQL("testdquot", "'c$;'")); + + stmt.close(); + } + + protected void tearDown() throws Exception + { + TestUtil.dropTable(con, "testdquot"); + TestUtil.closeDB(con); + } + + public void testSimple() throws SQLException + { + Statement st = con.createStatement(); + ResultSet rs; + + rs = st.executeQuery("SELECT t FROM testdquot WHERE t='$a$ $a$'"); + assertTrue(rs.next()); + assertEquals("$a$ $a$", rs.getObject(1)); + rs.close(); + + rs = st.executeQuery("SELECT t FROM testdquot WHERE t=$$;$$"); + assertTrue(rs.next()); + assertEquals(";", rs.getObject(1)); + rs.close(); + + rs = st.executeQuery("SELECT t FROM testdquot WHERE t=$OR$$a$'$b$a$$OR$OR t=';;'"); + assertTrue(rs.next()); + assertEquals("$a$'$b$a$", rs.getObject(1)); + assertFalse(rs.next()); + rs.close(); + + rs = st.executeQuery("SELECT t FROM testdquot WHERE t=$B$;$b$B$"); + assertTrue(rs.next()); + assertEquals(";$b", rs.getObject(1)); + rs.close(); + + rs = st.executeQuery("SELECT t FROM testdquot WHERE t=$c$c$;$c$"); + assertTrue(rs.next()); + assertEquals("c$;", rs.getObject(1)); + rs.close(); + + rs = st.executeQuery("SELECT t FROM testdquot WHERE t=$t$t$t$ OR t=';'"); + assertTrue(rs.next()); + assertEquals(";", rs.getObject(1)); + assertFalse(rs.next()); + rs.close(); + + st.executeUpdate("UPDATE testdquot SET t=';;' WHERE t=$q0$;$q0$;" + + "UPDATE testdquot SET t='' WHERE t=';;'"); + rs = st.executeQuery("SELECT t FROM testdquot WHERE t=''"); + assertTrue(rs.next()); + assertEquals("", rs.getObject(1)); + assertFalse(rs.next()); + rs.close(); + + st.close(); + } + + public void testParameterized() throws SQLException + { + PreparedStatement st; + ResultSet rs; + + st = con.prepareStatement("SELECT t FROM testdquot WHERE t=$$?$$ AND t=?"); + st.setString(1, "?"); + rs = st.executeQuery(); + assertTrue(rs.next()); + assertEquals("?", rs.getString(1)); + assertFalse(rs.next()); + st.close(); + + st = con.prepareStatement("SELECT t FROM testdquot WHERE t=$q_1$'$q_1$ OR t=?;" + + "SELECT t FROM testdquot WHERE t=? OR t=$c$c$;$c$;" + + "SELECT t FROM testdquot WHERE t=?"); + 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()); + assertTrue(rs.next()); + assertFalse(rs.next()); + + assertTrue(st.getMoreResults()); + rs = st.getResultSet(); + assertTrue(rs.next()); + assertEquals("$a$ $a$", rs.getString(1)); + assertFalse(rs.next()); + + } + + } diff -crN pgjdbc-orig/org/postgresql/test/jdbc3/Jdbc3TestSuite.java pgjdbc/org/postgresql/test/jdbc3/Jdbc3TestSuite.java *** pgjdbc-orig/org/postgresql/test/jdbc3/Jdbc3TestSuite.java Mon May 15 11:35:57 2006 --- pgjdbc/org/postgresql/test/jdbc3/Jdbc3TestSuite.java Sun Oct 1 15:17:30 2006 *************** *** 34,39 **** --- 34,43 ---- { suite.addTestSuite(Jdbc3CallableStatementTest.class); } + if ( TestUtil.haveMinimumServerVersion(con, "8.0") && TestUtil.isProtocolVersion(con, 3)) + { + suite.addTestSuite(Jdbc3DollarQuotingTest.class); + } con.close(); } catch (Exception ex )
pgsql-jdbc by date: