Re: Patch for jdbc escaped functions - Mailing list pgsql-jdbc
| From | Xavier Poinsard |
|---|---|
| Subject | Re: Patch for jdbc escaped functions |
| Date | |
| Msg-id | 41A1F323.1070508@free.fr Whole thread Raw |
| In response to | Re: Patch for jdbc escaped functions (Kris Jurka <books@ejurka.com>) |
| Responses |
Re: Patch for jdbc escaped functions
|
| List | pgsql-jdbc |
Kris Jurka wrote:
> This code is not quite right in its determination of function arguments
> because it stops checking for literal and identifier markers.
> Consider that {fn concat('(', a."(")} should work.
Thanks for pointing these defects.
I added this to the parsing and your example to the tests.
>
> I also don't like the prospect of a giant if/else block that has every
> function that must do some kind of mapping/translation. What about a more
> pluggable architecture perhaps along the lines of the following:
>
> public interface StandardFunction {
> public void toSQL(StringBuffer sb, ArrayList args);
> }
>
> Then a static HashMap of say lowercase function name -> StandardFunction
> implementation can move all of the mapping/translation into a separate
> place. Maybe that's overkill in the opposite direction. Thoughts?
I used reflection to move the translation part to EscapedFunctions class.
Right ?
Index: postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java,v
retrieving revision 1.16
diff -u -r1.16 AbstractJdbc2DatabaseMetaData.java
--- postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java 9 Nov 2004 08:48:31 -0000 1.16
+++ postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java 22 Nov 2004 13:48:22 -0000
@@ -475,7 +475,7 @@
{
if (Driver.logDebug)
Driver.debug("getStringFunctions");
- return "";
+ return EscapedFunctions.CONCAT;
}
public String getSystemFunctions() throws SQLException
Index: postgresql/jdbc2/AbstractJdbc2Statement.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v
retrieving revision 1.50
diff -u -r1.50 AbstractJdbc2Statement.java
--- postgresql/jdbc2/AbstractJdbc2Statement.java 17 Nov 2004 02:43:49 -0000 1.50
+++ postgresql/jdbc2/AbstractJdbc2Statement.java 22 Nov 2004 13:48:28 -0000
@@ -11,9 +11,11 @@
import java.io.*;
+import java.lang.reflect.Method;
import java.math.*;
import java.sql.*;
import java.util.ArrayList;
+import java.util.List;
import java.util.Vector;
import org.postgresql.Driver;
import org.postgresql.largeobject.*;
@@ -61,12 +63,12 @@
// Static variables for parsing SQL when replaceProcessing is true.
private static final short IN_SQLCODE = 0;
private static final short IN_STRING = 1;
+ private static final short IN_IDENTIFIER = 6;
private static final short BACKSLASH = 2;
private static final short ESC_TIMEDATE = 3;
private static final short ESC_FUNCTION = 4;
private static final short ESC_OUTERJOIN = 5;
-
// Some performance caches
private StringBuffer sbuf = new StringBuffer(32);
@@ -655,8 +657,9 @@
*
* Currently implemented Escape clauses are those mentioned in 11.3
* in the specification. Basically we look through the sql string for
- * {d xxx}, {t xxx} or {ts xxx} in non-string sql code. When we find
- * them, we just strip the escape part leaving only the xxx part.
+ * {d xxx}, {t xxx}, {ts xxx}, {oj xxx} or {fn xxx} in non-string sql
+ * code. When we find them, we just strip the escape part leaving only
+ * the xxx part.
* So, something like "select * from x where d={d '2001-10-09'}" would
* return "select * from x where d= '2001-10-09'".
*/
@@ -667,83 +670,172 @@
// Since escape codes can only appear in SQL CODE, we keep track
// of if we enter a string or not.
StringBuffer newsql = new StringBuffer(p_sql.length());
- short state = IN_SQLCODE;
-
- int i = -1;
- int len = p_sql.length();
- while (++i < len)
- {
- char c = p_sql.charAt(i);
- switch (state)
- {
- case IN_SQLCODE:
- if (c == '\'') // start of a string?
- state = IN_STRING;
- else if (c == '{') // start of an escape code?
- if (i + 1 < len)
+ parseSql(p_sql,0,newsql,false);
+ return newsql.toString();
+ }
+ else
+ {
+ return p_sql;
+ }
+ }
+
+ /**
+ * parse the given sql from index i, appending it to the gven buffer
+ * until we got an ) or end of string or , with the flag stopFirstArg
+ * @param p_sql
+ * @param i
+ * @param newsql
+ * @return
+ */
+ private int parseSql(String p_sql,int i,StringBuffer newsql, boolean stopFirstArg){
+ short state = IN_SQLCODE;
+ // because of the ++i loop
+ i--;
+ int len = p_sql.length();
+ int nestedParenthesis=0;
+ boolean endOfNested=false;
+ while (!endOfNested && ++i < len)
+ {
+ char c = p_sql.charAt(i);
+ switch (state)
+ {
+ case IN_SQLCODE:
+ if (c == '\'') // start of a string?
+ state = IN_STRING;
+ else if (c == '"') // start of a identifer?
+ state = IN_IDENTIFIER;
+ else if (c=='('){ // begin nested sql
+ nestedParenthesis++;
+ } else if (c==')'){ // end of nested sql
+ nestedParenthesis--;
+ if (nestedParenthesis<0){
+ endOfNested=true;
+ break;
+ }
+ } else if (stopFirstArg && c==',' && nestedParenthesis==0) {
+ endOfNested=true;
+ break;
+ }else if (c == '{') // start of an escape code?
+ if (i + 1 < len)
+ {
+ char next = p_sql.charAt(i + 1);
+ char nextnext = (i + 2 < len) ? p_sql.charAt(i + 2) : '\0';
+ if (next == 'd' || next == 'D')
{
- char next = p_sql.charAt(i + 1);
- char nextnext = (i + 2 < len) ? p_sql.charAt(i + 2) : '\0';
- if (next == 'd' || next == 'D')
- {
- state = ESC_TIMEDATE;
- i++;
- break;
- }
- else if (next == 't' || next == 'T')
- {
- state = ESC_TIMEDATE;
- i += (nextnext == 's' || nextnext == 'S') ? 2 : 1;
- break;
- }
- else if ( next == 'f' || next == 'F' )
- {
- state = ESC_FUNCTION;
- i += (nextnext == 'n' || nextnext == 'N') ? 2 : 1;
- break;
- }
- else if ( next == 'o' || next == 'O' )
- {
- state = ESC_OUTERJOIN;
- i += (nextnext == 'j' || nextnext == 'J') ? 2 : 1;
- break;
- }
-
+ state = ESC_TIMEDATE;
+ i++;
+ break;
}
- newsql.append(c);
- break;
-
- case IN_STRING:
- if (c == '\'') // end of string?
- state = IN_SQLCODE;
- else if (c == '\\') // a backslash?
- state = BACKSLASH;
+ else if (next == 't' || next == 'T')
+ {
+ state = ESC_TIMEDATE;
+ i += (nextnext == 's' || nextnext == 'S') ? 2 : 1;
+ break;
+ }
+ else if ( next == 'f' || next == 'F' )
+ {
+ state = ESC_FUNCTION;
+ i += (nextnext == 'n' || nextnext == 'N') ? 2 : 1;
+ break;
+ }
+ else if ( next == 'o' || next == 'O' )
+ {
+ state = ESC_OUTERJOIN;
+ i += (nextnext == 'j' || nextnext == 'J') ? 2 : 1;
+ break;
+ }
+ }
+ newsql.append(c);
+ break;
- newsql.append(c);
- break;
+ case IN_STRING:
+ if (c == '\'') // end of string?
+ state = IN_SQLCODE;
+ else if (c == '\\') // a backslash?
+ state = BACKSLASH;
- case BACKSLASH:
- state = IN_STRING;
+ newsql.append(c);
+ break;
+
+ case IN_IDENTIFIER:
+ if (c == '"') // end of identifier
+ state = IN_SQLCODE;
+ newsql.append(c);
+ break;
- newsql.append(c);
- break;
+ case BACKSLASH:
+ state = IN_STRING;
- case ESC_TIMEDATE:
- case ESC_FUNCTION:
- case ESC_OUTERJOIN:
- if (c == '}')
- state = IN_SQLCODE; // end of escape code.
- else
- newsql.append(c);
- break;
- } // end switch
- }
+ newsql.append(c);
+ break;
- return newsql.toString();
+ case ESC_FUNCTION:
+ // extract function name
+ String functionName;
+ int posArgs = p_sql.indexOf('(',i);
+ if (posArgs!=-1){
+ functionName=p_sql.substring(i,posArgs).trim();
+ // extract arguments
+ i= posArgs+1;// we start the scan after the first (
+ StringBuffer args=new StringBuffer();
+ i = parseSql(p_sql,i,args,false);
+ // translate the function and parse arguments
+ newsql.append(escapeFunction(functionName,args.toString()));
+ }
+ // go to the end of the function copying anything found
+ i++;
+ while (i<len && p_sql.charAt(i)!='}')
+ newsql.append(p_sql.charAt(i));
+ state = IN_SQLCODE; // end of escaped function (or query)
+ break;
+ case ESC_TIMEDATE:
+ case ESC_OUTERJOIN:
+ if (c == '}')
+ state = IN_SQLCODE; // end of escape code.
+ else
+ newsql.append(c);
+ break;
+ } // end switch
}
- else
- {
- return p_sql;
+ return i;
+ }
+
+ /**
+ * generate sql for escaped functions
+ * @param functionName the escaped function name
+ * @param args the arguments for this functin
+ * @return the right postgreSql sql
+ */
+ protected String escapeFunction(String functionName, String args){
+ // parse function arguments
+ int len = args.length();
+ int i=0;
+ ArrayList parsedArgs = new ArrayList();
+ while (i<len){
+ StringBuffer arg = new StringBuffer();
+ int lastPos=i;
+ i=parseSql(args,i,arg,true);
+ int nestedCount=0;
+ if (lastPos!=i){
+ parsedArgs.add(arg);
+ }
+ i++;
+ }
+ // we can now tranlate escape functions
+ try{
+ Method escapeMethod = EscapedFunctions.class.getDeclaredMethod(functionName.toLowerCase(),new Class [] {
List.class});
+ return (String) escapeMethod.invoke(null,new Object[] {parsedArgs});
+ }catch (Exception e){
+ // by default the function name is kept unchanged
+ StringBuffer buf = new StringBuffer();
+ buf.append(functionName).append('(');
+ for (int iArg = 0;iArg<parsedArgs.size();iArg++){
+ buf.append(parsedArgs.get(iArg));
+ if (iArg!=(parsedArgs.size()-1))
+ buf.append(',');
+ }
+ buf.append(')');
+ return buf.toString();
}
}
Index: postgresql/test/jdbc2/StatementTest.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/test/jdbc2/StatementTest.java,v
retrieving revision 1.9
diff -u -r1.9 StatementTest.java
--- postgresql/test/jdbc2/StatementTest.java 9 Nov 2004 08:55:11 -0000 1.9
+++ postgresql/test/jdbc2/StatementTest.java 22 Nov 2004 13:48:29 -0000
@@ -32,13 +32,14 @@
TestUtil.createTempTable(con, "test_statement",
"i int");
TestUtil.createTempTable(con, "escapetest",
- "ts timestamp, d date, t time");
+ "ts timestamp, d date, t time, \")\" varchar(5) ");
}
protected void tearDown() throws Exception
{
super.tearDown();
TestUtil.dropTable( con, "test_statement" );
+ TestUtil.dropTable( con, "escapetest" );
con.close();
}
@@ -116,10 +117,26 @@
count = stmt.executeUpdate("insert into escapetest (t) values ({t '00:00:00'})");
assertEquals(1, count);
-
ResultSet rs = stmt.executeQuery( "select {fn version()} as version" );
assertTrue(rs.next());
+
+ // check default replacement as-is
+ rs = stmt.executeQuery( "select {fn log(2,3)} as log" );
+ assertTrue(rs.next());
+
+ // check nested and multiple escaped functions
+ rs = stmt.executeQuery( "select {fn version()} as version, {fn log({fn log(3,2)},3)} as log" );
+ assertTrue(rs.next());
+
+ // check concat
+ rs = stmt.executeQuery( "select {fn concat('ab','cd')} as concat" );
+ assertTrue(rs.next());
+ assertEquals(rs.getString(1),"abcd");
+ // check concat with ( in strings
+ rs = stmt.executeQuery( "select {fn concat(')',escapetest.\")\")} as concat" );
+ assertTrue(rs.next());
+
count = stmt.executeUpdate( "create temp table b (i int)" );
assertEquals(0, count);
Index: postgresql/jdbc2/EscapedFunctions.java
===================================================================
RCS file: postgresql/jdbc2/EscapedFunctions.java
diff -N postgresql/jdbc2/EscapedFunctions.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ postgresql/jdbc2/EscapedFunctions.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,30 @@
+/*-------------------------------------------------------------------------
+*
+* Copyright (c) 2004, PostgreSQL Global Development Group
+*
+* IDENTIFICATION
+*
+*-------------------------------------------------------------------------
+*/
+package org.postgresql.jdbc2;
+
+import java.util.List;
+
+/**
+ * this class stores supported escaped function
+ */
+public class EscapedFunctions {
+ public final static String CONCAT="concat";
+
+ /** concat translation */
+ public static String concat(List parsedArgs){
+ StringBuffer buf = new StringBuffer();
+ buf.append('(');
+ for (int iArg = 0;iArg<parsedArgs.size();iArg++){
+ buf.append(parsedArgs.get(iArg));
+ if (iArg!=(parsedArgs.size()-1))
+ buf.append(" || ");
+ }
+ return buf.append(')').toString();
+ }
+}
pgsql-jdbc by date: