diff options
Diffstat (limited to 'src/interfaces/jdbc/postgresql/fastpath/Fastpath.java')
-rw-r--r-- | src/interfaces/jdbc/postgresql/fastpath/Fastpath.java | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java b/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java new file mode 100644 index 00000000000..ce278acef71 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java @@ -0,0 +1,291 @@ +package postgresql.fastpath; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import postgresql.util.*; + +/** + * This class implements the Fastpath api. + * + * <p>This is a means of executing functions imbeded in the postgresql backend + * from within a java application. + * + * <p>It is based around the file src/interfaces/libpq/fe-exec.c + * + * + * <p><b>Implementation notes:</b> + * + * <p><b><em>Network protocol:</em></b> + * + * <p>The code within the backend reads integers in reverse. + * + * <p>There is work in progress to convert all of the protocol to + * network order but it may not be there for v6.3 + * + * <p>When fastpath switches, simply replace SendIntegerReverse() with + * SendInteger() + * + * @see postgresql.FastpathFastpathArg + * @see postgresql.LargeObject + */ +public class Fastpath +{ + // This maps the functions names to their id's (possible unique just + // to a connection). + protected Hashtable func = new Hashtable(); + + protected postgresql.Connection conn; // our connection + protected postgresql.PG_Stream stream; // the network stream + + /** + * Initialises the fastpath system + * + * <p><b>Important Notice</b> + * <br>This is called from postgresql.Connection, and should not be called + * from client code. + * + * @param conn postgresql.Connection to attach to + * @param stream The network stream to the backend + */ + public Fastpath(postgresql.Connection conn,postgresql.PG_Stream stream) + { + this.conn=conn; + this.stream=stream; + DriverManager.println("Fastpath initialised"); + } + + /** + * Send a function call to the PostgreSQL backend + * + * @param fnid Function id + * @param resulttype True if the result is an integer, false for other results + * @param args FastpathArguments to pass to fastpath + * @return null if no data, Integer if an integer result, or byte[] otherwise + * @exception SQLException if a database-access error occurs. + */ + public Object fastpath(int fnid,boolean resulttype,FastpathArg[] args) throws SQLException + { + // send the function call + try { + // 70 is 'F' in ASCII. Note: don't use SendChar() here as it adds padding + // that confuses the backend. The 0 terminates the command line. + stream.SendInteger(70,1); + stream.SendInteger(0,1); + + stream.SendIntegerReverse(fnid,4); + stream.SendIntegerReverse(args.length,4); + + for(int i=0;i<args.length;i++) + args[i].send(stream); + + // This is needed, otherwise data can be lost + stream.flush(); + + } catch(IOException ioe) { + throw new SQLException("Failed to send fastpath call "+fnid+"\n"+ioe); + } + + // Now handle the result + + // We should get 'V' on sucess or 'E' on error. Anything else is treated + // as an error. + //int in = stream.ReceiveChar(); + //DriverManager.println("ReceiveChar() = "+in+" '"+((char)in)+"'"); + //if(in!='V') { + //if(in=='E') + //throw new SQLException(stream.ReceiveString(4096)); + //throw new SQLException("Fastpath: expected 'V' from backend, got "+((char)in)); + //} + + // Now loop, reading the results + Object result = null; // our result + while(true) { + int in = stream.ReceiveChar(); + DriverManager.println("ReceiveChar() = "+in+" '"+((char)in)+"'"); + switch(in) + { + case 'V': + break; + + //------------------------------ + // Function returned properly + // + case 'G': + int sz = stream.ReceiveInteger(4); + DriverManager.println("G: size="+sz); //debug + + // Return an Integer if + if(resulttype) + result = new Integer(stream.ReceiveInteger(sz)); + else { + byte buf[] = new byte[sz]; + stream.Receive(buf,0,sz); + result = buf; + } + break; + + //------------------------------ + // Error message returned + case 'E': + throw new SQLException("Fastpath: "+stream.ReceiveString(4096)); + + //------------------------------ + // Notice from backend + case 'N': + conn.addWarning(stream.ReceiveString(4096)); + break; + + //------------------------------ + // End of results + // + // Here we simply return res, which would contain the result + // processed earlier. If no result, this already contains null + case '0': + DriverManager.println("returning "+result); + return result; + + default: + throw new SQLException("Fastpath: protocol error. Got '"+((char)in)+"'"); + } + } + } + + /** + * Send a function call to the PostgreSQL backend by name. + * + * Note: the mapping for the procedure name to function id needs to exist, + * usually to an earlier call to addfunction(). + * + * This is the prefered method to call, as function id's can/may change + * between versions of the backend. + * + * For an example of how this works, refer to postgresql.LargeObject + * + * @param name Function name + * @param resulttype True if the result is an integer, false for other + * results + * @param args FastpathArguments to pass to fastpath + * @return null if no data, Integer if an integer result, or byte[] otherwise + * @exception SQLException if name is unknown or if a database-access error + * occurs. + * @see postgresql.LargeObject + */ + public Object fastpath(String name,boolean resulttype,FastpathArg[] args) throws SQLException + { + DriverManager.println("Fastpath: calling "+name); + return fastpath(getID(name),resulttype,args); + } + + /** + * This convenience method assumes that the return value is an Integer + * @param name Function name + * @param args Function arguments + * @return integer result + * @exception SQLException if a database-access error occurs or no result + */ + public int getInteger(String name,FastpathArg[] args) throws SQLException + { + Integer i = (Integer)fastpath(name,true,args); + if(i==null) + throw new SQLException("Fastpath:"+name+": no result returned, expected integer"); + return i.intValue(); + } + + /** + * This convenience method assumes that the return value is an Integer + * @param name Function name + * @param args Function arguments + * @return byte[] array containing result + * @exception SQLException if a database-access error occurs or no result + */ + public byte[] getData(String name,FastpathArg[] args) throws SQLException + { + return (byte[])fastpath(name,false,args); + } + + /** + * This adds a function to our lookup table. + * + * <p>User code should use the addFunctions method, which is based upon a + * query, rather than hard coding the oid. The oid for a function is not + * guaranteed to remain static, even on different servers of the same + * version. + * + * @param name Function name + * @param fnid Function id + */ + public void addFunction(String name,int fnid) + { + func.put(name,new Integer(fnid)); + } + + /** + * This takes a ResultSet containing two columns. Column 1 contains the + * function name, Column 2 the oid. + * + * <p>It reads the entire ResultSet, loading the values into the function + * table. + * + * <p><b>REMEMBER</b> to close() the resultset after calling this!! + * + * <p><b><em>Implementation note about function name lookups:</em></b> + * + * <p>PostgreSQL stores the function id's and their corresponding names in + * the pg_proc table. To speed things up locally, instead of querying each + * function from that table when required, a Hashtable is used. Also, only + * the function's required are entered into this table, keeping connection + * times as fast as possible. + * + * <p>The postgresql.LargeObject class performs a query upon it's startup, + * and passes the returned ResultSet to the addFunctions() method here. + * + * <p>Once this has been done, the LargeObject api refers to the functions by + * name. + * + * <p>Dont think that manually converting them to the oid's will work. Ok, + * they will for now, but they can change during development (there was some + * discussion about this for V7.0), so this is implemented to prevent any + * unwarranted headaches in the future. + * + * @param rs ResultSet + * @exception SQLException if a database-access error occurs. + * @see postgresql.LargeObjectManager + */ + public void addFunctions(ResultSet rs) throws SQLException + { + while(rs.next()) { + func.put(rs.getString(1),new Integer(rs.getInt(2))); + } + } + + /** + * This returns the function id associated by its name + * + * <p>If addFunction() or addFunctions() have not been called for this name, + * then an SQLException is thrown. + * + * @param name Function name to lookup + * @return Function ID for fastpath call + * @exception SQLException is function is unknown. + */ + public int getID(String name) throws SQLException + { + Integer id = (Integer)func.get(name); + + // may be we could add a lookup to the database here, and store the result + // in our lookup table, throwing the exception if that fails. + // We must, however, ensure that if we do, any existing ResultSet is + // unaffected, otherwise we could break user code. + // + // so, until we know we can do this (needs testing, on the TODO list) + // for now, we throw the exception and do no lookups. + if(id==null) + throw new SQLException("Fastpath: function "+name+" is unknown"); + + return id.intValue(); + } +} + |