diff --git a/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java b/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java new file mode 100644 index 0000000000000000000000000000000000000000..9e9b07f89bec72121987ed9ecb67025bfdcae1ed --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java @@ -0,0 +1,303 @@ +package org.postgresql.fastpath; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import org.postgresql.util.*; + +// Important: There are a lot of debug code commented out. Please do not +// delete these. + +/** + * This class implements the Fastpath api. + * + * <p>This is a means of executing functions imbeded in the org.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 org.postgresql.FastpathFastpathArg + * @see org.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 org.postgresql.Connection conn; // our connection + protected org.postgresql.PG_Stream stream; // the network stream + + /** + * Initialises the fastpath system + * + * <p><b>Important Notice</b> + * <br>This is called from org.postgresql.Connection, and should not be called + * from client code. + * + * @param conn org.postgresql.Connection to attach to + * @param stream The network stream to the backend + */ + public Fastpath(org.postgresql.Connection conn,org.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 + { + // added Oct 7 1998 to give us thread safety + synchronized(stream) { + + // 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); + stream.SendInteger(fnid,4); + stream.SendInteger(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 PSQLException("postgresql.fp.send",new Integer(fnid),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.ReceiveIntegerR(4); + //DriverManager.println("G: size="+sz); //debug + + // Return an Integer if + if(resulttype) + result = new Integer(stream.ReceiveIntegerR(sz)); + else { + byte buf[] = new byte[sz]; + stream.Receive(buf,0,sz); + result = buf; + } + break; + + //------------------------------ + // Error message returned + case 'E': + throw new PSQLException("postgresql.fp.error",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; + + case 'Z': + break; + + default: + throw new PSQLException("postgresql.fp.protocol",new Character((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 org.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 org.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 PSQLException("postgresql.fp.expint",name); + 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 org.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 org.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 PSQLException("postgresql.fp.unknown",name); + + return id.intValue(); + } +} + diff --git a/src/interfaces/jdbc/org/postgresql/fastpath/FastpathArg.java b/src/interfaces/jdbc/org/postgresql/fastpath/FastpathArg.java new file mode 100644 index 0000000000000000000000000000000000000000..87b8475f6453c77f5a1cd39e00db59fbe714533e --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/fastpath/FastpathArg.java @@ -0,0 +1,106 @@ +package org.postgresql.fastpath; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import org.postgresql.util.*; + +/** + * Each fastpath call requires an array of arguments, the number and type + * dependent on the function being called. + * + * <p>This class implements methods needed to provide this capability. + * + * <p>For an example on how to use this, refer to the org.postgresql.largeobject + * package + * + * @see org.postgresql.fastpath.Fastpath + * @see org.postgresql.largeobject.LargeObjectManager + * @see org.postgresql.largeobject.LargeObject + */ +public class FastpathArg +{ + /** + * Type of argument, true=integer, false=byte[] + */ + public boolean type; + + /** + * Integer value if type=true + */ + public int value; + + /** + * Byte value if type=false; + */ + public byte[] bytes; + + /** + * Constructs an argument that consists of an integer value + * @param value int value to set + */ + public FastpathArg(int value) + { + type=true; + this.value=value; + } + + /** + * Constructs an argument that consists of an array of bytes + * @param bytes array to store + */ + public FastpathArg(byte bytes[]) + { + type=false; + this.bytes=bytes; + } + + /** + * Constructs an argument that consists of part of a byte array + * @param buf source array + * @param off offset within array + * @param len length of data to include + */ + public FastpathArg(byte buf[],int off,int len) + { + type=false; + bytes = new byte[len]; + System.arraycopy(buf,off,bytes,0,len); + } + + /** + * Constructs an argument that consists of a String. + * @param s String to store + */ + public FastpathArg(String s) + { + this(s.getBytes()); + } + + /** + * This sends this argument down the network stream. + * + * <p>The stream sent consists of the length.int4 then the contents. + * + * <p><b>Note:</b> This is called from Fastpath, and cannot be called from + * client code. + * + * @param s output stream + * @exception IOException if something failed on the network stream + */ + protected void send(org.postgresql.PG_Stream s) throws IOException + { + if(type) { + // argument is an integer + s.SendInteger(4,4); // size of an integer + s.SendInteger(value,4); // integer value of argument + } else { + // argument is a byte array + s.SendInteger(bytes.length,4); // size of array + s.Send(bytes); + } + } +} + diff --git a/src/interfaces/jdbc/org/postgresql/geometric/PGbox.java b/src/interfaces/jdbc/org/postgresql/geometric/PGbox.java new file mode 100644 index 0000000000000000000000000000000000000000..f092133ad4499126906603d0516ea8c47827ad47 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/geometric/PGbox.java @@ -0,0 +1,105 @@ +package org.postgresql.geometric; + +import java.io.*; +import java.sql.*; +import org.postgresql.util.*; + +/** + * This represents the box datatype within org.postgresql. + */ +public class PGbox extends PGobject implements Serializable,Cloneable +{ + /** + * These are the two points. + */ + public PGpoint point[] = new PGpoint[2]; + + /** + * @param x1 first x coordinate + * @param y1 first y coordinate + * @param x2 second x coordinate + * @param y2 second y coordinate + */ + public PGbox(double x1,double y1,double x2,double y2) + { + this(); + this.point[0] = new PGpoint(x1,y1); + this.point[1] = new PGpoint(x2,y2); + } + + /** + * @param p1 first point + * @param p2 second point + */ + public PGbox(PGpoint p1,PGpoint p2) + { + this(); + this.point[0] = p1; + this.point[1] = p2; + } + + /** + * @param s Box definition in PostgreSQL syntax + * @exception SQLException if definition is invalid + */ + public PGbox(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * Required constructor + */ + public PGbox() + { + setType("box"); + } + + /** + * This method sets the value of this object. It should be overidden, + * but still called by subclasses. + * + * @param value a string representation of the value of the object + * @exception SQLException thrown if value is invalid for this type + */ + public void setValue(String value) throws SQLException + { + PGtokenizer t = new PGtokenizer(value,','); + if(t.getSize() != 2) + throw new PSQLException("postgresql.geo.box",value); + + point[0] = new PGpoint(t.getToken(0)); + point[1] = new PGpoint(t.getToken(1)); + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGbox) { + PGbox p = (PGbox)obj; + return (p.point[0].equals(point[0]) && p.point[1].equals(point[1])) || + (p.point[0].equals(point[1]) && p.point[1].equals(point[0])); + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGbox((PGpoint)point[0].clone(),(PGpoint)point[1].clone()); + } + + /** + * @return the PGbox in the syntax expected by org.postgresql + */ + public String getValue() + { + return point[0].toString()+","+point[1].toString(); + } +} diff --git a/src/interfaces/jdbc/org/postgresql/geometric/PGcircle.java b/src/interfaces/jdbc/org/postgresql/geometric/PGcircle.java new file mode 100644 index 0000000000000000000000000000000000000000..9a1333e4e8c7c4bfb9896078b5538241a91b9900 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/geometric/PGcircle.java @@ -0,0 +1,108 @@ +package org.postgresql.geometric; + +import java.io.*; +import java.sql.*; +import org.postgresql.util.*; + +/** + * This represents org.postgresql's circle datatype, consisting of a point and + * a radius + */ +public class PGcircle extends PGobject implements Serializable,Cloneable +{ + /** + * This is the centre point + */ + public PGpoint center; + + /** + * This is the radius + */ + double radius; + + /** + * @param x coordinate of centre + * @param y coordinate of centre + * @param r radius of circle + */ + public PGcircle(double x,double y,double r) + { + this(new PGpoint(x,y),r); + } + + /** + * @param c PGpoint describing the circle's centre + * @param r radius of circle + */ + public PGcircle(PGpoint c,double r) + { + this(); + this.center = c; + this.radius = r; + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGcircle(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * This constructor is used by the driver. + */ + public PGcircle() + { + setType("circle"); + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removeAngle(s),','); + if(t.getSize() != 2) + throw new PSQLException("postgresql.geo.circle",s); + + try { + center = new PGpoint(t.getToken(0)); + radius = Double.valueOf(t.getToken(1)).doubleValue(); + } catch(NumberFormatException e) { + throw new PSQLException("postgresql.geo.circle",e); + } + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGcircle) { + PGcircle p = (PGcircle)obj; + return p.center.equals(center) && p.radius==radius; + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGcircle((PGpoint)center.clone(),radius); + } + + /** + * @return the PGcircle in the syntax expected by org.postgresql + */ + public String getValue() + { + return "<"+center+","+radius+">"; + } +} diff --git a/src/interfaces/jdbc/org/postgresql/geometric/PGline.java b/src/interfaces/jdbc/org/postgresql/geometric/PGline.java new file mode 100644 index 0000000000000000000000000000000000000000..4901b6f2f16d551be1225c0ae139a3b96b5b5b13 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/geometric/PGline.java @@ -0,0 +1,103 @@ +package org.postgresql.geometric; + +import java.io.*; +import java.sql.*; +import org.postgresql.util.*; + +/** + * This implements a line consisting of two points. + * + * Currently line is not yet implemented in the backend, but this class + * ensures that when it's done were ready for it. + */ +public class PGline extends PGobject implements Serializable,Cloneable +{ + /** + * These are the two points. + */ + public PGpoint point[] = new PGpoint[2]; + + /** + * @param x1 coordinate for first point + * @param y1 coordinate for first point + * @param x2 coordinate for second point + * @param y2 coordinate for second point + */ + public PGline(double x1,double y1,double x2,double y2) + { + this(new PGpoint(x1,y1),new PGpoint(x2,y2)); + } + + /** + * @param p1 first point + * @param p2 second point + */ + public PGline(PGpoint p1,PGpoint p2) + { + this(); + this.point[0] = p1; + this.point[1] = p2; + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGline(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * reuired by the driver + */ + public PGline() + { + setType("line"); + } + + /** + * @param s Definition of the line segment in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removeBox(s),','); + if(t.getSize() != 2) + throw new PSQLException("postgresql.geo.line",s); + + point[0] = new PGpoint(t.getToken(0)); + point[1] = new PGpoint(t.getToken(1)); + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGline) { + PGline p = (PGline)obj; + return (p.point[0].equals(point[0]) && p.point[1].equals(point[1])) || + (p.point[0].equals(point[1]) && p.point[1].equals(point[0])); + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGline((PGpoint)point[0].clone(),(PGpoint)point[1].clone()); + } + + /** + * @return the PGline in the syntax expected by org.postgresql + */ + public String getValue() + { + return "["+point[0]+","+point[1]+"]"; + } +} diff --git a/src/interfaces/jdbc/org/postgresql/geometric/PGlseg.java b/src/interfaces/jdbc/org/postgresql/geometric/PGlseg.java new file mode 100644 index 0000000000000000000000000000000000000000..ec0986963bd55442e0c1a50e7885d2b3ccd48b64 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/geometric/PGlseg.java @@ -0,0 +1,100 @@ +package org.postgresql.geometric; + +import java.io.*; +import java.sql.*; +import org.postgresql.util.*; + +/** + * This implements a lseg (line segment) consisting of two points + */ +public class PGlseg extends PGobject implements Serializable,Cloneable +{ + /** + * These are the two points. + */ + public PGpoint point[] = new PGpoint[2]; + + /** + * @param x1 coordinate for first point + * @param y1 coordinate for first point + * @param x2 coordinate for second point + * @param y2 coordinate for second point + */ + public PGlseg(double x1,double y1,double x2,double y2) + { + this(new PGpoint(x1,y1),new PGpoint(x2,y2)); + } + + /** + * @param p1 first point + * @param p2 second point + */ + public PGlseg(PGpoint p1,PGpoint p2) + { + this(); + this.point[0] = p1; + this.point[1] = p2; + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGlseg(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * reuired by the driver + */ + public PGlseg() + { + setType("lseg"); + } + + /** + * @param s Definition of the line segment in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removeBox(s),','); + if(t.getSize() != 2) + throw new PSQLException("postgresql.geo.lseg"); + + point[0] = new PGpoint(t.getToken(0)); + point[1] = new PGpoint(t.getToken(1)); + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGlseg) { + PGlseg p = (PGlseg)obj; + return (p.point[0].equals(point[0]) && p.point[1].equals(point[1])) || + (p.point[0].equals(point[1]) && p.point[1].equals(point[0])); + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGlseg((PGpoint)point[0].clone(),(PGpoint)point[1].clone()); + } + + /** + * @return the PGlseg in the syntax expected by org.postgresql + */ + public String getValue() + { + return "["+point[0]+","+point[1]+"]"; + } +} diff --git a/src/interfaces/jdbc/org/postgresql/geometric/PGpath.java b/src/interfaces/jdbc/org/postgresql/geometric/PGpath.java new file mode 100644 index 0000000000000000000000000000000000000000..2e5b4674fbbf6b0ba306871640ae007d13286d8e --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/geometric/PGpath.java @@ -0,0 +1,145 @@ +package org.postgresql.geometric; + +import java.io.*; +import java.sql.*; +import org.postgresql.util.*; + +/** + * This implements a path (a multiple segmented line, which may be closed) + */ +public class PGpath extends PGobject implements Serializable,Cloneable +{ + /** + * True if the path is open, false if closed + */ + public boolean open; + + /** + * The points defining this path + */ + public PGpoint points[]; + + /** + * @param points the PGpoints that define the path + * @param open True if the path is open, false if closed + */ + public PGpath(PGpoint[] points,boolean open) + { + this(); + this.points = points; + this.open = open; + } + + /** + * Required by the driver + */ + public PGpath() + { + setType("path"); + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGpath(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * @param s Definition of the path in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + // First test to see if were open + if(s.startsWith("[") && s.endsWith("]")) { + open = true; + s = PGtokenizer.removeBox(s); + } else if(s.startsWith("(") && s.endsWith(")")) { + open = false; + s = PGtokenizer.removePara(s); + } else + throw new PSQLException("postgresql.geo.path"); + + PGtokenizer t = new PGtokenizer(s,','); + int npoints = t.getSize(); + points = new PGpoint[npoints]; + for(int p=0;p<npoints;p++) + points[p] = new PGpoint(t.getToken(p)); + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGpath) { + PGpath p = (PGpath)obj; + + if(p.points.length != points.length) + return false; + + if(p.open != open) + return false; + + for(int i=0;i<points.length;i++) + if(!points[i].equals(p.points[i])) + return false; + + return true; + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + PGpoint ary[] = new PGpoint[points.length]; + for(int i=0;i<points.length;i++) + ary[i]=(PGpoint)points[i].clone(); + return new PGpath(ary,open); + } + + /** + * This returns the polygon in the syntax expected by org.postgresql + */ + public String getValue() + { + StringBuffer b = new StringBuffer(open?"[":"("); + + for(int p=0;p<points.length;p++) { + if(p>0) b.append(","); + b.append(points[p].toString()); + } + b.append(open?"]":")"); + + return b.toString(); + } + + public boolean isOpen() + { + return open; + } + + public boolean isClosed() + { + return !open; + } + + public void closePath() + { + open = false; + } + + public void openPath() + { + open = true; + } + +} diff --git a/src/interfaces/jdbc/org/postgresql/geometric/PGpoint.java b/src/interfaces/jdbc/org/postgresql/geometric/PGpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..eeb71b2773d694d7d00957f5057239238a53eb74 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/geometric/PGpoint.java @@ -0,0 +1,167 @@ +package org.postgresql.geometric; + +import java.awt.Point; +import java.io.*; +import java.sql.*; + +import org.postgresql.util.*; + +/** + * This implements a version of java.awt.Point, except it uses double + * to represent the coordinates. + * + * <p>It maps to the point datatype in org.postgresql. + */ +public class PGpoint extends PGobject implements Serializable,Cloneable +{ + /** + * The X coordinate of the point + */ + public double x; + + /** + * The Y coordinate of the point + */ + public double y; + + /** + * @param x coordinate + * @param y coordinate + */ + public PGpoint(double x,double y) + { + this(); + this.x = x; + this.y = y; + } + + /** + * This is called mainly from the other geometric types, when a + * point is imbeded within their definition. + * + * @param value Definition of this point in PostgreSQL's syntax + */ + public PGpoint(String value) throws SQLException + { + this(); + setValue(value); + } + + /** + * Required by the driver + */ + public PGpoint() + { + setType("point"); + } + + /** + * @param s Definition of this point in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removePara(s),','); + try { + x = Double.valueOf(t.getToken(0)).doubleValue(); + y = Double.valueOf(t.getToken(1)).doubleValue(); + } catch(NumberFormatException e) { + throw new PSQLException("postgresql.geo.point",e.toString()); + } + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGpoint) { + PGpoint p = (PGpoint)obj; + return x == p.x && y == p.y; + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGpoint(x,y); + } + + /** + * @return the PGpoint in the syntax expected by org.postgresql + */ + public String getValue() + { + return "("+x+","+y+")"; + } + + /** + * Translate the point with the supplied amount. + * @param x integer amount to add on the x axis + * @param y integer amount to add on the y axis + */ + public void translate(int x,int y) + { + translate((double)x,(double)y); + } + + /** + * Translate the point with the supplied amount. + * @param x double amount to add on the x axis + * @param y double amount to add on the y axis + */ + public void translate(double x,double y) + { + this.x += x; + this.y += y; + } + + /** + * Moves the point to the supplied coordinates. + * @param x integer coordinate + * @param y integer coordinate + */ + public void move(int x,int y) + { + setLocation(x,y); + } + + /** + * Moves the point to the supplied coordinates. + * @param x double coordinate + * @param y double coordinate + */ + public void move(double x,double y) + { + this.x = x; + this.y = y; + } + + /** + * Moves the point to the supplied coordinates. + * refer to java.awt.Point for description of this + * @param x integer coordinate + * @param y integer coordinate + * @see java.awt.Point + */ + public void setLocation(int x,int y) + { + move((double)x,(double)y); + } + + /** + * Moves the point to the supplied java.awt.Point + * refer to java.awt.Point for description of this + * @param p Point to move to + * @see java.awt.Point + */ + public void setLocation(Point p) + { + setLocation(p.x,p.y); + } + +} diff --git a/src/interfaces/jdbc/org/postgresql/geometric/PGpolygon.java b/src/interfaces/jdbc/org/postgresql/geometric/PGpolygon.java new file mode 100644 index 0000000000000000000000000000000000000000..b805a1df308e452612f833e91f670bc4385de90a --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/geometric/PGpolygon.java @@ -0,0 +1,105 @@ +package org.postgresql.geometric; + +import java.io.*; +import java.sql.*; +import org.postgresql.util.*; + +/** + * This implements the polygon datatype within PostgreSQL. + */ +public class PGpolygon extends PGobject implements Serializable,Cloneable +{ + /** + * The points defining the polygon + */ + public PGpoint points[]; + + /** + * Creates a polygon using an array of PGpoints + * + * @param points the points defining the polygon + */ + public PGpolygon(PGpoint[] points) + { + this(); + this.points = points; + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGpolygon(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * Required by the driver + */ + public PGpolygon() + { + setType("polygon"); + } + + /** + * @param s Definition of the polygon in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removePara(s),','); + int npoints = t.getSize(); + points = new PGpoint[npoints]; + for(int p=0;p<npoints;p++) + points[p] = new PGpoint(t.getToken(p)); + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGpolygon) { + PGpolygon p = (PGpolygon)obj; + + if(p.points.length != points.length) + return false; + + for(int i=0;i<points.length;i++) + if(!points[i].equals(p.points[i])) + return false; + + return true; + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + PGpoint ary[] = new PGpoint[points.length]; + for(int i=0;i<points.length;i++) + ary[i] = (PGpoint)points[i].clone(); + return new PGpolygon(ary); + } + + /** + * @return the PGpolygon in the syntax expected by org.postgresql + */ + public String getValue() + { + StringBuffer b = new StringBuffer(); + b.append("("); + for(int p=0;p<points.length;p++) { + if(p>0) b.append(","); + b.append(points[p].toString()); + } + b.append(")"); + return b.toString(); + } +} diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/CallableStatement.java b/src/interfaces/jdbc/org/postgresql/jdbc1/CallableStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..be5276683593baf51af7a70225568a83041ef875 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/CallableStatement.java @@ -0,0 +1,308 @@ +package org.postgresql.jdbc1; + +// IMPORTANT NOTE: This file implements the JDBC 1 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 2 class in the +// org.postgresql.jdbc2 package. + +import java.sql.*; +import java.math.*; + +/** + * CallableStatement is used to execute SQL stored procedures. + * + * <p>JDBC provides a stored procedure SQL escape that allows stored + * procedures to be called in a standard way for all RDBMS's. This escape + * syntax has one form that includes a result parameter and one that does + * not. If used, the result parameter must be registered as an OUT + * parameter. The other parameters may be used for input, output or both. + * Parameters are refered to sequentially, by number. The first parameter + * is 1. + * + * {?= call <procedure-name>[<arg1>,<arg2>, ...]} + * {call <procedure-name>[<arg1>,<arg2>, ...]} + * + * + * <p>IN parameter values are set using the set methods inherited from + * PreparedStatement. The type of all OUT parameters must be registered + * prior to executing the stored procedure; their values are retrieved + * after execution via the get methods provided here. + * + * <p>A Callable statement may return a ResultSet or multiple ResultSets. + * Multiple ResultSets are handled using operations inherited from + * Statement. + * + * <p>For maximum portability, a call's ResultSets and update counts should + * be processed prior to getting the values of output parameters. + * + * @see Connection#prepareCall + * @see ResultSet + */ + +public class CallableStatement extends PreparedStatement implements java.sql.CallableStatement +{ + /** + * @exception SQLException on failure + */ + CallableStatement(Connection c,String q) throws SQLException + { + super(c,q); + } + + /** + * Before executing a stored procedure call you must explicitly + * call registerOutParameter to register the java.sql.Type of each + * out parameter. + * + * <p>Note: When reading the value of an out parameter, you must use + * the getXXX method whose Java type XXX corresponds to the + * parameter's registered SQL type. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param sqlType SQL type code defined by java.sql.Types; for + * parameters of type Numeric or Decimal use the version of + * registerOutParameter that accepts a scale value + * @exception SQLException if a database-access error occurs. + */ + public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException { + } + + /** + * You must also specify the scale for numeric/decimal types: + * + * <p>Note: When reading the value of an out parameter, you must use + * the getXXX method whose Java type XXX corresponds to the + * parameter's registered SQL type. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param sqlType use either java.sql.Type.NUMERIC or java.sql.Type.DECIMAL + * @param scale a value greater than or equal to zero representing the + * desired number of digits to the right of the decimal point + * @exception SQLException if a database-access error occurs. + */ + public void registerOutParameter(int parameterIndex, int sqlType, + int scale) throws SQLException + { + } + + // Old api? + //public boolean isNull(int parameterIndex) throws SQLException { + //return true; + //} + + /** + * An OUT parameter may have the value of SQL NULL; wasNull + * reports whether the last value read has this special value. + * + * <p>Note: You must first call getXXX on a parameter to read its + * value and then call wasNull() to see if the value was SQL NULL. + * @return true if the last parameter read was SQL NULL + * @exception SQLException if a database-access error occurs. + */ + public boolean wasNull() throws SQLException { + // check to see if the last access threw an exception + return false; // fake it for now + } + + // Old api? + //public String getChar(int parameterIndex) throws SQLException { + //return null; + //} + + /** + * Get the value of a CHAR, VARCHAR, or LONGVARCHAR parameter as a + * Java String. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public String getString(int parameterIndex) throws SQLException { + return null; + } + //public String getVarChar(int parameterIndex) throws SQLException { + // return null; + //} + + //public String getLongVarChar(int parameterIndex) throws SQLException { + //return null; + //} + + /** + * Get the value of a BIT parameter as a Java boolean. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is false + * @exception SQLException if a database-access error occurs. + */ + public boolean getBoolean(int parameterIndex) throws SQLException { + return false; + } + + /** + * Get the value of a TINYINT parameter as a Java byte. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public byte getByte(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of a SMALLINT parameter as a Java short. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public short getShort(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of an INTEGER parameter as a Java int. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ +public int getInt(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of a BIGINT parameter as a Java long. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public long getLong(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of a FLOAT parameter as a Java float. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public float getFloat(int parameterIndex) throws SQLException { + return (float) 0.0; + } + + /** + * Get the value of a DOUBLE parameter as a Java double. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public double getDouble(int parameterIndex) throws SQLException { + return 0.0; + } + + /** + * Get the value of a NUMERIC parameter as a java.math.BigDecimal + * object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param scale a value greater than or equal to zero representing the + * desired number of digits to the right of the decimal point + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public BigDecimal getBigDecimal(int parameterIndex, int scale) + throws SQLException { + return null; + } + + /** + * Get the value of a SQL BINARY or VARBINARY parameter as a Java + * byte[] + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public byte[] getBytes(int parameterIndex) throws SQLException { + return null; + } + + // New API (JPM) (getLongVarBinary) + //public byte[] getBinaryStream(int parameterIndex) throws SQLException { + //return null; + //} + + /** + * Get the value of a SQL DATE parameter as a java.sql.Date object + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Date getDate(int parameterIndex) throws SQLException { + return null; + } + + /** + * Get the value of a SQL TIME parameter as a java.sql.Time object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Time getTime(int parameterIndex) throws SQLException { + return null; + } + + /** + * Get the value of a SQL TIMESTAMP parameter as a java.sql.Timestamp object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Timestamp getTimestamp(int parameterIndex) + throws SQLException { + return null; + } + + //---------------------------------------------------------------------- + // Advanced features: + + // You can obtain a ParameterMetaData object to get information + // about the parameters to this CallableStatement. + //public DatabaseMetaData getMetaData() { + //return null; + //} + + // getObject returns a Java object for the parameter. + // See the JDBC spec's "Dynamic Programming" chapter for details. + /** + * Get the value of a parameter as a Java object. + * + * <p>This method returns a Java object whose type coresponds to the + * SQL type that was registered for this parameter using + * registerOutParameter. + * + * <P>Note that this method may be used to read datatabase-specific, + * abstract data types. This is done by specifying a targetSqlType + * of java.sql.types.OTHER, which allows the driver to return a + * database-specific Java type. + * + * <p>See the JDBC spec's "Dynamic Programming" chapter for details. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return A java.lang.Object holding the OUT parameter value. + * @exception SQLException if a database-access error occurs. + */ + public Object getObject(int parameterIndex) + throws SQLException { + return null; + } +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/Connection.java b/src/interfaces/jdbc/org/postgresql/jdbc1/Connection.java new file mode 100644 index 0000000000000000000000000000000000000000..d178fa74ad92d49ab010f30f4fad1367dc82f1db --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/Connection.java @@ -0,0 +1,389 @@ +package org.postgresql.jdbc1; + +// IMPORTANT NOTE: This file implements the JDBC 1 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 2 class in the +// org.postgresql.jdbc2 package. + +import java.io.*; +import java.lang.*; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import org.postgresql.Field; +import org.postgresql.fastpath.*; +import org.postgresql.largeobject.*; +import org.postgresql.util.*; + +/** + * $Id: Connection.java,v 1.1 2000/04/17 20:07:48 peter Exp $ + * + * A Connection represents a session with a specific database. Within the + * context of a Connection, SQL statements are executed and results are + * returned. + * + * <P>A Connection's database is able to provide information describing + * its tables, its supported SQL grammar, its stored procedures, the + * capabilities of this connection, etc. This information is obtained + * with the getMetaData method. + * + * <p><B>Note:</B> By default, the Connection automatically commits changes + * after executing each statement. If auto-commit has been disabled, an + * explicit commit must be done or database changes will not be saved. + * + * @see java.sql.Connection + */ +public class Connection extends org.postgresql.Connection implements java.sql.Connection +{ + // This is a cache of the DatabaseMetaData instance for this connection + protected DatabaseMetaData metadata; + + /** + * SQL statements without parameters are normally executed using + * Statement objects. If the same SQL statement is executed many + * times, it is more efficient to use a PreparedStatement + * + * @return a new Statement object + * @exception SQLException passed through from the constructor + */ + public java.sql.Statement createStatement() throws SQLException + { + return new Statement(this); + } + + /** + * A SQL statement with or without IN parameters can be pre-compiled + * and stored in a PreparedStatement object. This object can then + * be used to efficiently execute this statement multiple times. + * + * <B>Note:</B> This method is optimized for handling parametric + * SQL statements that benefit from precompilation if the drivers + * supports precompilation. PostgreSQL does not support precompilation. + * In this case, the statement is not sent to the database until the + * PreparedStatement is executed. This has no direct effect on users; + * however it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' IN + * parameter placeholders + * @return a new PreparedStatement object containing the pre-compiled + * statement. + * @exception SQLException if a database access error occurs. + */ + public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException + { + return new PreparedStatement(this, sql); + } + + /** + * A SQL stored procedure call statement is handled by creating a + * CallableStatement for it. The CallableStatement provides methods + * for setting up its IN and OUT parameters and methods for executing + * it. + * + * <B>Note:</B> This method is optimised for handling stored procedure + * call statements. Some drivers may send the call statement to the + * database when the prepareCall is done; others may wait until the + * CallableStatement is executed. This has no direct effect on users; + * however, it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' parameter + * placeholders. Typically this statement is a JDBC function call + * escape string. + * @return a new CallableStatement object containing the pre-compiled + * SQL statement + * @exception SQLException if a database access error occurs + */ + public java.sql.CallableStatement prepareCall(String sql) throws SQLException + { + throw new PSQLException("postgresql.con.call"); + // return new CallableStatement(this, sql); + } + + /** + * A driver may convert the JDBC sql grammar into its system's + * native SQL grammar prior to sending it; nativeSQL returns the + * native form of the statement that the driver would have sent. + * + * @param sql a SQL statement that may contain one or more '?' + * parameter placeholders + * @return the native form of this statement + * @exception SQLException if a database access error occurs + */ + public String nativeSQL(String sql) throws SQLException + { + return sql; + } + + /** + * If a connection is in auto-commit mode, than all its SQL + * statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped + * into transactions that are terminated by either commit() + * or rollback(). By default, new connections are in auto- + * commit mode. The commit occurs when the statement completes + * or the next execute occurs, whichever comes first. In the + * case of statements returning a ResultSet, the statement + * completes when the last row of the ResultSet has been retrieved + * or the ResultSet has been closed. In advanced cases, a single + * statement may return multiple results as well as output parameter + * values. Here the commit occurs when all results and output param + * values have been retrieved. + * + * @param autoCommit - true enables auto-commit; false disables it + * @exception SQLException if a database access error occurs + */ + public void setAutoCommit(boolean autoCommit) throws SQLException + { + if (this.autoCommit == autoCommit) + return; + if (autoCommit) + ExecSQL("end"); + else + ExecSQL("begin"); + this.autoCommit = autoCommit; + } + + /** + * gets the current auto-commit state + * + * @return Current state of the auto-commit mode + * @exception SQLException (why?) + * @see setAutoCommit + */ + public boolean getAutoCommit() throws SQLException + { + return this.autoCommit; + } + + /** + * The method commit() makes all changes made since the previous + * commit/rollback permanent and releases any database locks currently + * held by the Connection. This method should only be used when + * auto-commit has been disabled. (If autoCommit == true, then we + * just return anyhow) + * + * @exception SQLException if a database access error occurs + * @see setAutoCommit + */ + public void commit() throws SQLException + { + if (autoCommit) + return; + ExecSQL("commit"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * The method rollback() drops all changes made since the previous + * commit/rollback and releases any database locks currently held by + * the Connection. + * + * @exception SQLException if a database access error occurs + * @see commit + */ + public void rollback() throws SQLException + { + if (autoCommit) + return; + ExecSQL("rollback"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * In some cases, it is desirable to immediately release a Connection's + * database and JDBC resources instead of waiting for them to be + * automatically released (cant think why off the top of my head) + * + * <B>Note:</B> A Connection is automatically closed when it is + * garbage collected. Certain fatal errors also result in a closed + * connection. + * + * @exception SQLException if a database access error occurs + */ + public void close() throws SQLException + { + if (pg_stream != null) + { + try + { + pg_stream.close(); + } catch (IOException e) {} + pg_stream = null; + } + } + + /** + * Tests to see if a Connection is closed + * + * @return the status of the connection + * @exception SQLException (why?) + */ + public boolean isClosed() throws SQLException + { + return (pg_stream == null); + } + + /** + * A connection's database is able to provide information describing + * its tables, its supported SQL grammar, its stored procedures, the + * capabilities of this connection, etc. This information is made + * available through a DatabaseMetaData object. + * + * @return a DatabaseMetaData object for this connection + * @exception SQLException if a database access error occurs + */ + public java.sql.DatabaseMetaData getMetaData() throws SQLException + { + if(metadata==null) + metadata = new DatabaseMetaData(this); + return metadata; + } + + /** + * You can put a connection in read-only mode as a hunt to enable + * database optimizations + * + * <B>Note:</B> setReadOnly cannot be called while in the middle + * of a transaction + * + * @param readOnly - true enables read-only mode; false disables it + * @exception SQLException if a database access error occurs + */ + public void setReadOnly (boolean readOnly) throws SQLException + { + this.readOnly = readOnly; + } + + /** + * Tests to see if the connection is in Read Only Mode. Note that + * we cannot really put the database in read only mode, but we pretend + * we can by returning the value of the readOnly flag + * + * @return true if the connection is read only + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return readOnly; + } + + /** + * A sub-space of this Connection's database may be selected by + * setting a catalog name. If the driver does not support catalogs, + * it will silently ignore this request + * + * @exception SQLException if a database access error occurs + */ + public void setCatalog(String catalog) throws SQLException + { + // No-op + } + + /** + * Return the connections current catalog name, or null if no + * catalog name is set, or we dont support catalogs. + * + * @return the current catalog name or null + * @exception SQLException if a database access error occurs + */ + public String getCatalog() throws SQLException + { + return null; + } + + /** + * You can call this method to try to change the transaction + * isolation level using one of the TRANSACTION_* values. + * + * <B>Note:</B> setTransactionIsolation cannot be called while + * in the middle of a transaction + * + * @param level one of the TRANSACTION_* isolation values with + * the exception of TRANSACTION_NONE; some databases may + * not support other values + * @exception SQLException if a database access error occurs + * @see java.sql.DatabaseMetaData#supportsTransactionIsolationLevel + */ + public void setTransactionIsolation(int level) throws SQLException + { + String q = "SET TRANSACTION ISOLATION LEVEL"; + + switch(level) { + + case java.sql.Connection.TRANSACTION_READ_COMMITTED: + ExecSQL(q + " READ COMMITTED"); + return; + + case java.sql.Connection.TRANSACTION_SERIALIZABLE: + ExecSQL(q + " SERIALIZABLE"); + return; + + default: + throw new PSQLException("postgresql.con.isolevel",new Integer(level)); + } + } + + /** + * Get this Connection's current transaction isolation mode. + * + * @return the current TRANSACTION_* mode value + * @exception SQLException if a database access error occurs + */ + public int getTransactionIsolation() throws SQLException + { + ExecSQL("show xactisolevel"); + + SQLWarning w = getWarnings(); + if (w != null) { + if (w.getMessage().indexOf("READ COMMITTED") != -1) return java.sql.Connection.TRANSACTION_READ_COMMITTED; else + if (w.getMessage().indexOf("READ UNCOMMITTED") != -1) return java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; else + if (w.getMessage().indexOf("REPEATABLE READ") != -1) return java.sql.Connection.TRANSACTION_REPEATABLE_READ; else + if (w.getMessage().indexOf("SERIALIZABLE") != -1) return java.sql.Connection.TRANSACTION_SERIALIZABLE; + } + return java.sql.Connection.TRANSACTION_READ_COMMITTED; + } + + /** + * The first warning reported by calls on this Connection is + * returned. + * + * <B>Note:</B> Sebsequent warnings will be changed to this + * SQLWarning + * + * @return the first SQLWarning or null + * @exception SQLException if a database access error occurs + */ + public SQLWarning getWarnings() throws SQLException + { + return firstWarning; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this connection. + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + firstWarning = null; + } + + /** + * This overides the method in org.postgresql.Connection and returns a + * ResultSet. + */ + protected java.sql.ResultSet getResultSet(org.postgresql.Connection conn, Field[] fields, Vector tuples, String status, int updateCount) throws SQLException + { + return new org.postgresql.jdbc1.ResultSet((org.postgresql.jdbc1.Connection)conn,fields,tuples,status,updateCount); + } + +} + +// *********************************************************************** + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/DatabaseMetaData.java b/src/interfaces/jdbc/org/postgresql/jdbc1/DatabaseMetaData.java new file mode 100644 index 0000000000000000000000000000000000000000..babc4fa03e05e4564523a5b2f2804b3ad87c7719 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/DatabaseMetaData.java @@ -0,0 +1,2526 @@ +package org.postgresql.jdbc1; + +// IMPORTANT NOTE: This file implements the JDBC 1 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 2 class in the +// org.postgresql.jdbc2 package. + +import java.sql.*; +import java.util.*; +import org.postgresql.Field; + +/** + * This class provides information about the database as a whole. + * + * <p>Many of the methods here return lists of information in ResultSets. You + * can use the normal ResultSet methods such as getString and getInt to + * retrieve the data from these ResultSets. If a given form of metadata is + * not available, these methods should throw a SQLException. + * + * <p>Some of these methods take arguments that are String patterns. These + * arguments all have names such as fooPattern. Within a pattern String, + * "%" means match any substring of 0 or more characters, and "_" means + * match any one character. Only metadata entries matching the search + * pattern are returned. if a search pattern argument is set to a null + * ref, it means that argument's criteria should be dropped from the + * search. + * + * <p>A SQLException will be throws if a driver does not support a meta + * data method. In the case of methods that return a ResultSet, either + * a ResultSet (which may be empty) is returned or a SQLException is + * thrown. + * + * @see java.sql.DatabaseMetaData + */ +public class DatabaseMetaData implements java.sql.DatabaseMetaData +{ + Connection connection; // The connection association + + // These define various OID's. Hopefully they will stay constant. + static final int iVarcharOid = 1043; // OID for varchar + static final int iBoolOid = 16; // OID for bool + static final int iInt2Oid = 21; // OID for int2 + static final int iInt4Oid = 23; // OID for int4 + static final int VARHDRSZ = 4; // length for int4 + + // This is a default value for remarks + private static final byte defaultRemarks[]="no remarks".getBytes(); + + public DatabaseMetaData(Connection conn) + { + this.connection = conn; + } + + /** + * Can all the procedures returned by getProcedures be called + * by the current user? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean allProceduresAreCallable() throws SQLException + { + return true; // For now... + } + + /** + * Can all the tables returned by getTable be SELECTed by + * the current user? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean allTablesAreSelectable() throws SQLException + { + return true; // For now... + } + + /** + * What is the URL for this database? + * + * @return the url or null if it cannott be generated + * @exception SQLException if a database access error occurs + */ + public String getURL() throws SQLException + { + return connection.getURL(); + } + + /** + * What is our user name as known to the database? + * + * @return our database user name + * @exception SQLException if a database access error occurs + */ + public String getUserName() throws SQLException + { + return connection.getUserName(); + } + + /** + * Is the database in read-only mode? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return connection.isReadOnly(); + } + + /** + * Are NULL values sorted high? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedHigh() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted low? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedLow() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted at the start regardless of sort order? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedAtStart() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted at the end regardless of sort order? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedAtEnd() throws SQLException + { + return true; + } + + /** + * What is the name of this database product - we hope that it is + * PostgreSQL, so we return that explicitly. + * + * @return the database product name + * @exception SQLException if a database access error occurs + */ + public String getDatabaseProductName() throws SQLException + { + return new String("PostgreSQL"); + } + + /** + * What is the version of this database product. + * + * <p>Note that PostgreSQL 6.3 has a system catalog called pg_version - + * however, select * from pg_version on any database retrieves + * no rows. + * + * <p>For now, we will return the version 6.3 (in the hope that we change + * this driver as often as we change the database) + * + * @return the database version + * @exception SQLException if a database access error occurs + */ + public String getDatabaseProductVersion() throws SQLException + { + return ("6.5.2"); + } + + /** + * What is the name of this JDBC driver? If we don't know this + * we are doing something wrong! + * + * @return the JDBC driver name + * @exception SQLException why? + */ + public String getDriverName() throws SQLException + { + return new String("PostgreSQL Native Driver"); + } + + /** + * What is the version string of this JDBC driver? Again, this is + * static. + * + * @return the JDBC driver name. + * @exception SQLException why? + */ + public String getDriverVersion() throws SQLException + { + return new String(Integer.toString(connection.this_driver.getMajorVersion())+"."+Integer.toString(connection.this_driver.getMinorVersion())); + } + + /** + * What is this JDBC driver's major version number? + * + * @return the JDBC driver major version + */ + public int getDriverMajorVersion() + { + return connection.this_driver.getMajorVersion(); + } + + /** + * What is this JDBC driver's minor version number? + * + * @return the JDBC driver minor version + */ + public int getDriverMinorVersion() + { + return connection.this_driver.getMinorVersion(); + } + + /** + * Does the database store tables in a local file? No - it + * stores them in a file on the server. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean usesLocalFiles() throws SQLException + { + return false; + } + + /** + * Does the database use a file for each table? Well, not really, + * since it doesnt use local files. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean usesLocalFilePerTable() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers + * as case sensitive and as a result store them in mixed case? + * A JDBC-Compliant driver will always return false. + * + * <p>Predicament - what do they mean by "SQL identifiers" - if it + * means the names of the tables and columns, then the answers + * given below are correct - otherwise I don't know. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in upper case? + * + * @return true if so + */ + public boolean storesUpperCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in lower case? + * + * @return true if so + */ + public boolean storesLowerCaseIdentifiers() throws SQLException + { + return true; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in mixed case? + * + * @return true if so + */ + public boolean storesMixedCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as + * case sensitive and as a result store them in mixed case? A + * JDBC compliant driver will always return true. + * + * <p>Predicament - what do they mean by "SQL identifiers" - if it + * means the names of the tables and columns, then the answers + * given below are correct - otherwise I don't know. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException + { + return true; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as + * case insensitive and store them in upper case? + * + * @return true if so + */ + public boolean storesUpperCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as case + * insensitive and store them in lower case? + * + * @return true if so + */ + public boolean storesLowerCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as case + * insensitive and store them in mixed case? + * + * @return true if so + */ + public boolean storesMixedCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * What is the string used to quote SQL identifiers? This returns + * a space if identifier quoting isn't supported. A JDBC Compliant + * driver will always use a double quote character. + * + * <p>If an SQL identifier is a table name, column name, etc. then + * we do not support it. + * + * @return the quoting string + * @exception SQLException if a database access error occurs + */ + public String getIdentifierQuoteString() throws SQLException + { + return null; + } + + /** + * Get a comma separated list of all a database's SQL keywords that + * are NOT also SQL92 keywords. + * + * <p>Within PostgreSQL, the keywords are found in + * src/backend/parser/keywords.c + * + * <p>For SQL Keywords, I took the list provided at + * <a href="http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt"> + * http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt</a> + * which is for SQL3, not SQL-92, but it is close enough for + * this purpose. + * + * @return a comma separated list of keywords we use + * @exception SQLException if a database access error occurs + */ + public String getSQLKeywords() throws SQLException + { + return new String("abort,acl,add,aggregate,append,archive,arch_store,backward,binary,change,cluster,copy,database,delimiters,do,extend,explain,forward,heavy,index,inherits,isnull,light,listen,load,merge,nothing,notify,notnull,oids,purge,rename,replace,retrieve,returns,rule,recipe,setof,stdin,stdout,store,vacuum,verbose,version"); + } + + public String getNumericFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + public String getStringFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + public String getSystemFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + public String getTimeDateFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + /** + * This is the string that can be used to escape '_' and '%' in + * a search string pattern style catalog search parameters + * + * @return the string used to escape wildcard characters + * @exception SQLException if a database access error occurs + */ + public String getSearchStringEscape() throws SQLException + { + return new String("\\"); + } + + /** + * Get all the "extra" characters that can bew used in unquoted + * identifier names (those beyond a-zA-Z0-9 and _) + * + * <p>From the file src/backend/parser/scan.l, an identifier is + * {letter}{letter_or_digit} which makes it just those listed + * above. + * + * @return a string containing the extra characters + * @exception SQLException if a database access error occurs + */ + public String getExtraNameCharacters() throws SQLException + { + return new String(""); + } + + /** + * Is "ALTER TABLE" with an add column supported? + * Yes for PostgreSQL 6.1 + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsAlterTableWithAddColumn() throws SQLException + { + return true; + } + + /** + * Is "ALTER TABLE" with a drop column supported? + * Yes for PostgreSQL 6.1 + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsAlterTableWithDropColumn() throws SQLException + { + return true; + } + + /** + * Is column aliasing supported? + * + * <p>If so, the SQL AS clause can be used to provide names for + * computed columns or to provide alias names for columns as + * required. A JDBC Compliant driver always returns true. + * + * <p>e.g. + * + * <br><pre> + * select count(C) as C_COUNT from T group by C; + * + * </pre><br> + * should return a column named as C_COUNT instead of count(C) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsColumnAliasing() throws SQLException + { + return true; + } + + /** + * Are concatenations between NULL and non-NULL values NULL? A + * JDBC Compliant driver always returns true + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullPlusNonNullIsNull() throws SQLException + { + return true; + } + + public boolean supportsConvert() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsConvert(int fromType, int toType) throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsTableCorrelationNames() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsDifferentTableCorrelationNames() throws SQLException + { + // XXX-Not Implemented + return false; + } + + /** + * Are expressions in "ORCER BY" lists supported? + * + * <br>e.g. select * from t order by a + b; + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsExpressionsInOrderBy() throws SQLException + { + return true; + } + + /** + * Can an "ORDER BY" clause use columns not in the SELECT? + * I checked it, and you can't. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOrderByUnrelated() throws SQLException + { + return false; + } + + /** + * Is some form of "GROUP BY" clause supported? + * I checked it, and yes it is. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupBy() throws SQLException + { + return true; + } + + /** + * Can a "GROUP BY" clause use columns not in the SELECT? + * I checked it - it seems to allow it + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByUnrelated() throws SQLException + { + return true; + } + + /** + * Can a "GROUP BY" clause add columns not in the SELECT provided + * it specifies all the columns in the SELECT? Does anyone actually + * understand what they mean here? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByBeyondSelect() throws SQLException + { + return true; // For now... + } + + /** + * Is the escape character in "LIKE" clauses supported? A + * JDBC compliant driver always returns true. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsLikeEscapeClause() throws SQLException + { + return true; + } + + /** + * Are multiple ResultSets from a single execute supported? + * Well, I implemented it, but I dont think this is possible from + * the back ends point of view. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMultipleResultSets() throws SQLException + { + return false; + } + + /** + * Can we have multiple transactions open at once (on different + * connections?) + * I guess we can have, since Im relying on it. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMultipleTransactions() throws SQLException + { + return true; + } + + /** + * Can columns be defined as non-nullable. A JDBC Compliant driver + * always returns true. + * + * <p>This changed from false to true in v6.2 of the driver, as this + * support was added to the backend. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsNonNullableColumns() throws SQLException + { + return true; + } + + /** + * Does this driver support the minimum ODBC SQL grammar. This + * grammar is defined at: + * + * <p><a href="http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm">http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm</a> + * + * <p>In Appendix C. From this description, we seem to support the + * ODBC minimal (Level 0) grammar. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMinimumSQLGrammar() throws SQLException + { + return true; + } + + /** + * Does this driver support the Core ODBC SQL grammar. We need + * SQL-92 conformance for this. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCoreSQLGrammar() throws SQLException + { + return false; + } + + /** + * Does this driver support the Extended (Level 2) ODBC SQL + * grammar. We don't conform to the Core (Level 1), so we can't + * conform to the Extended SQL Grammar. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsExtendedSQLGrammar() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 entry level SQL grammar? + * All JDBC Compliant drivers must return true. I think we have + * to support outer joins for this to be true. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92EntryLevelSQL() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 intermediate level SQL + * grammar? Anyone who does not support Entry level cannot support + * Intermediate level. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92IntermediateSQL() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 full SQL grammar? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92FullSQL() throws SQLException + { + return false; + } + + /** + * Is the SQL Integrity Enhancement Facility supported? + * I haven't seen this mentioned anywhere, so I guess not + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsIntegrityEnhancementFacility() throws SQLException + { + return false; + } + + /** + * Is some form of outer join supported? From my knowledge, nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOuterJoins() throws SQLException + { + return false; + } + + /** + * Are full nexted outer joins supported? Well, we dont support any + * form of outer join, so this is no as well + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsFullOuterJoins() throws SQLException + { + return false; + } + + /** + * Is there limited support for outer joins? (This will be true if + * supportFullOuterJoins is true) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsLimitedOuterJoins() throws SQLException + { + return false; + } + + /** + * What is the database vendor's preferred term for "schema" - well, + * we do not provide support for schemas, so lets just use that + * term. + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getSchemaTerm() throws SQLException + { + return new String("Schema"); + } + + /** + * What is the database vendor's preferred term for "procedure" - + * I kind of like "Procedure" myself. + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getProcedureTerm() throws SQLException + { + return new String("Procedure"); + } + + /** + * What is the database vendor's preferred term for "catalog"? - + * we dont have a preferred term, so just use Catalog + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getCatalogTerm() throws SQLException + { + return new String("Catalog"); + } + + /** + * Does a catalog appear at the start of a qualified table name? + * (Otherwise it appears at the end). + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isCatalogAtStart() throws SQLException + { + return false; + } + + /** + * What is the Catalog separator. Hmmm....well, I kind of like + * a period (so we get catalog.table definitions). - I don't think + * PostgreSQL supports catalogs anyhow, so it makes no difference. + * + * @return the catalog separator string + * @exception SQLException if a database access error occurs + */ + public String getCatalogSeparator() throws SQLException + { + // PM Sep 29 97 - changed from "." as we don't support catalogs. + return new String(""); + } + + /** + * Can a schema name be used in a data manipulation statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInDataManipulation() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in a procedure call statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInProcedureCalls() throws SQLException + { + return false; + } + + /** + * Can a schema be used in a table definition statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInTableDefinitions() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in an index definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInIndexDefinitions() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in a privilege definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a data manipulation statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInDataManipulation() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a procedure call statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInProcedureCalls() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a table definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInTableDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in an index definition? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInIndexDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a privilege definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException + { + return false; + } + + /** + * We support cursors for gets only it seems. I dont see a method + * to get a positioned delete. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsPositionedDelete() throws SQLException + { + return false; // For now... + } + + /** + * Is positioned UPDATE supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsPositionedUpdate() throws SQLException + { + return false; // For now... + } + + public boolean supportsSelectForUpdate() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsStoredProcedures() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInComparisons() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInExists() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInIns() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInQuantifieds() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsCorrelatedSubqueries() throws SQLException + { + // XXX-Not Implemented + return false; + } + + /** + * Is SQL UNION supported? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnion() throws SQLException + { + return false; + } + + /** + * Is SQL UNION ALL supported? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnionAll() throws SQLException + { + return false; + } + + /** + * In PostgreSQL, Cursors are only open within transactions. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenCursorsAcrossCommit() throws SQLException + { + return false; + } + + /** + * Do we support open cursors across multiple transactions? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenCursorsAcrossRollback() throws SQLException + { + return false; + } + + /** + * Can statements remain open across commits? They may, but + * this driver cannot guarentee that. In further reflection. + * we are talking a Statement object jere, so the answer is + * yes, since the Statement is only a vehicle to ExecSQL() + * + * @return true if they always remain open; false otherwise + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenStatementsAcrossCommit() throws SQLException + { + return true; + } + + /** + * Can statements remain open across rollbacks? They may, but + * this driver cannot guarentee that. In further contemplation, + * we are talking a Statement object here, so the answer is yes, + * since the Statement is only a vehicle to ExecSQL() in Connection + * + * @return true if they always remain open; false otherwise + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenStatementsAcrossRollback() throws SQLException + { + return true; + } + + /** + * How many hex characters can you have in an inline binary literal + * + * @return the max literal length + * @exception SQLException if a database access error occurs + */ + public int getMaxBinaryLiteralLength() throws SQLException + { + return 0; // For now... + } + + /** + * What is the maximum length for a character literal + * I suppose it is 8190 (8192 - 2 for the quotes) + * + * @return the max literal length + * @exception SQLException if a database access error occurs + */ + public int getMaxCharLiteralLength() throws SQLException + { + return 8190; + } + + /** + * Whats the limit on column name length. The description of + * pg_class would say '32' (length of pg_class.relname) - we + * should probably do a query for this....but.... + * + * @return the maximum column name length + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum number of columns in a "GROUP BY" clause? + * + * @return the max number of columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInGroupBy() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What's the maximum number of columns allowed in an index? + * 6.0 only allowed one column, but 6.1 introduced multi-column + * indices, so, theoretically, its all of them. + * + * @return max number of columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInIndex() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What's the maximum number of columns in an "ORDER BY clause? + * Theoretically, all of them! + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInOrderBy() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What is the maximum number of columns in a "SELECT" list? + * Theoretically, all of them! + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInSelect() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What is the maximum number of columns in a table? From the + * create_table(l) manual page... + * + * <p>"The new class is created as a heap with no initial data. A + * class can have no more than 1600 attributes (realistically, + * this is limited by the fact that tuple sizes must be less than + * 8192 bytes)..." + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInTable() throws SQLException + { + return 1600; + } + + /** + * How many active connection can we have at a time to this + * database? Well, since it depends on postmaster, which just + * does a listen() followed by an accept() and fork(), its + * basically very high. Unless the system runs out of processes, + * it can be 65535 (the number of aux. ports on a TCP/IP system). + * I will return 8192 since that is what even the largest system + * can realistically handle, + * + * @return the maximum number of connections + * @exception SQLException if a database access error occurs + */ + public int getMaxConnections() throws SQLException + { + return 8192; + } + + /** + * What is the maximum cursor name length (the same as all + * the other F***** identifiers!) + * + * @return max cursor name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxCursorNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum length of an index (in bytes)? Now, does + * the spec. mean name of an index (in which case its 32, the + * same as a table) or does it mean length of an index element + * (in which case its 8192, the size of a row) or does it mean + * the number of rows it can access (in which case it 2^32 - + * a 4 byte OID number)? I think its the length of an index + * element, personally, so Im setting it to 8192. + * + * @return max index length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxIndexLength() throws SQLException + { + return 8192; + } + + public int getMaxSchemaNameLength() throws SQLException + { + // XXX-Not Implemented + return 0; + } + + /** + * What is the maximum length of a procedure name? + * (length of pg_proc.proname used) - again, I really + * should do a query here to get it. + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxProcedureNameLength() throws SQLException + { + return 32; + } + + public int getMaxCatalogNameLength() throws SQLException + { + // XXX-Not Implemented + return 0; + } + + /** + * What is the maximum length of a single row? (not including + * blobs). 8192 is defined in PostgreSQL. + * + * @return max row size in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxRowSize() throws SQLException + { + return 8192; + } + + /** + * Did getMaxRowSize() include LONGVARCHAR and LONGVARBINARY + * blobs? We don't handle blobs yet + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean doesMaxRowSizeIncludeBlobs() throws SQLException + { + return false; + } + + /** + * What is the maximum length of a SQL statement? + * + * @return max length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxStatementLength() throws SQLException + { + return 8192; + } + + /** + * How many active statements can we have open at one time to + * this database? Basically, since each Statement downloads + * the results as the query is executed, we can have many. However, + * we can only really have one statement per connection going + * at once (since they are executed serially) - so we return + * one. + * + * @return the maximum + * @exception SQLException if a database access error occurs + */ + public int getMaxStatements() throws SQLException + { + return 1; + } + + /** + * What is the maximum length of a table name? This was found + * from pg_class.relname length + * + * @return max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxTableNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum number of tables that can be specified + * in a SELECT? Theoretically, this is the same number as the + * number of tables allowable. In practice tho, it is much smaller + * since the number of tables is limited by the statement, we + * return 1024 here - this is just a number I came up with (being + * the number of tables roughly of three characters each that you + * can fit inside a 8192 character buffer with comma separators). + * + * @return the maximum + * @exception SQLException if a database access error occurs + */ + public int getMaxTablesInSelect() throws SQLException + { + return 1024; + } + + /** + * What is the maximum length of a user name? Well, we generally + * use UNIX like user names in PostgreSQL, so I think this would + * be 8. However, showing the schema for pg_user shows a length + * for username of 32. + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxUserNameLength() throws SQLException + { + return 32; + } + + + /** + * What is the database's default transaction isolation level? We + * do not support this, so all transactions are SERIALIZABLE. + * + * @return the default isolation level + * @exception SQLException if a database access error occurs + * @see Connection + */ + public int getDefaultTransactionIsolation() throws SQLException + { + return Connection.TRANSACTION_READ_COMMITTED; + } + + /** + * Are transactions supported? If not, commit and rollback are noops + * and the isolation level is TRANSACTION_NONE. We do support + * transactions. + * + * @return true if transactions are supported + * @exception SQLException if a database access error occurs + */ + public boolean supportsTransactions() throws SQLException + { + return true; + } + + /** + * Does the database support the given transaction isolation level? + * We only support TRANSACTION_SERIALIZABLE and TRANSACTION_READ_COMMITTED + * + * @param level the values are defined in java.sql.Connection + * @return true if so + * @exception SQLException if a database access error occurs + * @see Connection + */ + public boolean supportsTransactionIsolationLevel(int level) throws SQLException + { + if (level == Connection.TRANSACTION_SERIALIZABLE || + level == Connection.TRANSACTION_READ_COMMITTED) + return true; + else + return false; + } + + /** + * Are both data definition and data manipulation transactions + * supported? I checked it, and could not do a CREATE TABLE + * within a transaction, so I am assuming that we don't + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException + { + return false; + } + + /** + * Are only data manipulation statements withing a transaction + * supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsDataManipulationTransactionsOnly() throws SQLException + { + return true; + } + + /** + * Does a data definition statement within a transaction force + * the transaction to commit? I think this means something like: + * + * <p><pre> + * CREATE TABLE T (A INT); + * INSERT INTO T (A) VALUES (2); + * BEGIN; + * UPDATE T SET A = A + 1; + * CREATE TABLE X (A INT); + * SELECT A FROM T INTO X; + * COMMIT; + * </pre><p> + * + * does the CREATE TABLE call cause a commit? The answer is no. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean dataDefinitionCausesTransactionCommit() throws SQLException + { + return false; + } + + /** + * Is a data definition statement within a transaction ignored? + * It seems to be (from experiment in previous method) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean dataDefinitionIgnoredInTransactions() throws SQLException + { + return true; + } + + /** + * Get a description of stored procedures available in a catalog + * + * <p>Only procedure descriptions matching the schema and procedure + * name criteria are returned. They are ordered by PROCEDURE_SCHEM + * and PROCEDURE_NAME + * + * <p>Each procedure description has the following columns: + * <ol> + * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) + * <li><b>PROCEDURE_SCHEM</b> String => procedure schema (may be null) + * <li><b>PROCEDURE_NAME</b> String => procedure name + * <li><b>Field 4</b> reserved (make it null) + * <li><b>Field 5</b> reserved (make it null) + * <li><b>Field 6</b> reserved (make it null) + * <li><b>REMARKS</b> String => explanatory comment on the procedure + * <li><b>PROCEDURE_TYPE</b> short => kind of procedure + * <ul> + * <li> procedureResultUnknown - May return a result + * <li> procedureNoResult - Does not return a result + * <li> procedureReturnsResult - Returns a result + * </ul> + * </ol> + * + * @param catalog - a catalog name; "" retrieves those without a + * catalog; null means drop catalog name from criteria + * @param schemaParrern - a schema name pattern; "" retrieves those + * without a schema - we ignore this parameter + * @param procedureNamePattern - a procedure name pattern + * @return ResultSet - each row is a procedure description + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException + { + // the field descriptors for the new ResultSet + Field f[] = new Field[8]; + java.sql.ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + byte remarks[] = defaultRemarks; + + f[0] = new Field(connection, "PROCEDURE_CAT", iVarcharOid, 32); + f[1] = new Field(connection, "PROCEDURE_SCHEM", iVarcharOid, 32); + f[2] = new Field(connection, "PROCEDURE_NAME", iVarcharOid, 32); + f[3] = f[4] = f[5] = null; // reserved, must be null for now + f[6] = new Field(connection, "REMARKS", iVarcharOid, 8192); + f[7] = new Field(connection, "PROCEDURE_TYPE", iInt2Oid, 2); + + // If the pattern is null, then set it to the default + if(procedureNamePattern==null) + procedureNamePattern="%"; + + r = connection.ExecSQL("select proname, proretset from pg_proc where proname like '"+procedureNamePattern.toLowerCase()+"' order by proname"); + + while (r.next()) + { + byte[][] tuple = new byte[8][0]; + + tuple[0] = null; // Catalog name + tuple[1] = null; // Schema name + tuple[2] = r.getBytes(1); // Procedure name + tuple[3] = tuple[4] = tuple[5] = null; // Reserved + tuple[6] = remarks; // Remarks + + if (r.getBoolean(2)) + tuple[7] = Integer.toString(java.sql.DatabaseMetaData.procedureReturnsResult).getBytes(); + else + tuple[7] = Integer.toString(java.sql.DatabaseMetaData.procedureNoResult).getBytes(); + + v.addElement(tuple); + } + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of a catalog's stored procedure parameters + * and result columns. + * + * <p>Only descriptions matching the schema, procedure and parameter + * name criteria are returned. They are ordered by PROCEDURE_SCHEM + * and PROCEDURE_NAME. Within this, the return value, if any, is + * first. Next are the parameter descriptions in call order. The + * column descriptions follow in column number order. + * + * <p>Each row in the ResultSet is a parameter description or column + * description with the following fields: + * <ol> + * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) + * <li><b>PROCEDURE_SCHE</b>M String => procedure schema (may be null) + * <li><b>PROCEDURE_NAME</b> String => procedure name + * <li><b>COLUMN_NAME</b> String => column/parameter name + * <li><b>COLUMN_TYPE</b> Short => kind of column/parameter: + * <ul><li>procedureColumnUnknown - nobody knows + * <li>procedureColumnIn - IN parameter + * <li>procedureColumnInOut - INOUT parameter + * <li>procedureColumnOut - OUT parameter + * <li>procedureColumnReturn - procedure return value + * <li>procedureColumnResult - result column in ResultSet + * </ul> + * <li><b>DATA_TYPE</b> short => SQL type from java.sql.Types + * <li><b>TYPE_NAME</b> String => SQL type name + * <li><b>PRECISION</b> int => precision + * <li><b>LENGTH</b> int => length in bytes of data + * <li><b>SCALE</b> short => scale + * <li><b>RADIX</b> short => radix + * <li><b>NULLABLE</b> short => can it contain NULL? + * <ul><li>procedureNoNulls - does not allow NULL values + * <li>procedureNullable - allows NULL values + * <li>procedureNullableUnknown - nullability unknown + * <li><b>REMARKS</b> String => comment describing parameter/column + * </ol> + * @param catalog This is ignored in org.postgresql, advise this is set to null + * @param schemaPattern This is ignored in org.postgresql, advise this is set to null + * @param procedureNamePattern a procedure name pattern + * @param columnNamePattern a column name pattern + * @return each row is a stored procedure parameter or column description + * @exception SQLException if a database-access error occurs + * @see #getSearchStringEscape + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException + { + if(procedureNamePattern==null) + procedureNamePattern="%"; + + if(columnNamePattern==null) + columnNamePattern="%"; + + // for now, this returns an empty result set. + Field f[] = new Field[13]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("PROCEDURE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("PROCEDURE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("PROCEDURE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[4] = new Field(connection, new String("COLUMN_TYPE"), iInt2Oid, 2); + f[5] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[6] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[7] = new Field(connection, new String("PRECISION"), iInt4Oid, 4); + f[8] = new Field(connection, new String("LENGTH"), iInt4Oid, 4); + f[9] = new Field(connection, new String("SCALE"), iInt2Oid, 2); + f[10] = new Field(connection, new String("RADIX"), iInt2Oid, 2); + f[11] = new Field(connection, new String("NULLABLE"), iInt2Oid, 2); + f[12] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); + + // add query loop here + + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of tables available in a catalog. + * + * <p>Only table descriptions matching the catalog, schema, table + * name and type criteria are returned. They are ordered by + * TABLE_TYPE, TABLE_SCHEM and TABLE_NAME. + * + * <p>Each table description has the following columns: + * + * <ol> + * <li><b>TABLE_CAT</b> String => table catalog (may be null) + * <li><b>TABLE_SCHEM</b> String => table schema (may be null) + * <li><b>TABLE_NAME</b> String => table name + * <li><b>TABLE_TYPE</b> String => table type. Typical types are "TABLE", + * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL + * TEMPORARY", "ALIAS", "SYNONYM". + * <li><b>REMARKS</b> String => explanatory comment on the table + * </ol> + * + * <p>The valid values for the types parameter are: + * "TABLE", "INDEX", "LARGE OBJECT", "SEQUENCE", "SYSTEM TABLE" and + * "SYSTEM INDEX" + * + * @param catalog a catalog name; For org.postgresql, this is ignored, and + * should be set to null + * @param schemaPattern a schema name pattern; For org.postgresql, this is ignored, and + * should be set to null + * @param tableNamePattern a table name pattern. For all tables this should be "%" + * @param types a list of table types to include; null returns + * all types + * @return each row is a table description + * @exception SQLException if a database-access error occurs. + */ + public java.sql.ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException + { + // Handle default value for types + if(types==null) + types = defaultTableTypes; + + if(tableNamePattern==null) + tableNamePattern="%"; + + // the field descriptors for the new ResultSet + Field f[] = new Field[5]; + java.sql.ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TABLE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("TABLE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("TABLE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("TABLE_TYPE"), iVarcharOid, 32); + f[4] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); + + // Now form the query + StringBuffer sql = new StringBuffer("select relname,oid from pg_class where ("); + boolean notFirst=false; + for(int i=0;i<types.length;i++) { + if(notFirst) + sql.append(" or "); + for(int j=0;j<getTableTypes.length;j++) + if(getTableTypes[j][0].equals(types[i])) { + sql.append(getTableTypes[j][1]); + notFirst=true; + } + } + + // Added by Stefan Andreasen <stefan@linux.kapow.dk> + // Now take the pattern into account + sql.append(") and relname like '"); + sql.append(tableNamePattern.toLowerCase()); + sql.append("'"); + + // Now run the query + r = connection.ExecSQL(sql.toString()); + + byte remarks[]; + + while (r.next()) + { + byte[][] tuple = new byte[5][0]; + + // Fetch the description for the table (if any) + java.sql.ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(2)); + if(((org.postgresql.ResultSet)dr).getTupleCount()==1) { + dr.next(); + remarks = dr.getBytes(1); + } else + remarks = defaultRemarks; + dr.close(); + + tuple[0] = null; // Catalog name + tuple[1] = null; // Schema name + tuple[2] = r.getBytes(1); // Table name + tuple[3] = null; // Table type + tuple[4] = remarks; // Remarks + v.addElement(tuple); + } + r.close(); + return new ResultSet(connection, f, v, "OK", 1); + } + + // This array contains the valid values for the types argument + // in getTables(). + // + // Each supported type consists of it's name, and the sql where + // clause to retrieve that value. + // + // IMPORTANT: the query must be enclosed in ( ) + private static final String getTableTypes[][] = { + {"TABLE", "(relkind='r' and relname !~ '^pg_' and relname !~ '^xinv')"}, + {"INDEX", "(relkind='i' and relname !~ '^pg_' and relname !~ '^xinx')"}, + {"LARGE OBJECT", "(relkind='r' and relname ~ '^xinv')"}, + {"SEQUENCE", "(relkind='S' and relname !~ '^pg_')"}, + {"SYSTEM TABLE", "(relkind='r' and relname ~ '^pg_')"}, + {"SYSTEM INDEX", "(relkind='i' and relname ~ '^pg_')"} + }; + + // These are the default tables, used when NULL is passed to getTables + // The choice of these provide the same behaviour as psql's \d + private static final String defaultTableTypes[] = { + "TABLE","INDEX","SEQUENCE" + }; + + /** + * Get the schema names available in this database. The results + * are ordered by schema name. + * + * <P>The schema column is: + * <OL> + * <LI><B>TABLE_SCHEM</B> String => schema name + * </OL> + * + * @return ResultSet each row has a single String column that is a + * schema name + */ + public java.sql.ResultSet getSchemas() throws SQLException + { + // We don't use schemas, so we simply return a single schema name "". + // + Field f[] = new Field[1]; + Vector v = new Vector(); + byte[][] tuple = new byte[1][0]; + f[0] = new Field(connection,new String("TABLE_SCHEM"),iVarcharOid,32); + tuple[0] = "".getBytes(); + v.addElement(tuple); + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get the catalog names available in this database. The results + * are ordered by catalog name. + * + * <P>The catalog column is: + * <OL> + * <LI><B>TABLE_CAT</B> String => catalog name + * </OL> + * + * @return ResultSet each row has a single String column that is a + * catalog name + */ + public java.sql.ResultSet getCatalogs() throws SQLException + { + // We don't use catalogs, so we simply return a single catalog name "". + Field f[] = new Field[1]; + Vector v = new Vector(); + byte[][] tuple = new byte[1][0]; + f[0] = new Field(connection,new String("TABLE_CAT"),iVarcharOid,32); + tuple[0] = "".getBytes(); + v.addElement(tuple); + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get the table types available in this database. The results + * are ordered by table type. + * + * <P>The table type is: + * <OL> + * <LI><B>TABLE_TYPE</B> String => table type. Typical types are "TABLE", + * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", + * "LOCAL TEMPORARY", "ALIAS", "SYNONYM". + * </OL> + * + * @return ResultSet each row has a single String column that is a + * table type + */ + public java.sql.ResultSet getTableTypes() throws SQLException + { + Field f[] = new Field[1]; + Vector v = new Vector(); + byte[][] tuple = new byte[1][0]; + f[0] = new Field(connection,new String("TABLE_TYPE"),iVarcharOid,32); + for(int i=0;i<getTableTypes.length;i++) { + tuple[0] = getTableTypes[i][0].getBytes(); + v.addElement(tuple); + } + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get a description of table columns available in a catalog. + * + * <P>Only column descriptions matching the catalog, schema, table + * and column name criteria are returned. They are ordered by + * TABLE_SCHEM, TABLE_NAME and ORDINAL_POSITION. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => column size. For char or date + * types this is the maximum number of characters, for numeric or + * decimal types this is precision. + * <LI><B>BUFFER_LENGTH</B> is not used. + * <LI><B>DECIMAL_DIGITS</B> int => the number of fractional digits + * <LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2) + * <LI><B>NULLABLE</B> int => is NULL allowed? + * <UL> + * <LI> columnNoNulls - might not allow NULL values + * <LI> columnNullable - definitely allows NULL values + * <LI> columnNullableUnknown - nullability unknown + * </UL> + * <LI><B>REMARKS</B> String => comment describing column (may be null) + * <LI><B>COLUMN_DEF</B> String => default value (may be null) + * <LI><B>SQL_DATA_TYPE</B> int => unused + * <LI><B>SQL_DATETIME_SUB</B> int => unused + * <LI><B>CHAR_OCTET_LENGTH</B> int => for char types the + * maximum number of bytes in the column + * <LI><B>ORDINAL_POSITION</B> int => index of column in table + * (starting at 1) + * <LI><B>IS_NULLABLE</B> String => "NO" means column definitely + * does not allow NULL values; "YES" means the column might + * allow NULL values. An empty string means nobody knows. + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schemaPattern a schema name pattern; "" retrieves those + * without a schema + * @param tableNamePattern a table name pattern + * @param columnNamePattern a column name pattern + * @return ResultSet each row is a column description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException + { + // the field descriptors for the new ResultSet + Field f[] = new Field[18]; + java.sql.ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TABLE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("TABLE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("TABLE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[4] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[5] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[6] = new Field(connection, new String("COLUMN_SIZE"), iInt4Oid, 4); + f[7] = new Field(connection, new String("BUFFER_LENGTH"), iVarcharOid, 32); + f[8] = new Field(connection, new String("DECIMAL_DIGITS"), iInt4Oid, 4); + f[9] = new Field(connection, new String("NUM_PREC_RADIX"), iInt4Oid, 4); + f[10] = new Field(connection, new String("NULLABLE"), iInt4Oid, 4); + f[11] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); + f[12] = new Field(connection, new String("COLUMN_DEF"), iVarcharOid, 32); + f[13] = new Field(connection, new String("SQL_DATA_TYPE"), iInt4Oid, 4); + f[14] = new Field(connection, new String("SQL_DATETIME_SUB"), iInt4Oid, 4); + f[15] = new Field(connection, new String("CHAR_OCTET_LENGTH"), iVarcharOid, 32); + f[16] = new Field(connection, new String("ORDINAL_POSITION"), iInt4Oid,4); + f[17] = new Field(connection, new String("IS_NULLABLE"), iVarcharOid, 32); + + // Added by Stefan Andreasen <stefan@linux.kapow.dk> + // If the pattern are null then set them to % + if (tableNamePattern == null) tableNamePattern="%"; + if (columnNamePattern == null) columnNamePattern="%"; + + // Now form the query + // Modified by Stefan Andreasen <stefan@linux.kapow.dk> + r = connection.ExecSQL("select a.oid,c.relname,a.attname,a.atttypid,a.attnum,a.attnotnull,a.attlen,a.atttypmod from pg_class c, pg_attribute a where a.attrelid=c.oid and c.relname like '"+tableNamePattern.toLowerCase()+"' and a.attname like '"+columnNamePattern.toLowerCase()+"' and a.attnum>0 order by c.relname,a.attnum"); + + byte remarks[]; + + while(r.next()) { + byte[][] tuple = new byte[18][0]; + + // Fetch the description for the table (if any) + java.sql.ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(1)); + if(((org.postgresql.ResultSet)dr).getTupleCount()==1) { + dr.next(); + tuple[11] = dr.getBytes(1); + } else + tuple[11] = defaultRemarks; + + dr.close(); + + tuple[0] = "".getBytes(); // Catalog name + tuple[1] = "".getBytes(); // Schema name + tuple[2] = r.getBytes(2); // Table name + tuple[3] = r.getBytes(3); // Column name + + dr = connection.ExecSQL("select typname from pg_type where oid = "+r.getString(4)); + dr.next(); + String typname=dr.getString(1); + dr.close(); + tuple[4] = Integer.toString(Field.getSQLType(typname)).getBytes(); // Data type + tuple[5] = typname.getBytes(); // Type name + + // Column size + // Looking at the psql source, + // I think the length of a varchar as specified when the table was created + // should be extracted from atttypmod which contains this length + sizeof(int32) + if (typname.equals("bpchar") || typname.equals("varchar")) { + int atttypmod = r.getInt(8); + tuple[6] = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0).getBytes(); + } else + tuple[6] = r.getBytes(7); + + tuple[7] = null; // Buffer length + + tuple[8] = "0".getBytes(); // Decimal Digits - how to get this? + tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal + + // tuple[10] is below + // tuple[11] is above + + tuple[12] = null; // column default + + tuple[13] = null; // sql data type (unused) + tuple[14] = null; // sql datetime sub (unused) + + tuple[15] = tuple[6]; // char octet length + + tuple[16] = r.getBytes(5); // ordinal position + + String nullFlag = r.getString(6); + tuple[10] = Integer.toString(nullFlag.equals("f")?java.sql.DatabaseMetaData.columnNullable:java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable + tuple[17] = (nullFlag.equals("f")?"YES":"NO").getBytes(); // is nullable + + v.addElement(tuple); + } + r.close(); + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of the access rights for a table's columns. + * + * <P>Only privileges matching the column name criteria are + * returned. They are ordered by COLUMN_NAME and PRIVILEGE. + * + * <P>Each privilige description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>GRANTOR</B> => grantor of access (may be null) + * <LI><B>GRANTEE</B> String => grantee of access + * <LI><B>PRIVILEGE</B> String => name of access (SELECT, + * INSERT, UPDATE, REFRENCES, ...) + * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted + * to grant to others; "NO" if not; null if unknown + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @param columnNamePattern a column name pattern + * @return ResultSet each row is a column privilege description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException + { + Field f[] = new Field[8]; + Vector v = new Vector(); + + if(table==null) + table="%"; + + if(columnNamePattern==null) + columnNamePattern="%"; + else + columnNamePattern=columnNamePattern.toLowerCase(); + + f[0] = new Field(connection,new String("TABLE_CAT"),iVarcharOid,32); + f[1] = new Field(connection,new String("TABLE_SCHEM"),iVarcharOid,32); + f[2] = new Field(connection,new String("TABLE_NAME"),iVarcharOid,32); + f[3] = new Field(connection,new String("COLUMN_NAME"),iVarcharOid,32); + f[4] = new Field(connection,new String("GRANTOR"),iVarcharOid,32); + f[5] = new Field(connection,new String("GRANTEE"),iVarcharOid,32); + f[6] = new Field(connection,new String("PRIVILEGE"),iVarcharOid,32); + f[7] = new Field(connection,new String("IS_GRANTABLE"),iVarcharOid,32); + + // This is taken direct from the psql source + java.sql.ResultSet r = connection.ExecSQL("SELECT relname, relacl FROM pg_class, pg_user WHERE ( relkind = 'r' OR relkind = 'i') and relname !~ '^pg_' and relname !~ '^xin[vx][0-9]+' and usesysid = relowner and relname like '"+table.toLowerCase()+"' ORDER BY relname"); + while(r.next()) { + byte[][] tuple = new byte[8][0]; + tuple[0] = tuple[1]= "".getBytes(); + DriverManager.println("relname=\""+r.getString(1)+"\" relacl=\""+r.getString(2)+"\""); + + // For now, don't add to the result as relacl needs to be processed. + //v.addElement(tuple); + } + + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get a description of the access rights for each table available + * in a catalog. + * + * <P>Only privileges matching the schema and table name + * criteria are returned. They are ordered by TABLE_SCHEM, + * TABLE_NAME, and PRIVILEGE. + * + * <P>Each privilige description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>GRANTOR</B> => grantor of access (may be null) + * <LI><B>GRANTEE</B> String => grantee of access + * <LI><B>PRIVILEGE</B> String => name of access (SELECT, + * INSERT, UPDATE, REFRENCES, ...) + * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted + * to grant to others; "NO" if not; null if unknown + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schemaPattern a schema name pattern; "" retrieves those + * without a schema + * @param tableNamePattern a table name pattern + * @return ResultSet each row is a table privilege description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of a table's optimal set of columns that + * uniquely identifies a row. They are ordered by SCOPE. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>SCOPE</B> short => actual scope of result + * <UL> + * <LI> bestRowTemporary - very temporary, while using row + * <LI> bestRowTransaction - valid for remainder of current transaction + * <LI> bestRowSession - valid for remainder of current session + * </UL> + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => precision + * <LI><B>BUFFER_LENGTH</B> int => not used + * <LI><B>DECIMAL_DIGITS</B> short => scale + * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column + * like an Oracle ROWID + * <UL> + * <LI> bestRowUnknown - may or may not be pseudo column + * <LI> bestRowNotPseudo - is NOT a pseudo column + * <LI> bestRowPseudo - is a pseudo column + * </UL> + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @param scope the scope of interest; use same values as SCOPE + * @param nullable include columns that are nullable? + * @return ResultSet each row is a column description + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException + { + // for now, this returns an empty result set. + Field f[] = new Field[8]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("SCOPE"), iInt2Oid, 2); + f[1] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[2] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[3] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[4] = new Field(connection, new String("COLUMN_SIZE"), iInt4Oid, 4); + f[5] = new Field(connection, new String("BUFFER_LENGTH"), iInt4Oid, 4); + f[6] = new Field(connection, new String("DECIMAL_DIGITS"), iInt2Oid, 2); + f[7] = new Field(connection, new String("PSEUDO_COLUMN"), iInt2Oid, 2); + + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of a table's columns that are automatically + * updated when any value in a row is updated. They are + * unordered. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>SCOPE</B> short => is not used + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => precision + * <LI><B>BUFFER_LENGTH</B> int => length of column value in bytes + * <LI><B>DECIMAL_DIGITS</B> short => scale + * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column + * like an Oracle ROWID + * <UL> + * <LI> versionColumnUnknown - may or may not be pseudo column + * <LI> versionColumnNotPseudo - is NOT a pseudo column + * <LI> versionColumnPseudo - is a pseudo column + * </UL> + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @return ResultSet each row is a column description + */ + public java.sql.ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of a table's primary key columns. They + * are ordered by COLUMN_NAME. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>KEY_SEQ</B> short => sequence number within primary key + * <LI><B>PK_NAME</B> String => primary key name (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a primary key column description + */ + public java.sql.ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException + { + return connection.createStatement().executeQuery("SELECT " + + "'' as TABLE_CAT," + + "'' AS TABLE_SCHEM," + + "bc.relname AS TABLE_NAME," + + "a.attname AS COLUMN_NAME," + + "a.attnum as KEY_SEQ,"+ + "ic.relname as PK_NAME " + + " FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a" + + " WHERE bc.relkind = 'r' " + // -- not indices + " and upper(bc.relname) = upper('"+table+"')" + + " and i.indrelid = bc.oid" + + " and i.indexrelid = ic.oid" + + " and ic.oid = a.attrelid" + + " and i.indisprimary='t' " + + " ORDER BY table_name, pk_name, key_seq" + ); + } + + /** + * Get a description of the primary key columns that are + * referenced by a table's foreign key columns (the primary keys + * imported by a table). They are ordered by PKTABLE_CAT, + * PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ. + * + * <P>Each primary key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog + * being imported (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema + * being imported (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * being imported + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * being imported + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key name (may be null) + * <LI><B>PK_NAME</B> String => primary key name (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a primary key column description + * @see #getExportedKeys + */ + public java.sql.ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of a foreign key columns that reference a + * table's primary key columns (the foreign keys exported by a + * table). They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, + * FKTABLE_NAME, and KEY_SEQ. + * + * <P>Each foreign key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * being exported + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * being exported + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) + * <LI><B>PK_NAME</B> String => primary key identifier (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a foreign key column description + * @see #getImportedKeys + */ + public java.sql.ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of the foreign key columns in the foreign key + * table that reference the primary key columns of the primary key + * table (describe how one table imports another's key.) This + * should normally return a single foreign key/primary key pair + * (most tables only import a foreign key from a table once.) They + * are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and + * KEY_SEQ. + * + * <P>Each foreign key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * being exported + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * being exported + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) + * <LI><B>PK_NAME</B> String => primary key identifier (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a foreign key column description + * @see #getImportedKeys + */ + public java.sql.ResultSet getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of all the standard SQL types supported by + * this database. They are ordered by DATA_TYPE and then by how + * closely the data type maps to the corresponding JDBC SQL type. + * + * <P>Each type description has the following columns: + * <OL> + * <LI><B>TYPE_NAME</B> String => Type name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>PRECISION</B> int => maximum precision + * <LI><B>LITERAL_PREFIX</B> String => prefix used to quote a literal + * (may be null) + * <LI><B>LITERAL_SUFFIX</B> String => suffix used to quote a literal + (may be null) + * <LI><B>CREATE_PARAMS</B> String => parameters used in creating + * the type (may be null) + * <LI><B>NULLABLE</B> short => can you use NULL for this type? + * <UL> + * <LI> typeNoNulls - does not allow NULL values + * <LI> typeNullable - allows NULL values + * <LI> typeNullableUnknown - nullability unknown + * </UL> + * <LI><B>CASE_SENSITIVE</B> boolean=> is it case sensitive? + * <LI><B>SEARCHABLE</B> short => can you use "WHERE" based on this type: + * <UL> + * <LI> typePredNone - No support + * <LI> typePredChar - Only supported with WHERE .. LIKE + * <LI> typePredBasic - Supported except for WHERE .. LIKE + * <LI> typeSearchable - Supported for all WHERE .. + * </UL> + * <LI><B>UNSIGNED_ATTRIBUTE</B> boolean => is it unsigned? + * <LI><B>FIXED_PREC_SCALE</B> boolean => can it be a money value? + * <LI><B>AUTO_INCREMENT</B> boolean => can it be used for an + * auto-increment value? + * <LI><B>LOCAL_TYPE_NAME</B> String => localized version of type name + * (may be null) + * <LI><B>MINIMUM_SCALE</B> short => minimum scale supported + * <LI><B>MAXIMUM_SCALE</B> short => maximum scale supported + * <LI><B>SQL_DATA_TYPE</B> int => unused + * <LI><B>SQL_DATETIME_SUB</B> int => unused + * <LI><B>NUM_PREC_RADIX</B> int => usually 2 or 10 + * </OL> + * + * @return ResultSet each row is a SQL type description + */ + public java.sql.ResultSet getTypeInfo() throws SQLException + { + java.sql.ResultSet rs = connection.ExecSQL("select typname from pg_type"); + if(rs!=null) { + Field f[] = new Field[18]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[1] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[2] = new Field(connection, new String("PRECISION"), iInt4Oid, 4); + f[3] = new Field(connection, new String("LITERAL_PREFIX"), iVarcharOid, 32); + f[4] = new Field(connection, new String("LITERAL_SUFFIX"), iVarcharOid, 32); + f[5] = new Field(connection, new String("CREATE_PARAMS"), iVarcharOid, 32); + f[6] = new Field(connection, new String("NULLABLE"), iInt2Oid, 2); + f[7] = new Field(connection, new String("CASE_SENSITIVE"), iBoolOid, 1); + f[8] = new Field(connection, new String("SEARCHABLE"), iInt2Oid, 2); + f[9] = new Field(connection, new String("UNSIGNED_ATTRIBUTE"), iBoolOid, 1); + f[10] = new Field(connection, new String("FIXED_PREC_SCALE"), iBoolOid, 1); + f[11] = new Field(connection, new String("AUTO_INCREMENT"), iBoolOid, 1); + f[12] = new Field(connection, new String("LOCAL_TYPE_NAME"), iVarcharOid, 32); + f[13] = new Field(connection, new String("MINIMUM_SCALE"), iInt2Oid, 2); + f[14] = new Field(connection, new String("MAXIMUM_SCALE"), iInt2Oid, 2); + f[15] = new Field(connection, new String("SQL_DATA_TYPE"), iInt4Oid, 4); + f[16] = new Field(connection, new String("SQL_DATETIME_SUB"), iInt4Oid, 4); + f[17] = new Field(connection, new String("NUM_PREC_RADIX"), iInt4Oid, 4); + + // cache some results, this will keep memory useage down, and speed + // things up a little. + byte b9[] = "9".getBytes(); + byte b10[] = "10".getBytes(); + byte bf[] = "f".getBytes(); + byte bnn[] = Integer.toString(typeNoNulls).getBytes(); + byte bts[] = Integer.toString(typeSearchable).getBytes(); + + while(rs.next()) { + byte[][] tuple = new byte[18][]; + String typname=rs.getString(1); + tuple[0] = typname.getBytes(); + tuple[1] = Integer.toString(Field.getSQLType(typname)).getBytes(); + tuple[2] = b9; // for now + tuple[6] = bnn; // for now + tuple[7] = bf; // false for now - not case sensitive + tuple[8] = bts; + tuple[9] = bf; // false for now - it's signed + tuple[10] = bf; // false for now - must handle money + tuple[11] = bf; // false for now - handle autoincrement + // 12 - LOCAL_TYPE_NAME is null + // 13 & 14 ? + // 15 & 16 are unused so we return null + tuple[17] = b10; // everything is base 10 + v.addElement(tuple); + } + rs.close(); + return new ResultSet(connection, f, v, "OK", 1); + } + + return null; + } + + /** + * Get a description of a table's indices and statistics. They are + * ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION. + * + * <P>Each index column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>NON_UNIQUE</B> boolean => Can index values be non-unique? + * false when TYPE is tableIndexStatistic + * <LI><B>INDEX_QUALIFIER</B> String => index catalog (may be null); + * null when TYPE is tableIndexStatistic + * <LI><B>INDEX_NAME</B> String => index name; null when TYPE is + * tableIndexStatistic + * <LI><B>TYPE</B> short => index type: + * <UL> + * <LI> tableIndexStatistic - this identifies table statistics that are + * returned in conjuction with a table's index descriptions + * <LI> tableIndexClustered - this is a clustered index + * <LI> tableIndexHashed - this is a hashed index + * <LI> tableIndexOther - this is some other style of index + * </UL> + * <LI><B>ORDINAL_POSITION</B> short => column sequence number + * within index; zero when TYPE is tableIndexStatistic + * <LI><B>COLUMN_NAME</B> String => column name; null when TYPE is + * tableIndexStatistic + * <LI><B>ASC_OR_DESC</B> String => column sort sequence, "A" => ascending + * "D" => descending, may be null if sort sequence is not supported; + * null when TYPE is tableIndexStatistic + * <LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then + * this is the number of rows in the table; otherwise it is the + * number of unique values in the index. + * <LI><B>PAGES</B> int => When TYPE is tableIndexStatisic then + * this is the number of pages used for the table, otherwise it + * is the number of pages used for the current index. + * <LI><B>FILTER_CONDITION</B> String => Filter condition, if any. + * (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those without a schema + * @param table a table name + * @param unique when true, return only indices for unique values; + * when false, return indices regardless of whether unique or not + * @param approximate when true, result is allowed to reflect approximate + * or out of data values; when false, results are requested to be + * accurate + * @return ResultSet each row is an index column description + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException + { + // for now, this returns an empty result set. + Field f[] = new Field[13]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TABLE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("TABLE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("TABLE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("NON_UNIQUE"), iBoolOid, 1); + f[4] = new Field(connection, new String("INDEX_QUALIFIER"), iVarcharOid, 32); + f[5] = new Field(connection, new String("INDEX_NAME"), iVarcharOid, 32); + f[6] = new Field(connection, new String("TYPE"), iInt2Oid, 2); + f[7] = new Field(connection, new String("ORDINAL_POSITION"), iInt2Oid, 2); + f[8] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[9] = new Field(connection, new String("ASC_OR_DESC"), iVarcharOid, 32); + f[10] = new Field(connection, new String("CARDINALITY"), iInt4Oid, 4); + f[11] = new Field(connection, new String("PAGES"), iInt4Oid, 4); + f[12] = new Field(connection, new String("FILTER_CONDITION"), iVarcharOid, 32); + + return new ResultSet(connection, f, v, "OK", 1); + } +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java b/src/interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..237a58486d8d77e26c7bddc8ba57598dc24fcb63 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/PreparedStatement.java @@ -0,0 +1,600 @@ +package org.postgresql.jdbc1; + +// IMPORTANT NOTE: This file implements the JDBC 1 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 2 class in the +// org.postgresql.jdbc2 package. + +import java.io.*; +import java.math.*; +import java.sql.*; +import java.text.*; +import java.util.*; +import org.postgresql.largeobject.*; +import org.postgresql.util.*; + +/** + * A SQL Statement is pre-compiled and stored in a PreparedStatement object. + * This object can then be used to efficiently execute this statement multiple + * times. + * + * <p><B>Note:</B> The setXXX methods for setting IN parameter values must + * specify types that are compatible with the defined SQL type of the input + * parameter. For instance, if the IN parameter has SQL type Integer, then + * setInt should be used. + * + * <p>If arbitrary parameter type conversions are required, then the setObject + * method should be used with a target SQL type. + * + * @see ResultSet + * @see java.sql.PreparedStatement + */ +public class PreparedStatement extends Statement implements java.sql.PreparedStatement +{ + String sql; + String[] templateStrings; + String[] inStrings; + Connection connection; + + /** + * Constructor for the PreparedStatement class. + * Split the SQL statement into segments - separated by the arguments. + * When we rebuild the thing with the arguments, we can substitute the + * args and join the whole thing together. + * + * @param conn the instanatiating connection + * @param sql the SQL statement with ? for IN markers + * @exception SQLException if something bad occurs + */ + public PreparedStatement(Connection connection, String sql) throws SQLException + { + super(connection); + + Vector v = new Vector(); + boolean inQuotes = false; + int lastParmEnd = 0, i; + + this.sql = sql; + this.connection = connection; + for (i = 0; i < sql.length(); ++i) + { + int c = sql.charAt(i); + + if (c == '\'') + inQuotes = !inQuotes; + if (c == '?' && !inQuotes) + { + v.addElement(sql.substring (lastParmEnd, i)); + lastParmEnd = i + 1; + } + } + v.addElement(sql.substring (lastParmEnd, sql.length())); + + templateStrings = new String[v.size()]; + inStrings = new String[v.size() - 1]; + clearParameters(); + + for (i = 0 ; i < templateStrings.length; ++i) + templateStrings[i] = (String)v.elementAt(i); + } + + /** + * A Prepared SQL query is executed and its ResultSet is returned + * + * @return a ResultSet that contains the data produced by the + * query - never null + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet executeQuery() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new PSQLException("postgresql.prep.param",new Integer(i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.executeQuery(s.toString()); // in Statement class + } + + /** + * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, + * SQL statements that return nothing such as SQL DDL statements can + * be executed. + * + * @return either the row count for INSERT, UPDATE or DELETE; or + * 0 for SQL statements that return nothing. + * @exception SQLException if a database access error occurs + */ + public int executeUpdate() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new PSQLException("postgresql.prep.param",new Integer(i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.executeUpdate(s.toString()); // in Statement class + } + + /** + * Set a parameter to SQL NULL + * + * <p><B>Note:</B> You must specify the parameters SQL type (although + * PostgreSQL ignores it) + * + * @param parameterIndex the first parameter is 1, etc... + * @param sqlType the SQL type code defined in java.sql.Types + * @exception SQLException if a database access error occurs + */ + public void setNull(int parameterIndex, int sqlType) throws SQLException + { + set(parameterIndex, "null"); + } + + /** + * Set a parameter to a Java boolean value. The driver converts this + * to a SQL BIT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBoolean(int parameterIndex, boolean x) throws SQLException + { + set(parameterIndex, x ? "'t'" : "'f'"); + } + + /** + * Set a parameter to a Java byte value. The driver converts this to + * a SQL TINYINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setByte(int parameterIndex, byte x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java short value. The driver converts this + * to a SQL SMALLINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setShort(int parameterIndex, short x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java int value. The driver converts this to + * a SQL INTEGER value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setInt(int parameterIndex, int x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java long value. The driver converts this to + * a SQL BIGINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setLong(int parameterIndex, long x) throws SQLException + { + set(parameterIndex, (new Long(x)).toString()); + } + + /** + * Set a parameter to a Java float value. The driver converts this + * to a SQL FLOAT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setFloat(int parameterIndex, float x) throws SQLException + { + set(parameterIndex, (new Float(x)).toString()); + } + + /** + * Set a parameter to a Java double value. The driver converts this + * to a SQL DOUBLE value when it sends it to the database + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setDouble(int parameterIndex, double x) throws SQLException + { + set(parameterIndex, (new Double(x)).toString()); + } + + /** + * Set a parameter to a java.lang.BigDecimal value. The driver + * converts this to a SQL NUMERIC value when it sends it to the + * database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException + { + set(parameterIndex, x.toString()); + } + + /** + * Set a parameter to a Java String value. The driver converts this + * to a SQL VARCHAR or LONGVARCHAR value (depending on the arguments + * size relative to the driver's limits on VARCHARs) when it sends it + * to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setString(int parameterIndex, String x) throws SQLException + { + // if the passed string is null, then set this column to null + if(x==null) + set(parameterIndex,"null"); + else { + StringBuffer b = new StringBuffer(); + int i; + + b.append('\''); + for (i = 0 ; i < x.length() ; ++i) + { + char c = x.charAt(i); + if (c == '\\' || c == '\'') + b.append((char)'\\'); + b.append(c); + } + b.append('\''); + set(parameterIndex, b.toString()); + } + } + + /** + * Set a parameter to a Java array of bytes. The driver converts this + * to a SQL VARBINARY or LONGVARBINARY (depending on the argument's + * size relative to the driver's limits on VARBINARYs) when it sends + * it to the database. + * + * <p>Implementation note: + * <br>With org.postgresql, this creates a large object, and stores the + * objects oid in this column. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBytes(int parameterIndex, byte x[]) throws SQLException + { + LargeObjectManager lom = connection.getLargeObjectAPI(); + int oid = lom.create(); + LargeObject lob = lom.open(oid); + lob.write(x); + lob.close(); + setInt(parameterIndex,oid); + } + + /** + * Set a parameter to a java.sql.Date value. The driver converts this + * to a SQL DATE value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setDate(int parameterIndex, java.sql.Date x) throws SQLException + { + SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''"); + + set(parameterIndex, df.format(x)); + + // The above is how the date should be handled. + // + // However, in JDK's prior to 1.1.6 (confirmed with the + // Linux jdk1.1.3 and the Win95 JRE1.1.5), SimpleDateFormat seems + // to format a date to the previous day. So the fix is to add a day + // before formatting. + // + // PS: 86400000 is one day + // + //set(parameterIndex, df.format(new java.util.Date(x.getTime()+86400000))); + } + + /** + * Set a parameter to a java.sql.Time value. The driver converts + * this to a SQL TIME value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1...)); + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setTime(int parameterIndex, Time x) throws SQLException + { + set(parameterIndex, "'" + x.toString() + "'"); + } + + /** + * Set a parameter to a java.sql.Timestamp value. The driver converts + * this to a SQL TIMESTAMP value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException + { + set(parameterIndex, "'" + x.toString() + "'"); + } + + /** + * When a very large ASCII value is input to a LONGVARCHAR parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. The JDBC driver will do any necessary conversion from + * ASCII to the database char format. + * + * <P><B>Note:</B> This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @param length the number of bytes in the stream + * @exception SQLException if a database access error occurs + */ + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException + { + setBinaryStream(parameterIndex, x, length); + } + + /** + * When a very large Unicode value is input to a LONGVARCHAR parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. The JDBC driver will do any necessary conversion from + * UNICODE to the database char format. + * + * <P><B>Note:</B> This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException + { + setBinaryStream(parameterIndex, x, length); + } + + /** + * When a very large binary value is input to a LONGVARBINARY parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. + * + * <P><B>Note:</B> This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + /** + * In general, parameter values remain in force for repeated used of a + * Statement. Setting a parameter value automatically clears its + * previous value. However, in coms cases, it is useful to immediately + * release the resources used by the current parameter values; this + * can be done by calling clearParameters + * + * @exception SQLException if a database access error occurs + */ + public void clearParameters() throws SQLException + { + int i; + + for (i = 0 ; i < inStrings.length ; i++) + inStrings[i] = null; + } + + /** + * Set the value of a parameter using an object; use the java.lang + * equivalent objects for integral values. + * + * <P>The given Java object will be converted to the targetSqlType before + * being sent to the database. + * + * <P>note that this method may be used to pass database-specific + * abstract data types. This is done by using a Driver-specific + * Java type and using a targetSqlType of java.sql.Types.OTHER + * + * @param parameterIndex the first parameter is 1... + * @param x the object containing the input parameter value + * @param targetSqlType The SQL type to be send to the database + * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC + * types this is the number of digits after the decimal. For + * all other types this value will be ignored. + * @exception SQLException if a database access error occurs + */ + public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException + { + switch (targetSqlType) + { + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + case Types.DECIMAL: + case Types.NUMERIC: + if (x instanceof Boolean) + set(parameterIndex, ((Boolean)x).booleanValue() ? "1" : "0"); + else + set(parameterIndex, x.toString()); + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, x.toString()); + break; + case Types.DATE: + setDate(parameterIndex, (java.sql.Date)x); + break; + case Types.TIME: + setTime(parameterIndex, (Time)x); + break; + case Types.TIMESTAMP: + setTimestamp(parameterIndex, (Timestamp)x); + break; + case Types.OTHER: + setString(parameterIndex, ((PGobject)x).getValue()); + break; + default: + throw new PSQLException("postgresql.prep.type"); + } + } + + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException + { + setObject(parameterIndex, x, targetSqlType, 0); + } + + /** + * This stores an Object into a parameter. + * <p>New for 6.4, if the object is not recognised, but it is + * Serializable, then the object is serialised using the + * org.postgresql.util.Serialize class. + */ + public void setObject(int parameterIndex, Object x) throws SQLException + { + if (x instanceof String) + setString(parameterIndex, (String)x); + else if (x instanceof BigDecimal) + setBigDecimal(parameterIndex, (BigDecimal)x); + else if (x instanceof Short) + setShort(parameterIndex, ((Short)x).shortValue()); + else if (x instanceof Integer) + setInt(parameterIndex, ((Integer)x).intValue()); + else if (x instanceof Long) + setLong(parameterIndex, ((Long)x).longValue()); + else if (x instanceof Float) + setFloat(parameterIndex, ((Float)x).floatValue()); + else if (x instanceof Double) + setDouble(parameterIndex, ((Double)x).doubleValue()); + else if (x instanceof byte[]) + setBytes(parameterIndex, (byte[])x); + else if (x instanceof java.sql.Date) + setDate(parameterIndex, (java.sql.Date)x); + else if (x instanceof Time) + setTime(parameterIndex, (Time)x); + else if (x instanceof Timestamp) + setTimestamp(parameterIndex, (Timestamp)x); + else if (x instanceof Boolean) + setBoolean(parameterIndex, ((Boolean)x).booleanValue()); + else if (x instanceof PGobject) + setString(parameterIndex, ((PGobject)x).getValue()); + else + setLong(parameterIndex, connection.putObject(x)); + } + + /** + * Some prepared statements return multiple results; the execute method + * handles these complex statements as well as the simpler form of + * statements handled by executeQuery and executeUpdate + * + * @return true if the next result is a ResultSet; false if it is an + * update count or there are no more results + * @exception SQLException if a database access error occurs + */ + public boolean execute() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new PSQLException("postgresql.prep.param",new Integer(i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.execute(s.toString()); // in Statement class + } + + /** + * Returns the SQL statement with the current template values + * substituted. + */ + public String toString() { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + s.append( '?' ); + else + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return s.toString(); + } + + // ************************************************************** + // END OF PUBLIC INTERFACE + // ************************************************************** + + /** + * There are a lot of setXXX classes which all basically do + * the same thing. We need a method which actually does the + * set for us. + * + * @param paramIndex the index into the inString + * @param s a string to be stored + * @exception SQLException if something goes wrong + */ + private void set(int paramIndex, String s) throws SQLException + { + if (paramIndex < 1 || paramIndex > inStrings.length) + throw new PSQLException("postgresql.prep.range"); + inStrings[paramIndex - 1] = s; + } +} diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java new file mode 100644 index 0000000000000000000000000000000000000000..6bd748e12528ca4d270be13c5ebfbeed8f23e17d --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java @@ -0,0 +1,768 @@ +package org.postgresql.jdbc1; + +// IMPORTANT NOTE: This file implements the JDBC 1 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 2 class in the +// org.postgresql.jdbc2 package. + +import java.lang.*; +import java.io.*; +import java.math.*; +import java.text.*; +import java.util.*; +import java.sql.*; +import org.postgresql.Field; +import org.postgresql.largeobject.*; +import org.postgresql.util.*; + +/** + * A ResultSet provides access to a table of data generated by executing a + * Statement. The table rows are retrieved in sequence. Within a row its + * column values can be accessed in any order. + * + * <P>A ResultSet maintains a cursor pointing to its current row of data. + * Initially the cursor is positioned before the first row. The 'next' + * method moves the cursor to the next row. + * + * <P>The getXXX methods retrieve column values for the current row. You can + * retrieve values either using the index number of the column, or by using + * the name of the column. In general using the column index will be more + * efficient. Columns are numbered from 1. + * + * <P>For maximum portability, ResultSet columns within each row should be read + * in left-to-right order and each column should be read only once. + * + *<P> For the getXXX methods, the JDBC driver attempts to convert the + * underlying data to the specified Java type and returns a suitable Java + * value. See the JDBC specification for allowable mappings from SQL types + * to Java types with the ResultSet getXXX methods. + * + * <P>Column names used as input to getXXX methods are case insenstive. When + * performing a getXXX using a column name, if several columns have the same + * name, then the value of the first matching column will be returned. The + * column name option is designed to be used when column names are used in the + * SQL Query. For columns that are NOT explicitly named in the query, it is + * best to use column numbers. If column names were used there is no way for + * the programmer to guarentee that they actually refer to the intended + * columns. + * + * <P>A ResultSet is automatically closed by the Statement that generated it + * when that Statement is closed, re-executed, or is used to retrieve the + * next result from a sequence of multiple results. + * + * <P>The number, types and properties of a ResultSet's columns are provided by + * the ResultSetMetaData object returned by the getMetaData method. + * + * @see ResultSetMetaData + * @see java.sql.ResultSet + */ +public class ResultSet extends org.postgresql.ResultSet implements java.sql.ResultSet +{ + /** + * Create a new ResultSet - Note that we create ResultSets to + * represent the results of everything. + * + * @param fields an array of Field objects (basically, the + * ResultSet MetaData) + * @param tuples Vector of the actual data + * @param status the status string returned from the back end + * @param updateCount the number of rows affected by the operation + * @param cursor the positioned update/delete cursor name + */ + public ResultSet(Connection conn, Field[] fields, Vector tuples, String status, int updateCount) + { + super(conn,fields,tuples,status,updateCount); + } + + /** + * A ResultSet is initially positioned before its first row, + * the first call to next makes the first row the current row; + * the second call makes the second row the current row, etc. + * + * <p>If an input stream from the previous row is open, it is + * implicitly closed. The ResultSet's warning chain is cleared + * when a new row is read + * + * @return true if the new current is valid; false if there are no + * more rows + * @exception SQLException if a database access error occurs + */ + public boolean next() throws SQLException + { + if (++current_row >= rows.size()) + return false; + this_row = (byte [][])rows.elementAt(current_row); + return true; + } + + /** + * In some cases, it is desirable to immediately release a ResultSet + * database and JDBC resources instead of waiting for this to happen + * when it is automatically closed. The close method provides this + * immediate release. + * + * <p><B>Note:</B> A ResultSet is automatically closed by the Statement + * the Statement that generated it when that Statement is closed, + * re-executed, or is used to retrieve the next result from a sequence + * of multiple results. A ResultSet is also automatically closed + * when it is garbage collected. + * + * @exception SQLException if a database access error occurs + */ + public void close() throws SQLException + { + // No-op + } + + /** + * A column may have the value of SQL NULL; wasNull() reports whether + * the last column read had this special value. Note that you must + * first call getXXX on a column to try to read its value and then + * call wasNull() to find if the value was SQL NULL + * + * @return true if the last column read was SQL NULL + * @exception SQLException if a database access error occurred + */ + public boolean wasNull() throws SQLException + { + return wasNullFlag; + } + + /** + * Get the value of a column in the current row as a Java String + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value, null for SQL NULL + * @exception SQLException if a database access error occurs + */ + public String getString(int columnIndex) throws SQLException + { + //byte[] bytes = getBytes(columnIndex); + // + //if (bytes == null) + //return null; + //return new String(bytes); + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + wasNullFlag = (this_row[columnIndex - 1] == null); + if(wasNullFlag) + return null; + return new String(this_row[columnIndex - 1]); + } + + /** + * Get the value of a column in the current row as a Java boolean + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value, false for SQL NULL + * @exception SQLException if a database access error occurs + */ + public boolean getBoolean(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + int c = s.charAt(0); + return ((c == 't') || (c == 'T')); + } + return false; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java byte. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public byte getByte(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Byte.parseByte(s); + } catch (NumberFormatException e) { + throw new PSQLException("postgresql.res.badbyte",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java short. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public short getShort(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Short.parseShort(s); + } catch (NumberFormatException e) { + throw new PSQLException("postgresql.res.badshort",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java int. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public int getInt(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.badint",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java long. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public long getLong(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Long.parseLong(s); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badlong",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java float. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public float getFloat(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Float.valueOf(s).floatValue(); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badfloat",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java double. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public double getDouble(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Double.valueOf(s).doubleValue(); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.baddouble",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a + * java.math.BigDecimal object + * + * @param columnIndex the first column is 1, the second is 2... + * @param scale the number of digits to the right of the decimal + * @return the column value; if the value is SQL NULL, null + * @exception SQLException if a database access error occurs + */ + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException + { + String s = getString(columnIndex); + BigDecimal val; + + if (s != null) + { + try + { + val = new BigDecimal(s); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badbigdec",s); + } + try + { + return val.setScale(scale); + } catch (ArithmeticException e) { + throw new PSQLException ("postgresql.res.badbigdec",s); + } + } + return null; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java byte array. + * + * <p>In normal use, the bytes represent the raw values returned by the + * backend. However, if the column is an OID, then it is assumed to + * refer to a Large Object, and that object is returned as a byte array. + * + * <p><b>Be warned</b> If the large object is huge, then you may run out + * of memory. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the result + * is null + * @exception SQLException if a database access error occurs + */ + public byte[] getBytes(int columnIndex) throws SQLException + { + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + wasNullFlag = (this_row[columnIndex - 1] == null); + + // Handle OID's as BLOBS + if(!wasNullFlag) + if( fields[columnIndex - 1].getOID() == 26) { + LargeObjectManager lom = connection.getLargeObjectAPI(); + LargeObject lob = lom.open(getInt(columnIndex)); + byte buf[] = lob.read(lob.size()); + lob.close(); + return buf; + } + + return this_row[columnIndex - 1]; + } + + /** + * Get the value of a column in the current row as a java.sql.Date + * object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public java.sql.Date getDate(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + if(s==null) + return null; + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + try { + return new java.sql.Date(df.parse(s).getTime()); + } catch (ParseException e) { + throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s); + } + } + + /** + * Get the value of a column in the current row as a java.sql.Time + * object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public Time getTime(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + if (s.length() != 5 && s.length() != 8) + throw new NumberFormatException("Wrong Length!"); + int hr = Integer.parseInt(s.substring(0,2)); + int min = Integer.parseInt(s.substring(3,5)); + int sec = (s.length() == 5) ? 0 : Integer.parseInt(s.substring(6)); + return new Time(hr, min, sec); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badtime",s); + } + } + return null; // SQL NULL + } + + /** + * Get the value of a column in the current row as a + * java.sql.Timestamp object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public Timestamp getTimestamp(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + if(s==null) + return null; + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzz"); + + try { + return new Timestamp(df.parse(s).getTime()); + } catch(ParseException e) { + throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s); + } + } + + /** + * A column value can be retrieved as a stream of ASCII characters + * and then read in chunks from the stream. This method is + * particular suitable for retrieving large LONGVARCHAR values. + * The JDBC driver will do any necessary conversion from the + * database format into ASCII. + * + * <p><B>Note:</B> All the data in the returned stream must be read + * prior to getting the value of any other column. The next call + * to a get method implicitly closes the stream. Also, a stream + * may return 0 for available() whether there is data available + * or not. + * + *<p> We implement an ASCII stream as a Binary stream - we should really + * do the data conversion, but I cannot be bothered to implement this + * right now. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return a Java InputStream that delivers the database column + * value as a stream of one byte ASCII characters. If the + * value is SQL NULL then the result is null + * @exception SQLException if a database access error occurs + * @see getBinaryStream + */ + public InputStream getAsciiStream(int columnIndex) throws SQLException + { + return getBinaryStream(columnIndex); + } + + /** + * A column value can also be retrieved as a stream of Unicode + * characters. We implement this as a binary stream. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Java InputStream that delivers the database column value + * as a stream of two byte Unicode characters. If the value is + * SQL NULL, then the result is null + * @exception SQLException if a database access error occurs + * @see getAsciiStream + * @see getBinaryStream + */ + public InputStream getUnicodeStream(int columnIndex) throws SQLException + { + return getBinaryStream(columnIndex); + } + + /** + * A column value can also be retrieved as a binary strea. This + * method is suitable for retrieving LONGVARBINARY values. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Java InputStream that delivers the database column value + * as a stream of bytes. If the value is SQL NULL, then the result + * is null + * @exception SQLException if a database access error occurs + * @see getAsciiStream + * @see getUnicodeStream + */ + public InputStream getBinaryStream(int columnIndex) throws SQLException + { + byte b[] = getBytes(columnIndex); + + if (b != null) + return new ByteArrayInputStream(b); + return null; // SQL NULL + } + + /** + * The following routines simply convert the columnName into + * a columnIndex and then call the appropriate routine above. + * + * @param columnName is the SQL name of the column + * @return the column value + * @exception SQLException if a database access error occurs + */ + public String getString(String columnName) throws SQLException + { + return getString(findColumn(columnName)); + } + + public boolean getBoolean(String columnName) throws SQLException + { + return getBoolean(findColumn(columnName)); + } + + public byte getByte(String columnName) throws SQLException + { + + return getByte(findColumn(columnName)); + } + + public short getShort(String columnName) throws SQLException + { + return getShort(findColumn(columnName)); + } + + public int getInt(String columnName) throws SQLException + { + return getInt(findColumn(columnName)); + } + + public long getLong(String columnName) throws SQLException + { + return getLong(findColumn(columnName)); + } + + public float getFloat(String columnName) throws SQLException + { + return getFloat(findColumn(columnName)); + } + + public double getDouble(String columnName) throws SQLException + { + return getDouble(findColumn(columnName)); + } + + public BigDecimal getBigDecimal(String columnName, int scale) throws SQLException + { + return getBigDecimal(findColumn(columnName), scale); + } + + public byte[] getBytes(String columnName) throws SQLException + { + return getBytes(findColumn(columnName)); + } + + public java.sql.Date getDate(String columnName) throws SQLException + { + return getDate(findColumn(columnName)); + } + + public Time getTime(String columnName) throws SQLException + { + return getTime(findColumn(columnName)); + } + + public Timestamp getTimestamp(String columnName) throws SQLException + { + return getTimestamp(findColumn(columnName)); + } + + public InputStream getAsciiStream(String columnName) throws SQLException + { + return getAsciiStream(findColumn(columnName)); + } + + public InputStream getUnicodeStream(String columnName) throws SQLException + { + return getUnicodeStream(findColumn(columnName)); + } + + public InputStream getBinaryStream(String columnName) throws SQLException + { + return getBinaryStream(findColumn(columnName)); + } + + /** + * The first warning reported by calls on this ResultSet is + * returned. Subsequent ResultSet warnings will be chained + * to this SQLWarning. + * + * <p>The warning chain is automatically cleared each time a new + * row is read. + * + * <p><B>Note:</B> This warning chain only covers warnings caused by + * ResultSet methods. Any warnings caused by statement methods + * (such as reading OUT parameters) will be chained on the + * Statement object. + * + * @return the first SQLWarning or null; + * @exception SQLException if a database access error occurs. + */ + public SQLWarning getWarnings() throws SQLException + { + return warnings; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this ResultSet + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + warnings = null; + } + + /** + * Get the name of the SQL cursor used by this ResultSet + * + * <p>In SQL, a result table is retrieved though a cursor that is + * named. The current row of a result can be updated or deleted + * using a positioned update/delete statement that references + * the cursor name. + * + * <p>JDBC supports this SQL feature by providing the name of the + * SQL cursor used by a ResultSet. The current row of a ResulSet + * is also the current row of this SQL cursor. + * + * <p><B>Note:</B> If positioned update is not supported, a SQLException + * is thrown. + * + * @return the ResultSet's SQL cursor name. + * @exception SQLException if a database access error occurs + */ + public String getCursorName() throws SQLException + { + return connection.getCursorName(); + } + + /** + * The numbers, types and properties of a ResultSet's columns are + * provided by the getMetaData method + * + * @return a description of the ResultSet's columns + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSetMetaData getMetaData() throws SQLException + { + return new ResultSetMetaData(rows, fields); + } + + /** + * Get the value of a column in the current row as a Java object + * + * <p>This method will return the value of the given column as a + * Java object. The type of the Java object will be the default + * Java Object type corresponding to the column's SQL type, following + * the mapping specified in the JDBC specification. + * + * <p>This method may also be used to read database specific abstract + * data types. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Object holding the column value + * @exception SQLException if a database access error occurs + */ + public Object getObject(int columnIndex) throws SQLException + { + Field field; + + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + field = fields[columnIndex - 1]; + + // some fields can be null, mainly from those returned by MetaData methods + if(field==null) { + wasNullFlag=true; + return null; + } + + switch (field.getSQLType()) + { + case Types.BIT: + return new Boolean(getBoolean(columnIndex)); + case Types.SMALLINT: + return new Integer(getInt(columnIndex)); + case Types.INTEGER: + return new Integer(getInt(columnIndex)); + case Types.BIGINT: + return new Long(getLong(columnIndex)); + case Types.NUMERIC: + return getBigDecimal(columnIndex, ((field.mod-4) & 0xffff)); + case Types.REAL: + return new Float(getFloat(columnIndex)); + case Types.DOUBLE: + return new Double(getDouble(columnIndex)); + case Types.CHAR: + case Types.VARCHAR: + return getString(columnIndex); + case Types.DATE: + return getDate(columnIndex); + case Types.TIME: + return getTime(columnIndex); + case Types.TIMESTAMP: + return getTimestamp(columnIndex); + default: + return connection.getObject(field.getTypeName(), getString(columnIndex)); + } + } + + /** + * Get the value of a column in the current row as a Java object + * + *<p> This method will return the value of the given column as a + * Java object. The type of the Java object will be the default + * Java Object type corresponding to the column's SQL type, following + * the mapping specified in the JDBC specification. + * + * <p>This method may also be used to read database specific abstract + * data types. + * + * @param columnName is the SQL name of the column + * @return a Object holding the column value + * @exception SQLException if a database access error occurs + */ + public Object getObject(String columnName) throws SQLException + { + return getObject(findColumn(columnName)); + } + + /** + * Map a ResultSet column name to a ResultSet column index + * + * @param columnName the name of the column + * @return the column index + * @exception SQLException if a database access error occurs + */ + public int findColumn(String columnName) throws SQLException + { + int i; + + for (i = 0 ; i < fields.length; ++i) + if (fields[i].name.equalsIgnoreCase(columnName)) + return (i+1); + throw new PSQLException ("postgresql.res.colname",columnName); + } +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSetMetaData.java b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSetMetaData.java new file mode 100644 index 0000000000000000000000000000000000000000..a78612b085b9b9b6cd9428f212f16456bc7b2aff --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSetMetaData.java @@ -0,0 +1,451 @@ +package org.postgresql.jdbc1; + +// IMPORTANT NOTE: This file implements the JDBC 1 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 2 class in the +// org.postgresql.jdbc2 package. + +import java.lang.*; +import java.util.*; +import org.postgresql.*; +import org.postgresql.util.*; + +// We explicitly import classes here as the original line: +//import java.sql.*; +// causes javac to get confused. +import java.sql.SQLException; +import java.sql.Types; + +/** + * A ResultSetMetaData object can be used to find out about the types and + * properties of the columns in a ResultSet + * + * @see java.sql.ResultSetMetaData + */ +public class ResultSetMetaData implements java.sql.ResultSetMetaData +{ + Vector rows; + Field[] fields; + + /** + * Initialise for a result with a tuple set and + * a field descriptor set + * + * @param rows the Vector of rows returned by the ResultSet + * @param fields the array of field descriptors + */ + public ResultSetMetaData(Vector rows, Field[] fields) + { + this.rows = rows; + this.fields = fields; + } + + /** + * Whats the number of columns in the ResultSet? + * + * @return the number + * @exception SQLException if a database access error occurs + */ + public int getColumnCount() throws SQLException + { + return fields.length; + } + + /** + * Is the column automatically numbered (and thus read-only) + * I believe that PostgreSQL does not support this feature. + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isAutoIncrement(int column) throws SQLException + { + return false; + } + + /** + * Does a column's case matter? ASSUMPTION: Any field that is + * not obviously case insensitive is assumed to be case sensitive + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isCaseSensitive(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + case Types.INTEGER: + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + return false; + default: + return true; + } + } + + /** + * Can the column be used in a WHERE clause? Basically for + * this, I split the functions into two types: recognised + * types (which are always useable), and OTHER types (which + * may or may not be useable). The OTHER types, for now, I + * will assume they are useable. We should really query the + * catalog to see if they are useable. + * + * @param column the first column is 1, the second is 2... + * @return true if they can be used in a WHERE clause + * @exception SQLException if a database access error occurs + */ + public boolean isSearchable(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + // This switch is pointless, I know - but it is a set-up + // for further expansion. + switch (sql_type) + { + case Types.OTHER: + return true; + default: + return true; + } + } + + /** + * Is the column a cash value? 6.1 introduced the cash/money + * type, which haven't been incorporated as of 970414, so I + * just check the type name for both 'cash' and 'money' + * + * @param column the first column is 1, the second is 2... + * @return true if its a cash column + * @exception SQLException if a database access error occurs + */ + public boolean isCurrency(int column) throws SQLException + { + String type_name = getField(column).getTypeName(); + + return type_name.equals("cash") || type_name.equals("money"); + } + + /** + * Can you put a NULL in this column? I think this is always + * true in 6.1's case. It would only be false if the field had + * been defined NOT NULL (system catalogs could be queried?) + * + * @param column the first column is 1, the second is 2... + * @return one of the columnNullable values + * @exception SQLException if a database access error occurs + */ + public int isNullable(int column) throws SQLException + { + return columnNullable; // We can always put NULL in + } + + /** + * Is the column a signed number? In PostgreSQL, all numbers + * are signed, so this is trivial. However, strings are not + * signed (duh!) + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isSigned(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + case Types.INTEGER: + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + return true; + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + return false; // I don't know about these? + default: + return false; + } + } + + /** + * What is the column's normal maximum width in characters? + * + * @param column the first column is 1, the second is 2, etc. + * @return the maximum width + * @exception SQLException if a database access error occurs + */ + public int getColumnDisplaySize(int column) throws SQLException + { + Field f = getField(column); + String type_name = f.getTypeName(); + int sql_type = f.getSQLType(); + int typmod = f.mod; + + // I looked at other JDBC implementations and couldn't find a consistent + // interpretation of the "display size" for numeric values, so this is our's + // FIXME: currently, only types with a SQL92 or SQL3 pendant are implemented - jens@jens.de + + // fixed length data types + if (type_name.equals( "int2" )) return 6; // -32768 to +32768 (5 digits and a sign) + if (type_name.equals( "int4" ) + || type_name.equals( "oid" )) return 11; // -2147483648 to +2147483647 + if (type_name.equals( "int8" )) return 20; // -9223372036854775808 to +9223372036854775807 + if (type_name.equals( "money" )) return 12; // MONEY = DECIMAL(9,2) + if (type_name.equals( "float4" )) return 11; // i checked it out ans wasn't able to produce more than 11 digits + if (type_name.equals( "float8" )) return 20; // dito, 20 + if (type_name.equals( "char" )) return 1; + if (type_name.equals( "bool" )) return 1; + if (type_name.equals( "date" )) return 14; // "01/01/4713 BC" - "31/12/32767 AD" + if (type_name.equals( "time" )) return 8; // 00:00:00-23:59:59 + if (type_name.equals( "timestamp" )) return 22; // hhmmm ... the output looks like this: 1999-08-03 22:22:08+02 + + // variable length fields + typmod -= 4; + if (type_name.equals( "bpchar" ) + || type_name.equals( "varchar" )) return typmod; // VARHDRSZ=sizeof(int32)=4 + if (type_name.equals( "numeric" )) return ( (typmod >>16) & 0xffff ) + + 1 + ( typmod & 0xffff ); // DECIMAL(p,s) = (p digits).(s digits) + + // if we don't know better + return f.length; + } + + /** + * What is the suggested column title for use in printouts and + * displays? We suggest the ColumnName! + * + * @param column the first column is 1, the second is 2, etc. + * @return the column label + * @exception SQLException if a database access error occurs + */ + public String getColumnLabel(int column) throws SQLException + { + return getColumnName(column); + } + + /** + * What's a column's name? + * + * @param column the first column is 1, the second is 2, etc. + * @return the column name + * @exception SQLException if a database access error occurs + */ + public String getColumnName(int column) throws SQLException + { + Field f = getField(column); + if(f!=null) + return f.name; + return "field"+column; + } + + /** + * What is a column's table's schema? This relies on us knowing + * the table name....which I don't know how to do as yet. The + * JDBC specification allows us to return "" if this is not + * applicable. + * + * @param column the first column is 1, the second is 2... + * @return the Schema + * @exception SQLException if a database access error occurs + */ + public String getSchemaName(int column) throws SQLException + { + return ""; + } + + /** + * What is a column's number of decimal digits. + * + * @param column the first column is 1, the second is 2... + * @return the precision + * @exception SQLException if a database access error occurs + */ + public int getPrecision(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + return 5; + case Types.INTEGER: + return 10; + case Types.REAL: + return 8; + case Types.FLOAT: + return 16; + case Types.DOUBLE: + return 16; + case Types.VARCHAR: + return 0; + default: + return 0; + } + } + + /** + * What is a column's number of digits to the right of the + * decimal point? + * + * @param column the first column is 1, the second is 2... + * @return the scale + * @exception SQLException if a database access error occurs + */ + public int getScale(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + return 0; + case Types.INTEGER: + return 0; + case Types.REAL: + return 8; + case Types.FLOAT: + return 16; + case Types.DOUBLE: + return 16; + case Types.VARCHAR: + return 0; + default: + return 0; + } + } + + /** + * Whats a column's table's name? How do I find this out? Both + * getSchemaName() and getCatalogName() rely on knowing the table + * Name, so we need this before we can work on them. + * + * @param column the first column is 1, the second is 2... + * @return column name, or "" if not applicable + * @exception SQLException if a database access error occurs + */ + public String getTableName(int column) throws SQLException + { + return ""; + } + + /** + * What's a column's table's catalog name? As with getSchemaName(), + * we can say that if getTableName() returns n/a, then we can too - + * otherwise, we need to work on it. + * + * @param column the first column is 1, the second is 2... + * @return catalog name, or "" if not applicable + * @exception SQLException if a database access error occurs + */ + public String getCatalogName(int column) throws SQLException + { + return ""; + } + + /** + * What is a column's SQL Type? (java.sql.Type int) + * + * @param column the first column is 1, the second is 2, etc. + * @return the java.sql.Type value + * @exception SQLException if a database access error occurs + * @see org.postgresql.Field#getSQLType + * @see java.sql.Types + */ + public int getColumnType(int column) throws SQLException + { + return getField(column).getSQLType(); + } + + /** + * Whats is the column's data source specific type name? + * + * @param column the first column is 1, the second is 2, etc. + * @return the type name + * @exception SQLException if a database access error occurs + */ + public String getColumnTypeName(int column) throws SQLException + { + return getField(column).getTypeName(); + } + + /** + * Is the column definitely not writable? In reality, we would + * have to check the GRANT/REVOKE stuff for this to be effective, + * and I haven't really looked into that yet, so this will get + * re-visited. + * + * @param column the first column is 1, the second is 2, etc. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly(int column) throws SQLException + { + return false; + } + + /** + * Is it possible for a write on the column to succeed? Again, we + * would in reality have to check the GRANT/REVOKE stuff, which + * I haven't worked with as yet. However, if it isn't ReadOnly, then + * it is obviously writable. + * + * @param column the first column is 1, the second is 2, etc. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isWritable(int column) throws SQLException + { + if (isReadOnly(column)) + return true; + else + return false; + } + + /** + * Will a write on this column definately succeed? Hmmm...this + * is a bad one, since the two preceding functions have not been + * really defined. I cannot tell is the short answer. I thus + * return isWritable() just to give us an idea. + * + * @param column the first column is 1, the second is 2, etc.. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isDefinitelyWritable(int column) throws SQLException + { + return isWritable(column); + } + + // ******************************************************** + // END OF PUBLIC INTERFACE + // ******************************************************** + + /** + * For several routines in this package, we need to convert + * a columnIndex into a Field[] descriptor. Rather than do + * the same code several times, here it is. + * + * @param columnIndex the first column is 1, the second is 2... + * @return the Field description + * @exception SQLException if a database access error occurs + */ + private Field getField(int columnIndex) throws SQLException + { + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + return fields[columnIndex - 1]; + } +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/Statement.java b/src/interfaces/jdbc/org/postgresql/jdbc1/Statement.java new file mode 100644 index 0000000000000000000000000000000000000000..43e5d381434ee7203d3732cf6478fe1a577a8b2c --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/Statement.java @@ -0,0 +1,325 @@ +package org.postgresql.jdbc1; + +// IMPORTANT NOTE: This file implements the JDBC 1 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 2 class in the +// org.postgresql.jdbc2 package. + +import java.sql.*; + +import org.postgresql.util.PSQLException; + +/** + * A Statement object is used for executing a static SQL statement and + * obtaining the results produced by it. + * + * <p>Only one ResultSet per Statement can be open at any point in time. + * Therefore, if the reading of one ResultSet is interleaved with the + * reading of another, each must have been generated by different + * Statements. All statement execute methods implicitly close a + * statement's current ResultSet if an open one exists. + * + * @see java.sql.Statement + * @see ResultSet + */ +public class Statement implements java.sql.Statement +{ + Connection connection; // The connection who created us + java.sql.ResultSet result = null; // The current results + SQLWarning warnings = null; // The warnings chain. + int timeout = 0; // The timeout for a query (not used) + boolean escapeProcessing = true;// escape processing flag + + /** + * Constructor for a Statement. It simply sets the connection + * that created us. + * + * @param c the Connection instantation that creates us + */ + public Statement (Connection c) + { + connection = c; + } + + /** + * Execute a SQL statement that retruns a single ResultSet + * + * @param sql typically a static SQL SELECT statement + * @return a ResulSet that contains the data produced by the query + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet executeQuery(String sql) throws SQLException + { + this.execute(sql); + while (result != null && !((org.postgresql.ResultSet)result).reallyResultSet()) + result = ((org.postgresql.ResultSet)result).getNext(); + if (result == null) + throw new PSQLException("postgresql.stat.noresult"); + return result; + } + + /** + * Execute a SQL INSERT, UPDATE or DELETE statement. In addition + * SQL statements that return nothing such as SQL DDL statements + * can be executed + * + * @param sql a SQL statement + * @return either a row count, or 0 for SQL commands + * @exception SQLException if a database access error occurs + */ + public int executeUpdate(String sql) throws SQLException + { + this.execute(sql); + if (((org.postgresql.ResultSet)result).reallyResultSet()) + throw new PSQLException("postgresql.stat.result"); + return this.getUpdateCount(); + } + + /** + * In many cases, it is desirable to immediately release a + * Statement's database and JDBC resources instead of waiting + * for this to happen when it is automatically closed. The + * close method provides this immediate release. + * + * <p><B>Note:</B> A Statement is automatically closed when it is + * garbage collected. When a Statement is closed, its current + * ResultSet, if one exists, is also closed. + * + * @exception SQLException if a database access error occurs (why?) + */ + public void close() throws SQLException + { + result = null; + } + + /** + * The maxFieldSize limit (in bytes) is the maximum amount of + * data returned for any column value; it only applies to + * BINARY, VARBINARY, LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR + * columns. If the limit is exceeded, the excess data is silently + * discarded. + * + * @return the current max column size limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public int getMaxFieldSize() throws SQLException + { + return 8192; // We cannot change this + } + + /** + * Sets the maxFieldSize - NOT! - We throw an SQLException just + * to inform them to stop doing this. + * + * @param max the new max column size limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public void setMaxFieldSize(int max) throws SQLException + { + throw new PSQLException("postgresql.stat.maxfieldsize"); + } + + /** + * The maxRows limit is set to limit the number of rows that + * any ResultSet can contain. If the limit is exceeded, the + * excess rows are silently dropped. + * + * @return the current maximum row limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public int getMaxRows() throws SQLException + { + return connection.maxrows; + } + + /** + * Set the maximum number of rows + * + * @param max the new max rows limit; zero means unlimited + * @exception SQLException if a database access error occurs + * @see getMaxRows + */ + public void setMaxRows(int max) throws SQLException + { + connection.maxrows = max; + } + + /** + * If escape scanning is on (the default), the driver will do escape + * substitution before sending the SQL to the database. + * + * @param enable true to enable; false to disable + * @exception SQLException if a database access error occurs + */ + public void setEscapeProcessing(boolean enable) throws SQLException + { + escapeProcessing = enable; + } + + /** + * The queryTimeout limit is the number of seconds the driver + * will wait for a Statement to execute. If the limit is + * exceeded, a SQLException is thrown. + * + * @return the current query timeout limit in seconds; 0 = unlimited + * @exception SQLException if a database access error occurs + */ + public int getQueryTimeout() throws SQLException + { + return timeout; + } + + /** + * Sets the queryTimeout limit + * + * @param seconds - the new query timeout limit in seconds + * @exception SQLException if a database access error occurs + */ + public void setQueryTimeout(int seconds) throws SQLException + { + timeout = seconds; + } + + /** + * Cancel can be used by one thread to cancel a statement that + * is being executed by another thread. However, PostgreSQL is + * a sync. sort of thing, so this really has no meaning - we + * define it as a no-op (i.e. you can't cancel, but there is no + * error if you try.) + * + * 6.4 introduced a cancel operation, but we have not implemented it + * yet. Sometime before 6.5, this method will be implemented. + * + * @exception SQLException only because thats the spec. + */ + public void cancel() throws SQLException + { + // No-op + } + + /** + * The first warning reported by calls on this Statement is + * returned. A Statement's execute methods clear its SQLWarning + * chain. Subsequent Statement warnings will be chained to this + * SQLWarning. + * + * <p>The Warning chain is automatically cleared each time a statement + * is (re)executed. + * + * <p><B>Note:</B> If you are processing a ResultSet then any warnings + * associated with ResultSet reads will be chained on the ResultSet + * object. + * + * @return the first SQLWarning on null + * @exception SQLException if a database access error occurs + */ + public SQLWarning getWarnings() throws SQLException + { + return warnings; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this Statement. + * + * @exception SQLException if a database access error occurs (why?) + */ + public void clearWarnings() throws SQLException + { + warnings = null; + } + + /** + * setCursorName defines the SQL cursor name that will be used by + * subsequent execute methods. This name can then be used in SQL + * positioned update/delete statements to identify the current row + * in the ResultSet generated by this statement. If a database + * doesn't support positioned update/delete, this method is a + * no-op. + * + * <p><B>Note:</B> By definition, positioned update/delete execution + * must be done by a different Statement than the one which + * generated the ResultSet being used for positioning. Also, cursor + * names must be unique within a Connection. + * + * <p>We throw an additional constriction. There can only be one + * cursor active at any one time. + * + * @param name the new cursor name + * @exception SQLException if a database access error occurs + */ + public void setCursorName(String name) throws SQLException + { + connection.setCursorName(name); + } + + /** + * Execute a SQL statement that may return multiple results. We + * don't have to worry about this since we do not support multiple + * ResultSets. You can use getResultSet or getUpdateCount to + * retrieve the result. + * + * @param sql any SQL statement + * @return true if the next result is a ResulSet, false if it is + * an update count or there are no more results + * @exception SQLException if a database access error occurs + */ + public boolean execute(String sql) throws SQLException + { + result = connection.ExecSQL(sql); + return (result != null && ((org.postgresql.ResultSet)result).reallyResultSet()); + } + + /** + * getResultSet returns the current result as a ResultSet. It + * should only be called once per result. + * + * @return the current result set; null if there are no more + * @exception SQLException if a database access error occurs (why?) + */ + public java.sql.ResultSet getResultSet() throws SQLException + { + return result; + } + + /** + * getUpdateCount returns the current result as an update count, + * if the result is a ResultSet or there are no more results, -1 + * is returned. It should only be called once per result. + * + * @return the current result as an update count. + * @exception SQLException if a database access error occurs + */ + public int getUpdateCount() throws SQLException + { + if (result == null) return -1; + if (((org.postgresql.ResultSet)result).reallyResultSet()) return -1; + return ((org.postgresql.ResultSet)result).getResultCount(); + } + + /** + * getMoreResults moves to a Statement's next result. If it returns + * true, this result is a ResulSet. + * + * @return true if the next ResultSet is valid + * @exception SQLException if a database access error occurs + */ + public boolean getMoreResults() throws SQLException + { + result = ((org.postgresql.ResultSet)result).getNext(); + return (result != null && ((org.postgresql.ResultSet)result).reallyResultSet()); + } + + /** + * Returns the status message from the current Result.<p> + * This is used internally by the driver. + * + * @return status message from backend + */ + public String getResultStatusString() + { + if(result == null) + return null; + return ((org.postgresql.ResultSet)result).getStatusString(); + } +} diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/CallableStatement.java b/src/interfaces/jdbc/org/postgresql/jdbc2/CallableStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..1ea33231a98d47c55b07275d00d57ca9559b44f6 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/CallableStatement.java @@ -0,0 +1,361 @@ +package org.postgresql.jdbc2; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + +import java.sql.*; +import java.math.*; + +/** + * CallableStatement is used to execute SQL stored procedures. + * + * <p>JDBC provides a stored procedure SQL escape that allows stored + * procedures to be called in a standard way for all RDBMS's. This escape + * syntax has one form that includes a result parameter and one that does + * not. If used, the result parameter must be registered as an OUT + * parameter. The other parameters may be used for input, output or both. + * Parameters are refered to sequentially, by number. The first parameter + * is 1. + * + * {?= call <procedure-name>[<arg1>,<arg2>, ...]} + * {call <procedure-name>[<arg1>,<arg2>, ...]} + * + * + * <p>IN parameter values are set using the set methods inherited from + * PreparedStatement. The type of all OUT parameters must be registered + * prior to executing the stored procedure; their values are retrieved + * after execution via the get methods provided here. + * + * <p>A Callable statement may return a ResultSet or multiple ResultSets. + * Multiple ResultSets are handled using operations inherited from + * Statement. + * + * <p>For maximum portability, a call's ResultSets and update counts should + * be processed prior to getting the values of output parameters. + * + * @see Connection#prepareCall + * @see ResultSet + */ + +public class CallableStatement extends org.postgresql.jdbc2.PreparedStatement implements java.sql.CallableStatement +{ + /** + * @exception SQLException on failure + */ + public CallableStatement(Connection c,String q) throws SQLException + { + super(c,q); + } + + /** + * Before executing a stored procedure call you must explicitly + * call registerOutParameter to register the java.sql.Type of each + * out parameter. + * + * <p>Note: When reading the value of an out parameter, you must use + * the getXXX method whose Java type XXX corresponds to the + * parameter's registered SQL type. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param sqlType SQL type code defined by java.sql.Types; for + * parameters of type Numeric or Decimal use the version of + * registerOutParameter that accepts a scale value + * @exception SQLException if a database-access error occurs. + */ + public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException { + } + + /** + * You must also specify the scale for numeric/decimal types: + * + * <p>Note: When reading the value of an out parameter, you must use + * the getXXX method whose Java type XXX corresponds to the + * parameter's registered SQL type. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param sqlType use either java.sql.Type.NUMERIC or java.sql.Type.DECIMAL + * @param scale a value greater than or equal to zero representing the + * desired number of digits to the right of the decimal point + * @exception SQLException if a database-access error occurs. + */ + public void registerOutParameter(int parameterIndex, int sqlType, + int scale) throws SQLException + { + } + + // Old api? + //public boolean isNull(int parameterIndex) throws SQLException { + //return true; + //} + + /** + * An OUT parameter may have the value of SQL NULL; wasNull + * reports whether the last value read has this special value. + * + * <p>Note: You must first call getXXX on a parameter to read its + * value and then call wasNull() to see if the value was SQL NULL. + * @return true if the last parameter read was SQL NULL + * @exception SQLException if a database-access error occurs. + */ + public boolean wasNull() throws SQLException { + // check to see if the last access threw an exception + return false; // fake it for now + } + + // Old api? + //public String getChar(int parameterIndex) throws SQLException { + //return null; + //} + + /** + * Get the value of a CHAR, VARCHAR, or LONGVARCHAR parameter as a + * Java String. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public String getString(int parameterIndex) throws SQLException { + return null; + } + //public String getVarChar(int parameterIndex) throws SQLException { + // return null; + //} + + //public String getLongVarChar(int parameterIndex) throws SQLException { + //return null; + //} + + /** + * Get the value of a BIT parameter as a Java boolean. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is false + * @exception SQLException if a database-access error occurs. + */ + public boolean getBoolean(int parameterIndex) throws SQLException { + return false; + } + + /** + * Get the value of a TINYINT parameter as a Java byte. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public byte getByte(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of a SMALLINT parameter as a Java short. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public short getShort(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of an INTEGER parameter as a Java int. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ +public int getInt(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of a BIGINT parameter as a Java long. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public long getLong(int parameterIndex) throws SQLException { + return 0; + } + + /** + * Get the value of a FLOAT parameter as a Java float. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public float getFloat(int parameterIndex) throws SQLException { + return (float) 0.0; + } + + /** + * Get the value of a DOUBLE parameter as a Java double. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public double getDouble(int parameterIndex) throws SQLException { + return 0.0; + } + + /** + * Get the value of a NUMERIC parameter as a java.math.BigDecimal + * object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param scale a value greater than or equal to zero representing the + * desired number of digits to the right of the decimal point + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public BigDecimal getBigDecimal(int parameterIndex, int scale) + throws SQLException { + return null; + } + + /** + * Get the value of a SQL BINARY or VARBINARY parameter as a Java + * byte[] + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public byte[] getBytes(int parameterIndex) throws SQLException { + return null; + } + + // New API (JPM) (getLongVarBinary) + //public byte[] getBinaryStream(int parameterIndex) throws SQLException { + //return null; + //} + + /** + * Get the value of a SQL DATE parameter as a java.sql.Date object + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Date getDate(int parameterIndex) throws SQLException { + return null; + } + + /** + * Get the value of a SQL TIME parameter as a java.sql.Time object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Time getTime(int parameterIndex) throws SQLException { + return null; + } + + /** + * Get the value of a SQL TIMESTAMP parameter as a java.sql.Timestamp object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Timestamp getTimestamp(int parameterIndex) + throws SQLException { + return null; + } + + //---------------------------------------------------------------------- + // Advanced features: + + // You can obtain a ParameterMetaData object to get information + // about the parameters to this CallableStatement. + //public DatabaseMetaData getMetaData() { + //return null; + //} + + // getObject returns a Java object for the parameter. + // See the JDBC spec's "Dynamic Programming" chapter for details. + /** + * Get the value of a parameter as a Java object. + * + * <p>This method returns a Java object whose type coresponds to the + * SQL type that was registered for this parameter using + * registerOutParameter. + * + * <P>Note that this method may be used to read datatabase-specific, + * abstract data types. This is done by specifying a targetSqlType + * of java.sql.types.OTHER, which allows the driver to return a + * database-specific Java type. + * + * <p>See the JDBC spec's "Dynamic Programming" chapter for details. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return A java.lang.Object holding the OUT parameter value. + * @exception SQLException if a database-access error occurs. + */ + public Object getObject(int parameterIndex) + throws SQLException { + return null; + } + + // ** JDBC 2 Extensions ** + + public Array getArray(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.math.BigDecimal getBigDecimal(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Blob getBlob(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Clob getClob(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Object getObject(int i,java.util.Map map) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Ref getRef(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.sql.Date getDate(int i,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Time getTime(int i,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Timestamp getTimestamp(int i,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void registerOutParameter(int parameterIndex, int sqlType,String typeName) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java b/src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java new file mode 100644 index 0000000000000000000000000000000000000000..62b3f6f4459f43375b54eebdf25548711f540d36 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/Connection.java @@ -0,0 +1,441 @@ +package org.postgresql.jdbc2; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + +import java.io.*; +import java.lang.*; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import org.postgresql.Field; +import org.postgresql.fastpath.*; +import org.postgresql.largeobject.*; +import org.postgresql.util.*; + +/** + * $Id: Connection.java,v 1.1 2000/04/17 20:07:50 peter Exp $ + * + * A Connection represents a session with a specific database. Within the + * context of a Connection, SQL statements are executed and results are + * returned. + * + * <P>A Connection's database is able to provide information describing + * its tables, its supported SQL grammar, its stored procedures, the + * capabilities of this connection, etc. This information is obtained + * with the getMetaData method. + * + * <p><B>Note:</B> By default, the Connection automatically commits changes + * after executing each statement. If auto-commit has been disabled, an + * explicit commit must be done or database changes will not be saved. + * + * @see java.sql.Connection + */ +public class Connection extends org.postgresql.Connection implements java.sql.Connection +{ + // This is a cache of the DatabaseMetaData instance for this connection + protected DatabaseMetaData metadata; + + /** + * SQL statements without parameters are normally executed using + * Statement objects. If the same SQL statement is executed many + * times, it is more efficient to use a PreparedStatement + * + * @return a new Statement object + * @exception SQLException passed through from the constructor + */ + public java.sql.Statement createStatement() throws SQLException + { + return new Statement(this); + } + + /** + * A SQL statement with or without IN parameters can be pre-compiled + * and stored in a PreparedStatement object. This object can then + * be used to efficiently execute this statement multiple times. + * + * <B>Note:</B> This method is optimized for handling parametric + * SQL statements that benefit from precompilation if the drivers + * supports precompilation. PostgreSQL does not support precompilation. + * In this case, the statement is not sent to the database until the + * PreparedStatement is executed. This has no direct effect on users; + * however it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' IN + * parameter placeholders + * @return a new PreparedStatement object containing the pre-compiled + * statement. + * @exception SQLException if a database access error occurs. + */ + public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException + { + return new PreparedStatement(this, sql); + } + + /** + * A SQL stored procedure call statement is handled by creating a + * CallableStatement for it. The CallableStatement provides methods + * for setting up its IN and OUT parameters and methods for executing + * it. + * + * <B>Note:</B> This method is optimised for handling stored procedure + * call statements. Some drivers may send the call statement to the + * database when the prepareCall is done; others may wait until the + * CallableStatement is executed. This has no direct effect on users; + * however, it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' parameter + * placeholders. Typically this statement is a JDBC function call + * escape string. + * @return a new CallableStatement object containing the pre-compiled + * SQL statement + * @exception SQLException if a database access error occurs + */ + public java.sql.CallableStatement prepareCall(String sql) throws SQLException + { + throw new PSQLException("postgresql.con.call"); + // return new CallableStatement(this, sql); + } + + /** + * A driver may convert the JDBC sql grammar into its system's + * native SQL grammar prior to sending it; nativeSQL returns the + * native form of the statement that the driver would have sent. + * + * @param sql a SQL statement that may contain one or more '?' + * parameter placeholders + * @return the native form of this statement + * @exception SQLException if a database access error occurs + */ + public String nativeSQL(String sql) throws SQLException + { + return sql; + } + + /** + * If a connection is in auto-commit mode, than all its SQL + * statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped + * into transactions that are terminated by either commit() + * or rollback(). By default, new connections are in auto- + * commit mode. The commit occurs when the statement completes + * or the next execute occurs, whichever comes first. In the + * case of statements returning a ResultSet, the statement + * completes when the last row of the ResultSet has been retrieved + * or the ResultSet has been closed. In advanced cases, a single + * statement may return multiple results as well as output parameter + * values. Here the commit occurs when all results and output param + * values have been retrieved. + * + * @param autoCommit - true enables auto-commit; false disables it + * @exception SQLException if a database access error occurs + */ + public void setAutoCommit(boolean autoCommit) throws SQLException + { + if (this.autoCommit == autoCommit) + return; + if (autoCommit) + ExecSQL("end"); + else + ExecSQL("begin"); + this.autoCommit = autoCommit; + } + + /** + * gets the current auto-commit state + * + * @return Current state of the auto-commit mode + * @exception SQLException (why?) + * @see setAutoCommit + */ + public boolean getAutoCommit() throws SQLException + { + return this.autoCommit; + } + + /** + * The method commit() makes all changes made since the previous + * commit/rollback permanent and releases any database locks currently + * held by the Connection. This method should only be used when + * auto-commit has been disabled. (If autoCommit == true, then we + * just return anyhow) + * + * @exception SQLException if a database access error occurs + * @see setAutoCommit + */ + public void commit() throws SQLException + { + if (autoCommit) + return; + ExecSQL("commit"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * The method rollback() drops all changes made since the previous + * commit/rollback and releases any database locks currently held by + * the Connection. + * + * @exception SQLException if a database access error occurs + * @see commit + */ + public void rollback() throws SQLException + { + if (autoCommit) + return; + ExecSQL("rollback"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * In some cases, it is desirable to immediately release a Connection's + * database and JDBC resources instead of waiting for them to be + * automatically released (cant think why off the top of my head) + * + * <B>Note:</B> A Connection is automatically closed when it is + * garbage collected. Certain fatal errors also result in a closed + * connection. + * + * @exception SQLException if a database access error occurs + */ + public void close() throws SQLException + { + if (pg_stream != null) + { + try + { + pg_stream.close(); + } catch (IOException e) {} + pg_stream = null; + } + } + + /** + * Tests to see if a Connection is closed + * + * @return the status of the connection + * @exception SQLException (why?) + */ + public boolean isClosed() throws SQLException + { + return (pg_stream == null); + } + + /** + * A connection's database is able to provide information describing + * its tables, its supported SQL grammar, its stored procedures, the + * capabilities of this connection, etc. This information is made + * available through a DatabaseMetaData object. + * + * @return a DatabaseMetaData object for this connection + * @exception SQLException if a database access error occurs + */ + public java.sql.DatabaseMetaData getMetaData() throws SQLException + { + if(metadata==null) + metadata = new DatabaseMetaData(this); + return metadata; + } + + /** + * You can put a connection in read-only mode as a hunt to enable + * database optimizations + * + * <B>Note:</B> setReadOnly cannot be called while in the middle + * of a transaction + * + * @param readOnly - true enables read-only mode; false disables it + * @exception SQLException if a database access error occurs + */ + public void setReadOnly (boolean readOnly) throws SQLException + { + this.readOnly = readOnly; + } + + /** + * Tests to see if the connection is in Read Only Mode. Note that + * we cannot really put the database in read only mode, but we pretend + * we can by returning the value of the readOnly flag + * + * @return true if the connection is read only + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return readOnly; + } + + /** + * A sub-space of this Connection's database may be selected by + * setting a catalog name. If the driver does not support catalogs, + * it will silently ignore this request + * + * @exception SQLException if a database access error occurs + */ + public void setCatalog(String catalog) throws SQLException + { + // No-op + } + + /** + * Return the connections current catalog name, or null if no + * catalog name is set, or we dont support catalogs. + * + * @return the current catalog name or null + * @exception SQLException if a database access error occurs + */ + public String getCatalog() throws SQLException + { + return null; + } + + /** + * You can call this method to try to change the transaction + * isolation level using one of the TRANSACTION_* values. + * + * <B>Note:</B> setTransactionIsolation cannot be called while + * in the middle of a transaction + * + * @param level one of the TRANSACTION_* isolation values with + * the exception of TRANSACTION_NONE; some databases may + * not support other values + * @exception SQLException if a database access error occurs + * @see java.sql.DatabaseMetaData#supportsTransactionIsolationLevel + */ + public void setTransactionIsolation(int level) throws SQLException + { + String q = "SET TRANSACTION ISOLATION LEVEL"; + + switch(level) { + + case java.sql.Connection.TRANSACTION_READ_COMMITTED: + ExecSQL(q + " READ COMMITTED"); + return; + + case java.sql.Connection.TRANSACTION_SERIALIZABLE: + ExecSQL(q + " SERIALIZABLE"); + return; + + default: + throw new PSQLException("postgresql.con.isolevel",new Integer(level)); + } + } + + /** + * Get this Connection's current transaction isolation mode. + * + * @return the current TRANSACTION_* mode value + * @exception SQLException if a database access error occurs + */ + public int getTransactionIsolation() throws SQLException + { + ExecSQL("show xactisolevel"); + + SQLWarning w = getWarnings(); + if (w != null) { + if (w.getMessage().indexOf("READ COMMITTED") != -1) return java.sql.Connection.TRANSACTION_READ_COMMITTED; else + if (w.getMessage().indexOf("READ UNCOMMITTED") != -1) return java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; else + if (w.getMessage().indexOf("REPEATABLE READ") != -1) return java.sql.Connection.TRANSACTION_REPEATABLE_READ; else + if (w.getMessage().indexOf("SERIALIZABLE") != -1) return java.sql.Connection.TRANSACTION_SERIALIZABLE; + } + return java.sql.Connection.TRANSACTION_READ_COMMITTED; + } + + /** + * The first warning reported by calls on this Connection is + * returned. + * + * <B>Note:</B> Sebsequent warnings will be changed to this + * SQLWarning + * + * @return the first SQLWarning or null + * @exception SQLException if a database access error occurs + */ + public SQLWarning getWarnings() throws SQLException + { + return firstWarning; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this connection. + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + firstWarning = null; + } + + /** + * This overides the method in org.postgresql.Connection and returns a + * ResultSet. + */ + protected java.sql.ResultSet getResultSet(org.postgresql.Connection conn, Field[] fields, Vector tuples, String status, int updateCount) throws SQLException + { + return new org.postgresql.jdbc2.ResultSet((org.postgresql.jdbc2.Connection)conn,fields,tuples,status,updateCount); + } + + // ***************** + // JDBC 2 extensions + // ***************** + + public java.sql.Statement createStatement(int resultSetType,int resultSetConcurrency) throws SQLException + { + // normal create followed by 2 sets? + throw org.postgresql.Driver.notImplemented(); + } + + public java.sql.PreparedStatement prepareStatement(String sql,int resultSetType,int resultSetConcurrency) throws SQLException + { + // normal prepare followed by 2 sets? + throw org.postgresql.Driver.notImplemented(); + } + + public java.sql.CallableStatement prepareCall(String sql,int resultSetType,int resultSetConcurrency) throws SQLException + { + // normal prepare followed by 2 sets? + throw org.postgresql.Driver.notImplemented(); + } + + public int getResultSetConcurrency() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getResultSetType() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.util.Map getTypeMap() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setResultSetConcurrency(int value) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setResultSetType(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setTypeMap(java.util.Map map) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + +} + +// *********************************************************************** + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java b/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java new file mode 100644 index 0000000000000000000000000000000000000000..7f46c1cf7e56ca923bab9c3a4857f6953dc67052 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/DatabaseMetaData.java @@ -0,0 +1,2623 @@ +package org.postgresql.jdbc2; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + +import java.sql.*; +import java.util.*; +import org.postgresql.Field; + +/** + * This class provides information about the database as a whole. + * + * <p>Many of the methods here return lists of information in ResultSets. You + * can use the normal ResultSet methods such as getString and getInt to + * retrieve the data from these ResultSets. If a given form of metadata is + * not available, these methods should throw a SQLException. + * + * <p>Some of these methods take arguments that are String patterns. These + * arguments all have names such as fooPattern. Within a pattern String, + * "%" means match any substring of 0 or more characters, and "_" means + * match any one character. Only metadata entries matching the search + * pattern are returned. if a search pattern argument is set to a null + * ref, it means that argument's criteria should be dropped from the + * search. + * + * <p>A SQLException will be throws if a driver does not support a meta + * data method. In the case of methods that return a ResultSet, either + * a ResultSet (which may be empty) is returned or a SQLException is + * thrown. + * + * @see java.sql.DatabaseMetaData + */ +public class DatabaseMetaData implements java.sql.DatabaseMetaData +{ + Connection connection; // The connection association + + // These define various OID's. Hopefully they will stay constant. + static final int iVarcharOid = 1043; // OID for varchar + static final int iBoolOid = 16; // OID for bool + static final int iInt2Oid = 21; // OID for int2 + static final int iInt4Oid = 23; // OID for int4 + static final int VARHDRSZ = 4; // length for int4 + + // This is a default value for remarks + private static final byte defaultRemarks[]="no remarks".getBytes(); + + public DatabaseMetaData(Connection conn) + { + this.connection = conn; + } + + /** + * Can all the procedures returned by getProcedures be called + * by the current user? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean allProceduresAreCallable() throws SQLException + { + return true; // For now... + } + + /** + * Can all the tables returned by getTable be SELECTed by + * the current user? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean allTablesAreSelectable() throws SQLException + { + return true; // For now... + } + + /** + * What is the URL for this database? + * + * @return the url or null if it cannott be generated + * @exception SQLException if a database access error occurs + */ + public String getURL() throws SQLException + { + return connection.getURL(); + } + + /** + * What is our user name as known to the database? + * + * @return our database user name + * @exception SQLException if a database access error occurs + */ + public String getUserName() throws SQLException + { + return connection.getUserName(); + } + + /** + * Is the database in read-only mode? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return connection.isReadOnly(); + } + + /** + * Are NULL values sorted high? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedHigh() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted low? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedLow() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted at the start regardless of sort order? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedAtStart() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted at the end regardless of sort order? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedAtEnd() throws SQLException + { + return true; + } + + /** + * What is the name of this database product - we hope that it is + * PostgreSQL, so we return that explicitly. + * + * @return the database product name + * @exception SQLException if a database access error occurs + */ + public String getDatabaseProductName() throws SQLException + { + return new String("PostgreSQL"); + } + + /** + * What is the version of this database product. + * + * <p>Note that PostgreSQL 6.3 has a system catalog called pg_version - + * however, select * from pg_version on any database retrieves + * no rows. + * + * <p>For now, we will return the version 6.3 (in the hope that we change + * this driver as often as we change the database) + * + * @return the database version + * @exception SQLException if a database access error occurs + */ + public String getDatabaseProductVersion() throws SQLException + { + return ("6.5.2"); + } + + /** + * What is the name of this JDBC driver? If we don't know this + * we are doing something wrong! + * + * @return the JDBC driver name + * @exception SQLException why? + */ + public String getDriverName() throws SQLException + { + return new String("PostgreSQL Native Driver"); + } + + /** + * What is the version string of this JDBC driver? Again, this is + * static. + * + * @return the JDBC driver name. + * @exception SQLException why? + */ + public String getDriverVersion() throws SQLException + { + return new String(Integer.toString(connection.this_driver.getMajorVersion())+"."+Integer.toString(connection.this_driver.getMinorVersion())); + } + + /** + * What is this JDBC driver's major version number? + * + * @return the JDBC driver major version + */ + public int getDriverMajorVersion() + { + return connection.this_driver.getMajorVersion(); + } + + /** + * What is this JDBC driver's minor version number? + * + * @return the JDBC driver minor version + */ + public int getDriverMinorVersion() + { + return connection.this_driver.getMinorVersion(); + } + + /** + * Does the database store tables in a local file? No - it + * stores them in a file on the server. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean usesLocalFiles() throws SQLException + { + return false; + } + + /** + * Does the database use a file for each table? Well, not really, + * since it doesnt use local files. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean usesLocalFilePerTable() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers + * as case sensitive and as a result store them in mixed case? + * A JDBC-Compliant driver will always return false. + * + * <p>Predicament - what do they mean by "SQL identifiers" - if it + * means the names of the tables and columns, then the answers + * given below are correct - otherwise I don't know. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in upper case? + * + * @return true if so + */ + public boolean storesUpperCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in lower case? + * + * @return true if so + */ + public boolean storesLowerCaseIdentifiers() throws SQLException + { + return true; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in mixed case? + * + * @return true if so + */ + public boolean storesMixedCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as + * case sensitive and as a result store them in mixed case? A + * JDBC compliant driver will always return true. + * + * <p>Predicament - what do they mean by "SQL identifiers" - if it + * means the names of the tables and columns, then the answers + * given below are correct - otherwise I don't know. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException + { + return true; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as + * case insensitive and store them in upper case? + * + * @return true if so + */ + public boolean storesUpperCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as case + * insensitive and store them in lower case? + * + * @return true if so + */ + public boolean storesLowerCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as case + * insensitive and store them in mixed case? + * + * @return true if so + */ + public boolean storesMixedCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * What is the string used to quote SQL identifiers? This returns + * a space if identifier quoting isn't supported. A JDBC Compliant + * driver will always use a double quote character. + * + * <p>If an SQL identifier is a table name, column name, etc. then + * we do not support it. + * + * @return the quoting string + * @exception SQLException if a database access error occurs + */ + public String getIdentifierQuoteString() throws SQLException + { + return null; + } + + /** + * Get a comma separated list of all a database's SQL keywords that + * are NOT also SQL92 keywords. + * + * <p>Within PostgreSQL, the keywords are found in + * src/backend/parser/keywords.c + * + * <p>For SQL Keywords, I took the list provided at + * <a href="http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt"> + * http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt</a> + * which is for SQL3, not SQL-92, but it is close enough for + * this purpose. + * + * @return a comma separated list of keywords we use + * @exception SQLException if a database access error occurs + */ + public String getSQLKeywords() throws SQLException + { + return new String("abort,acl,add,aggregate,append,archive,arch_store,backward,binary,change,cluster,copy,database,delimiters,do,extend,explain,forward,heavy,index,inherits,isnull,light,listen,load,merge,nothing,notify,notnull,oids,purge,rename,replace,retrieve,returns,rule,recipe,setof,stdin,stdout,store,vacuum,verbose,version"); + } + + public String getNumericFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + public String getStringFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + public String getSystemFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + public String getTimeDateFunctions() throws SQLException + { + // XXX-Not Implemented + return ""; + } + + /** + * This is the string that can be used to escape '_' and '%' in + * a search string pattern style catalog search parameters + * + * @return the string used to escape wildcard characters + * @exception SQLException if a database access error occurs + */ + public String getSearchStringEscape() throws SQLException + { + return new String("\\"); + } + + /** + * Get all the "extra" characters that can bew used in unquoted + * identifier names (those beyond a-zA-Z0-9 and _) + * + * <p>From the file src/backend/parser/scan.l, an identifier is + * {letter}{letter_or_digit} which makes it just those listed + * above. + * + * @return a string containing the extra characters + * @exception SQLException if a database access error occurs + */ + public String getExtraNameCharacters() throws SQLException + { + return new String(""); + } + + /** + * Is "ALTER TABLE" with an add column supported? + * Yes for PostgreSQL 6.1 + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsAlterTableWithAddColumn() throws SQLException + { + return true; + } + + /** + * Is "ALTER TABLE" with a drop column supported? + * Yes for PostgreSQL 6.1 + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsAlterTableWithDropColumn() throws SQLException + { + return true; + } + + /** + * Is column aliasing supported? + * + * <p>If so, the SQL AS clause can be used to provide names for + * computed columns or to provide alias names for columns as + * required. A JDBC Compliant driver always returns true. + * + * <p>e.g. + * + * <br><pre> + * select count(C) as C_COUNT from T group by C; + * + * </pre><br> + * should return a column named as C_COUNT instead of count(C) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsColumnAliasing() throws SQLException + { + return true; + } + + /** + * Are concatenations between NULL and non-NULL values NULL? A + * JDBC Compliant driver always returns true + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullPlusNonNullIsNull() throws SQLException + { + return true; + } + + public boolean supportsConvert() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsConvert(int fromType, int toType) throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsTableCorrelationNames() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsDifferentTableCorrelationNames() throws SQLException + { + // XXX-Not Implemented + return false; + } + + /** + * Are expressions in "ORCER BY" lists supported? + * + * <br>e.g. select * from t order by a + b; + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsExpressionsInOrderBy() throws SQLException + { + return true; + } + + /** + * Can an "ORDER BY" clause use columns not in the SELECT? + * I checked it, and you can't. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOrderByUnrelated() throws SQLException + { + return false; + } + + /** + * Is some form of "GROUP BY" clause supported? + * I checked it, and yes it is. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupBy() throws SQLException + { + return true; + } + + /** + * Can a "GROUP BY" clause use columns not in the SELECT? + * I checked it - it seems to allow it + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByUnrelated() throws SQLException + { + return true; + } + + /** + * Can a "GROUP BY" clause add columns not in the SELECT provided + * it specifies all the columns in the SELECT? Does anyone actually + * understand what they mean here? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByBeyondSelect() throws SQLException + { + return true; // For now... + } + + /** + * Is the escape character in "LIKE" clauses supported? A + * JDBC compliant driver always returns true. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsLikeEscapeClause() throws SQLException + { + return true; + } + + /** + * Are multiple ResultSets from a single execute supported? + * Well, I implemented it, but I dont think this is possible from + * the back ends point of view. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMultipleResultSets() throws SQLException + { + return false; + } + + /** + * Can we have multiple transactions open at once (on different + * connections?) + * I guess we can have, since Im relying on it. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMultipleTransactions() throws SQLException + { + return true; + } + + /** + * Can columns be defined as non-nullable. A JDBC Compliant driver + * always returns true. + * + * <p>This changed from false to true in v6.2 of the driver, as this + * support was added to the backend. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsNonNullableColumns() throws SQLException + { + return true; + } + + /** + * Does this driver support the minimum ODBC SQL grammar. This + * grammar is defined at: + * + * <p><a href="http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm">http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm</a> + * + * <p>In Appendix C. From this description, we seem to support the + * ODBC minimal (Level 0) grammar. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMinimumSQLGrammar() throws SQLException + { + return true; + } + + /** + * Does this driver support the Core ODBC SQL grammar. We need + * SQL-92 conformance for this. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCoreSQLGrammar() throws SQLException + { + return false; + } + + /** + * Does this driver support the Extended (Level 2) ODBC SQL + * grammar. We don't conform to the Core (Level 1), so we can't + * conform to the Extended SQL Grammar. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsExtendedSQLGrammar() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 entry level SQL grammar? + * All JDBC Compliant drivers must return true. I think we have + * to support outer joins for this to be true. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92EntryLevelSQL() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 intermediate level SQL + * grammar? Anyone who does not support Entry level cannot support + * Intermediate level. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92IntermediateSQL() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 full SQL grammar? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92FullSQL() throws SQLException + { + return false; + } + + /** + * Is the SQL Integrity Enhancement Facility supported? + * I haven't seen this mentioned anywhere, so I guess not + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsIntegrityEnhancementFacility() throws SQLException + { + return false; + } + + /** + * Is some form of outer join supported? From my knowledge, nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOuterJoins() throws SQLException + { + return false; + } + + /** + * Are full nexted outer joins supported? Well, we dont support any + * form of outer join, so this is no as well + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsFullOuterJoins() throws SQLException + { + return false; + } + + /** + * Is there limited support for outer joins? (This will be true if + * supportFullOuterJoins is true) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsLimitedOuterJoins() throws SQLException + { + return false; + } + + /** + * What is the database vendor's preferred term for "schema" - well, + * we do not provide support for schemas, so lets just use that + * term. + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getSchemaTerm() throws SQLException + { + return new String("Schema"); + } + + /** + * What is the database vendor's preferred term for "procedure" - + * I kind of like "Procedure" myself. + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getProcedureTerm() throws SQLException + { + return new String("Procedure"); + } + + /** + * What is the database vendor's preferred term for "catalog"? - + * we dont have a preferred term, so just use Catalog + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getCatalogTerm() throws SQLException + { + return new String("Catalog"); + } + + /** + * Does a catalog appear at the start of a qualified table name? + * (Otherwise it appears at the end). + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isCatalogAtStart() throws SQLException + { + return false; + } + + /** + * What is the Catalog separator. Hmmm....well, I kind of like + * a period (so we get catalog.table definitions). - I don't think + * PostgreSQL supports catalogs anyhow, so it makes no difference. + * + * @return the catalog separator string + * @exception SQLException if a database access error occurs + */ + public String getCatalogSeparator() throws SQLException + { + // PM Sep 29 97 - changed from "." as we don't support catalogs. + return new String(""); + } + + /** + * Can a schema name be used in a data manipulation statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInDataManipulation() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in a procedure call statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInProcedureCalls() throws SQLException + { + return false; + } + + /** + * Can a schema be used in a table definition statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInTableDefinitions() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in an index definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInIndexDefinitions() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in a privilege definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a data manipulation statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInDataManipulation() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a procedure call statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInProcedureCalls() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a table definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInTableDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in an index definition? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInIndexDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a privilege definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException + { + return false; + } + + /** + * We support cursors for gets only it seems. I dont see a method + * to get a positioned delete. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsPositionedDelete() throws SQLException + { + return false; // For now... + } + + /** + * Is positioned UPDATE supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsPositionedUpdate() throws SQLException + { + return false; // For now... + } + + public boolean supportsSelectForUpdate() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsStoredProcedures() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInComparisons() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInExists() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInIns() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsSubqueriesInQuantifieds() throws SQLException + { + // XXX-Not Implemented + return false; + } + + public boolean supportsCorrelatedSubqueries() throws SQLException + { + // XXX-Not Implemented + return false; + } + + /** + * Is SQL UNION supported? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnion() throws SQLException + { + return false; + } + + /** + * Is SQL UNION ALL supported? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnionAll() throws SQLException + { + return false; + } + + /** + * In PostgreSQL, Cursors are only open within transactions. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenCursorsAcrossCommit() throws SQLException + { + return false; + } + + /** + * Do we support open cursors across multiple transactions? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenCursorsAcrossRollback() throws SQLException + { + return false; + } + + /** + * Can statements remain open across commits? They may, but + * this driver cannot guarentee that. In further reflection. + * we are talking a Statement object jere, so the answer is + * yes, since the Statement is only a vehicle to ExecSQL() + * + * @return true if they always remain open; false otherwise + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenStatementsAcrossCommit() throws SQLException + { + return true; + } + + /** + * Can statements remain open across rollbacks? They may, but + * this driver cannot guarentee that. In further contemplation, + * we are talking a Statement object here, so the answer is yes, + * since the Statement is only a vehicle to ExecSQL() in Connection + * + * @return true if they always remain open; false otherwise + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenStatementsAcrossRollback() throws SQLException + { + return true; + } + + /** + * How many hex characters can you have in an inline binary literal + * + * @return the max literal length + * @exception SQLException if a database access error occurs + */ + public int getMaxBinaryLiteralLength() throws SQLException + { + return 0; // For now... + } + + /** + * What is the maximum length for a character literal + * I suppose it is 8190 (8192 - 2 for the quotes) + * + * @return the max literal length + * @exception SQLException if a database access error occurs + */ + public int getMaxCharLiteralLength() throws SQLException + { + return 8190; + } + + /** + * Whats the limit on column name length. The description of + * pg_class would say '32' (length of pg_class.relname) - we + * should probably do a query for this....but.... + * + * @return the maximum column name length + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum number of columns in a "GROUP BY" clause? + * + * @return the max number of columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInGroupBy() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What's the maximum number of columns allowed in an index? + * 6.0 only allowed one column, but 6.1 introduced multi-column + * indices, so, theoretically, its all of them. + * + * @return max number of columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInIndex() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What's the maximum number of columns in an "ORDER BY clause? + * Theoretically, all of them! + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInOrderBy() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What is the maximum number of columns in a "SELECT" list? + * Theoretically, all of them! + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInSelect() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What is the maximum number of columns in a table? From the + * create_table(l) manual page... + * + * <p>"The new class is created as a heap with no initial data. A + * class can have no more than 1600 attributes (realistically, + * this is limited by the fact that tuple sizes must be less than + * 8192 bytes)..." + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInTable() throws SQLException + { + return 1600; + } + + /** + * How many active connection can we have at a time to this + * database? Well, since it depends on postmaster, which just + * does a listen() followed by an accept() and fork(), its + * basically very high. Unless the system runs out of processes, + * it can be 65535 (the number of aux. ports on a TCP/IP system). + * I will return 8192 since that is what even the largest system + * can realistically handle, + * + * @return the maximum number of connections + * @exception SQLException if a database access error occurs + */ + public int getMaxConnections() throws SQLException + { + return 8192; + } + + /** + * What is the maximum cursor name length (the same as all + * the other F***** identifiers!) + * + * @return max cursor name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxCursorNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum length of an index (in bytes)? Now, does + * the spec. mean name of an index (in which case its 32, the + * same as a table) or does it mean length of an index element + * (in which case its 8192, the size of a row) or does it mean + * the number of rows it can access (in which case it 2^32 - + * a 4 byte OID number)? I think its the length of an index + * element, personally, so Im setting it to 8192. + * + * @return max index length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxIndexLength() throws SQLException + { + return 8192; + } + + public int getMaxSchemaNameLength() throws SQLException + { + // XXX-Not Implemented + return 0; + } + + /** + * What is the maximum length of a procedure name? + * (length of pg_proc.proname used) - again, I really + * should do a query here to get it. + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxProcedureNameLength() throws SQLException + { + return 32; + } + + public int getMaxCatalogNameLength() throws SQLException + { + // XXX-Not Implemented + return 0; + } + + /** + * What is the maximum length of a single row? (not including + * blobs). 8192 is defined in PostgreSQL. + * + * @return max row size in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxRowSize() throws SQLException + { + return 8192; + } + + /** + * Did getMaxRowSize() include LONGVARCHAR and LONGVARBINARY + * blobs? We don't handle blobs yet + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean doesMaxRowSizeIncludeBlobs() throws SQLException + { + return false; + } + + /** + * What is the maximum length of a SQL statement? + * + * @return max length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxStatementLength() throws SQLException + { + return 8192; + } + + /** + * How many active statements can we have open at one time to + * this database? Basically, since each Statement downloads + * the results as the query is executed, we can have many. However, + * we can only really have one statement per connection going + * at once (since they are executed serially) - so we return + * one. + * + * @return the maximum + * @exception SQLException if a database access error occurs + */ + public int getMaxStatements() throws SQLException + { + return 1; + } + + /** + * What is the maximum length of a table name? This was found + * from pg_class.relname length + * + * @return max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxTableNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum number of tables that can be specified + * in a SELECT? Theoretically, this is the same number as the + * number of tables allowable. In practice tho, it is much smaller + * since the number of tables is limited by the statement, we + * return 1024 here - this is just a number I came up with (being + * the number of tables roughly of three characters each that you + * can fit inside a 8192 character buffer with comma separators). + * + * @return the maximum + * @exception SQLException if a database access error occurs + */ + public int getMaxTablesInSelect() throws SQLException + { + return 1024; + } + + /** + * What is the maximum length of a user name? Well, we generally + * use UNIX like user names in PostgreSQL, so I think this would + * be 8. However, showing the schema for pg_user shows a length + * for username of 32. + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxUserNameLength() throws SQLException + { + return 32; + } + + + /** + * What is the database's default transaction isolation level? We + * do not support this, so all transactions are SERIALIZABLE. + * + * @return the default isolation level + * @exception SQLException if a database access error occurs + * @see Connection + */ + public int getDefaultTransactionIsolation() throws SQLException + { + return Connection.TRANSACTION_READ_COMMITTED; + } + + /** + * Are transactions supported? If not, commit and rollback are noops + * and the isolation level is TRANSACTION_NONE. We do support + * transactions. + * + * @return true if transactions are supported + * @exception SQLException if a database access error occurs + */ + public boolean supportsTransactions() throws SQLException + { + return true; + } + + /** + * Does the database support the given transaction isolation level? + * We only support TRANSACTION_SERIALIZABLE and TRANSACTION_READ_COMMITTED + * + * @param level the values are defined in java.sql.Connection + * @return true if so + * @exception SQLException if a database access error occurs + * @see Connection + */ + public boolean supportsTransactionIsolationLevel(int level) throws SQLException + { + if (level == Connection.TRANSACTION_SERIALIZABLE || + level == Connection.TRANSACTION_READ_COMMITTED) + return true; + else + return false; + } + + /** + * Are both data definition and data manipulation transactions + * supported? I checked it, and could not do a CREATE TABLE + * within a transaction, so I am assuming that we don't + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException + { + return false; + } + + /** + * Are only data manipulation statements withing a transaction + * supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsDataManipulationTransactionsOnly() throws SQLException + { + return true; + } + + /** + * Does a data definition statement within a transaction force + * the transaction to commit? I think this means something like: + * + * <p><pre> + * CREATE TABLE T (A INT); + * INSERT INTO T (A) VALUES (2); + * BEGIN; + * UPDATE T SET A = A + 1; + * CREATE TABLE X (A INT); + * SELECT A FROM T INTO X; + * COMMIT; + * </pre><p> + * + * does the CREATE TABLE call cause a commit? The answer is no. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean dataDefinitionCausesTransactionCommit() throws SQLException + { + return false; + } + + /** + * Is a data definition statement within a transaction ignored? + * It seems to be (from experiment in previous method) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean dataDefinitionIgnoredInTransactions() throws SQLException + { + return true; + } + + /** + * Get a description of stored procedures available in a catalog + * + * <p>Only procedure descriptions matching the schema and procedure + * name criteria are returned. They are ordered by PROCEDURE_SCHEM + * and PROCEDURE_NAME + * + * <p>Each procedure description has the following columns: + * <ol> + * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) + * <li><b>PROCEDURE_SCHEM</b> String => procedure schema (may be null) + * <li><b>PROCEDURE_NAME</b> String => procedure name + * <li><b>Field 4</b> reserved (make it null) + * <li><b>Field 5</b> reserved (make it null) + * <li><b>Field 6</b> reserved (make it null) + * <li><b>REMARKS</b> String => explanatory comment on the procedure + * <li><b>PROCEDURE_TYPE</b> short => kind of procedure + * <ul> + * <li> procedureResultUnknown - May return a result + * <li> procedureNoResult - Does not return a result + * <li> procedureReturnsResult - Returns a result + * </ul> + * </ol> + * + * @param catalog - a catalog name; "" retrieves those without a + * catalog; null means drop catalog name from criteria + * @param schemaParrern - a schema name pattern; "" retrieves those + * without a schema - we ignore this parameter + * @param procedureNamePattern - a procedure name pattern + * @return ResultSet - each row is a procedure description + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException + { + // the field descriptors for the new ResultSet + Field f[] = new Field[8]; + java.sql.ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + byte remarks[] = defaultRemarks; + + f[0] = new Field(connection, "PROCEDURE_CAT", iVarcharOid, 32); + f[1] = new Field(connection, "PROCEDURE_SCHEM", iVarcharOid, 32); + f[2] = new Field(connection, "PROCEDURE_NAME", iVarcharOid, 32); + f[3] = f[4] = f[5] = null; // reserved, must be null for now + f[6] = new Field(connection, "REMARKS", iVarcharOid, 8192); + f[7] = new Field(connection, "PROCEDURE_TYPE", iInt2Oid, 2); + + // If the pattern is null, then set it to the default + if(procedureNamePattern==null) + procedureNamePattern="%"; + + r = connection.ExecSQL("select proname, proretset from pg_proc where proname like '"+procedureNamePattern.toLowerCase()+"' order by proname"); + + while (r.next()) + { + byte[][] tuple = new byte[8][0]; + + tuple[0] = null; // Catalog name + tuple[1] = null; // Schema name + tuple[2] = r.getBytes(1); // Procedure name + tuple[3] = tuple[4] = tuple[5] = null; // Reserved + tuple[6] = remarks; // Remarks + + if (r.getBoolean(2)) + tuple[7] = Integer.toString(java.sql.DatabaseMetaData.procedureReturnsResult).getBytes(); + else + tuple[7] = Integer.toString(java.sql.DatabaseMetaData.procedureNoResult).getBytes(); + + v.addElement(tuple); + } + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of a catalog's stored procedure parameters + * and result columns. + * + * <p>Only descriptions matching the schema, procedure and parameter + * name criteria are returned. They are ordered by PROCEDURE_SCHEM + * and PROCEDURE_NAME. Within this, the return value, if any, is + * first. Next are the parameter descriptions in call order. The + * column descriptions follow in column number order. + * + * <p>Each row in the ResultSet is a parameter description or column + * description with the following fields: + * <ol> + * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) + * <li><b>PROCEDURE_SCHE</b>M String => procedure schema (may be null) + * <li><b>PROCEDURE_NAME</b> String => procedure name + * <li><b>COLUMN_NAME</b> String => column/parameter name + * <li><b>COLUMN_TYPE</b> Short => kind of column/parameter: + * <ul><li>procedureColumnUnknown - nobody knows + * <li>procedureColumnIn - IN parameter + * <li>procedureColumnInOut - INOUT parameter + * <li>procedureColumnOut - OUT parameter + * <li>procedureColumnReturn - procedure return value + * <li>procedureColumnResult - result column in ResultSet + * </ul> + * <li><b>DATA_TYPE</b> short => SQL type from java.sql.Types + * <li><b>TYPE_NAME</b> String => SQL type name + * <li><b>PRECISION</b> int => precision + * <li><b>LENGTH</b> int => length in bytes of data + * <li><b>SCALE</b> short => scale + * <li><b>RADIX</b> short => radix + * <li><b>NULLABLE</b> short => can it contain NULL? + * <ul><li>procedureNoNulls - does not allow NULL values + * <li>procedureNullable - allows NULL values + * <li>procedureNullableUnknown - nullability unknown + * <li><b>REMARKS</b> String => comment describing parameter/column + * </ol> + * @param catalog This is ignored in org.postgresql, advise this is set to null + * @param schemaPattern This is ignored in org.postgresql, advise this is set to null + * @param procedureNamePattern a procedure name pattern + * @param columnNamePattern a column name pattern + * @return each row is a stored procedure parameter or column description + * @exception SQLException if a database-access error occurs + * @see #getSearchStringEscape + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException + { + if(procedureNamePattern==null) + procedureNamePattern="%"; + + if(columnNamePattern==null) + columnNamePattern="%"; + + // for now, this returns an empty result set. + Field f[] = new Field[13]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("PROCEDURE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("PROCEDURE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("PROCEDURE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[4] = new Field(connection, new String("COLUMN_TYPE"), iInt2Oid, 2); + f[5] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[6] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[7] = new Field(connection, new String("PRECISION"), iInt4Oid, 4); + f[8] = new Field(connection, new String("LENGTH"), iInt4Oid, 4); + f[9] = new Field(connection, new String("SCALE"), iInt2Oid, 2); + f[10] = new Field(connection, new String("RADIX"), iInt2Oid, 2); + f[11] = new Field(connection, new String("NULLABLE"), iInt2Oid, 2); + f[12] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); + + // add query loop here + + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of tables available in a catalog. + * + * <p>Only table descriptions matching the catalog, schema, table + * name and type criteria are returned. They are ordered by + * TABLE_TYPE, TABLE_SCHEM and TABLE_NAME. + * + * <p>Each table description has the following columns: + * + * <ol> + * <li><b>TABLE_CAT</b> String => table catalog (may be null) + * <li><b>TABLE_SCHEM</b> String => table schema (may be null) + * <li><b>TABLE_NAME</b> String => table name + * <li><b>TABLE_TYPE</b> String => table type. Typical types are "TABLE", + * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL + * TEMPORARY", "ALIAS", "SYNONYM". + * <li><b>REMARKS</b> String => explanatory comment on the table + * </ol> + * + * <p>The valid values for the types parameter are: + * "TABLE", "INDEX", "LARGE OBJECT", "SEQUENCE", "SYSTEM TABLE" and + * "SYSTEM INDEX" + * + * @param catalog a catalog name; For org.postgresql, this is ignored, and + * should be set to null + * @param schemaPattern a schema name pattern; For org.postgresql, this is ignored, and + * should be set to null + * @param tableNamePattern a table name pattern. For all tables this should be "%" + * @param types a list of table types to include; null returns + * all types + * @return each row is a table description + * @exception SQLException if a database-access error occurs. + */ + public java.sql.ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException + { + // Handle default value for types + if(types==null) + types = defaultTableTypes; + + if(tableNamePattern==null) + tableNamePattern="%"; + + // the field descriptors for the new ResultSet + Field f[] = new Field[5]; + java.sql.ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TABLE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("TABLE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("TABLE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("TABLE_TYPE"), iVarcharOid, 32); + f[4] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); + + // Now form the query + StringBuffer sql = new StringBuffer("select relname,oid from pg_class where ("); + boolean notFirst=false; + for(int i=0;i<types.length;i++) { + if(notFirst) + sql.append(" or "); + for(int j=0;j<getTableTypes.length;j++) + if(getTableTypes[j][0].equals(types[i])) { + sql.append(getTableTypes[j][1]); + notFirst=true; + } + } + + // Added by Stefan Andreasen <stefan@linux.kapow.dk> + // Now take the pattern into account + sql.append(") and relname like '"); + sql.append(tableNamePattern.toLowerCase()); + sql.append("'"); + + // Now run the query + r = connection.ExecSQL(sql.toString()); + + byte remarks[]; + + while (r.next()) + { + byte[][] tuple = new byte[5][0]; + + // Fetch the description for the table (if any) + java.sql.ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(2)); + if(((org.postgresql.ResultSet)dr).getTupleCount()==1) { + dr.next(); + remarks = dr.getBytes(1); + } else + remarks = defaultRemarks; + dr.close(); + + tuple[0] = null; // Catalog name + tuple[1] = null; // Schema name + tuple[2] = r.getBytes(1); // Table name + tuple[3] = null; // Table type + tuple[4] = remarks; // Remarks + v.addElement(tuple); + } + r.close(); + return new ResultSet(connection, f, v, "OK", 1); + } + + // This array contains the valid values for the types argument + // in getTables(). + // + // Each supported type consists of it's name, and the sql where + // clause to retrieve that value. + // + // IMPORTANT: the query must be enclosed in ( ) + private static final String getTableTypes[][] = { + {"TABLE", "(relkind='r' and relname !~ '^pg_' and relname !~ '^xinv')"}, + {"INDEX", "(relkind='i' and relname !~ '^pg_' and relname !~ '^xinx')"}, + {"LARGE OBJECT", "(relkind='r' and relname ~ '^xinv')"}, + {"SEQUENCE", "(relkind='S' and relname !~ '^pg_')"}, + {"SYSTEM TABLE", "(relkind='r' and relname ~ '^pg_')"}, + {"SYSTEM INDEX", "(relkind='i' and relname ~ '^pg_')"} + }; + + // These are the default tables, used when NULL is passed to getTables + // The choice of these provide the same behaviour as psql's \d + private static final String defaultTableTypes[] = { + "TABLE","INDEX","SEQUENCE" + }; + + /** + * Get the schema names available in this database. The results + * are ordered by schema name. + * + * <P>The schema column is: + * <OL> + * <LI><B>TABLE_SCHEM</B> String => schema name + * </OL> + * + * @return ResultSet each row has a single String column that is a + * schema name + */ + public java.sql.ResultSet getSchemas() throws SQLException + { + // We don't use schemas, so we simply return a single schema name "". + // + Field f[] = new Field[1]; + Vector v = new Vector(); + byte[][] tuple = new byte[1][0]; + f[0] = new Field(connection,new String("TABLE_SCHEM"),iVarcharOid,32); + tuple[0] = "".getBytes(); + v.addElement(tuple); + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get the catalog names available in this database. The results + * are ordered by catalog name. + * + * <P>The catalog column is: + * <OL> + * <LI><B>TABLE_CAT</B> String => catalog name + * </OL> + * + * @return ResultSet each row has a single String column that is a + * catalog name + */ + public java.sql.ResultSet getCatalogs() throws SQLException + { + // We don't use catalogs, so we simply return a single catalog name "". + Field f[] = new Field[1]; + Vector v = new Vector(); + byte[][] tuple = new byte[1][0]; + f[0] = new Field(connection,new String("TABLE_CAT"),iVarcharOid,32); + tuple[0] = "".getBytes(); + v.addElement(tuple); + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get the table types available in this database. The results + * are ordered by table type. + * + * <P>The table type is: + * <OL> + * <LI><B>TABLE_TYPE</B> String => table type. Typical types are "TABLE", + * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", + * "LOCAL TEMPORARY", "ALIAS", "SYNONYM". + * </OL> + * + * @return ResultSet each row has a single String column that is a + * table type + */ + public java.sql.ResultSet getTableTypes() throws SQLException + { + Field f[] = new Field[1]; + Vector v = new Vector(); + byte[][] tuple = new byte[1][0]; + f[0] = new Field(connection,new String("TABLE_TYPE"),iVarcharOid,32); + for(int i=0;i<getTableTypes.length;i++) { + tuple[0] = getTableTypes[i][0].getBytes(); + v.addElement(tuple); + } + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get a description of table columns available in a catalog. + * + * <P>Only column descriptions matching the catalog, schema, table + * and column name criteria are returned. They are ordered by + * TABLE_SCHEM, TABLE_NAME and ORDINAL_POSITION. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => column size. For char or date + * types this is the maximum number of characters, for numeric or + * decimal types this is precision. + * <LI><B>BUFFER_LENGTH</B> is not used. + * <LI><B>DECIMAL_DIGITS</B> int => the number of fractional digits + * <LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2) + * <LI><B>NULLABLE</B> int => is NULL allowed? + * <UL> + * <LI> columnNoNulls - might not allow NULL values + * <LI> columnNullable - definitely allows NULL values + * <LI> columnNullableUnknown - nullability unknown + * </UL> + * <LI><B>REMARKS</B> String => comment describing column (may be null) + * <LI><B>COLUMN_DEF</B> String => default value (may be null) + * <LI><B>SQL_DATA_TYPE</B> int => unused + * <LI><B>SQL_DATETIME_SUB</B> int => unused + * <LI><B>CHAR_OCTET_LENGTH</B> int => for char types the + * maximum number of bytes in the column + * <LI><B>ORDINAL_POSITION</B> int => index of column in table + * (starting at 1) + * <LI><B>IS_NULLABLE</B> String => "NO" means column definitely + * does not allow NULL values; "YES" means the column might + * allow NULL values. An empty string means nobody knows. + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schemaPattern a schema name pattern; "" retrieves those + * without a schema + * @param tableNamePattern a table name pattern + * @param columnNamePattern a column name pattern + * @return ResultSet each row is a column description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException + { + // the field descriptors for the new ResultSet + Field f[] = new Field[18]; + java.sql.ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TABLE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("TABLE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("TABLE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[4] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[5] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[6] = new Field(connection, new String("COLUMN_SIZE"), iInt4Oid, 4); + f[7] = new Field(connection, new String("BUFFER_LENGTH"), iVarcharOid, 32); + f[8] = new Field(connection, new String("DECIMAL_DIGITS"), iInt4Oid, 4); + f[9] = new Field(connection, new String("NUM_PREC_RADIX"), iInt4Oid, 4); + f[10] = new Field(connection, new String("NULLABLE"), iInt4Oid, 4); + f[11] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); + f[12] = new Field(connection, new String("COLUMN_DEF"), iVarcharOid, 32); + f[13] = new Field(connection, new String("SQL_DATA_TYPE"), iInt4Oid, 4); + f[14] = new Field(connection, new String("SQL_DATETIME_SUB"), iInt4Oid, 4); + f[15] = new Field(connection, new String("CHAR_OCTET_LENGTH"), iVarcharOid, 32); + f[16] = new Field(connection, new String("ORDINAL_POSITION"), iInt4Oid,4); + f[17] = new Field(connection, new String("IS_NULLABLE"), iVarcharOid, 32); + + // Added by Stefan Andreasen <stefan@linux.kapow.dk> + // If the pattern are null then set them to % + if (tableNamePattern == null) tableNamePattern="%"; + if (columnNamePattern == null) columnNamePattern="%"; + + // Now form the query + // Modified by Stefan Andreasen <stefan@linux.kapow.dk> + r = connection.ExecSQL("select a.oid,c.relname,a.attname,a.atttypid,a.attnum,a.attnotnull,a.attlen,a.atttypmod from pg_class c, pg_attribute a where a.attrelid=c.oid and c.relname like '"+tableNamePattern.toLowerCase()+"' and a.attname like '"+columnNamePattern.toLowerCase()+"' and a.attnum>0 order by c.relname,a.attnum"); + + byte remarks[]; + + while(r.next()) { + byte[][] tuple = new byte[18][0]; + + // Fetch the description for the table (if any) + java.sql.ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(1)); + if(((org.postgresql.ResultSet)dr).getTupleCount()==1) { + dr.next(); + tuple[11] = dr.getBytes(1); + } else + tuple[11] = defaultRemarks; + + dr.close(); + + tuple[0] = "".getBytes(); // Catalog name + tuple[1] = "".getBytes(); // Schema name + tuple[2] = r.getBytes(2); // Table name + tuple[3] = r.getBytes(3); // Column name + + dr = connection.ExecSQL("select typname from pg_type where oid = "+r.getString(4)); + dr.next(); + String typname=dr.getString(1); + dr.close(); + tuple[4] = Integer.toString(Field.getSQLType(typname)).getBytes(); // Data type + tuple[5] = typname.getBytes(); // Type name + + // Column size + // Looking at the psql source, + // I think the length of a varchar as specified when the table was created + // should be extracted from atttypmod which contains this length + sizeof(int32) + if (typname.equals("bpchar") || typname.equals("varchar")) { + int atttypmod = r.getInt(8); + tuple[6] = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0).getBytes(); + } else + tuple[6] = r.getBytes(7); + + tuple[7] = null; // Buffer length + + tuple[8] = "0".getBytes(); // Decimal Digits - how to get this? + tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal + + // tuple[10] is below + // tuple[11] is above + + tuple[12] = null; // column default + + tuple[13] = null; // sql data type (unused) + tuple[14] = null; // sql datetime sub (unused) + + tuple[15] = tuple[6]; // char octet length + + tuple[16] = r.getBytes(5); // ordinal position + + String nullFlag = r.getString(6); + tuple[10] = Integer.toString(nullFlag.equals("f")?java.sql.DatabaseMetaData.columnNullable:java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable + tuple[17] = (nullFlag.equals("f")?"YES":"NO").getBytes(); // is nullable + + v.addElement(tuple); + } + r.close(); + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of the access rights for a table's columns. + * + * <P>Only privileges matching the column name criteria are + * returned. They are ordered by COLUMN_NAME and PRIVILEGE. + * + * <P>Each privilige description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>GRANTOR</B> => grantor of access (may be null) + * <LI><B>GRANTEE</B> String => grantee of access + * <LI><B>PRIVILEGE</B> String => name of access (SELECT, + * INSERT, UPDATE, REFRENCES, ...) + * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted + * to grant to others; "NO" if not; null if unknown + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @param columnNamePattern a column name pattern + * @return ResultSet each row is a column privilege description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException + { + Field f[] = new Field[8]; + Vector v = new Vector(); + + if(table==null) + table="%"; + + if(columnNamePattern==null) + columnNamePattern="%"; + else + columnNamePattern=columnNamePattern.toLowerCase(); + + f[0] = new Field(connection,new String("TABLE_CAT"),iVarcharOid,32); + f[1] = new Field(connection,new String("TABLE_SCHEM"),iVarcharOid,32); + f[2] = new Field(connection,new String("TABLE_NAME"),iVarcharOid,32); + f[3] = new Field(connection,new String("COLUMN_NAME"),iVarcharOid,32); + f[4] = new Field(connection,new String("GRANTOR"),iVarcharOid,32); + f[5] = new Field(connection,new String("GRANTEE"),iVarcharOid,32); + f[6] = new Field(connection,new String("PRIVILEGE"),iVarcharOid,32); + f[7] = new Field(connection,new String("IS_GRANTABLE"),iVarcharOid,32); + + // This is taken direct from the psql source + java.sql.ResultSet r = connection.ExecSQL("SELECT relname, relacl FROM pg_class, pg_user WHERE ( relkind = 'r' OR relkind = 'i') and relname !~ '^pg_' and relname !~ '^xin[vx][0-9]+' and usesysid = relowner and relname like '"+table.toLowerCase()+"' ORDER BY relname"); + while(r.next()) { + byte[][] tuple = new byte[8][0]; + tuple[0] = tuple[1]= "".getBytes(); + DriverManager.println("relname=\""+r.getString(1)+"\" relacl=\""+r.getString(2)+"\""); + + // For now, don't add to the result as relacl needs to be processed. + //v.addElement(tuple); + } + + return new ResultSet(connection,f,v,"OK",1); + } + + /** + * Get a description of the access rights for each table available + * in a catalog. + * + * <P>Only privileges matching the schema and table name + * criteria are returned. They are ordered by TABLE_SCHEM, + * TABLE_NAME, and PRIVILEGE. + * + * <P>Each privilige description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>GRANTOR</B> => grantor of access (may be null) + * <LI><B>GRANTEE</B> String => grantee of access + * <LI><B>PRIVILEGE</B> String => name of access (SELECT, + * INSERT, UPDATE, REFRENCES, ...) + * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted + * to grant to others; "NO" if not; null if unknown + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schemaPattern a schema name pattern; "" retrieves those + * without a schema + * @param tableNamePattern a table name pattern + * @return ResultSet each row is a table privilege description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of a table's optimal set of columns that + * uniquely identifies a row. They are ordered by SCOPE. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>SCOPE</B> short => actual scope of result + * <UL> + * <LI> bestRowTemporary - very temporary, while using row + * <LI> bestRowTransaction - valid for remainder of current transaction + * <LI> bestRowSession - valid for remainder of current session + * </UL> + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => precision + * <LI><B>BUFFER_LENGTH</B> int => not used + * <LI><B>DECIMAL_DIGITS</B> short => scale + * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column + * like an Oracle ROWID + * <UL> + * <LI> bestRowUnknown - may or may not be pseudo column + * <LI> bestRowNotPseudo - is NOT a pseudo column + * <LI> bestRowPseudo - is a pseudo column + * </UL> + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @param scope the scope of interest; use same values as SCOPE + * @param nullable include columns that are nullable? + * @return ResultSet each row is a column description + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException + { + // for now, this returns an empty result set. + Field f[] = new Field[8]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("SCOPE"), iInt2Oid, 2); + f[1] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[2] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[3] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[4] = new Field(connection, new String("COLUMN_SIZE"), iInt4Oid, 4); + f[5] = new Field(connection, new String("BUFFER_LENGTH"), iInt4Oid, 4); + f[6] = new Field(connection, new String("DECIMAL_DIGITS"), iInt2Oid, 2); + f[7] = new Field(connection, new String("PSEUDO_COLUMN"), iInt2Oid, 2); + + return new ResultSet(connection, f, v, "OK", 1); + } + + /** + * Get a description of a table's columns that are automatically + * updated when any value in a row is updated. They are + * unordered. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>SCOPE</B> short => is not used + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => precision + * <LI><B>BUFFER_LENGTH</B> int => length of column value in bytes + * <LI><B>DECIMAL_DIGITS</B> short => scale + * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column + * like an Oracle ROWID + * <UL> + * <LI> versionColumnUnknown - may or may not be pseudo column + * <LI> versionColumnNotPseudo - is NOT a pseudo column + * <LI> versionColumnPseudo - is a pseudo column + * </UL> + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @return ResultSet each row is a column description + */ + public java.sql.ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of a table's primary key columns. They + * are ordered by COLUMN_NAME. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>KEY_SEQ</B> short => sequence number within primary key + * <LI><B>PK_NAME</B> String => primary key name (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a primary key column description + */ + public java.sql.ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException + { + return connection.createStatement().executeQuery("SELECT " + + "'' as TABLE_CAT," + + "'' AS TABLE_SCHEM," + + "bc.relname AS TABLE_NAME," + + "a.attname AS COLUMN_NAME," + + "a.attnum as KEY_SEQ,"+ + "ic.relname as PK_NAME " + + " FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a" + + " WHERE bc.relkind = 'r' " + // -- not indices + " and upper(bc.relname) = upper('"+table+"')" + + " and i.indrelid = bc.oid" + + " and i.indexrelid = ic.oid" + + " and ic.oid = a.attrelid" + + " and i.indisprimary='t' " + + " ORDER BY table_name, pk_name, key_seq" + ); + } + + /** + * Get a description of the primary key columns that are + * referenced by a table's foreign key columns (the primary keys + * imported by a table). They are ordered by PKTABLE_CAT, + * PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ. + * + * <P>Each primary key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog + * being imported (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema + * being imported (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * being imported + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * being imported + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key name (may be null) + * <LI><B>PK_NAME</B> String => primary key name (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a primary key column description + * @see #getExportedKeys + */ + public java.sql.ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of a foreign key columns that reference a + * table's primary key columns (the foreign keys exported by a + * table). They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, + * FKTABLE_NAME, and KEY_SEQ. + * + * <P>Each foreign key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * being exported + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * being exported + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) + * <LI><B>PK_NAME</B> String => primary key identifier (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a foreign key column description + * @see #getImportedKeys + */ + public java.sql.ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of the foreign key columns in the foreign key + * table that reference the primary key columns of the primary key + * table (describe how one table imports another's key.) This + * should normally return a single foreign key/primary key pair + * (most tables only import a foreign key from a table once.) They + * are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and + * KEY_SEQ. + * + * <P>Each foreign key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * being exported + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * being exported + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) + * <LI><B>PK_NAME</B> String => primary key identifier (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a foreign key column description + * @see #getImportedKeys + */ + public java.sql.ResultSet getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException + { + // XXX-Not Implemented + return null; + } + + /** + * Get a description of all the standard SQL types supported by + * this database. They are ordered by DATA_TYPE and then by how + * closely the data type maps to the corresponding JDBC SQL type. + * + * <P>Each type description has the following columns: + * <OL> + * <LI><B>TYPE_NAME</B> String => Type name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>PRECISION</B> int => maximum precision + * <LI><B>LITERAL_PREFIX</B> String => prefix used to quote a literal + * (may be null) + * <LI><B>LITERAL_SUFFIX</B> String => suffix used to quote a literal + (may be null) + * <LI><B>CREATE_PARAMS</B> String => parameters used in creating + * the type (may be null) + * <LI><B>NULLABLE</B> short => can you use NULL for this type? + * <UL> + * <LI> typeNoNulls - does not allow NULL values + * <LI> typeNullable - allows NULL values + * <LI> typeNullableUnknown - nullability unknown + * </UL> + * <LI><B>CASE_SENSITIVE</B> boolean=> is it case sensitive? + * <LI><B>SEARCHABLE</B> short => can you use "WHERE" based on this type: + * <UL> + * <LI> typePredNone - No support + * <LI> typePredChar - Only supported with WHERE .. LIKE + * <LI> typePredBasic - Supported except for WHERE .. LIKE + * <LI> typeSearchable - Supported for all WHERE .. + * </UL> + * <LI><B>UNSIGNED_ATTRIBUTE</B> boolean => is it unsigned? + * <LI><B>FIXED_PREC_SCALE</B> boolean => can it be a money value? + * <LI><B>AUTO_INCREMENT</B> boolean => can it be used for an + * auto-increment value? + * <LI><B>LOCAL_TYPE_NAME</B> String => localized version of type name + * (may be null) + * <LI><B>MINIMUM_SCALE</B> short => minimum scale supported + * <LI><B>MAXIMUM_SCALE</B> short => maximum scale supported + * <LI><B>SQL_DATA_TYPE</B> int => unused + * <LI><B>SQL_DATETIME_SUB</B> int => unused + * <LI><B>NUM_PREC_RADIX</B> int => usually 2 or 10 + * </OL> + * + * @return ResultSet each row is a SQL type description + */ + public java.sql.ResultSet getTypeInfo() throws SQLException + { + java.sql.ResultSet rs = connection.ExecSQL("select typname from pg_type"); + if(rs!=null) { + Field f[] = new Field[18]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TYPE_NAME"), iVarcharOid, 32); + f[1] = new Field(connection, new String("DATA_TYPE"), iInt2Oid, 2); + f[2] = new Field(connection, new String("PRECISION"), iInt4Oid, 4); + f[3] = new Field(connection, new String("LITERAL_PREFIX"), iVarcharOid, 32); + f[4] = new Field(connection, new String("LITERAL_SUFFIX"), iVarcharOid, 32); + f[5] = new Field(connection, new String("CREATE_PARAMS"), iVarcharOid, 32); + f[6] = new Field(connection, new String("NULLABLE"), iInt2Oid, 2); + f[7] = new Field(connection, new String("CASE_SENSITIVE"), iBoolOid, 1); + f[8] = new Field(connection, new String("SEARCHABLE"), iInt2Oid, 2); + f[9] = new Field(connection, new String("UNSIGNED_ATTRIBUTE"), iBoolOid, 1); + f[10] = new Field(connection, new String("FIXED_PREC_SCALE"), iBoolOid, 1); + f[11] = new Field(connection, new String("AUTO_INCREMENT"), iBoolOid, 1); + f[12] = new Field(connection, new String("LOCAL_TYPE_NAME"), iVarcharOid, 32); + f[13] = new Field(connection, new String("MINIMUM_SCALE"), iInt2Oid, 2); + f[14] = new Field(connection, new String("MAXIMUM_SCALE"), iInt2Oid, 2); + f[15] = new Field(connection, new String("SQL_DATA_TYPE"), iInt4Oid, 4); + f[16] = new Field(connection, new String("SQL_DATETIME_SUB"), iInt4Oid, 4); + f[17] = new Field(connection, new String("NUM_PREC_RADIX"), iInt4Oid, 4); + + // cache some results, this will keep memory useage down, and speed + // things up a little. + byte b9[] = "9".getBytes(); + byte b10[] = "10".getBytes(); + byte bf[] = "f".getBytes(); + byte bnn[] = Integer.toString(typeNoNulls).getBytes(); + byte bts[] = Integer.toString(typeSearchable).getBytes(); + + while(rs.next()) { + byte[][] tuple = new byte[18][]; + String typname=rs.getString(1); + tuple[0] = typname.getBytes(); + tuple[1] = Integer.toString(Field.getSQLType(typname)).getBytes(); + tuple[2] = b9; // for now + tuple[6] = bnn; // for now + tuple[7] = bf; // false for now - not case sensitive + tuple[8] = bts; + tuple[9] = bf; // false for now - it's signed + tuple[10] = bf; // false for now - must handle money + tuple[11] = bf; // false for now - handle autoincrement + // 12 - LOCAL_TYPE_NAME is null + // 13 & 14 ? + // 15 & 16 are unused so we return null + tuple[17] = b10; // everything is base 10 + v.addElement(tuple); + } + rs.close(); + return new ResultSet(connection, f, v, "OK", 1); + } + + return null; + } + + /** + * Get a description of a table's indices and statistics. They are + * ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION. + * + * <P>Each index column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>NON_UNIQUE</B> boolean => Can index values be non-unique? + * false when TYPE is tableIndexStatistic + * <LI><B>INDEX_QUALIFIER</B> String => index catalog (may be null); + * null when TYPE is tableIndexStatistic + * <LI><B>INDEX_NAME</B> String => index name; null when TYPE is + * tableIndexStatistic + * <LI><B>TYPE</B> short => index type: + * <UL> + * <LI> tableIndexStatistic - this identifies table statistics that are + * returned in conjuction with a table's index descriptions + * <LI> tableIndexClustered - this is a clustered index + * <LI> tableIndexHashed - this is a hashed index + * <LI> tableIndexOther - this is some other style of index + * </UL> + * <LI><B>ORDINAL_POSITION</B> short => column sequence number + * within index; zero when TYPE is tableIndexStatistic + * <LI><B>COLUMN_NAME</B> String => column name; null when TYPE is + * tableIndexStatistic + * <LI><B>ASC_OR_DESC</B> String => column sort sequence, "A" => ascending + * "D" => descending, may be null if sort sequence is not supported; + * null when TYPE is tableIndexStatistic + * <LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then + * this is the number of rows in the table; otherwise it is the + * number of unique values in the index. + * <LI><B>PAGES</B> int => When TYPE is tableIndexStatisic then + * this is the number of pages used for the table, otherwise it + * is the number of pages used for the current index. + * <LI><B>FILTER_CONDITION</B> String => Filter condition, if any. + * (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those without a schema + * @param table a table name + * @param unique when true, return only indices for unique values; + * when false, return indices regardless of whether unique or not + * @param approximate when true, result is allowed to reflect approximate + * or out of data values; when false, results are requested to be + * accurate + * @return ResultSet each row is an index column description + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException + { + // for now, this returns an empty result set. + Field f[] = new Field[13]; + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field(connection, new String("TABLE_CAT"), iVarcharOid, 32); + f[1] = new Field(connection, new String("TABLE_SCHEM"), iVarcharOid, 32); + f[2] = new Field(connection, new String("TABLE_NAME"), iVarcharOid, 32); + f[3] = new Field(connection, new String("NON_UNIQUE"), iBoolOid, 1); + f[4] = new Field(connection, new String("INDEX_QUALIFIER"), iVarcharOid, 32); + f[5] = new Field(connection, new String("INDEX_NAME"), iVarcharOid, 32); + f[6] = new Field(connection, new String("TYPE"), iInt2Oid, 2); + f[7] = new Field(connection, new String("ORDINAL_POSITION"), iInt2Oid, 2); + f[8] = new Field(connection, new String("COLUMN_NAME"), iVarcharOid, 32); + f[9] = new Field(connection, new String("ASC_OR_DESC"), iVarcharOid, 32); + f[10] = new Field(connection, new String("CARDINALITY"), iInt4Oid, 4); + f[11] = new Field(connection, new String("PAGES"), iInt4Oid, 4); + f[12] = new Field(connection, new String("FILTER_CONDITION"), iVarcharOid, 32); + + return new ResultSet(connection, f, v, "OK", 1); + } + + // ** JDBC 2 Extensions ** + + public boolean deletesAreDetected(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean othersDeletesAreVisible(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Class getClass(String catalog, + String schema, + String table, + String columnNamePattern + ) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.sql.Connection getConnection() throws SQLException + { + return (java.sql.Connection)connection; + } + + public java.sql.ResultSet getUDTs(String catalog, + String schemaPattern, + String typeNamePattern, + int[] types + ) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean othersInsertsAreVisible(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean updatesAreDetected(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean othersUpdatesAreVisible(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean ownUpdatesAreVisible(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean ownInsertsAreVisible(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean insertsAreDetected(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean ownDeletesAreVisible(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean rowChangesAreDetected(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean rowChangesAreVisible(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean supportsBatchUpdates() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean supportsResultSetConcurrency(int type,int concurrency) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean supportsResultSetType(int type) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java b/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java new file mode 100644 index 0000000000000000000000000000000000000000..a74d3c57b3cc3a637368b2e0f6aea9598ac7dc57 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java @@ -0,0 +1,661 @@ +package org.postgresql.jdbc2; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + +import java.io.*; +import java.math.*; +import java.sql.*; +import java.text.*; +import java.util.*; +import org.postgresql.largeobject.*; +import org.postgresql.util.*; + +/** + * A SQL Statement is pre-compiled and stored in a PreparedStatement object. + * This object can then be used to efficiently execute this statement multiple + * times. + * + * <p><B>Note:</B> The setXXX methods for setting IN parameter values must + * specify types that are compatible with the defined SQL type of the input + * parameter. For instance, if the IN parameter has SQL type Integer, then + * setInt should be used. + * + * <p>If arbitrary parameter type conversions are required, then the setObject + * method should be used with a target SQL type. + * + * @see ResultSet + * @see java.sql.PreparedStatement + */ +public class PreparedStatement extends Statement implements java.sql.PreparedStatement +{ + String sql; + String[] templateStrings; + String[] inStrings; + Connection connection; + + /** + * Constructor for the PreparedStatement class. + * Split the SQL statement into segments - separated by the arguments. + * When we rebuild the thing with the arguments, we can substitute the + * args and join the whole thing together. + * + * @param conn the instanatiating connection + * @param sql the SQL statement with ? for IN markers + * @exception SQLException if something bad occurs + */ + public PreparedStatement(Connection connection, String sql) throws SQLException + { + super(connection); + + Vector v = new Vector(); + boolean inQuotes = false; + int lastParmEnd = 0, i; + + this.sql = sql; + this.connection = connection; + for (i = 0; i < sql.length(); ++i) + { + int c = sql.charAt(i); + + if (c == '\'') + inQuotes = !inQuotes; + if (c == '?' && !inQuotes) + { + v.addElement(sql.substring (lastParmEnd, i)); + lastParmEnd = i + 1; + } + } + v.addElement(sql.substring (lastParmEnd, sql.length())); + + templateStrings = new String[v.size()]; + inStrings = new String[v.size() - 1]; + clearParameters(); + + for (i = 0 ; i < templateStrings.length; ++i) + templateStrings[i] = (String)v.elementAt(i); + } + + /** + * A Prepared SQL query is executed and its ResultSet is returned + * + * @return a ResultSet that contains the data produced by the + * query - never null + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet executeQuery() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new PSQLException("postgresql.prep.param",new Integer(i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.executeQuery(s.toString()); // in Statement class + } + + /** + * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, + * SQL statements that return nothing such as SQL DDL statements can + * be executed. + * + * @return either the row count for INSERT, UPDATE or DELETE; or + * 0 for SQL statements that return nothing. + * @exception SQLException if a database access error occurs + */ + public int executeUpdate() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new PSQLException("postgresql.prep.param",new Integer(i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.executeUpdate(s.toString()); // in Statement class + } + + /** + * Set a parameter to SQL NULL + * + * <p><B>Note:</B> You must specify the parameters SQL type (although + * PostgreSQL ignores it) + * + * @param parameterIndex the first parameter is 1, etc... + * @param sqlType the SQL type code defined in java.sql.Types + * @exception SQLException if a database access error occurs + */ + public void setNull(int parameterIndex, int sqlType) throws SQLException + { + set(parameterIndex, "null"); + } + + /** + * Set a parameter to a Java boolean value. The driver converts this + * to a SQL BIT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBoolean(int parameterIndex, boolean x) throws SQLException + { + set(parameterIndex, x ? "'t'" : "'f'"); + } + + /** + * Set a parameter to a Java byte value. The driver converts this to + * a SQL TINYINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setByte(int parameterIndex, byte x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java short value. The driver converts this + * to a SQL SMALLINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setShort(int parameterIndex, short x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java int value. The driver converts this to + * a SQL INTEGER value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setInt(int parameterIndex, int x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java long value. The driver converts this to + * a SQL BIGINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setLong(int parameterIndex, long x) throws SQLException + { + set(parameterIndex, (new Long(x)).toString()); + } + + /** + * Set a parameter to a Java float value. The driver converts this + * to a SQL FLOAT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setFloat(int parameterIndex, float x) throws SQLException + { + set(parameterIndex, (new Float(x)).toString()); + } + + /** + * Set a parameter to a Java double value. The driver converts this + * to a SQL DOUBLE value when it sends it to the database + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setDouble(int parameterIndex, double x) throws SQLException + { + set(parameterIndex, (new Double(x)).toString()); + } + + /** + * Set a parameter to a java.lang.BigDecimal value. The driver + * converts this to a SQL NUMERIC value when it sends it to the + * database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException + { + set(parameterIndex, x.toString()); + } + + /** + * Set a parameter to a Java String value. The driver converts this + * to a SQL VARCHAR or LONGVARCHAR value (depending on the arguments + * size relative to the driver's limits on VARCHARs) when it sends it + * to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setString(int parameterIndex, String x) throws SQLException + { + // if the passed string is null, then set this column to null + if(x==null) + set(parameterIndex,"null"); + else { + StringBuffer b = new StringBuffer(); + int i; + + b.append('\''); + for (i = 0 ; i < x.length() ; ++i) + { + char c = x.charAt(i); + if (c == '\\' || c == '\'') + b.append((char)'\\'); + b.append(c); + } + b.append('\''); + set(parameterIndex, b.toString()); + } + } + + /** + * Set a parameter to a Java array of bytes. The driver converts this + * to a SQL VARBINARY or LONGVARBINARY (depending on the argument's + * size relative to the driver's limits on VARBINARYs) when it sends + * it to the database. + * + * <p>Implementation note: + * <br>With org.postgresql, this creates a large object, and stores the + * objects oid in this column. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBytes(int parameterIndex, byte x[]) throws SQLException + { + LargeObjectManager lom = connection.getLargeObjectAPI(); + int oid = lom.create(); + LargeObject lob = lom.open(oid); + lob.write(x); + lob.close(); + setInt(parameterIndex,oid); + } + + /** + * Set a parameter to a java.sql.Date value. The driver converts this + * to a SQL DATE value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setDate(int parameterIndex, java.sql.Date x) throws SQLException + { + SimpleDateFormat df = new SimpleDateFormat("''yyyy-MM-dd''"); + + set(parameterIndex, df.format(x)); + + // The above is how the date should be handled. + // + // However, in JDK's prior to 1.1.6 (confirmed with the + // Linux jdk1.1.3 and the Win95 JRE1.1.5), SimpleDateFormat seems + // to format a date to the previous day. So the fix is to add a day + // before formatting. + // + // PS: 86400000 is one day + // + //set(parameterIndex, df.format(new java.util.Date(x.getTime()+86400000))); + } + + /** + * Set a parameter to a java.sql.Time value. The driver converts + * this to a SQL TIME value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1...)); + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setTime(int parameterIndex, Time x) throws SQLException + { + set(parameterIndex, "'" + x.toString() + "'"); + } + + /** + * Set a parameter to a java.sql.Timestamp value. The driver converts + * this to a SQL TIMESTAMP value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException + { + set(parameterIndex, "'" + x.toString() + "'"); + } + + /** + * When a very large ASCII value is input to a LONGVARCHAR parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. The JDBC driver will do any necessary conversion from + * ASCII to the database char format. + * + * <P><B>Note:</B> This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @param length the number of bytes in the stream + * @exception SQLException if a database access error occurs + */ + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException + { + setBinaryStream(parameterIndex, x, length); + } + + /** + * When a very large Unicode value is input to a LONGVARCHAR parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. The JDBC driver will do any necessary conversion from + * UNICODE to the database char format. + * + * ** DEPRECIATED IN JDBC 2 ** + * + * <P><B>Note:</B> This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + * @deprecated + */ + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException + { + setBinaryStream(parameterIndex, x, length); + } + + /** + * When a very large binary value is input to a LONGVARBINARY parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. + * + * <P><B>Note:</B> This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException + { + throw new PSQLException("postgresql.prep.is"); + } + + /** + * In general, parameter values remain in force for repeated used of a + * Statement. Setting a parameter value automatically clears its + * previous value. However, in coms cases, it is useful to immediately + * release the resources used by the current parameter values; this + * can be done by calling clearParameters + * + * @exception SQLException if a database access error occurs + */ + public void clearParameters() throws SQLException + { + int i; + + for (i = 0 ; i < inStrings.length ; i++) + inStrings[i] = null; + } + + /** + * Set the value of a parameter using an object; use the java.lang + * equivalent objects for integral values. + * + * <P>The given Java object will be converted to the targetSqlType before + * being sent to the database. + * + * <P>note that this method may be used to pass database-specific + * abstract data types. This is done by using a Driver-specific + * Java type and using a targetSqlType of java.sql.Types.OTHER + * + * @param parameterIndex the first parameter is 1... + * @param x the object containing the input parameter value + * @param targetSqlType The SQL type to be send to the database + * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC + * types this is the number of digits after the decimal. For + * all other types this value will be ignored. + * @exception SQLException if a database access error occurs + */ + public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException + { + switch (targetSqlType) + { + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + case Types.DECIMAL: + case Types.NUMERIC: + if (x instanceof Boolean) + set(parameterIndex, ((Boolean)x).booleanValue() ? "1" : "0"); + else + set(parameterIndex, x.toString()); + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, x.toString()); + break; + case Types.DATE: + setDate(parameterIndex, (java.sql.Date)x); + break; + case Types.TIME: + setTime(parameterIndex, (Time)x); + break; + case Types.TIMESTAMP: + setTimestamp(parameterIndex, (Timestamp)x); + break; + case Types.OTHER: + setString(parameterIndex, ((PGobject)x).getValue()); + break; + default: + throw new PSQLException("postgresql.prep.type"); + } + } + + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException + { + setObject(parameterIndex, x, targetSqlType, 0); + } + + /** + * This stores an Object into a parameter. + * <p>New for 6.4, if the object is not recognised, but it is + * Serializable, then the object is serialised using the + * org.postgresql.util.Serialize class. + */ + public void setObject(int parameterIndex, Object x) throws SQLException + { + if (x instanceof String) + setString(parameterIndex, (String)x); + else if (x instanceof BigDecimal) + setBigDecimal(parameterIndex, (BigDecimal)x); + else if (x instanceof Short) + setShort(parameterIndex, ((Short)x).shortValue()); + else if (x instanceof Integer) + setInt(parameterIndex, ((Integer)x).intValue()); + else if (x instanceof Long) + setLong(parameterIndex, ((Long)x).longValue()); + else if (x instanceof Float) + setFloat(parameterIndex, ((Float)x).floatValue()); + else if (x instanceof Double) + setDouble(parameterIndex, ((Double)x).doubleValue()); + else if (x instanceof byte[]) + setBytes(parameterIndex, (byte[])x); + else if (x instanceof java.sql.Date) + setDate(parameterIndex, (java.sql.Date)x); + else if (x instanceof Time) + setTime(parameterIndex, (Time)x); + else if (x instanceof Timestamp) + setTimestamp(parameterIndex, (Timestamp)x); + else if (x instanceof Boolean) + setBoolean(parameterIndex, ((Boolean)x).booleanValue()); + else if (x instanceof PGobject) + setString(parameterIndex, ((PGobject)x).getValue()); + else + setLong(parameterIndex, connection.putObject(x)); + } + + /** + * Some prepared statements return multiple results; the execute method + * handles these complex statements as well as the simpler form of + * statements handled by executeQuery and executeUpdate + * + * @return true if the next result is a ResultSet; false if it is an + * update count or there are no more results + * @exception SQLException if a database access error occurs + */ + public boolean execute() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new PSQLException("postgresql.prep.param",new Integer(i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.execute(s.toString()); // in Statement class + } + + /** + * Returns the SQL statement with the current template values + * substituted. + */ + public String toString() { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + s.append( '?' ); + else + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return s.toString(); + } + + // ************************************************************** + // END OF PUBLIC INTERFACE + // ************************************************************** + + /** + * There are a lot of setXXX classes which all basically do + * the same thing. We need a method which actually does the + * set for us. + * + * @param paramIndex the index into the inString + * @param s a string to be stored + * @exception SQLException if something goes wrong + */ + private void set(int paramIndex, String s) throws SQLException + { + if (paramIndex < 1 || paramIndex > inStrings.length) + throw new PSQLException("postgresql.prep.range"); + inStrings[paramIndex - 1] = s; + } + + // ** JDBC 2 Extensions ** + + public void addBatch() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.sql.ResultSetMetaData getMetaData() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setArray(int i,Array x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setBlob(int i,Blob x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setCharacterStream(int i,java.io.Reader x,int length) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setClob(int i,Clob x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setNull(int i,int t,String s) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setRef(int i,Ref x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setDate(int i,java.sql.Date d,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setTime(int i,Time t,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setTimestamp(int i,Timestamp t,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + +} diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java new file mode 100644 index 0000000000000000000000000000000000000000..76280c304df3084b4223587c7e6c6b1a5eda266c --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java @@ -0,0 +1,1274 @@ +package org.postgresql.jdbc2; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + + +import java.lang.*; +import java.io.*; +import java.math.*; +import java.text.*; +import java.util.*; +import java.sql.*; +import org.postgresql.Field; +import org.postgresql.largeobject.*; +import org.postgresql.util.*; + +/** + * A ResultSet provides access to a table of data generated by executing a + * Statement. The table rows are retrieved in sequence. Within a row its + * column values can be accessed in any order. + * + * <P>A ResultSet maintains a cursor pointing to its current row of data. + * Initially the cursor is positioned before the first row. The 'next' + * method moves the cursor to the next row. + * + * <P>The getXXX methods retrieve column values for the current row. You can + * retrieve values either using the index number of the column, or by using + * the name of the column. In general using the column index will be more + * efficient. Columns are numbered from 1. + * + * <P>For maximum portability, ResultSet columns within each row should be read + * in left-to-right order and each column should be read only once. + * + *<P> For the getXXX methods, the JDBC driver attempts to convert the + * underlying data to the specified Java type and returns a suitable Java + * value. See the JDBC specification for allowable mappings from SQL types + * to Java types with the ResultSet getXXX methods. + * + * <P>Column names used as input to getXXX methods are case insenstive. When + * performing a getXXX using a column name, if several columns have the same + * name, then the value of the first matching column will be returned. The + * column name option is designed to be used when column names are used in the + * SQL Query. For columns that are NOT explicitly named in the query, it is + * best to use column numbers. If column names were used there is no way for + * the programmer to guarentee that they actually refer to the intended + * columns. + * + * <P>A ResultSet is automatically closed by the Statement that generated it + * when that Statement is closed, re-executed, or is used to retrieve the + * next result from a sequence of multiple results. + * + * <P>The number, types and properties of a ResultSet's columns are provided by + * the ResultSetMetaData object returned by the getMetaData method. + * + * @see ResultSetMetaData + * @see java.sql.ResultSet + */ +public class ResultSet extends org.postgresql.ResultSet implements java.sql.ResultSet +{ + /** + * Create a new ResultSet - Note that we create ResultSets to + * represent the results of everything. + * + * @param fields an array of Field objects (basically, the + * ResultSet MetaData) + * @param tuples Vector of the actual data + * @param status the status string returned from the back end + * @param updateCount the number of rows affected by the operation + * @param cursor the positioned update/delete cursor name + */ + public ResultSet(Connection conn, Field[] fields, Vector tuples, String status, int updateCount) + { + super(conn,fields,tuples,status,updateCount); + } + + /** + * A ResultSet is initially positioned before its first row, + * the first call to next makes the first row the current row; + * the second call makes the second row the current row, etc. + * + * <p>If an input stream from the previous row is open, it is + * implicitly closed. The ResultSet's warning chain is cleared + * when a new row is read + * + * @return true if the new current is valid; false if there are no + * more rows + * @exception SQLException if a database access error occurs + */ + public boolean next() throws SQLException + { + if (++current_row >= rows.size()) + return false; + this_row = (byte [][])rows.elementAt(current_row); + return true; + } + + /** + * In some cases, it is desirable to immediately release a ResultSet + * database and JDBC resources instead of waiting for this to happen + * when it is automatically closed. The close method provides this + * immediate release. + * + * <p><B>Note:</B> A ResultSet is automatically closed by the Statement + * the Statement that generated it when that Statement is closed, + * re-executed, or is used to retrieve the next result from a sequence + * of multiple results. A ResultSet is also automatically closed + * when it is garbage collected. + * + * @exception SQLException if a database access error occurs + */ + public void close() throws SQLException + { + // No-op + } + + /** + * A column may have the value of SQL NULL; wasNull() reports whether + * the last column read had this special value. Note that you must + * first call getXXX on a column to try to read its value and then + * call wasNull() to find if the value was SQL NULL + * + * @return true if the last column read was SQL NULL + * @exception SQLException if a database access error occurred + */ + public boolean wasNull() throws SQLException + { + return wasNullFlag; + } + + /** + * Get the value of a column in the current row as a Java String + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value, null for SQL NULL + * @exception SQLException if a database access error occurs + */ + public String getString(int columnIndex) throws SQLException + { + //byte[] bytes = getBytes(columnIndex); + // + //if (bytes == null) + //return null; + //return new String(bytes); + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + wasNullFlag = (this_row[columnIndex - 1] == null); + if(wasNullFlag) + return null; + return new String(this_row[columnIndex - 1]); + } + + /** + * Get the value of a column in the current row as a Java boolean + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value, false for SQL NULL + * @exception SQLException if a database access error occurs + */ + public boolean getBoolean(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + int c = s.charAt(0); + return ((c == 't') || (c == 'T')); + } + return false; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java byte. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public byte getByte(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Byte.parseByte(s); + } catch (NumberFormatException e) { + throw new PSQLException("postgresql.res.badbyte",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java short. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public short getShort(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Short.parseShort(s); + } catch (NumberFormatException e) { + throw new PSQLException("postgresql.res.badshort",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java int. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public int getInt(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badint",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java long. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public long getLong(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Long.parseLong(s); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badlong",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java float. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public float getFloat(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Float.valueOf(s).floatValue(); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badfloat",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java double. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public double getDouble(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Double.valueOf(s).doubleValue(); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.baddouble",s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a + * java.math.BigDecimal object + * + * @param columnIndex the first column is 1, the second is 2... + * @param scale the number of digits to the right of the decimal + * @return the column value; if the value is SQL NULL, null + * @exception SQLException if a database access error occurs + * @deprecated + */ + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException + { + String s = getString(columnIndex); + BigDecimal val; + + if (s != null) + { + try + { + val = new BigDecimal(s); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badbigdec",s); + } + try + { + return val.setScale(scale); + } catch (ArithmeticException e) { + throw new PSQLException ("postgresql.res.badbigdec",s); + } + } + return null; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java byte array. + * + * <p>In normal use, the bytes represent the raw values returned by the + * backend. However, if the column is an OID, then it is assumed to + * refer to a Large Object, and that object is returned as a byte array. + * + * <p><b>Be warned</b> If the large object is huge, then you may run out + * of memory. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the result + * is null + * @exception SQLException if a database access error occurs + */ + public byte[] getBytes(int columnIndex) throws SQLException + { + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + wasNullFlag = (this_row[columnIndex - 1] == null); + + // Handle OID's as BLOBS + if(!wasNullFlag) + if( fields[columnIndex - 1].getOID() == 26) { + LargeObjectManager lom = connection.getLargeObjectAPI(); + LargeObject lob = lom.open(getInt(columnIndex)); + byte buf[] = lob.read(lob.size()); + lob.close(); + return buf; + } + + return this_row[columnIndex - 1]; + } + + /** + * Get the value of a column in the current row as a java.sql.Date + * object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public java.sql.Date getDate(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + if(s==null) + return null; + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + try { + return new java.sql.Date(df.parse(s).getTime()); + } catch (ParseException e) { + throw new PSQLException("postgresql.res.baddate",new Integer(e.getErrorOffset()),s); + } + } + + /** + * Get the value of a column in the current row as a java.sql.Time + * object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public Time getTime(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + if (s.length() != 5 && s.length() != 8) + throw new NumberFormatException("Wrong Length!"); + int hr = Integer.parseInt(s.substring(0,2)); + int min = Integer.parseInt(s.substring(3,5)); + int sec = (s.length() == 5) ? 0 : Integer.parseInt(s.substring(6)); + return new Time(hr, min, sec); + } catch (NumberFormatException e) { + throw new PSQLException ("postgresql.res.badtime",s); + } + } + return null; // SQL NULL + } + + /** + * Get the value of a column in the current row as a + * java.sql.Timestamp object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public Timestamp getTimestamp(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + if(s==null) + return null; + + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:sszzz"); + + try { + return new Timestamp(df.parse(s).getTime()); + } catch(ParseException e) { + throw new PSQLException("postgresql.res.badtimestamp",new Integer(e.getErrorOffset()),s); + } + } + + /** + * A column value can be retrieved as a stream of ASCII characters + * and then read in chunks from the stream. This method is + * particular suitable for retrieving large LONGVARCHAR values. + * The JDBC driver will do any necessary conversion from the + * database format into ASCII. + * + * <p><B>Note:</B> All the data in the returned stream must be read + * prior to getting the value of any other column. The next call + * to a get method implicitly closes the stream. Also, a stream + * may return 0 for available() whether there is data available + * or not. + * + *<p> We implement an ASCII stream as a Binary stream - we should really + * do the data conversion, but I cannot be bothered to implement this + * right now. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return a Java InputStream that delivers the database column + * value as a stream of one byte ASCII characters. If the + * value is SQL NULL then the result is null + * @exception SQLException if a database access error occurs + * @see getBinaryStream + */ + public InputStream getAsciiStream(int columnIndex) throws SQLException + { + return getBinaryStream(columnIndex); + } + + /** + * A column value can also be retrieved as a stream of Unicode + * characters. We implement this as a binary stream. + * + * ** DEPRECATED IN JDBC 2 ** + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Java InputStream that delivers the database column value + * as a stream of two byte Unicode characters. If the value is + * SQL NULL, then the result is null + * @exception SQLException if a database access error occurs + * @see getAsciiStream + * @see getBinaryStream + * @deprecated + */ + public InputStream getUnicodeStream(int columnIndex) throws SQLException + { + return getBinaryStream(columnIndex); + } + + /** + * A column value can also be retrieved as a binary strea. This + * method is suitable for retrieving LONGVARBINARY values. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Java InputStream that delivers the database column value + * as a stream of bytes. If the value is SQL NULL, then the result + * is null + * @exception SQLException if a database access error occurs + * @see getAsciiStream + * @see getUnicodeStream + */ + public InputStream getBinaryStream(int columnIndex) throws SQLException + { + byte b[] = getBytes(columnIndex); + + if (b != null) + return new ByteArrayInputStream(b); + return null; // SQL NULL + } + + /** + * The following routines simply convert the columnName into + * a columnIndex and then call the appropriate routine above. + * + * @param columnName is the SQL name of the column + * @return the column value + * @exception SQLException if a database access error occurs + */ + public String getString(String columnName) throws SQLException + { + return getString(findColumn(columnName)); + } + + public boolean getBoolean(String columnName) throws SQLException + { + return getBoolean(findColumn(columnName)); + } + + public byte getByte(String columnName) throws SQLException + { + + return getByte(findColumn(columnName)); + } + + public short getShort(String columnName) throws SQLException + { + return getShort(findColumn(columnName)); + } + + public int getInt(String columnName) throws SQLException + { + return getInt(findColumn(columnName)); + } + + public long getLong(String columnName) throws SQLException + { + return getLong(findColumn(columnName)); + } + + public float getFloat(String columnName) throws SQLException + { + return getFloat(findColumn(columnName)); + } + + public double getDouble(String columnName) throws SQLException + { + return getDouble(findColumn(columnName)); + } + + /** + * @deprecated + */ + public BigDecimal getBigDecimal(String columnName, int scale) throws SQLException + { + return getBigDecimal(findColumn(columnName), scale); + } + + public byte[] getBytes(String columnName) throws SQLException + { + return getBytes(findColumn(columnName)); + } + + public java.sql.Date getDate(String columnName) throws SQLException + { + return getDate(findColumn(columnName)); + } + + public Time getTime(String columnName) throws SQLException + { + return getTime(findColumn(columnName)); + } + + public Timestamp getTimestamp(String columnName) throws SQLException + { + return getTimestamp(findColumn(columnName)); + } + + public InputStream getAsciiStream(String columnName) throws SQLException + { + return getAsciiStream(findColumn(columnName)); + } + + /** + * + * ** DEPRECATED IN JDBC 2 ** + * + * @deprecated + */ + public InputStream getUnicodeStream(String columnName) throws SQLException + { + return getUnicodeStream(findColumn(columnName)); + } + + public InputStream getBinaryStream(String columnName) throws SQLException + { + return getBinaryStream(findColumn(columnName)); + } + + /** + * The first warning reported by calls on this ResultSet is + * returned. Subsequent ResultSet warnings will be chained + * to this SQLWarning. + * + * <p>The warning chain is automatically cleared each time a new + * row is read. + * + * <p><B>Note:</B> This warning chain only covers warnings caused by + * ResultSet methods. Any warnings caused by statement methods + * (such as reading OUT parameters) will be chained on the + * Statement object. + * + * @return the first SQLWarning or null; + * @exception SQLException if a database access error occurs. + */ + public SQLWarning getWarnings() throws SQLException + { + return warnings; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this ResultSet + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + warnings = null; + } + + /** + * Get the name of the SQL cursor used by this ResultSet + * + * <p>In SQL, a result table is retrieved though a cursor that is + * named. The current row of a result can be updated or deleted + * using a positioned update/delete statement that references + * the cursor name. + * + * <p>JDBC supports this SQL feature by providing the name of the + * SQL cursor used by a ResultSet. The current row of a ResulSet + * is also the current row of this SQL cursor. + * + * <p><B>Note:</B> If positioned update is not supported, a SQLException + * is thrown. + * + * @return the ResultSet's SQL cursor name. + * @exception SQLException if a database access error occurs + */ + public String getCursorName() throws SQLException + { + return connection.getCursorName(); + } + + /** + * The numbers, types and properties of a ResultSet's columns are + * provided by the getMetaData method + * + * @return a description of the ResultSet's columns + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSetMetaData getMetaData() throws SQLException + { + return new ResultSetMetaData(rows, fields); + } + + /** + * Get the value of a column in the current row as a Java object + * + * <p>This method will return the value of the given column as a + * Java object. The type of the Java object will be the default + * Java Object type corresponding to the column's SQL type, following + * the mapping specified in the JDBC specification. + * + * <p>This method may also be used to read database specific abstract + * data types. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Object holding the column value + * @exception SQLException if a database access error occurs + */ + public Object getObject(int columnIndex) throws SQLException + { + Field field; + + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + field = fields[columnIndex - 1]; + + // some fields can be null, mainly from those returned by MetaData methods + if(field==null) { + wasNullFlag=true; + return null; + } + + switch (field.getSQLType()) + { + case Types.BIT: + return new Boolean(getBoolean(columnIndex)); + case Types.SMALLINT: + return new Integer(getInt(columnIndex)); + case Types.INTEGER: + return new Integer(getInt(columnIndex)); + case Types.BIGINT: + return new Long(getLong(columnIndex)); + case Types.NUMERIC: + return getBigDecimal(columnIndex, ((field.mod-4) & 0xffff)); + case Types.REAL: + return new Float(getFloat(columnIndex)); + case Types.DOUBLE: + return new Double(getDouble(columnIndex)); + case Types.CHAR: + case Types.VARCHAR: + return getString(columnIndex); + case Types.DATE: + return getDate(columnIndex); + case Types.TIME: + return getTime(columnIndex); + case Types.TIMESTAMP: + return getTimestamp(columnIndex); + default: + return connection.getObject(field.getTypeName(), getString(columnIndex)); + } + } + + /** + * Get the value of a column in the current row as a Java object + * + *<p> This method will return the value of the given column as a + * Java object. The type of the Java object will be the default + * Java Object type corresponding to the column's SQL type, following + * the mapping specified in the JDBC specification. + * + * <p>This method may also be used to read database specific abstract + * data types. + * + * @param columnName is the SQL name of the column + * @return a Object holding the column value + * @exception SQLException if a database access error occurs + */ + public Object getObject(String columnName) throws SQLException + { + return getObject(findColumn(columnName)); + } + + /** + * Map a ResultSet column name to a ResultSet column index + * + * @param columnName the name of the column + * @return the column index + * @exception SQLException if a database access error occurs + */ + public int findColumn(String columnName) throws SQLException + { + int i; + + for (i = 0 ; i < fields.length; ++i) + if (fields[i].name.equalsIgnoreCase(columnName)) + return (i+1); + throw new PSQLException ("postgresql.res.colname",columnName); + } + + // ** JDBC 2 Extensions ** + + public boolean absolute(int index) throws SQLException + { + // Peter: Added because negative indices read from the end of the + // ResultSet + if(index<0) + index=rows.size()+index; + + if (index==0 || index > rows.size()) + return false; + + this_row = (byte [][])rows.elementAt(index); + return true; + } + + public void afterLast() throws SQLException + { + current_row = rows.size() + 1; + } + + public void beforeFirst() throws SQLException + { + current_row = 0; + } + + public void cancelRowUpdates() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void deleteRow() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean first() throws SQLException + { + if (rows.size() <= 0) + return false; + current_row = 0; + this_row = (byte [][])rows.elementAt(current_row); + return true; + } + + public Array getArray(String colName) throws SQLException + { + return getArray(findColumn(colName)); + } + + public Array getArray(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.math.BigDecimal getBigDecimal(int columnIndex) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.math.BigDecimal getBigDecimal(String columnName) throws SQLException + { + return getBigDecimal(findColumn(columnName)); + } + + public Blob getBlob(String columnName) throws SQLException + { + return getBlob(findColumn(columnName)); + } + + public Blob getBlob(int i) throws SQLException + { + return new org.postgresql.largeobject.PGblob(connection,getInt(i)); + } + + public java.io.Reader getCharacterStream(String columnName) throws SQLException + { + return getCharacterStream(findColumn(columnName)); + } + + public java.io.Reader getCharacterStream(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Clob getClob(String columnName) throws SQLException + { + return getClob(findColumn(columnName)); + } + + public Clob getClob(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getConcurrency() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.sql.Date getDate(int i,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Time getTime(int i,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Timestamp getTimestamp(int i,java.util.Calendar cal) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public java.sql.Date getDate(String c,java.util.Calendar cal) throws SQLException + { + return getDate(findColumn(c),cal); + } + + public Time getTime(String c,java.util.Calendar cal) throws SQLException + { + return getTime(findColumn(c),cal); + } + + public Timestamp getTimestamp(String c,java.util.Calendar cal) throws SQLException + { + return getTimestamp(findColumn(c),cal); + } + + public int getFetchDirection() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getFetchSize() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getKeysetSize() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Object getObject(String columnName,java.util.Map map) throws SQLException + { + return getObject(findColumn(columnName),map); + } + + public Object getObject(int i,java.util.Map map) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public Ref getRef(String columnName) throws SQLException + { + return getRef(findColumn(columnName)); + } + + public Ref getRef(int i) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getRow() throws SQLException + { + return current_row; + } + + // This one needs some thought, as not all ResultSets come from a statement + public java.sql.Statement getStatement() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getType() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void insertRow() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean isAfterLast() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean isBeforeFirst() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean isFirst() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean isLast() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean last() throws SQLException + { + if (rows.size() <= 0) + return false; + current_row = rows.size() - 1; + this_row = (byte [][])rows.elementAt(current_row); + return true; + } + + public void moveToCurrentRow() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void moveToInsertRow() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean previous() throws SQLException + { + if (--current_row < 0) + return false; + this_row = (byte [][])rows.elementAt(current_row); + return true; + } + + public void refreshRow() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + // Peter: Implemented in 7.0 + public boolean relative(int rows) throws SQLException + { + return absolute(current_row+rows); + } + + public boolean rowDeleted() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean rowInserted() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public boolean rowUpdated() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setFetchDirection(int direction) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setFetchSize(int rows) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setKeysetSize(int keys) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateAsciiStream(int columnIndex, + java.io.InputStream x, + int length + ) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateAsciiStream(String columnName, + java.io.InputStream x, + int length + ) throws SQLException + { + updateAsciiStream(findColumn(columnName),x,length); + } + + public void updateBigDecimal(int columnIndex, + java.math.BigDecimal x + ) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateBigDecimal(String columnName, + java.math.BigDecimal x + ) throws SQLException + { + updateBigDecimal(findColumn(columnName),x); + } + + public void updateBinaryStream(int columnIndex, + java.io.InputStream x, + int length + ) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateBinaryStream(String columnName, + java.io.InputStream x, + int length + ) throws SQLException + { + updateBinaryStream(findColumn(columnName),x,length); + } + + public void updateBoolean(int columnIndex,boolean x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateBoolean(String columnName,boolean x) throws SQLException + { + updateBoolean(findColumn(columnName),x); + } + + public void updateByte(int columnIndex,byte x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateByte(String columnName,byte x) throws SQLException + { + updateByte(findColumn(columnName),x); + } + + public void updateBytes(String columnName,byte[] x) throws SQLException + { + updateBytes(findColumn(columnName),x); + } + + public void updateBytes(int columnIndex,byte[] x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateCharacterStream(int columnIndex, + java.io.Reader x, + int length + ) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateCharacterStream(String columnName, + java.io.Reader x, + int length + ) throws SQLException + { + updateCharacterStream(findColumn(columnName),x,length); + } + + public void updateDate(int columnIndex,java.sql.Date x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateDate(String columnName,java.sql.Date x) throws SQLException + { + updateDate(findColumn(columnName),x); + } + + public void updateDouble(int columnIndex,double x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateDouble(String columnName,double x) throws SQLException + { + updateDouble(findColumn(columnName),x); + } + + public void updateFloat(int columnIndex,float x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateFloat(String columnName,float x) throws SQLException + { + updateFloat(findColumn(columnName),x); + } + + public void updateInt(int columnIndex,int x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateInt(String columnName,int x) throws SQLException + { + updateInt(findColumn(columnName),x); + } + + public void updateLong(int columnIndex,long x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateLong(String columnName,long x) throws SQLException + { + updateLong(findColumn(columnName),x); + } + + public void updateNull(int columnIndex) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateNull(String columnName) throws SQLException + { + updateNull(findColumn(columnName)); + } + + public void updateObject(int columnIndex,Object x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateObject(String columnName,Object x) throws SQLException + { + updateObject(findColumn(columnName),x); + } + + public void updateObject(int columnIndex,Object x,int scale) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateObject(String columnName,Object x,int scale) throws SQLException + { + updateObject(findColumn(columnName),x,scale); + } + + public void updateRow() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateShort(int columnIndex,short x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateShort(String columnName,short x) throws SQLException + { + updateShort(findColumn(columnName),x); + } + + public void updateString(int columnIndex,String x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateString(String columnName,String x) throws SQLException + { + updateString(findColumn(columnName),x); + } + + public void updateTime(int columnIndex,Time x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateTime(String columnName,Time x) throws SQLException + { + updateTime(findColumn(columnName),x); + } + + public void updateTimestamp(int columnIndex,Timestamp x) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void updateTimestamp(String columnName,Timestamp x) throws SQLException + { + updateTimestamp(findColumn(columnName),x); + } + +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSetMetaData.java b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSetMetaData.java new file mode 100644 index 0000000000000000000000000000000000000000..ebcf137a1ebfaa5772354b92d9c956f11935c737 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSetMetaData.java @@ -0,0 +1,455 @@ +package org.postgresql.jdbc2; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + +import java.lang.*; +import java.sql.*; +import java.util.*; +import org.postgresql.*; +import org.postgresql.util.*; + +/** + * A ResultSetMetaData object can be used to find out about the types and + * properties of the columns in a ResultSet + * + * @see java.sql.ResultSetMetaData + */ +public class ResultSetMetaData implements java.sql.ResultSetMetaData +{ + Vector rows; + Field[] fields; + + /** + * Initialise for a result with a tuple set and + * a field descriptor set + * + * @param rows the Vector of rows returned by the ResultSet + * @param fields the array of field descriptors + */ + public ResultSetMetaData(Vector rows, Field[] fields) + { + this.rows = rows; + this.fields = fields; + } + + /** + * Whats the number of columns in the ResultSet? + * + * @return the number + * @exception SQLException if a database access error occurs + */ + public int getColumnCount() throws SQLException + { + return fields.length; + } + + /** + * Is the column automatically numbered (and thus read-only) + * I believe that PostgreSQL does not support this feature. + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isAutoIncrement(int column) throws SQLException + { + return false; + } + + /** + * Does a column's case matter? ASSUMPTION: Any field that is + * not obviously case insensitive is assumed to be case sensitive + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isCaseSensitive(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + case Types.INTEGER: + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + return false; + default: + return true; + } + } + + /** + * Can the column be used in a WHERE clause? Basically for + * this, I split the functions into two types: recognised + * types (which are always useable), and OTHER types (which + * may or may not be useable). The OTHER types, for now, I + * will assume they are useable. We should really query the + * catalog to see if they are useable. + * + * @param column the first column is 1, the second is 2... + * @return true if they can be used in a WHERE clause + * @exception SQLException if a database access error occurs + */ + public boolean isSearchable(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + // This switch is pointless, I know - but it is a set-up + // for further expansion. + switch (sql_type) + { + case Types.OTHER: + return true; + default: + return true; + } + } + + /** + * Is the column a cash value? 6.1 introduced the cash/money + * type, which haven't been incorporated as of 970414, so I + * just check the type name for both 'cash' and 'money' + * + * @param column the first column is 1, the second is 2... + * @return true if its a cash column + * @exception SQLException if a database access error occurs + */ + public boolean isCurrency(int column) throws SQLException + { + String type_name = getField(column).getTypeName(); + + return type_name.equals("cash") || type_name.equals("money"); + } + + /** + * Can you put a NULL in this column? I think this is always + * true in 6.1's case. It would only be false if the field had + * been defined NOT NULL (system catalogs could be queried?) + * + * @param column the first column is 1, the second is 2... + * @return one of the columnNullable values + * @exception SQLException if a database access error occurs + */ + public int isNullable(int column) throws SQLException + { + return columnNullable; // We can always put NULL in + } + + /** + * Is the column a signed number? In PostgreSQL, all numbers + * are signed, so this is trivial. However, strings are not + * signed (duh!) + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isSigned(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + case Types.INTEGER: + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + return true; + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + return false; // I don't know about these? + default: + return false; + } + } + + /** + * What is the column's normal maximum width in characters? + * + * @param column the first column is 1, the second is 2, etc. + * @return the maximum width + * @exception SQLException if a database access error occurs + */ + public int getColumnDisplaySize(int column) throws SQLException + { + Field f = getField(column); + String type_name = f.getTypeName(); + int sql_type = f.getSQLType(); + int typmod = f.mod; + + // I looked at other JDBC implementations and couldn't find a consistent + // interpretation of the "display size" for numeric values, so this is our's + // FIXME: currently, only types with a SQL92 or SQL3 pendant are implemented - jens@jens.de + + // fixed length data types + if (type_name.equals( "int2" )) return 6; // -32768 to +32768 (5 digits and a sign) + if (type_name.equals( "int4" ) + || type_name.equals( "oid" )) return 11; // -2147483648 to +2147483647 + if (type_name.equals( "int8" )) return 20; // -9223372036854775808 to +9223372036854775807 + if (type_name.equals( "money" )) return 12; // MONEY = DECIMAL(9,2) + if (type_name.equals( "float4" )) return 11; // i checked it out ans wasn't able to produce more than 11 digits + if (type_name.equals( "float8" )) return 20; // dito, 20 + if (type_name.equals( "char" )) return 1; + if (type_name.equals( "bool" )) return 1; + if (type_name.equals( "date" )) return 14; // "01/01/4713 BC" - "31/12/32767 AD" + if (type_name.equals( "time" )) return 8; // 00:00:00-23:59:59 + if (type_name.equals( "timestamp" )) return 22; // hhmmm ... the output looks like this: 1999-08-03 22:22:08+02 + + // variable length fields + typmod -= 4; + if (type_name.equals( "bpchar" ) + || type_name.equals( "varchar" )) return typmod; // VARHDRSZ=sizeof(int32)=4 + if (type_name.equals( "numeric" )) return ( (typmod >>16) & 0xffff ) + + 1 + ( typmod & 0xffff ); // DECIMAL(p,s) = (p digits).(s digits) + + // if we don't know better + return f.length; + } + + /** + * What is the suggested column title for use in printouts and + * displays? We suggest the ColumnName! + * + * @param column the first column is 1, the second is 2, etc. + * @return the column label + * @exception SQLException if a database access error occurs + */ + public String getColumnLabel(int column) throws SQLException + { + return getColumnName(column); + } + + /** + * What's a column's name? + * + * @param column the first column is 1, the second is 2, etc. + * @return the column name + * @exception SQLException if a database access error occurs + */ + public String getColumnName(int column) throws SQLException + { + Field f = getField(column); + if(f!=null) + return f.name; + return "field"+column; + } + + /** + * What is a column's table's schema? This relies on us knowing + * the table name....which I don't know how to do as yet. The + * JDBC specification allows us to return "" if this is not + * applicable. + * + * @param column the first column is 1, the second is 2... + * @return the Schema + * @exception SQLException if a database access error occurs + */ + public String getSchemaName(int column) throws SQLException + { + return ""; + } + + /** + * What is a column's number of decimal digits. + * + * @param column the first column is 1, the second is 2... + * @return the precision + * @exception SQLException if a database access error occurs + */ + public int getPrecision(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + return 5; + case Types.INTEGER: + return 10; + case Types.REAL: + return 8; + case Types.FLOAT: + return 16; + case Types.DOUBLE: + return 16; + case Types.VARCHAR: + return 0; + default: + return 0; + } + } + + /** + * What is a column's number of digits to the right of the + * decimal point? + * + * @param column the first column is 1, the second is 2... + * @return the scale + * @exception SQLException if a database access error occurs + */ + public int getScale(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + return 0; + case Types.INTEGER: + return 0; + case Types.REAL: + return 8; + case Types.FLOAT: + return 16; + case Types.DOUBLE: + return 16; + case Types.VARCHAR: + return 0; + default: + return 0; + } + } + + /** + * Whats a column's table's name? How do I find this out? Both + * getSchemaName() and getCatalogName() rely on knowing the table + * Name, so we need this before we can work on them. + * + * @param column the first column is 1, the second is 2... + * @return column name, or "" if not applicable + * @exception SQLException if a database access error occurs + */ + public String getTableName(int column) throws SQLException + { + return ""; + } + + /** + * What's a column's table's catalog name? As with getSchemaName(), + * we can say that if getTableName() returns n/a, then we can too - + * otherwise, we need to work on it. + * + * @param column the first column is 1, the second is 2... + * @return catalog name, or "" if not applicable + * @exception SQLException if a database access error occurs + */ + public String getCatalogName(int column) throws SQLException + { + return ""; + } + + /** + * What is a column's SQL Type? (java.sql.Type int) + * + * @param column the first column is 1, the second is 2, etc. + * @return the java.sql.Type value + * @exception SQLException if a database access error occurs + * @see org.postgresql.Field#getSQLType + * @see java.sql.Types + */ + public int getColumnType(int column) throws SQLException + { + return getField(column).getSQLType(); + } + + /** + * Whats is the column's data source specific type name? + * + * @param column the first column is 1, the second is 2, etc. + * @return the type name + * @exception SQLException if a database access error occurs + */ + public String getColumnTypeName(int column) throws SQLException + { + return getField(column).getTypeName(); + } + + /** + * Is the column definitely not writable? In reality, we would + * have to check the GRANT/REVOKE stuff for this to be effective, + * and I haven't really looked into that yet, so this will get + * re-visited. + * + * @param column the first column is 1, the second is 2, etc. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly(int column) throws SQLException + { + return false; + } + + /** + * Is it possible for a write on the column to succeed? Again, we + * would in reality have to check the GRANT/REVOKE stuff, which + * I haven't worked with as yet. However, if it isn't ReadOnly, then + * it is obviously writable. + * + * @param column the first column is 1, the second is 2, etc. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isWritable(int column) throws SQLException + { + if (isReadOnly(column)) + return true; + else + return false; + } + + /** + * Will a write on this column definately succeed? Hmmm...this + * is a bad one, since the two preceding functions have not been + * really defined. I cannot tell is the short answer. I thus + * return isWritable() just to give us an idea. + * + * @param column the first column is 1, the second is 2, etc.. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isDefinitelyWritable(int column) throws SQLException + { + return isWritable(column); + } + + // ******************************************************** + // END OF PUBLIC INTERFACE + // ******************************************************** + + /** + * For several routines in this package, we need to convert + * a columnIndex into a Field[] descriptor. Rather than do + * the same code several times, here it is. + * + * @param columnIndex the first column is 1, the second is 2... + * @return the Field description + * @exception SQLException if a database access error occurs + */ + private Field getField(int columnIndex) throws SQLException + { + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange"); + return fields[columnIndex - 1]; + } + + // ** JDBC 2 Extensions ** + + // This can hook into our PG_Object mechanism + public String getColumnClassName(int column) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + +} + diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java b/src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java new file mode 100644 index 0000000000000000000000000000000000000000..b96041c7268052558de5f9e7dd964c29fc2869ef --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/Statement.java @@ -0,0 +1,421 @@ +package org.postgresql.jdbc2; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + +import java.sql.*; +import java.util.Vector; +import org.postgresql.util.*; + +/** + * A Statement object is used for executing a static SQL statement and + * obtaining the results produced by it. + * + * <p>Only one ResultSet per Statement can be open at any point in time. + * Therefore, if the reading of one ResultSet is interleaved with the + * reading of another, each must have been generated by different + * Statements. All statement execute methods implicitly close a + * statement's current ResultSet if an open one exists. + * + * @see java.sql.Statement + * @see ResultSet + */ +public class Statement implements java.sql.Statement +{ + Connection connection; // The connection who created us + java.sql.ResultSet result = null; // The current results + SQLWarning warnings = null; // The warnings chain. + int timeout = 0; // The timeout for a query (not used) + boolean escapeProcessing = true;// escape processing flag + private Vector batch=null; + + /** + * Constructor for a Statement. It simply sets the connection + * that created us. + * + * @param c the Connection instantation that creates us + */ + public Statement (Connection c) + { + connection = c; + } + + /** + * Execute a SQL statement that retruns a single ResultSet + * + * @param sql typically a static SQL SELECT statement + * @return a ResulSet that contains the data produced by the query + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet executeQuery(String sql) throws SQLException + { + this.execute(sql); + while (result != null && !((org.postgresql.ResultSet)result).reallyResultSet()) + result = ((org.postgresql.ResultSet)result).getNext(); + if (result == null) + throw new PSQLException("postgresql.stat.noresult"); + return result; + } + + /** + * Execute a SQL INSERT, UPDATE or DELETE statement. In addition + * SQL statements that return nothing such as SQL DDL statements + * can be executed + * + * @param sql a SQL statement + * @return either a row count, or 0 for SQL commands + * @exception SQLException if a database access error occurs + */ + public int executeUpdate(String sql) throws SQLException + { + this.execute(sql); + if (((org.postgresql.ResultSet)result).reallyResultSet()) + throw new PSQLException("postgresql.stat.result"); + return this.getUpdateCount(); + } + + /** + * In many cases, it is desirable to immediately release a + * Statement's database and JDBC resources instead of waiting + * for this to happen when it is automatically closed. The + * close method provides this immediate release. + * + * <p><B>Note:</B> A Statement is automatically closed when it is + * garbage collected. When a Statement is closed, its current + * ResultSet, if one exists, is also closed. + * + * @exception SQLException if a database access error occurs (why?) + */ + public void close() throws SQLException + { + result = null; + } + + /** + * The maxFieldSize limit (in bytes) is the maximum amount of + * data returned for any column value; it only applies to + * BINARY, VARBINARY, LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR + * columns. If the limit is exceeded, the excess data is silently + * discarded. + * + * @return the current max column size limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public int getMaxFieldSize() throws SQLException + { + return 8192; // We cannot change this + } + + /** + * Sets the maxFieldSize - NOT! - We throw an SQLException just + * to inform them to stop doing this. + * + * @param max the new max column size limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public void setMaxFieldSize(int max) throws SQLException + { + throw new PSQLException("postgresql.stat.maxfieldsize"); + } + + /** + * The maxRows limit is set to limit the number of rows that + * any ResultSet can contain. If the limit is exceeded, the + * excess rows are silently dropped. + * + * @return the current maximum row limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public int getMaxRows() throws SQLException + { + return connection.maxrows; + } + + /** + * Set the maximum number of rows + * + * @param max the new max rows limit; zero means unlimited + * @exception SQLException if a database access error occurs + * @see getMaxRows + */ + public void setMaxRows(int max) throws SQLException + { + connection.maxrows = max; + } + + /** + * If escape scanning is on (the default), the driver will do escape + * substitution before sending the SQL to the database. + * + * @param enable true to enable; false to disable + * @exception SQLException if a database access error occurs + */ + public void setEscapeProcessing(boolean enable) throws SQLException + { + escapeProcessing = enable; + } + + /** + * The queryTimeout limit is the number of seconds the driver + * will wait for a Statement to execute. If the limit is + * exceeded, a SQLException is thrown. + * + * @return the current query timeout limit in seconds; 0 = unlimited + * @exception SQLException if a database access error occurs + */ + public int getQueryTimeout() throws SQLException + { + return timeout; + } + + /** + * Sets the queryTimeout limit + * + * @param seconds - the new query timeout limit in seconds + * @exception SQLException if a database access error occurs + */ + public void setQueryTimeout(int seconds) throws SQLException + { + timeout = seconds; + } + + /** + * Cancel can be used by one thread to cancel a statement that + * is being executed by another thread. However, PostgreSQL is + * a sync. sort of thing, so this really has no meaning - we + * define it as a no-op (i.e. you can't cancel, but there is no + * error if you try.) + * + * 6.4 introduced a cancel operation, but we have not implemented it + * yet. Sometime before 6.5, this method will be implemented. + * + * @exception SQLException only because thats the spec. + */ + public void cancel() throws SQLException + { + // No-op + } + + /** + * The first warning reported by calls on this Statement is + * returned. A Statement's execute methods clear its SQLWarning + * chain. Subsequent Statement warnings will be chained to this + * SQLWarning. + * + * <p>The Warning chain is automatically cleared each time a statement + * is (re)executed. + * + * <p><B>Note:</B> If you are processing a ResultSet then any warnings + * associated with ResultSet reads will be chained on the ResultSet + * object. + * + * @return the first SQLWarning on null + * @exception SQLException if a database access error occurs + */ + public SQLWarning getWarnings() throws SQLException + { + return warnings; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this Statement. + * + * @exception SQLException if a database access error occurs (why?) + */ + public void clearWarnings() throws SQLException + { + warnings = null; + } + + /** + * setCursorName defines the SQL cursor name that will be used by + * subsequent execute methods. This name can then be used in SQL + * positioned update/delete statements to identify the current row + * in the ResultSet generated by this statement. If a database + * doesn't support positioned update/delete, this method is a + * no-op. + * + * <p><B>Note:</B> By definition, positioned update/delete execution + * must be done by a different Statement than the one which + * generated the ResultSet being used for positioning. Also, cursor + * names must be unique within a Connection. + * + * <p>We throw an additional constriction. There can only be one + * cursor active at any one time. + * + * @param name the new cursor name + * @exception SQLException if a database access error occurs + */ + public void setCursorName(String name) throws SQLException + { + connection.setCursorName(name); + } + + /** + * Execute a SQL statement that may return multiple results. We + * don't have to worry about this since we do not support multiple + * ResultSets. You can use getResultSet or getUpdateCount to + * retrieve the result. + * + * @param sql any SQL statement + * @return true if the next result is a ResulSet, false if it is + * an update count or there are no more results + * @exception SQLException if a database access error occurs + */ + public boolean execute(String sql) throws SQLException + { + if(escapeProcessing) + sql=connection.EscapeSQL(sql); + + result = connection.ExecSQL(sql); + return (result != null && ((org.postgresql.ResultSet)result).reallyResultSet()); + } + + /** + * getResultSet returns the current result as a ResultSet. It + * should only be called once per result. + * + * @return the current result set; null if there are no more + * @exception SQLException if a database access error occurs (why?) + */ + public java.sql.ResultSet getResultSet() throws SQLException + { + return result; + } + + /** + * getUpdateCount returns the current result as an update count, + * if the result is a ResultSet or there are no more results, -1 + * is returned. It should only be called once per result. + * + * @return the current result as an update count. + * @exception SQLException if a database access error occurs + */ + public int getUpdateCount() throws SQLException + { + if (result == null) return -1; + if (((org.postgresql.ResultSet)result).reallyResultSet()) return -1; + return ((org.postgresql.ResultSet)result).getResultCount(); + } + + /** + * getMoreResults moves to a Statement's next result. If it returns + * true, this result is a ResulSet. + * + * @return true if the next ResultSet is valid + * @exception SQLException if a database access error occurs + */ + public boolean getMoreResults() throws SQLException + { + result = ((org.postgresql.ResultSet)result).getNext(); + return (result != null && ((org.postgresql.ResultSet)result).reallyResultSet()); + } + + /** + * Returns the status message from the current Result.<p> + * This is used internally by the driver. + * + * @return status message from backend + */ + public String getResultStatusString() + { + if(result == null) + return null; + return ((org.postgresql.ResultSet)result).getStatusString(); + } + + // ** JDBC 2 Extensions ** + + public void addBatch(String sql) throws SQLException + { + if(batch==null) + batch=new Vector(); + batch.addElement(sql); + } + + public void clearBatch() throws SQLException + { + if(batch!=null) + batch.removeAllElements(); + } + + public int[] executeBatch() throws SQLException + { + if(batch==null || batch.isEmpty()) + throw new PSQLException("postgresql.stat.batch.empty"); + + int size=batch.size(); + int[] result=new int[size]; + int i=0; + this.execute("begin"); // PTM: check this when autoCommit is false + try { + for(i=0;i<size;i++) + result[i]=this.executeUpdate((String)batch.elementAt(i)); + this.execute("commit"); // PTM: check this + } catch(SQLException e) { + this.execute("abort"); // PTM: check this + throw new PSQLException("postgresql.stat.batch.error",new Integer(i),batch.elementAt(i)); + } + return result; + } + + public java.sql.Connection getConnection() throws SQLException + { + return (java.sql.Connection)connection; + } + + public int getFetchDirection() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getFetchSize() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getKeysetSize() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getResultSetConcurrency() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public int getResultSetType() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setFetchDirection(int direction) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setFetchSize(int rows) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setKeysetSize(int keys) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setResultSetConcurrency(int value) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + public void setResultSetType(int value) throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + +} diff --git a/src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java b/src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java new file mode 100644 index 0000000000000000000000000000000000000000..5cfd1383ffb1c19e8f262d0ea1f32c62f7907493 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/largeobject/LargeObject.java @@ -0,0 +1,279 @@ +package org.postgresql.largeobject; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; + +import org.postgresql.fastpath.*; + +/** + * This class implements the large object interface to org.postgresql. + * + * <p>It provides the basic methods required to run the interface, plus + * a pair of methods that provide InputStream and OutputStream classes + * for this object. + * + * <p>Normally, client code would use the getAsciiStream, getBinaryStream, + * or getUnicodeStream methods in ResultSet, or setAsciiStream, + * setBinaryStream, or setUnicodeStream methods in PreparedStatement to + * access Large Objects. + * + * <p>However, sometimes lower level access to Large Objects are required, + * that are not supported by the JDBC specification. + * + * <p>Refer to org.postgresql.largeobject.LargeObjectManager on how to gain access + * to a Large Object, or how to create one. + * + * @see org.postgresql.largeobject.LargeObjectManager + * @see org.postgresql.ResultSet#getAsciiStream + * @see org.postgresql.ResultSet#getBinaryStream + * @see org.postgresql.ResultSet#getUnicodeStream + * @see org.postgresql.PreparedStatement#setAsciiStream + * @see org.postgresql.PreparedStatement#setBinaryStream + * @see org.postgresql.PreparedStatement#setUnicodeStream + * @see java.sql.ResultSet#getAsciiStream + * @see java.sql.ResultSet#getBinaryStream + * @see java.sql.ResultSet#getUnicodeStream + * @see java.sql.PreparedStatement#setAsciiStream + * @see java.sql.PreparedStatement#setBinaryStream + * @see java.sql.PreparedStatement#setUnicodeStream + * + */ +public class LargeObject +{ + /** + * Indicates a seek from the begining of a file + */ + public static final int SEEK_SET = 0; + + /** + * Indicates a seek from the current position + */ + public static final int SEEK_CUR = 1; + + /** + * Indicates a seek from the end of a file + */ + public static final int SEEK_END = 2; + + private Fastpath fp; // Fastpath API to use + private int oid; // OID of this object + private int fd; // the descriptor of the open large object + + /** + * This opens a large object. + * + * <p>If the object does not exist, then an SQLException is thrown. + * + * @param fp FastPath API for the connection to use + * @param oid of the Large Object to open + * @param mode Mode of opening the large object + * (defined in LargeObjectManager) + * @exception SQLException if a database-access error occurs. + * @see org.postgresql.largeobject.LargeObjectManager + */ + protected LargeObject(Fastpath fp,int oid,int mode) throws SQLException + { + this.fp = fp; + this.oid = oid; + + FastpathArg args[] = new FastpathArg[2]; + args[0] = new FastpathArg(oid); + args[1] = new FastpathArg(mode); + this.fd = fp.getInteger("lo_open",args); + } + + /** + * @return the OID of this LargeObject + */ + public int getOID() + { + return oid; + } + + /** + * This method closes the object. You must not call methods in this + * object after this is called. + * @exception SQLException if a database-access error occurs. + */ + public void close() throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(fd); + fp.fastpath("lo_close",false,args); // true here as we dont care!! + } + + /** + * Reads some data from the object, and return as a byte[] array + * + * @param len number of bytes to read + * @return byte[] array containing data read + * @exception SQLException if a database-access error occurs. + */ + public byte[] read(int len) throws SQLException + { + // This is the original method, where the entire block (len bytes) + // is retrieved in one go. + FastpathArg args[] = new FastpathArg[2]; + args[0] = new FastpathArg(fd); + args[1] = new FastpathArg(len); + return fp.getData("loread",args); + + // This version allows us to break this down into 4k blocks + //if(len<=4048) { + //// handle as before, return the whole block in one go + //FastpathArg args[] = new FastpathArg[2]; + //args[0] = new FastpathArg(fd); + //args[1] = new FastpathArg(len); + //return fp.getData("loread",args); + //} else { + //// return in 4k blocks + //byte[] buf=new byte[len]; + //int off=0; + //while(len>0) { + //int bs=4048; + //len-=bs; + //if(len<0) { + //bs+=len; + //len=0; + //} + //read(buf,off,bs); + //off+=bs; + //} + //return buf; + //} + } + + /** + * Reads some data from the object into an existing array + * + * @param buf destination array + * @param off offset within array + * @param len number of bytes to read + * @exception SQLException if a database-access error occurs. + */ + public void read(byte buf[],int off,int len) throws SQLException + { + System.arraycopy(read(len),0,buf,off,len); + } + + /** + * Writes an array to the object + * + * @param buf array to write + * @exception SQLException if a database-access error occurs. + */ + public void write(byte buf[]) throws SQLException + { + FastpathArg args[] = new FastpathArg[2]; + args[0] = new FastpathArg(fd); + args[1] = new FastpathArg(buf); + fp.fastpath("lowrite",false,args); + } + + /** + * Writes some data from an array to the object + * + * @param buf destination array + * @param off offset within array + * @param len number of bytes to write + * @exception SQLException if a database-access error occurs. + */ + public void write(byte buf[],int off,int len) throws SQLException + { + byte data[] = new byte[len]; + System.arraycopy(buf,off,data,0,len); + write(data); + } + + /** + * Sets the current position within the object. + * + * <p>This is similar to the fseek() call in the standard C library. It + * allows you to have random access to the large object. + * + * @param pos position within object + * @param ref Either SEEK_SET, SEEK_CUR or SEEK_END + * @exception SQLException if a database-access error occurs. + */ + public void seek(int pos,int ref) throws SQLException + { + FastpathArg args[] = new FastpathArg[3]; + args[0] = new FastpathArg(fd); + args[1] = new FastpathArg(pos); + args[2] = new FastpathArg(ref); + fp.fastpath("lo_lseek",false,args); + } + + /** + * Sets the current position within the object. + * + * <p>This is similar to the fseek() call in the standard C library. It + * allows you to have random access to the large object. + * + * @param pos position within object from begining + * @exception SQLException if a database-access error occurs. + */ + public void seek(int pos) throws SQLException + { + seek(pos,SEEK_SET); + } + + /** + * @return the current position within the object + * @exception SQLException if a database-access error occurs. + */ + public int tell() throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(fd); + return fp.getInteger("lo_tell",args); + } + + /** + * This method is inefficient, as the only way to find out the size of + * the object is to seek to the end, record the current position, then + * return to the original position. + * + * <p>A better method will be found in the future. + * + * @return the size of the large object + * @exception SQLException if a database-access error occurs. + */ + public int size() throws SQLException + { + int cp = tell(); + seek(0,SEEK_END); + int sz = tell(); + seek(cp,SEEK_SET); + return sz; + } + + /** + * Returns an InputStream from this object. + * + * <p>This InputStream can then be used in any method that requires an + * InputStream. + * + * @exception SQLException if a database-access error occurs. + */ + public InputStream getInputStream() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } + + /** + * Returns an OutputStream to this object + * + * <p>This OutputStream can then be used in any method that requires an + * OutputStream. + * + * @exception SQLException if a database-access error occurs. + */ + public OutputStream getOutputStream() throws SQLException + { + throw org.postgresql.Driver.notImplemented(); + } +} diff --git a/src/interfaces/jdbc/org/postgresql/largeobject/LargeObjectManager.java b/src/interfaces/jdbc/org/postgresql/largeobject/LargeObjectManager.java new file mode 100644 index 0000000000000000000000000000000000000000..07aafee9eabb8aec5cc66850d9eac892a1897789 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/largeobject/LargeObjectManager.java @@ -0,0 +1,206 @@ +package org.postgresql.largeobject; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; + +import org.postgresql.fastpath.*; +import org.postgresql.util.*; + +/** + * This class implements the large object interface to org.postgresql. + * + * <p>It provides methods that allow client code to create, open and delete + * large objects from the database. When opening an object, an instance of + * org.postgresql.largeobject.LargeObject is returned, and its methods then allow + * access to the object. + * + * <p>This class can only be created by org.postgresql.Connection + * + * <p>To get access to this class, use the following segment of code: + * <br><pre> + * import org.postgresql.largeobject.*; + * + * Connection conn; + * LargeObjectManager lobj; + * + * ... code that opens a connection ... + * + * lobj = ((org.postgresql.Connection)myconn).getLargeObjectAPI(); + * </pre> + * + * <p>Normally, client code would use the getAsciiStream, getBinaryStream, + * or getUnicodeStream methods in ResultSet, or setAsciiStream, + * setBinaryStream, or setUnicodeStream methods in PreparedStatement to + * access Large Objects. + * + * <p>However, sometimes lower level access to Large Objects are required, + * that are not supported by the JDBC specification. + * + * <p>Refer to org.postgresql.largeobject.LargeObject on how to manipulate the + * contents of a Large Object. + * + * @see org.postgresql.largeobject.LargeObject + * @see org.postgresql.ResultSet#getAsciiStream + * @see org.postgresql.ResultSet#getBinaryStream + * @see org.postgresql.ResultSet#getUnicodeStream + * @see org.postgresql.PreparedStatement#setAsciiStream + * @see org.postgresql.PreparedStatement#setBinaryStream + * @see org.postgresql.PreparedStatement#setUnicodeStream + * @see java.sql.ResultSet#getAsciiStream + * @see java.sql.ResultSet#getBinaryStream + * @see java.sql.ResultSet#getUnicodeStream + * @see java.sql.PreparedStatement#setAsciiStream + * @see java.sql.PreparedStatement#setBinaryStream + * @see java.sql.PreparedStatement#setUnicodeStream + */ +public class LargeObjectManager +{ + // the fastpath api for this connection + private Fastpath fp; + + /** + * This mode indicates we want to write to an object + */ + public static final int WRITE = 0x00020000; + + /** + * This mode indicates we want to read an object + */ + public static final int READ = 0x00040000; + + /** + * This mode is the default. It indicates we want read and write access to + * a large object + */ + public static final int READWRITE = READ | WRITE; + + /** + * This prevents us being created by mere mortals + */ + private LargeObjectManager() + { + } + + /** + * Constructs the LargeObject API. + * + * <p><b>Important Notice</b> + * <br>This method should only be called by org.postgresql.Connection + * + * <p>There should only be one LargeObjectManager per Connection. The + * org.postgresql.Connection class keeps track of the various extension API's + * and it's advised you use those to gain access, and not going direct. + */ + public LargeObjectManager(org.postgresql.Connection conn) throws SQLException + { + // We need Fastpath to do anything + this.fp = conn.getFastpathAPI(); + + // Now get the function oid's for the api + // + // This is an example of Fastpath.addFunctions(); + // + java.sql.ResultSet res = (java.sql.ResultSet)conn.createStatement().executeQuery("select proname, oid from pg_proc" + + " where proname = 'lo_open'" + + " or proname = 'lo_close'" + + " or proname = 'lo_creat'" + + " or proname = 'lo_unlink'" + + " or proname = 'lo_lseek'" + + " or proname = 'lo_tell'" + + " or proname = 'loread'" + + " or proname = 'lowrite'"); + + if(res==null) + throw new PSQLException("postgresql.lo.init"); + + fp.addFunctions(res); + res.close(); + DriverManager.println("Large Object initialised"); + } + + /** + * This opens an existing large object, based on its OID. This method + * assumes that READ and WRITE access is required (the default). + * + * @param oid of large object + * @return LargeObject instance providing access to the object + * @exception SQLException on error + */ + public LargeObject open(int oid) throws SQLException + { + return new LargeObject(fp,oid,READWRITE); + } + + /** + * This opens an existing large object, based on its OID + * + * @param oid of large object + * @param mode mode of open + * @return LargeObject instance providing access to the object + * @exception SQLException on error + */ + public LargeObject open(int oid,int mode) throws SQLException + { + return new LargeObject(fp,oid,mode); + } + + /** + * This creates a large object, returning its OID. + * + * <p>It defaults to READWRITE for the new object's attributes. + * + * @return oid of new object + * @exception SQLException on error + */ + public int create() throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(READWRITE); + return fp.getInteger("lo_creat",args); + } + + /** + * This creates a large object, returning its OID + * + * @param mode a bitmask describing different attributes of the new object + * @return oid of new object + * @exception SQLException on error + */ + public int create(int mode) throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(mode); + return fp.getInteger("lo_creat",args); + } + + /** + * This deletes a large object. + * + * @param oid describing object to delete + * @exception SQLException on error + */ + public void delete(int oid) throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(oid); + fp.fastpath("lo_unlink",false,args); + } + + /** + * This deletes a large object. + * + * <p>It is identical to the delete method, and is supplied as the C API uses + * unlink. + * + * @param oid describing object to delete + * @exception SQLException on error + */ + public void unlink(int oid) throws SQLException + { + delete(oid); + } + +} diff --git a/src/interfaces/jdbc/org/postgresql/largeobject/PGblob.java b/src/interfaces/jdbc/org/postgresql/largeobject/PGblob.java new file mode 100644 index 0000000000000000000000000000000000000000..1fbc84d87c2ce16289d813df29d098cf02adb404 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/largeobject/PGblob.java @@ -0,0 +1,66 @@ +package org.postgresql.largeobject; + +// IMPORTANT NOTE: This file implements the JDBC 2 version of the driver. +// If you make any modifications to this file, you must make sure that the +// changes are also made (if relevent) to the related JDBC 1 class in the +// org.postgresql.jdbc1 package. + + +import java.lang.*; +import java.io.*; +import java.math.*; +import java.text.*; +import java.util.*; +import java.sql.*; +import org.postgresql.Field; +import org.postgresql.largeobject.*; +import org.postgresql.largeobject.*; + +/** + * This implements the Blob interface, which is basically another way to + * access a LargeObject. + * + * $Id: PGblob.java,v 1.1 2000/04/17 20:07:52 peter Exp $ + * + */ +public class PGblob implements java.sql.Blob +{ + private org.postgresql.Connection conn; + private int oid; + private LargeObject lo; + + public PGblob(org.postgresql.Connection conn,int oid) throws SQLException { + this.conn=conn; + this.oid=oid; + LargeObjectManager lom = conn.getLargeObjectAPI(); + this.lo = lom.open(oid); + } + + public long length() throws SQLException { + return lo.size(); + } + + public InputStream getBinaryStream() throws SQLException { + return lo.getInputStream(); + } + + public byte[] getBytes(long pos,int length) throws SQLException { + lo.seek((int)pos,LargeObject.SEEK_SET); + return lo.read(length); + } + + /* + * For now, this is not implemented. + */ + public long position(byte[] pattern,long start) throws SQLException { + throw org.postgresql.Driver.notImplemented(); + } + + /* + * This should be simply passing the byte value of the pattern Blob + */ + public long position(Blob pattern,long start) throws SQLException { + return position(pattern.getBytes(0,(int)pattern.length()),start); + } + +} diff --git a/src/interfaces/jdbc/org/postgresql/util/PGmoney.java b/src/interfaces/jdbc/org/postgresql/util/PGmoney.java new file mode 100644 index 0000000000000000000000000000000000000000..99264345e1f055d5cd2169135e63cf56d41ab309 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/util/PGmoney.java @@ -0,0 +1,105 @@ +package org.postgresql.util; + +import java.io.*; +import java.sql.*; + +/** + * This implements a class that handles the PostgreSQL money and cash types + */ +public class PGmoney extends PGobject implements Serializable,Cloneable +{ + /** + * The value of the field + */ + public double val; + + /** + * @param value of field + */ + public PGmoney(double value) { + this(); + val = value; + } + + /** + * This is called mainly from the other geometric types, when a + * point is imbeded within their definition. + * + * @param value Definition of this point in PostgreSQL's syntax + */ + public PGmoney(String value) throws SQLException + { + this(); + setValue(value); + } + + /** + * Required by the driver + */ + public PGmoney() + { + setType("money"); + } + + /** + * @param s Definition of this point in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + try { + String s1; + boolean negative; + + negative = (s.charAt(0) == '-') ; + + s1 = s.substring(negative ? 2 : 1); + + int pos = s1.indexOf(','); + while (pos != -1) { + s1 = s1.substring(0,pos) + s1.substring(pos +1); + pos = s1.indexOf(','); + } + + val = Double.valueOf(s1).doubleValue(); + val = negative ? -val : val; + + } catch(NumberFormatException e) { + throw new PSQLException("postgresql.money",e); + } + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGmoney) { + PGmoney p = (PGmoney)obj; + return val == p.val; + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGmoney(val); + } + + /** + * @return the PGpoint in the syntax expected by org.postgresql + */ + public String getValue() + { + if (val < 0) { + return "-$" + (-val); + } + else { + return "$"+val; + } + } +} diff --git a/src/interfaces/jdbc/org/postgresql/util/PGobject.java b/src/interfaces/jdbc/org/postgresql/util/PGobject.java new file mode 100644 index 0000000000000000000000000000000000000000..c04d0b450e7c277a298b367c0191e5dc41f8c4f2 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/util/PGobject.java @@ -0,0 +1,102 @@ +package org.postgresql.util; + +import java.io.*; +import java.lang.*; +import java.sql.*; +import java.util.*; + +/** + * org.postgresql.PG_Object is a class used to describe unknown types + * An unknown type is any type that is unknown by JDBC Standards + * + * <p>As of PostgreSQL 6.3, this allows user code to add their own + * handlers via a call to org.postgresql.Connection. These handlers + * must extend this class. + */ +public class PGobject implements Serializable,Cloneable +{ + protected String type; + protected String value; + + /** + * This is called by org.postgresql.Connection.getObject() to create the + * object. + */ + public PGobject() + { + } + + /** + * This method sets the type of this object. + * + * <p>It should not be extended by subclasses, hence its final + * + * @param type a string describing the type of the object + */ + public final void setType(String type) + { + this.type = type; + } + + /** + * This method sets the value of this object. It must be overidden. + * + * @param value a string representation of the value of the object + * @exception SQLException thrown if value is invalid for this type + */ + public void setValue(String value) throws SQLException + { + this.value = value; + } + + /** + * As this cannot change during the life of the object, it's final. + * @return the type name of this object + */ + public final String getType() + { + return type; + } + + /** + * This must be overidden, to return the value of the object, in the + * form required by org.postgresql. + * @return the value of this object + */ + public String getValue() + { + return value; + } + + /** + * This must be overidden to allow comparisons of objects + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGobject) + return ((PGobject)obj).getValue().equals(getValue()); + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + PGobject obj = new PGobject(); + obj.type=type; + obj.value=value; + return obj; + } + + /** + * This is defined here, so user code need not overide it. + * @return the value of this object, in the syntax expected by org.postgresql + */ + public String toString() + { + return getValue(); + } +} diff --git a/src/interfaces/jdbc/org/postgresql/util/PGtokenizer.java b/src/interfaces/jdbc/org/postgresql/util/PGtokenizer.java new file mode 100644 index 0000000000000000000000000000000000000000..b9d1bb68ffeafbe9560893b5e0dfd6013a3a5f10 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/util/PGtokenizer.java @@ -0,0 +1,197 @@ +package org.postgresql.util; + +import java.sql.*; +import java.util.*; + +/** + * This class is used to tokenize the text output of org.postgres. + * + * <p>It's mainly used by the geometric classes, but is useful in parsing any + * output from custom data types output from org.postgresql. + * + * @see org.postgresql.geometric.PGbox + * @see org.postgresql.geometric.PGcircle + * @see org.postgresql.geometric.PGlseg + * @see org.postgresql.geometric.PGpath + * @see org.postgresql.geometric.PGpoint + * @see org.postgresql.geometric.PGpolygon + */ +public class PGtokenizer +{ + // Our tokens + protected Vector tokens; + + /** + * Create a tokeniser. + * + * <p>We could have used StringTokenizer to do this, however, we needed to + * handle nesting of '(' ')' '[' ']' '<' and '>' as these are used + * by the geometric data types. + * + * @param string containing tokens + * @param delim single character to split the tokens + */ + public PGtokenizer(String string,char delim) + { + tokenize(string,delim); + } + + /** + * This resets this tokenizer with a new string and/or delimiter. + * + * @param string containing tokens + * @param delim single character to split the tokens + */ + public int tokenize(String string,char delim) + { + tokens = new Vector(); + + // nest holds how many levels we are in the current token. + // if this is > 0 then we don't split a token when delim is matched. + // + // The Geometric datatypes use this, because often a type may have others + // (usualls PGpoint) imbedded within a token. + // + // Peter 1998 Jan 6 - Added < and > to the nesting rules + int nest=0,p,s; + + for(p=0,s=0;p<string.length();p++) { + char c = string.charAt(p); + + // increase nesting if an open character is found + if(c == '(' || c == '[' || c == '<') + nest++; + + // decrease nesting if a close character is found + if(c == ')' || c == ']' || c == '>') + nest--; + + if(nest==0 && c==delim) { + tokens.addElement(string.substring(s,p)); + s=p+1; // +1 to skip the delimiter + } + + } + + // Don't forget the last token ;-) + if(s<string.length()) + tokens.addElement(string.substring(s)); + + return tokens.size(); + } + + /** + * @return the number of tokens available + */ + public int getSize() + { + return tokens.size(); + } + + /** + * @param n Token number ( 0 ... getSize()-1 ) + * @return The token value + */ + public String getToken(int n) + { + return (String)tokens.elementAt(n); + } + + /** + * This returns a new tokenizer based on one of our tokens. + * + * The geometric datatypes use this to process nested tokens (usually + * PGpoint). + * + * @param n Token number ( 0 ... getSize()-1 ) + * @param delim The delimiter to use + * @return A new instance of PGtokenizer based on the token + */ + public PGtokenizer tokenizeToken(int n,char delim) + { + return new PGtokenizer(getToken(n),delim); + } + + /** + * This removes the lead/trailing strings from a string + * @param s Source string + * @param l Leading string to remove + * @param t Trailing string to remove + * @return String without the lead/trailing strings + */ + public static String remove(String s,String l,String t) + { + if(s.startsWith(l)) s = s.substring(l.length()); + if(s.endsWith(t)) s = s.substring(0,s.length()-t.length()); + return s; + } + + /** + * This removes the lead/trailing strings from all tokens + * @param l Leading string to remove + * @param t Trailing string to remove + */ + public void remove(String l,String t) + { + for(int i=0;i<tokens.size();i++) { + tokens.setElementAt(remove((String)tokens.elementAt(i),l,t),i); + } + } + + /** + * Removes ( and ) from the beginning and end of a string + * @param s String to remove from + * @return String without the ( or ) + */ + public static String removePara(String s) + { + return remove(s,"(",")"); + } + + /** + * Removes ( and ) from the beginning and end of all tokens + * @return String without the ( or ) + */ + public void removePara() + { + remove("(",")"); + } + + /** + * Removes [ and ] from the beginning and end of a string + * @param s String to remove from + * @return String without the [ or ] + */ + public static String removeBox(String s) + { + return remove(s,"[","]"); + } + + /** + * Removes [ and ] from the beginning and end of all tokens + * @return String without the [ or ] + */ + public void removeBox() + { + remove("[","]"); + } + + /** + * Removes < and > from the beginning and end of a string + * @param s String to remove from + * @return String without the < or > + */ + public static String removeAngle(String s) + { + return remove(s,"<",">"); + } + + /** + * Removes < and > from the beginning and end of all tokens + * @return String without the < or > + */ + public void removeAngle() + { + remove("<",">"); + } +} diff --git a/src/interfaces/jdbc/org/postgresql/util/PSQLException.java b/src/interfaces/jdbc/org/postgresql/util/PSQLException.java new file mode 100644 index 0000000000000000000000000000000000000000..36290a5db1531d8f584b5d2a51280fd42647634f --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/util/PSQLException.java @@ -0,0 +1,111 @@ +package org.postgresql.util; + +import java.sql.*; +import java.text.*; +import java.util.*; + +/** + * This class extends SQLException, and provides our internationalisation handling + */ +public class PSQLException extends SQLException +{ + private String message; + + // Cache for future errors + static ResourceBundle bundle; + + /** + * This provides the same functionality to SQLException + * @param error Error string + */ + public PSQLException(String error) { + super(); + translate(error,null); + } + + /** + * A more generic entry point. + * @param error Error string or standard message id + * @param args Array of arguments + */ + public PSQLException(String error,Object[] args) + { + //super(); + translate(error,args); + } + + /** + * Helper version for 1 arg + */ + public PSQLException(String error,Object arg) + { + super(); + Object[] argv = new Object[1]; + argv[0] = arg; + translate(error,argv); + } + + /** + * Helper version for 2 args + */ + public PSQLException(String error,Object arg1,Object arg2) + { + super(); + Object[] argv = new Object[2]; + argv[0] = arg1; + argv[1] = arg2; + translate(error,argv); + } + + /** + * This does the actual translation + */ + private void translate(String id,Object[] args) + { + if(bundle == null) { + try { + bundle = ResourceBundle.getBundle("org.postgresql.errors"); + } catch(MissingResourceException e) { + } + } + + // Now look up a localized message. If one is not found, then use + // the supplied message instead. + message = null; + try { + message = bundle.getString(id); + } catch(MissingResourceException e) { + message = id; + } + + // Expand any arguments + if(args!=null) + message = MessageFormat.format(message,args); + + } + + /** + * Overides Throwable + */ + public String getLocalizedMessage() + { + return message; + } + + /** + * Overides Throwable + */ + public String getMessage() + { + return message; + } + + /** + * Overides Object + */ + public String toString() + { + return message; + } + +} diff --git a/src/interfaces/jdbc/org/postgresql/util/Serialize.java b/src/interfaces/jdbc/org/postgresql/util/Serialize.java new file mode 100644 index 0000000000000000000000000000000000000000..3af43e6eb840e8a93391eea6411ce02b0fbc2b38 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/util/Serialize.java @@ -0,0 +1,342 @@ +package org.postgresql.util; + +import java.io.*; +import java.lang.*; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.sql.*; + +/** + * This class uses PostgreSQL's object oriented features to store Java Objects. + * + * It does this by mapping a Java Class name to a table in the database. Each + * entry in this new table then represents a Serialized instance of this + * class. As each entry has an OID (Object IDentifier), this OID can be + * included in another table. + * + * This is too complex to show here, and will be documented in the main + * documents in more detail. + * + */ +public class Serialize +{ + // This is the connection that the instance refers to + protected org.postgresql.Connection conn; + + // This is the table name + protected String tableName; + + // This is the class name + protected String className; + + // This is the Class for this serialzed object + protected Class ourClass; + + /** + * This creates an instance that can be used to serialize or deserialize + * a Java object from a PostgreSQL table. + */ + public Serialize(org.postgresql.Connection c,String type) throws SQLException + { + try { + conn = c; + tableName = type.toLowerCase(); + className = toClassName(type); + ourClass = Class.forName(className); + } catch(ClassNotFoundException cnfe) { + throw new PSQLException("postgresql.serial.noclass",type); + } + + // Second check, the type must be a table + boolean status = false; + ResultSet rs = conn.ExecSQL("select typname from pg_type,pg_class where typname=relname and typname='"+type+"'"); + if(rs!=null) { + if(rs.next()) + status=true; + rs.close(); + } + // This should never occur, as org.postgresql has it's own internal checks + if(!status) + throw new PSQLException("postgresql.serial.table",type); + + // Finally cache the fields within the table + } + + /** + * This fetches an object from a table, given it's OID + * @param oid The oid of the object + * @return Object relating to oid + * @exception SQLException on error + */ + public Object fetch(int oid) throws SQLException + { + try { + Object obj = ourClass.newInstance(); + + // NB: we use java.lang.reflect here to prevent confusion with + // the org.postgresql.Field + java.lang.reflect.Field f[] = ourClass.getDeclaredFields(); + boolean hasOID=false; + int oidFIELD=-1; + StringBuffer sb = new StringBuffer("select"); + char sep=' '; + for(int i=0;i<f.length;i++) { + String n = f[i].getName(); + if(n.equals("oid")) { + hasOID=true; + oidFIELD=i; + } + sb.append(sep); + sb.append(n); + sep=','; + } + sb.append(" from "); + sb.append(tableName); + sb.append(" where oid="); + sb.append(oid); + + DriverManager.println("store: "+sb.toString()); + ResultSet rs = conn.ExecSQL(sb.toString()); + if(rs!=null) { + if(rs.next()) { + for(int i=0;i<f.length;i++) { + f[i].set(obj,rs.getObject(i+1)); + } + } + rs.close(); + } else + throw new PSQLException("postgresql.unexpected"); + return obj; + } catch(IllegalAccessException iae) { + throw new SQLException(iae.toString()); + } catch(InstantiationException ie) { + throw new SQLException(ie.toString()); + } + } + + /** + * This stores an object into a table, returning it's OID.<p> + * + * If the object has an int called OID, and it is > 0, then + * that value is used for the OID, and the table will be updated. + * If the value of OID is 0, then a new row will be created, and the + * value of OID will be set in the object. This enables an object's + * value in the database to be updateable. + * + * If the object has no int called OID, then the object is stored. However + * if the object is later retrieved, amended and stored again, it's new + * state will be appended to the table, and will not overwrite the old + * entries. + * + * @param o Object to store (must implement Serializable) + * @return oid of stored object + * @exception SQLException on error + */ + public int store(Object o) throws SQLException + { + try { + // NB: we use java.lang.reflect here to prevent confusion with + // the org.postgresql.Field + java.lang.reflect.Field f[] = ourClass.getDeclaredFields(); + boolean hasOID=false; + int oidFIELD=-1; + boolean update=false; + + // Find out if we have an oid value + for(int i=0;i<f.length;i++) { + String n = f[i].getName(); + if(n.equals("oid")) { + hasOID=true; + oidFIELD=i; + + // We are an update if oid != 0 + update = f[i].getInt(o)>0; + } + } + + StringBuffer sb = new StringBuffer(update?"update "+tableName+" set":"insert into "+tableName+" values "); + char sep=update?' ':'('; + for(int i=0;i<f.length;i++) { + String n = f[i].getName(); + sb.append(sep); + sb.append(n); + sep=','; + if(update) { + sb.append('='); + if(f[i].getType().getName().equals("java.lang.String")) { + sb.append('\''); + sb.append(f[i].get(o).toString()); + sb.append('\''); + } else + sb.append(f[i].get(o).toString()); + } + } + + if(!update) { + sb.append(") values "); + sep='('; + for(int i=0;i<f.length;i++) { + String n = f[i].getName(); + if(f[i].getType().getName().equals("java.lang.String")) { + sb.append('\''); + sb.append(f[i].get(o).toString()); + sb.append('\''); + } else + sb.append(f[i].get(o).toString()); + } + sb.append(')'); + } + + DriverManager.println("store: "+sb.toString()); + ResultSet rs = conn.ExecSQL(sb.toString()); + if(rs!=null) { + rs.close(); + } + + // fetch the OID for returning + int oid=0; + if(hasOID) { + // set the oid in the object + f[oidFIELD].setInt(o,oid); + } + return oid; + + } catch(IllegalAccessException iae) { + throw new SQLException(iae.toString()); + } + } + + /** + * This method is not used by the driver, but it creates a table, given + * a Serializable Java Object. It should be used before serializing any + * objects. + * @param c Connection to database + * @param o Object to base table on + * @exception SQLException on error + */ + public static void create(org.postgresql.Connection con,Object o) throws SQLException + { + create(con,o.getClass()); + } + + /** + * This method is not used by the driver, but it creates a table, given + * a Serializable Java Object. It should be used before serializing any + * objects. + * @param c Connection to database + * @param o Class to base table on + * @exception SQLException on error + */ + public static void create(org.postgresql.Connection con,Class c) throws SQLException + { + if(c.isInterface()) + throw new PSQLException("postgresql.serial.interface"); + + // See if the table exists + String tableName = toPostgreSQL(c.getName()); + + ResultSet rs = con.ExecSQL("select relname from pg_class where relname = '"+tableName+"'"); + if(!rs.next()) { + DriverManager.println("found "+rs.getString(1)); + // No entries returned, so the table doesn't exist + + StringBuffer sb = new StringBuffer("create table "); + sb.append(tableName); + char sep='('; + + java.lang.reflect.Field[] fields = c.getDeclaredFields(); + for(int i=0;i<fields.length;i++) { + Class type = fields[i].getType(); + + // oid is a special field + if(!fields[i].getName().equals("oid")) { + sb.append(sep); + sb.append(fields[i].getName()); + sb.append(' '); + sep=','; + + if(type.isArray()) { + // array handling + } else { + // convert the java type to org.postgresql, recursing if a class + // is found + String n = fields[i].getType().getName(); + int j=0; + for(;j<tp.length && !tp[j][0].equals(n);j++); + if(j<tp.length) + sb.append(tp[j][1]); + else { + create(con,fields[i].getType()); + sb.append(toPostgreSQL(n)); + } + } + } + } + sb.append(")"); + + // Now create the table + DriverManager.println("Serialize.create:"+sb); + con.ExecSQL(sb.toString()); + rs.close(); + } else { + DriverManager.println("Serialize.create: table "+tableName+" exists, skipping"); + } + } + + // This is used to translate between Java primitives and PostgreSQL types. + private static final String tp[][] = { + {"boolean", "int1"}, + {"double", "float8"}, + {"float", "float4"}, + {"int", "int4"}, + {"long", "int4"}, + {"short", "int2"}, + {"java.lang.String", "text"}, + {"java.lang.Integer", "int4"}, + {"java.lang.Float", "float4"}, + {"java.lang.Double", "float8"}, + {"java.lang.Short", "int2"} + }; + + /** + * This converts a Java Class name to a org.postgresql table, by replacing . with + * _<p> + * + * Because of this, a Class name may not have _ in the name.<p> + * Another limitation, is that the entire class name (including packages) + * cannot be longer than 32 characters (a limit forced by PostgreSQL). + * + * @param name Class name + * @return PostgreSQL table name + * @exception SQLException on error + */ + public static String toPostgreSQL(String name) throws SQLException + { + name = name.toLowerCase(); + + if(name.indexOf("_")>-1) + throw new PSQLException("postgresql.serial.underscore"); + + if(name.length()>32) + throw new PSQLException("postgresql.serial.namelength",name,new Integer(name.length())); + + return name.replace('.','_'); + } + + + /** + * This converts a org.postgresql table to a Java Class name, by replacing _ with + * .<p> + * + * @param name PostgreSQL table name + * @return Class name + * @exception SQLException on error + */ + public static String toClassName(String name) throws SQLException + { + name = name.toLowerCase(); + return name.replace('_','.'); + } + +} diff --git a/src/interfaces/jdbc/org/postgresql/util/UnixCrypt.java b/src/interfaces/jdbc/org/postgresql/util/UnixCrypt.java new file mode 100644 index 0000000000000000000000000000000000000000..36c640c4b4e7d68e8d2871b9fa42efb3c948c020 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/util/UnixCrypt.java @@ -0,0 +1,675 @@ +package org.postgresql.util; + +/** + * This class provides us with the ability to encrypt passwords when sent + * over the network stream + * + * <P>Contains static methods to encrypt and compare + * passwords with Unix encrypted passwords.</P> + * + * <P>See <A HREF="http://www.zeh.com/local/jfd/crypt.html"> + * John Dumas's Java Crypt page</A> for the original source.</P> + * + * @author jdumas@zgs.com (John Dumas) + */ +public class UnixCrypt extends Object +{ + // + // Null constructor - can't instantiate class + private UnixCrypt() + { + } + + private static final char[] saltChars = + ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./".toCharArray()); + + private static final int ITERATIONS = 16; + + private static final int con_salt[] = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, + 0x23, 0x24, 0x25, 0x20, 0x21, 0x22, 0x23, 0x24, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, + 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, + 0x3D, 0x3E, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static final boolean shifts2[] = + { + false, false, true, true, true, true, true, true, + false, true, true, true, true, true, true, false + }; + + private static final int skb[][] = + { + { + /* for C bits (numbered as per FIPS 46) 1 2 3 4 5 6 */ + 0x00000000, 0x00000010, 0x20000000, 0x20000010, + 0x00010000, 0x00010010, 0x20010000, 0x20010010, + 0x00000800, 0x00000810, 0x20000800, 0x20000810, + 0x00010800, 0x00010810, 0x20010800, 0x20010810, + 0x00000020, 0x00000030, 0x20000020, 0x20000030, + 0x00010020, 0x00010030, 0x20010020, 0x20010030, + 0x00000820, 0x00000830, 0x20000820, 0x20000830, + 0x00010820, 0x00010830, 0x20010820, 0x20010830, + 0x00080000, 0x00080010, 0x20080000, 0x20080010, + 0x00090000, 0x00090010, 0x20090000, 0x20090010, + 0x00080800, 0x00080810, 0x20080800, 0x20080810, + 0x00090800, 0x00090810, 0x20090800, 0x20090810, + 0x00080020, 0x00080030, 0x20080020, 0x20080030, + 0x00090020, 0x00090030, 0x20090020, 0x20090030, + 0x00080820, 0x00080830, 0x20080820, 0x20080830, + 0x00090820, 0x00090830, 0x20090820, 0x20090830, + }, + { + /* for C bits (numbered as per FIPS 46) 7 8 10 11 12 13 */ + 0x00000000, 0x02000000, 0x00002000, 0x02002000, + 0x00200000, 0x02200000, 0x00202000, 0x02202000, + 0x00000004, 0x02000004, 0x00002004, 0x02002004, + 0x00200004, 0x02200004, 0x00202004, 0x02202004, + 0x00000400, 0x02000400, 0x00002400, 0x02002400, + 0x00200400, 0x02200400, 0x00202400, 0x02202400, + 0x00000404, 0x02000404, 0x00002404, 0x02002404, + 0x00200404, 0x02200404, 0x00202404, 0x02202404, + 0x10000000, 0x12000000, 0x10002000, 0x12002000, + 0x10200000, 0x12200000, 0x10202000, 0x12202000, + 0x10000004, 0x12000004, 0x10002004, 0x12002004, + 0x10200004, 0x12200004, 0x10202004, 0x12202004, + 0x10000400, 0x12000400, 0x10002400, 0x12002400, + 0x10200400, 0x12200400, 0x10202400, 0x12202400, + 0x10000404, 0x12000404, 0x10002404, 0x12002404, + 0x10200404, 0x12200404, 0x10202404, 0x12202404, + }, + { + /* for C bits (numbered as per FIPS 46) 14 15 16 17 19 20 */ + 0x00000000, 0x00000001, 0x00040000, 0x00040001, + 0x01000000, 0x01000001, 0x01040000, 0x01040001, + 0x00000002, 0x00000003, 0x00040002, 0x00040003, + 0x01000002, 0x01000003, 0x01040002, 0x01040003, + 0x00000200, 0x00000201, 0x00040200, 0x00040201, + 0x01000200, 0x01000201, 0x01040200, 0x01040201, + 0x00000202, 0x00000203, 0x00040202, 0x00040203, + 0x01000202, 0x01000203, 0x01040202, 0x01040203, + 0x08000000, 0x08000001, 0x08040000, 0x08040001, + 0x09000000, 0x09000001, 0x09040000, 0x09040001, + 0x08000002, 0x08000003, 0x08040002, 0x08040003, + 0x09000002, 0x09000003, 0x09040002, 0x09040003, + 0x08000200, 0x08000201, 0x08040200, 0x08040201, + 0x09000200, 0x09000201, 0x09040200, 0x09040201, + 0x08000202, 0x08000203, 0x08040202, 0x08040203, + 0x09000202, 0x09000203, 0x09040202, 0x09040203, + }, + { + /* for C bits (numbered as per FIPS 46) 21 23 24 26 27 28 */ + 0x00000000, 0x00100000, 0x00000100, 0x00100100, + 0x00000008, 0x00100008, 0x00000108, 0x00100108, + 0x00001000, 0x00101000, 0x00001100, 0x00101100, + 0x00001008, 0x00101008, 0x00001108, 0x00101108, + 0x04000000, 0x04100000, 0x04000100, 0x04100100, + 0x04000008, 0x04100008, 0x04000108, 0x04100108, + 0x04001000, 0x04101000, 0x04001100, 0x04101100, + 0x04001008, 0x04101008, 0x04001108, 0x04101108, + 0x00020000, 0x00120000, 0x00020100, 0x00120100, + 0x00020008, 0x00120008, 0x00020108, 0x00120108, + 0x00021000, 0x00121000, 0x00021100, 0x00121100, + 0x00021008, 0x00121008, 0x00021108, 0x00121108, + 0x04020000, 0x04120000, 0x04020100, 0x04120100, + 0x04020008, 0x04120008, 0x04020108, 0x04120108, + 0x04021000, 0x04121000, 0x04021100, 0x04121100, + 0x04021008, 0x04121008, 0x04021108, 0x04121108, + }, + { + /* for D bits (numbered as per FIPS 46) 1 2 3 4 5 6 */ + 0x00000000, 0x10000000, 0x00010000, 0x10010000, + 0x00000004, 0x10000004, 0x00010004, 0x10010004, + 0x20000000, 0x30000000, 0x20010000, 0x30010000, + 0x20000004, 0x30000004, 0x20010004, 0x30010004, + 0x00100000, 0x10100000, 0x00110000, 0x10110000, + 0x00100004, 0x10100004, 0x00110004, 0x10110004, + 0x20100000, 0x30100000, 0x20110000, 0x30110000, + 0x20100004, 0x30100004, 0x20110004, 0x30110004, + 0x00001000, 0x10001000, 0x00011000, 0x10011000, + 0x00001004, 0x10001004, 0x00011004, 0x10011004, + 0x20001000, 0x30001000, 0x20011000, 0x30011000, + 0x20001004, 0x30001004, 0x20011004, 0x30011004, + 0x00101000, 0x10101000, 0x00111000, 0x10111000, + 0x00101004, 0x10101004, 0x00111004, 0x10111004, + 0x20101000, 0x30101000, 0x20111000, 0x30111000, + 0x20101004, 0x30101004, 0x20111004, 0x30111004, + }, + { + /* for D bits (numbered as per FIPS 46) 8 9 11 12 13 14 */ + 0x00000000, 0x08000000, 0x00000008, 0x08000008, + 0x00000400, 0x08000400, 0x00000408, 0x08000408, + 0x00020000, 0x08020000, 0x00020008, 0x08020008, + 0x00020400, 0x08020400, 0x00020408, 0x08020408, + 0x00000001, 0x08000001, 0x00000009, 0x08000009, + 0x00000401, 0x08000401, 0x00000409, 0x08000409, + 0x00020001, 0x08020001, 0x00020009, 0x08020009, + 0x00020401, 0x08020401, 0x00020409, 0x08020409, + 0x02000000, 0x0A000000, 0x02000008, 0x0A000008, + 0x02000400, 0x0A000400, 0x02000408, 0x0A000408, + 0x02020000, 0x0A020000, 0x02020008, 0x0A020008, + 0x02020400, 0x0A020400, 0x02020408, 0x0A020408, + 0x02000001, 0x0A000001, 0x02000009, 0x0A000009, + 0x02000401, 0x0A000401, 0x02000409, 0x0A000409, + 0x02020001, 0x0A020001, 0x02020009, 0x0A020009, + 0x02020401, 0x0A020401, 0x02020409, 0x0A020409, + }, + { + /* for D bits (numbered as per FIPS 46) 16 17 18 19 20 21 */ + 0x00000000, 0x00000100, 0x00080000, 0x00080100, + 0x01000000, 0x01000100, 0x01080000, 0x01080100, + 0x00000010, 0x00000110, 0x00080010, 0x00080110, + 0x01000010, 0x01000110, 0x01080010, 0x01080110, + 0x00200000, 0x00200100, 0x00280000, 0x00280100, + 0x01200000, 0x01200100, 0x01280000, 0x01280100, + 0x00200010, 0x00200110, 0x00280010, 0x00280110, + 0x01200010, 0x01200110, 0x01280010, 0x01280110, + 0x00000200, 0x00000300, 0x00080200, 0x00080300, + 0x01000200, 0x01000300, 0x01080200, 0x01080300, + 0x00000210, 0x00000310, 0x00080210, 0x00080310, + 0x01000210, 0x01000310, 0x01080210, 0x01080310, + 0x00200200, 0x00200300, 0x00280200, 0x00280300, + 0x01200200, 0x01200300, 0x01280200, 0x01280300, + 0x00200210, 0x00200310, 0x00280210, 0x00280310, + 0x01200210, 0x01200310, 0x01280210, 0x01280310, + }, + { + /* for D bits (numbered as per FIPS 46) 22 23 24 25 27 28 */ + 0x00000000, 0x04000000, 0x00040000, 0x04040000, + 0x00000002, 0x04000002, 0x00040002, 0x04040002, + 0x00002000, 0x04002000, 0x00042000, 0x04042000, + 0x00002002, 0x04002002, 0x00042002, 0x04042002, + 0x00000020, 0x04000020, 0x00040020, 0x04040020, + 0x00000022, 0x04000022, 0x00040022, 0x04040022, + 0x00002020, 0x04002020, 0x00042020, 0x04042020, + 0x00002022, 0x04002022, 0x00042022, 0x04042022, + 0x00000800, 0x04000800, 0x00040800, 0x04040800, + 0x00000802, 0x04000802, 0x00040802, 0x04040802, + 0x00002800, 0x04002800, 0x00042800, 0x04042800, + 0x00002802, 0x04002802, 0x00042802, 0x04042802, + 0x00000820, 0x04000820, 0x00040820, 0x04040820, + 0x00000822, 0x04000822, 0x00040822, 0x04040822, + 0x00002820, 0x04002820, 0x00042820, 0x04042820, + 0x00002822, 0x04002822, 0x00042822, 0x04042822, + }, + }; + + private static final int SPtrans[][] = + { + { + /* nibble 0 */ + 0x00820200, 0x00020000, 0x80800000, 0x80820200, + 0x00800000, 0x80020200, 0x80020000, 0x80800000, + 0x80020200, 0x00820200, 0x00820000, 0x80000200, + 0x80800200, 0x00800000, 0x00000000, 0x80020000, + 0x00020000, 0x80000000, 0x00800200, 0x00020200, + 0x80820200, 0x00820000, 0x80000200, 0x00800200, + 0x80000000, 0x00000200, 0x00020200, 0x80820000, + 0x00000200, 0x80800200, 0x80820000, 0x00000000, + 0x00000000, 0x80820200, 0x00800200, 0x80020000, + 0x00820200, 0x00020000, 0x80000200, 0x00800200, + 0x80820000, 0x00000200, 0x00020200, 0x80800000, + 0x80020200, 0x80000000, 0x80800000, 0x00820000, + 0x80820200, 0x00020200, 0x00820000, 0x80800200, + 0x00800000, 0x80000200, 0x80020000, 0x00000000, + 0x00020000, 0x00800000, 0x80800200, 0x00820200, + 0x80000000, 0x80820000, 0x00000200, 0x80020200, + }, + { + /* nibble 1 */ + 0x10042004, 0x00000000, 0x00042000, 0x10040000, + 0x10000004, 0x00002004, 0x10002000, 0x00042000, + 0x00002000, 0x10040004, 0x00000004, 0x10002000, + 0x00040004, 0x10042000, 0x10040000, 0x00000004, + 0x00040000, 0x10002004, 0x10040004, 0x00002000, + 0x00042004, 0x10000000, 0x00000000, 0x00040004, + 0x10002004, 0x00042004, 0x10042000, 0x10000004, + 0x10000000, 0x00040000, 0x00002004, 0x10042004, + 0x00040004, 0x10042000, 0x10002000, 0x00042004, + 0x10042004, 0x00040004, 0x10000004, 0x00000000, + 0x10000000, 0x00002004, 0x00040000, 0x10040004, + 0x00002000, 0x10000000, 0x00042004, 0x10002004, + 0x10042000, 0x00002000, 0x00000000, 0x10000004, + 0x00000004, 0x10042004, 0x00042000, 0x10040000, + 0x10040004, 0x00040000, 0x00002004, 0x10002000, + 0x10002004, 0x00000004, 0x10040000, 0x00042000, + }, + { + /* nibble 2 */ + 0x41000000, 0x01010040, 0x00000040, 0x41000040, + 0x40010000, 0x01000000, 0x41000040, 0x00010040, + 0x01000040, 0x00010000, 0x01010000, 0x40000000, + 0x41010040, 0x40000040, 0x40000000, 0x41010000, + 0x00000000, 0x40010000, 0x01010040, 0x00000040, + 0x40000040, 0x41010040, 0x00010000, 0x41000000, + 0x41010000, 0x01000040, 0x40010040, 0x01010000, + 0x00010040, 0x00000000, 0x01000000, 0x40010040, + 0x01010040, 0x00000040, 0x40000000, 0x00010000, + 0x40000040, 0x40010000, 0x01010000, 0x41000040, + 0x00000000, 0x01010040, 0x00010040, 0x41010000, + 0x40010000, 0x01000000, 0x41010040, 0x40000000, + 0x40010040, 0x41000000, 0x01000000, 0x41010040, + 0x00010000, 0x01000040, 0x41000040, 0x00010040, + 0x01000040, 0x00000000, 0x41010000, 0x40000040, + 0x41000000, 0x40010040, 0x00000040, 0x01010000, + }, + { + /* nibble 3 */ + 0x00100402, 0x04000400, 0x00000002, 0x04100402, + 0x00000000, 0x04100000, 0x04000402, 0x00100002, + 0x04100400, 0x04000002, 0x04000000, 0x00000402, + 0x04000002, 0x00100402, 0x00100000, 0x04000000, + 0x04100002, 0x00100400, 0x00000400, 0x00000002, + 0x00100400, 0x04000402, 0x04100000, 0x00000400, + 0x00000402, 0x00000000, 0x00100002, 0x04100400, + 0x04000400, 0x04100002, 0x04100402, 0x00100000, + 0x04100002, 0x00000402, 0x00100000, 0x04000002, + 0x00100400, 0x04000400, 0x00000002, 0x04100000, + 0x04000402, 0x00000000, 0x00000400, 0x00100002, + 0x00000000, 0x04100002, 0x04100400, 0x00000400, + 0x04000000, 0x04100402, 0x00100402, 0x00100000, + 0x04100402, 0x00000002, 0x04000400, 0x00100402, + 0x00100002, 0x00100400, 0x04100000, 0x04000402, + 0x00000402, 0x04000000, 0x04000002, 0x04100400, + }, + { + /* nibble 4 */ + 0x02000000, 0x00004000, 0x00000100, 0x02004108, + 0x02004008, 0x02000100, 0x00004108, 0x02004000, + 0x00004000, 0x00000008, 0x02000008, 0x00004100, + 0x02000108, 0x02004008, 0x02004100, 0x00000000, + 0x00004100, 0x02000000, 0x00004008, 0x00000108, + 0x02000100, 0x00004108, 0x00000000, 0x02000008, + 0x00000008, 0x02000108, 0x02004108, 0x00004008, + 0x02004000, 0x00000100, 0x00000108, 0x02004100, + 0x02004100, 0x02000108, 0x00004008, 0x02004000, + 0x00004000, 0x00000008, 0x02000008, 0x02000100, + 0x02000000, 0x00004100, 0x02004108, 0x00000000, + 0x00004108, 0x02000000, 0x00000100, 0x00004008, + 0x02000108, 0x00000100, 0x00000000, 0x02004108, + 0x02004008, 0x02004100, 0x00000108, 0x00004000, + 0x00004100, 0x02004008, 0x02000100, 0x00000108, + 0x00000008, 0x00004108, 0x02004000, 0x02000008, + }, + { + /* nibble 5 */ + 0x20000010, 0x00080010, 0x00000000, 0x20080800, + 0x00080010, 0x00000800, 0x20000810, 0x00080000, + 0x00000810, 0x20080810, 0x00080800, 0x20000000, + 0x20000800, 0x20000010, 0x20080000, 0x00080810, + 0x00080000, 0x20000810, 0x20080010, 0x00000000, + 0x00000800, 0x00000010, 0x20080800, 0x20080010, + 0x20080810, 0x20080000, 0x20000000, 0x00000810, + 0x00000010, 0x00080800, 0x00080810, 0x20000800, + 0x00000810, 0x20000000, 0x20000800, 0x00080810, + 0x20080800, 0x00080010, 0x00000000, 0x20000800, + 0x20000000, 0x00000800, 0x20080010, 0x00080000, + 0x00080010, 0x20080810, 0x00080800, 0x00000010, + 0x20080810, 0x00080800, 0x00080000, 0x20000810, + 0x20000010, 0x20080000, 0x00080810, 0x00000000, + 0x00000800, 0x20000010, 0x20000810, 0x20080800, + 0x20080000, 0x00000810, 0x00000010, 0x20080010, + }, + { + /* nibble 6 */ + 0x00001000, 0x00000080, 0x00400080, 0x00400001, + 0x00401081, 0x00001001, 0x00001080, 0x00000000, + 0x00400000, 0x00400081, 0x00000081, 0x00401000, + 0x00000001, 0x00401080, 0x00401000, 0x00000081, + 0x00400081, 0x00001000, 0x00001001, 0x00401081, + 0x00000000, 0x00400080, 0x00400001, 0x00001080, + 0x00401001, 0x00001081, 0x00401080, 0x00000001, + 0x00001081, 0x00401001, 0x00000080, 0x00400000, + 0x00001081, 0x00401000, 0x00401001, 0x00000081, + 0x00001000, 0x00000080, 0x00400000, 0x00401001, + 0x00400081, 0x00001081, 0x00001080, 0x00000000, + 0x00000080, 0x00400001, 0x00000001, 0x00400080, + 0x00000000, 0x00400081, 0x00400080, 0x00001080, + 0x00000081, 0x00001000, 0x00401081, 0x00400000, + 0x00401080, 0x00000001, 0x00001001, 0x00401081, + 0x00400001, 0x00401080, 0x00401000, 0x00001001, + }, + { + /* nibble 7 */ + 0x08200020, 0x08208000, 0x00008020, 0x00000000, + 0x08008000, 0x00200020, 0x08200000, 0x08208020, + 0x00000020, 0x08000000, 0x00208000, 0x00008020, + 0x00208020, 0x08008020, 0x08000020, 0x08200000, + 0x00008000, 0x00208020, 0x00200020, 0x08008000, + 0x08208020, 0x08000020, 0x00000000, 0x00208000, + 0x08000000, 0x00200000, 0x08008020, 0x08200020, + 0x00200000, 0x00008000, 0x08208000, 0x00000020, + 0x00200000, 0x00008000, 0x08000020, 0x08208020, + 0x00008020, 0x08000000, 0x00000000, 0x00208000, + 0x08200020, 0x08008020, 0x08008000, 0x00200020, + 0x08208000, 0x00000020, 0x00200020, 0x08008000, + 0x08208020, 0x00200000, 0x08200000, 0x08000020, + 0x00208000, 0x00008020, 0x08008020, 0x08200000, + 0x00000020, 0x08208000, 0x00208020, 0x00000000, + 0x08000000, 0x08200020, 0x00008000, 0x00208020 + } + }; + + private static final int cov_2char[] = + { + 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, + 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, + 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A + }; + + private static final int byteToUnsigned(byte b) + { + int value = (int)b; + + return(value >= 0 ? value : value + 256); + } + + private static int fourBytesToInt(byte b[], int offset) + { + int value; + + value = byteToUnsigned(b[offset++]); + value |= (byteToUnsigned(b[offset++]) << 8); + value |= (byteToUnsigned(b[offset++]) << 16); + value |= (byteToUnsigned(b[offset++]) << 24); + + return(value); + } + + private static final void intToFourBytes(int iValue, byte b[], int offset) + { + b[offset++] = (byte)((iValue) & 0xff); + b[offset++] = (byte)((iValue >>> 8 ) & 0xff); + b[offset++] = (byte)((iValue >>> 16) & 0xff); + b[offset++] = (byte)((iValue >>> 24) & 0xff); + } + + private static final void PERM_OP(int a, int b, int n, int m, int results[]) + { + int t; + + t = ((a >>> n) ^ b) & m; + a ^= t << n; + b ^= t; + + results[0] = a; + results[1] = b; + } + + private static final int HPERM_OP(int a, int n, int m) + { + int t; + + t = ((a << (16 - n)) ^ a) & m; + a = a ^ t ^ (t >>> (16 - n)); + + return(a); + } + + private static int [] des_set_key(byte key[]) + { + int schedule[] = new int[ITERATIONS * 2]; + + int c = fourBytesToInt(key, 0); + int d = fourBytesToInt(key, 4); + + int results[] = new int[2]; + + PERM_OP(d, c, 4, 0x0f0f0f0f, results); + d = results[0]; c = results[1]; + + c = HPERM_OP(c, -2, 0xcccc0000); + d = HPERM_OP(d, -2, 0xcccc0000); + + PERM_OP(d, c, 1, 0x55555555, results); + d = results[0]; c = results[1]; + + PERM_OP(c, d, 8, 0x00ff00ff, results); + c = results[0]; d = results[1]; + + PERM_OP(d, c, 1, 0x55555555, results); + d = results[0]; c = results[1]; + + d = (((d & 0x000000ff) << 16) | (d & 0x0000ff00) | + ((d & 0x00ff0000) >>> 16) | ((c & 0xf0000000) >>> 4)); + c &= 0x0fffffff; + + int s, t; + int j = 0; + + for(int i = 0; i < ITERATIONS; i ++) + { + if(shifts2[i]) + { + c = (c >>> 2) | (c << 26); + d = (d >>> 2) | (d << 26); + } + else + { + c = (c >>> 1) | (c << 27); + d = (d >>> 1) | (d << 27); + } + + c &= 0x0fffffff; + d &= 0x0fffffff; + + s = skb[0][ (c ) & 0x3f ]| + skb[1][((c >>> 6) & 0x03) | ((c >>> 7) & 0x3c)]| + skb[2][((c >>> 13) & 0x0f) | ((c >>> 14) & 0x30)]| + skb[3][((c >>> 20) & 0x01) | ((c >>> 21) & 0x06) | + ((c >>> 22) & 0x38)]; + + t = skb[4][ (d ) & 0x3f ]| + skb[5][((d >>> 7) & 0x03) | ((d >>> 8) & 0x3c)]| + skb[6][ (d >>>15) & 0x3f ]| + skb[7][((d >>>21) & 0x0f) | ((d >>> 22) & 0x30)]; + + schedule[j++] = ((t << 16) | (s & 0x0000ffff)) & 0xffffffff; + s = ((s >>> 16) | (t & 0xffff0000)); + + s = (s << 4) | (s >>> 28); + schedule[j++] = s & 0xffffffff; + } + return(schedule); + } + + private static final int D_ENCRYPT + ( + int L, int R, int S, int E0, int E1, int s[] + ) + { + int t, u, v; + + v = R ^ (R >>> 16); + u = v & E0; + v = v & E1; + u = (u ^ (u << 16)) ^ R ^ s[S]; + t = (v ^ (v << 16)) ^ R ^ s[S + 1]; + t = (t >>> 4) | (t << 28); + + L ^= SPtrans[1][(t ) & 0x3f] | + SPtrans[3][(t >>> 8) & 0x3f] | + SPtrans[5][(t >>> 16) & 0x3f] | + SPtrans[7][(t >>> 24) & 0x3f] | + SPtrans[0][(u ) & 0x3f] | + SPtrans[2][(u >>> 8) & 0x3f] | + SPtrans[4][(u >>> 16) & 0x3f] | + SPtrans[6][(u >>> 24) & 0x3f]; + + return(L); + } + + private static final int [] body(int schedule[], int Eswap0, int Eswap1) + { + int left = 0; + int right = 0; + int t = 0; + + for(int j = 0; j < 25; j ++) + { + for(int i = 0; i < ITERATIONS * 2; i += 4) + { + left = D_ENCRYPT(left, right, i, Eswap0, Eswap1, schedule); + right = D_ENCRYPT(right, left, i + 2, Eswap0, Eswap1, schedule); + } + t = left; + left = right; + right = t; + } + + t = right; + + right = (left >>> 1) | (left << 31); + left = (t >>> 1) | (t << 31); + + left &= 0xffffffff; + right &= 0xffffffff; + + int results[] = new int[2]; + + PERM_OP(right, left, 1, 0x55555555, results); + right = results[0]; left = results[1]; + + PERM_OP(left, right, 8, 0x00ff00ff, results); + left = results[0]; right = results[1]; + + PERM_OP(right, left, 2, 0x33333333, results); + right = results[0]; left = results[1]; + + PERM_OP(left, right, 16, 0x0000ffff, results); + left = results[0]; right = results[1]; + + PERM_OP(right, left, 4, 0x0f0f0f0f, results); + right = results[0]; left = results[1]; + + int out[] = new int[2]; + + out[0] = left; out[1] = right; + + return(out); + } + + /** + * <P>Encrypt a password given the cleartext password and a "salt".</P> + * @param salt A two-character string representing the salt used to + * iterate the encryption engine in lots of different ways. If you + * are generating a new encryption then this value should be + * randomised. + * @param original The password to be encrypted. + * @return A string consisting of the 2-character salt followed by the + * encrypted password. + */ + public static final String crypt(String salt, String original) + { + while(salt.length() < 2) + salt += "A"; + + StringBuffer buffer = new StringBuffer(" "); + + char charZero = salt.charAt(0); + char charOne = salt.charAt(1); + + buffer.setCharAt(0, charZero); + buffer.setCharAt(1, charOne); + + int Eswap0 = con_salt[(int)charZero]; + int Eswap1 = con_salt[(int)charOne] << 4; + + byte key[] = new byte[8]; + + for(int i = 0; i < key.length; i ++) + key[i] = (byte)0; + + for(int i = 0; i < key.length && i < original.length(); i ++) + { + int iChar = (int)original.charAt(i); + + key[i] = (byte)(iChar << 1); + } + + int schedule[] = des_set_key(key); + int out[] = body(schedule, Eswap0, Eswap1); + + byte b[] = new byte[9]; + + intToFourBytes(out[0], b, 0); + intToFourBytes(out[1], b, 4); + b[8] = 0; + + for(int i = 2, y = 0, u = 0x80; i < 13; i ++) + { + for(int j = 0, c = 0; j < 6; j ++) + { + c <<= 1; + + if(((int)b[y] & u) != 0) + c |= 1; + + u >>>= 1; + + if(u == 0) + { + y++; + u = 0x80; + } + buffer.setCharAt(i, (char)cov_2char[c]); + } + } + return(buffer.toString()); + } + + /** + * <P>Encrypt a password given the cleartext password. This method + * generates a random salt using the 'java.util.Random' class.</P> + * @param original The password to be encrypted. + * @return A string consisting of the 2-character salt followed by the + * encrypted password. + */ + public static final String crypt(String original) + { + java.util.Random randomGenerator = new java.util.Random(); + int numSaltChars = saltChars.length; + String salt; + + salt = (new StringBuffer()).append(saltChars[Math.abs(randomGenerator.nextInt()) % numSaltChars]).append(saltChars[Math.abs(randomGenerator.nextInt()) % numSaltChars]).toString(); + + return crypt(salt, original); + } + + /** + * <P>Check that <I>enteredPassword</I> encrypts to + * <I>encryptedPassword</I>.</P> + * @param encryptedPassword The <I>encryptedPassword</I>. The first + * two characters are assumed to be the salt. This string would + * be the same as one found in a Unix <U>/etc/passwd</U> file. + * @param enteredPassword The password as entered by the user (or + * otherwise aquired). + * @return <B>true</B> if the password should be considered correct. + */ + public final static boolean matches(String encryptedPassword, String enteredPassword) + { + String salt = encryptedPassword.substring(0, 3); + String newCrypt = crypt(salt, enteredPassword); + + return newCrypt.equals(encryptedPassword); + } +} + diff --git a/src/interfaces/jdbc/org/postgresql/xa/ClientConnection.java b/src/interfaces/jdbc/org/postgresql/xa/ClientConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..3f06b09b4c261434e20d903bec502b8da49375b4 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/xa/ClientConnection.java @@ -0,0 +1,495 @@ +/** + * Redistribution and use of this software and associated documentation + * ("Software"), with or without modification, are permitted provided + * that the following conditions are met: + * + * 1. Redistributions of source code must retain copyright + * statements and notices. Redistributions must also contain a + * copy of this document. + * + * 2. Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. The name "Exolab" must not be used to endorse or promote + * products derived from this Software without prior written + * permission of Exoffice Technologies. For written permission, + * please contact info@exolab.org. + * + * 4. Products derived from this Software may not be called "Exolab" + * nor may "Exolab" appear in their names without prior written + * permission of Exoffice Technologies. Exolab is a registered + * trademark of Exoffice Technologies. + * + * 5. Due credit should be given to the Exolab Project + * (http://www.exolab.org/). + * + * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved. + * + * $Id: ClientConnection.java,v 1.1 2000/04/17 20:07:55 peter Exp $ + */ + + +package org.postgresql.xa; + + +import java.util.*; +import java.sql.*; + + +/** + * Encapsulates an application's view of an XA/pooled connection. + * The XA connection is managed by the application server through it's + * {@link javax.sql.XAConnection} interface. The underlying JDBC + * connection is a standard JDBC connection. The application's + * JDBC connection gives access to the underlying JDBC connection but + * is managed by the application server. The application is given an + * instance of this class and not the underlying connection directly. + * + * + * @author <a href="arkin@exoffice.com">Assaf Arkin</a> + * @version 1.0 + * @see XAConnectionImpl + * @see XADataSourceImpl + * @see Connection + */ +final class ClientConnection + implements Connection +{ + + + /** + * The pooled XA connection that created this client connection + * and should be used to report closure and fatal errors. + */ + private XAConnectionImpl _xaConn; + + + /** + * This identifier was handed on to use when we were created by + * {@link XAConnection}. If since then the XA connection was asked + * to create another connection or was closed, our identifier will + * no longer be valid and any call to {@link + * XAConnection#getUnderlying} will throw an exception. Previously, + * the XA connection would hold a reference to use and tell us to + * terminate, but that prevented ClientConnection from being + * finalized. + */ + private int _clientId; + + + + + /** + * Construct a new client connection to provide access to the + * underlying JDBC connection (<tt>underlying</tt>) on behalf of + * an XA/pooled connection (<tt>xaConn<tt/>). The pooled connection + * is required to notify of connection closure and fatal errors. + * + * @param xaConn The XA/pooled connection that created this + * client connection + * @param clientId A unique identifier handed to us by + * {@link XAConnection} + * @param underlying The underlying JDBC connection + */ + ClientConnection( XAConnectionImpl xaConn, int clientId ) + { + _xaConn = xaConn; + _clientId = clientId; + } + + + public Statement createStatement() + throws SQLException + { + try { + return getUnderlying().createStatement(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public Statement createStatement( int resultSetType, int resultSetConcurrency ) + throws SQLException + { + try { + return getUnderlying().createStatement( resultSetType, resultSetConcurrency ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public PreparedStatement prepareStatement( String sql ) + throws SQLException + { + try { + return getUnderlying().prepareStatement( sql ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency ) + throws SQLException + { + try { + return getUnderlying().prepareStatement( sql, resultSetType, resultSetConcurrency ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public CallableStatement prepareCall( String sql ) + throws SQLException + { + try { + return getUnderlying().prepareCall( sql ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency ) + throws SQLException + { + try { + return getUnderlying().prepareCall( sql, resultSetType, resultSetConcurrency ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public String nativeSQL( String sql ) + throws SQLException + { + try { + return getUnderlying().nativeSQL( sql ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public DatabaseMetaData getMetaData() + throws SQLException + { + try { + return getUnderlying().getMetaData(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public void setCatalog( String catalog ) + throws SQLException + { + try { + getUnderlying().setCatalog( catalog ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public String getCatalog() + throws SQLException + { + try { + return getUnderlying().getCatalog(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public SQLWarning getWarnings() + throws SQLException + { + try { + return getUnderlying().getWarnings(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public void clearWarnings() + throws SQLException + { + try { + getUnderlying().clearWarnings(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public Map getTypeMap() + throws SQLException + { + try { + return getUnderlying().getTypeMap(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public void setTypeMap( Map map ) + throws SQLException + { + try { + getUnderlying().setTypeMap( map ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public void setAutoCommit( boolean autoCommit ) + throws SQLException + { + // Cannot set auto-commit inside a transaction. + if ( _xaConn.insideGlobalTx() ) + throw new SQLException( "Cannot commit/rollback a connection managed by the transaction manager" ); + try { + getUnderlying().setAutoCommit( autoCommit ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public boolean getAutoCommit() + throws SQLException + { + try { + return getUnderlying().getAutoCommit(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public void commit() + throws SQLException + { + // Cannot commit directly if we're inside a global transaction. + if ( _xaConn.insideGlobalTx() ) + throw new SQLException( "Cannot commit/rollback a connection managed by the transaction manager" ); + // Cannot commit a read-only transaction. + if ( isReadOnly() ) + throw new SQLException( "Cannot commit/rollback a read-only transaction" ); + + // This only occurs if not inside a local transaction. + try { + getUnderlying().commit(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + + public void rollback() + throws SQLException + { + // Cannot commit directly if we're inside a global transaction. + if ( _xaConn.insideGlobalTx() ) + throw new SQLException( "Cannot commit/rollback a connection managed by the transaction manager" ); + + // This only occurs if not inside a local transaction. + try { + getUnderlying().rollback(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public void setReadOnly( boolean readOnly ) + throws SQLException + { + try { + getUnderlying().setReadOnly( readOnly ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public boolean isReadOnly() + throws SQLException + { + try { + return getUnderlying().isReadOnly(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public void setTransactionIsolation( int level ) + throws SQLException + { + try { + getUnderlying().setTransactionIsolation( level ); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public int getTransactionIsolation() + throws SQLException + { + try { + return getUnderlying().getTransactionIsolation(); + } catch ( SQLException except ) { + notifyError( except ); + throw except; + } + } + + + public synchronized void close() + throws SQLException + { + if ( _xaConn == null ) + return; + + // Notify the XA connection that we are no longer going + // to be used. Whether the underlying connection is released, + // held until the transaction terminates, etc is not + // a concern of us. + _xaConn.notifyClose( _clientId ); + _xaConn = null; + } + + + public synchronized boolean isClosed() + { + // Simple way of determining if this connection is closed. + // The actual connection is never closed, it is pooled. + return ( _xaConn == null ); + } + + + /** + * Called by {@link XAConnectionImpl} to terminate this connection + * by dissociating it from the underlying JDBC connection. + * The application would call {@link #close} but {@link + * XAConnectionImpl} cannot, since pooled connection requirements + * will cause an inifinite loop. This method should not attempt + * to notify either a closure or fatal error, but rather throw an + * exception if it fails. + */ + /* Deprecated: see XAConnection._clientId + void terminate() + { + _xaConn = null; + } + */ + + + protected void finalize() + throws Throwable + { + close(); + } + + + public String toString() + { + try { + return getUnderlying().toString(); + } catch ( SQLException except ) { + return "XAConnection: Connection closed"; + } + } + + + /** + * Called when an exception is thrown by the underlying connection + * to determine whether the exception is critical or not. If the + * exception is critical, notifies the XA connection to forget + * about this connection. + * + * @param except The exception thrown by the underlying + * connection + */ + void notifyError( SQLException except ) + { + if ( _xaConn != null ) + _xaConn.notifyError( _clientId, except ); + } + + + /** + * Called to retrieve the underlying JDBC connection. Actual JDBC + * operations are performed against it. Throws an SQLException if + * this connection has been closed. + */ + Connection getUnderlying() + throws SQLException + { + if ( _xaConn == null ) + throw new SQLException( "This connection has been closed" ); + // Must pass the client identifier so XAConnection can determine + // whether we are still valid. If it tells us we're no longer + // valid, we have little to do. + try { + return _xaConn.getUnderlying( _clientId ); + } catch ( SQLException except ) { + _xaConn = null; + throw except; + } + } + + +} + + + diff --git a/src/interfaces/jdbc/org/postgresql/xa/Test.java b/src/interfaces/jdbc/org/postgresql/xa/Test.java new file mode 100644 index 0000000000000000000000000000000000000000..193f0b18ae54547d7139a3856930b97fadf605ff --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/xa/Test.java @@ -0,0 +1,377 @@ +package org.postgresql.xa; + + +import java.sql.*; +import javax.sql.*; +import javax.transaction.xa.*; + + +public class Test +{ + + + public static void main( String args[] ) + { + XADataSource xaDS; + java.io.PrintWriter log; + + log = new java.io.PrintWriter( System.out ); + try { + + xaDS = new XADataSource(); + xaDS.setDatabaseName( "test" ); + xaDS.setUser( "arkin" ); + xaDS.setPassword( "natasha" ); + xaDS.setLogWriter( log ); + + Thread1 thread1; + + thread1 = new Thread1(); + thread1.xaConn = xaDS.getXAConnection(); + thread1.xid1 = new XidImpl(); + + Thread2 thread2; + + thread2 = new Thread2(); + thread1.thread2 = thread2; + thread2.thread1 = thread1; + thread2.xaConn = xaDS.getXAConnection(); + thread2.xid1 = thread1.xid1; + thread2.xid2 = new XidImpl(); + + thread1.start(); + thread2.start(); + + } catch ( Exception except ) { + System.out.println( except ); + except.printStackTrace(); + } + log.flush(); + } + + +} + + +class Thread1 + extends Thread +{ + + + public void run() + { + Connection conn; + XAResource xaRes; + Statement stmt; + ResultSet rs; + + try { + conn = xaConn.getConnection(); + xaRes = xaConn.getXAResource(); + } catch ( Exception except ) { + System.out.println( except ); + return; + } + // Initially the table should have no value. + try { + stmt = conn.createStatement(); + stmt.executeUpdate( "update test set text='nothing' where id=1" ); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // Begin a transaction on this connection. + // Perform an update on the table. + System.out.println( "[Thread1] Starting transaction" ); + try { + xaRes.start( xid1, XAResource.TMNOFLAGS ); + } catch ( XAException except ) { + System.out.println( except ); + return; + } + System.out.println( "[Thread1] Updating table" ); + try { + stmt = conn.createStatement(); + stmt.executeUpdate( "update test set text='first' where id=1" ); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // Thread2 will start a new transction and attempt + // to perform an update on the table and will lock. + System.out.println( "[Thread1] Waking up Thread2" ); + thread2.interrupt(); + try { + sleep( Integer.MAX_VALUE ); + } catch ( InterruptedException except ) { } + + + // Perform a select from the table just to prove + // that Thread2 failed in its update. + System.out.println( "[Thread1] Selecting from table" ); + try { + stmt = conn.createStatement(); + rs = stmt.executeQuery( "select text from test where id=1" ); + rs.next(); + System.out.println( "First = " + rs.getString( 1 ) ); + rs.close(); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // Thread2 will now attempt to join our transaction + // and perform an update on the table. + System.out.println( "[Thread1] Waking up Thread2" ); + thread2.interrupt(); + try { + sleep( Integer.MAX_VALUE ); + } catch ( InterruptedException except ) { } + + + // Perform a select from the table to prove that + // Thread2 managed to update it. + System.out.println( "[Thread1] Selecting from table" ); + try { + stmt = conn.createStatement(); + rs = stmt.executeQuery( "select text from test where id=1" ); + rs.next(); + System.out.println( "First = " + rs.getString( 1 ) ); + rs.close(); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // We now end the transaction for this thread. + // We are no longer in the shared transaction. + // Perform an update on the table and the update + // will lock. + System.out.println( "[Thread1] Ending transaction" ); + try { + xaRes.end( xid1, XAResource.TMSUCCESS ); + } catch ( XAException except ) { + System.out.println( except ); + return; + } + System.out.println( "[Thread1] Selecting from table" ); + try { + stmt = conn.createStatement(); + stmt.executeUpdate( "update test set text='first' where id=1" ); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // Thread 2 will now end the transcation and commit it. + System.out.println( "[Thread1] Waking up Thread2" ); + thread2.interrupt(); + try { + sleep( Integer.MAX_VALUE ); + } catch ( InterruptedException except ) { } + + + // Perform a select on the table to prove that it + // was only updated inside the transaction. + System.out.println( "[Thread1] Selecting from table" ); + try { + stmt = conn.createStatement(); + rs = stmt.executeQuery( "select text from test where id=1" ); + rs.next(); + System.out.println( "First = " + rs.getString( 1 ) ); + rs.close(); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + } + + + javax.sql.XAConnection xaConn; + + + Xid xid1; + + + Thread thread2; + + +} + + +class Thread2 + extends Thread +{ + + + public void run() + { + Connection conn; + XAResource xaRes; + Statement stmt; + ResultSet rs; + + + try { + conn = xaConn.getConnection(); + xaRes = xaConn.getXAResource(); + } catch ( Exception except ) { + System.out.println( except ); + return; + } + // Thread2 immediately goes to sleep, waits + // for Thread1 to wake it up. + try { + sleep( Integer.MAX_VALUE ); + } catch ( InterruptedException except ) { } + + + // Begin a transaction on this connection. + // Perform an update on the table. This will + // lock since Thread1 is in a different transaction + // updating the same table. + System.out.println( "[Thread2] Starting transaction" ); + try { + xaRes.start( xid2, XAResource.TMNOFLAGS ); + } catch ( XAException except ) { + System.out.println( except ); + return; + } + System.out.println( "[Thread2] Updating table" ); + try { + stmt = conn.createStatement(); + stmt.executeUpdate( "update test set text='second' where id=1" ); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // Thread1 will now proof that it owns the + // transaction. + System.out.println( "[Thread2] Waking up Thread1" ); + thread1.interrupt(); + try { + sleep( Integer.MAX_VALUE ); + } catch ( InterruptedException except ) { } + + + // We will now join the transaction shared with + // Thread1 and try to update the table again. + System.out.println( "[Thread2] Dumping transaction" ); + try { + xaRes.end( xid2, XAResource.TMFAIL ); + // xaRes.rollback( xid2 ); + xaRes.forget( xid2 ); + } catch ( XAException except ) { + System.out.println( except ); + return; + } + System.out.println( "[Thread2] Joining transaction of Thread1" ); + try { + xaRes.start( xid1, XAResource.TMJOIN ); + } catch ( XAException except ) { + System.out.println( except ); + return; + } + System.out.println( "[Thread2] Updating table" ); + try { + stmt = conn.createStatement(); + stmt.executeUpdate( "update test set text='second' where id=1" ); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // Thread1 will now proof that it could update + // the table. + System.out.println( "[Thread2] Waking up Thread1" ); + thread1.interrupt(); + try { + sleep( Integer.MAX_VALUE ); + } catch ( InterruptedException except ) { } + + + // We will now end the transaction and commit it. + System.out.println( "[Thread2] Commiting transaction" ); + try { + xaRes.end( xid1, XAResource.TMSUCCESS ); + xaRes.prepare( xid1 ); + xaRes.commit( xid1, false ); + xaRes.forget( xid1 ); + } catch ( XAException except ) { + System.out.println( except ); + return; + } + + + // Perform a select on the table to prove that it + // was only updated inside the transaction. + System.out.println( "[Thread2] Selecting from table" ); + try { + stmt = conn.createStatement(); + rs = stmt.executeQuery( "select text from test where id=1" ); + rs.next(); + System.out.println( "First = " + rs.getString( 1 ) ); + rs.close(); + stmt.close(); + } catch ( SQLException except ) { + System.out.println( except ); + } + + + // Thread1 will now proof that the table was only + // updated inside the transaction. Thread 2 will die. + System.out.println( "[Thread2] Waking up Thread1" ); + thread1.interrupt(); + } + + + javax.sql.XAConnection xaConn; + + + Xid xid1; + + + Xid xid2; + + + Thread thread1; + + +} + + + +class XidImpl + implements Xid +{ + + + public byte[] getBranchQualifier() + { + return null; + } + + + public byte[] getGlobalTransactionId() + { + return null; + } + + + public int getFormatId() + { + return 0; + } + + +} diff --git a/src/interfaces/jdbc/org/postgresql/xa/TwoPhaseConnection.java b/src/interfaces/jdbc/org/postgresql/xa/TwoPhaseConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..f80da15ed08d85b0d8421ccb34f7a262aeebfd2a --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/xa/TwoPhaseConnection.java @@ -0,0 +1,116 @@ +/** + * Redistribution and use of this software and associated documentation + * ("Software"), with or without modification, are permitted provided + * that the following conditions are met: + * + * 1. Redistributions of source code must retain copyright + * statements and notices. Redistributions must also contain a + * copy of this document. + * + * 2. Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. The name "Exolab" must not be used to endorse or promote + * products derived from this Software without prior written + * permission of Exoffice Technologies. For written permission, + * please contact info@exolab.org. + * + * 4. Products derived from this Software may not be called "Exolab" + * nor may "Exolab" appear in their names without prior written + * permission of Exoffice Technologies. Exolab is a registered + * trademark of Exoffice Technologies. + * + * 5. Due credit should be given to the Exolab Project + * (http://www.exolab.org/). + * + * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved. + * + * $Id: TwoPhaseConnection.java,v 1.1 2000/04/17 20:07:55 peter Exp $ + */ + + +package org.postgresql.xa; + + +import java.sql.SQLException; + + +/** + * Defines two-phase commit support for a JDBC connection used by + * {@link XAConnection}. A JDBC connection that can implement any of + * these features should extend this interface and attempt to + * implement as much as it can. + * <p> + * {@link #prepare} is used as part of the two phase commit protocol + * to determine whether the transaction can commit or must rollback. + * Failure to implement this method will cause all connections to vote + * for commit, whether or not they can actually commit, leading to + * mixed heuristics. + * <p> + * {@link #enableSQLTransactions} allows the SQL begin/commit/rollback + * commands to be disabled for the duration of a transaction managed + * through an {@link javax.transaction.xaXAResource}, preventing the + * application from demarcating transactions directly. + * <p> + * {@link #isCriticalError} is used to tell if an exception thrown by + * the connection is fatal and the connection should not be returned + * to the pool. + * + * + * @author <a href="arkin@exoffice.com">Assaf Arkin</a> + * @version 1.0 + */ +public interface TwoPhaseConnection +{ + + + /** + * Enables or disables transaction demarcation through SQL commit + * and rollback. When the connection falls under control of + * {@link XAConnection}, SQL commit/rollback commands will be + * disabled to prevent direct transaction demarcation. + * + * @param flag True to enable SQL transactions (the default) + */ + public void enableSQLTransactions( boolean flag ); + + + /** + * Called to prepare the transaction for commit. Returns true if + * the transaction is prepared, false if the transaction is + * read-only. If the transaction has been marked for rollback, + * throws a {@link RollbackException}. + * + * @return True if can commit, false if read-only + * @throws SQLException If transaction has been marked for + * rollback or cannot commit for any other reason + */ + public boolean prepare() + throws SQLException; + + + /** + * Returns true if the error issued by this connection is a + * critical error and the connection should be terminated. + * + * @param except The exception thrown by this connection + */ + public boolean isCriticalError( SQLException except ); + + +} diff --git a/src/interfaces/jdbc/org/postgresql/xa/TxConnection.java b/src/interfaces/jdbc/org/postgresql/xa/TxConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..5cf8836eb7f9536877696123ce5244707dc11680 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/xa/TxConnection.java @@ -0,0 +1,129 @@ +/** + * Redistribution and use of this software and associated documentation + * ("Software"), with or without modification, are permitted provided + * that the following conditions are met: + * + * 1. Redistributions of source code must retain copyright + * statements and notices. Redistributions must also contain a + * copy of this document. + * + * 2. Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. The name "Exolab" must not be used to endorse or promote + * products derived from this Software without prior written + * permission of Exoffice Technologies. For written permission, + * please contact info@exolab.org. + * + * 4. Products derived from this Software may not be called "Exolab" + * nor may "Exolab" appear in their names without prior written + * permission of Exoffice Technologies. Exolab is a registered + * trademark of Exoffice Technologies. + * + * 5. Due credit should be given to the Exolab Project + * (http://www.exolab.org/). + * + * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved. + * + * $Id: TxConnection.java,v 1.1 2000/04/17 20:07:56 peter Exp $ + */ + + +package org.postgresql.xa; + + +import java.sql.Connection; +import javax.transaction.xa.Xid; + + +/** + * Describes an open connection associated with a transaction. When a + * transaction is opened for a connection, this record is created for + * the connection. It indicates the underlying JDBC connection and + * transaction Xid. Multiple XA connection that fall under the same + * transaction Xid will share the same TxConnection object. + * + * + * @author <a href="arkin@exoffice.com">Assaf Arkin</a> + * @version 1.0 + * @see Xid + * @see XAConnectionImpl + */ +final class TxConnection +{ + + + /** + * The Xid of the transactions. Connections that are not + * associated with a transaction are not represented here. + */ + Xid xid; + + + /** + * Holds the underlying JDBC connection for as long as this + * connection is useable. If the connection has been rolled back, + * timed out or had any other error, this variable will null + * and the connection is considered failed. + */ + Connection conn; + + + + /** + * Indicates the clock time (in ms) when the transaction should + * time out. The transaction times out when + * <tt>System.currentTimeMillis() > timeout</tt>. + */ + long timeout; + + + /** + * Indicates the clock time (in ms) when the transaction started. + */ + long started; + + + /** + * Reference counter indicates how many XA connections share this + * underlying connection and transaction. Always one or more. + */ + int count; + + + /** + * True if the transaction has failed due to time out. + */ + boolean timedOut; + + + /** + * True if the transaction has already been prepared. + */ + boolean prepared; + + + /** + * True if the transaction has been prepared and found out to be + * read-only. Read-only transactions do not require commit/rollback. + */ + boolean readOnly; + + +} + diff --git a/src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java b/src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..896472d696f5f80747269ec90157abce07fd1b08 --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/xa/XAConnectionImpl.java @@ -0,0 +1,855 @@ +/** + * Redistribution and use of this software and associated documentation + * ("Software"), with or without modification, are permitted provided + * that the following conditions are met: + * + * 1. Redistributions of source code must retain copyright + * statements and notices. Redistributions must also contain a + * copy of this document. + * + * 2. Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. The name "Exolab" must not be used to endorse or promote + * products derived from this Software without prior written + * permission of Exoffice Technologies. For written permission, + * please contact info@exolab.org. + * + * 4. Products derived from this Software may not be called "Exolab" + * nor may "Exolab" appear in their names without prior written + * permission of Exoffice Technologies. Exolab is a registered + * trademark of Exoffice Technologies. + * + * 5. Due credit should be given to the Exolab Project + * (http://www.exolab.org/). + * + * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved. + * + * $Id: XAConnectionImpl.java,v 1.1 2000/04/17 20:07:56 peter Exp $ + */ + + +package org.postgresql.xa; + + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Vector; +import javax.sql.XAConnection; +import javax.sql.PooledConnection; +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.transaction.RollbackException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import javax.transaction.xa.XAException; + + +/** + * Implements an X/A connection that can be pooled and managed from + * inside a transaction monitor. This is the XA connection returned + * to the application server from the {@link XADataSourceImpl} and + * will be used to obtain {@link ClientConnection} for the + * application. + * <p> + * If the transaction is managed through the JDBC interface, this + * connection will reference the underlying JDBC connection directly. + * If this resource is enlisted with a global transaction through + * the {@link XAResource} interface, it will reference a transactional + * connection, or {@link TxConnection}. Such a connection may be + * shared by two or more XA connections enlisted with the same + * transaction. + * + * + * @author <a href="arkin@exoffice.com">Assaf Arkin</a> + * @version 1.0 + * @see ClientConnection + * @see ConnectionEventListener + * @see TxConnection + */ +public final class XAConnectionImpl + implements XAConnection, XAResource +{ + + + /** + * This is the underlying JDBC connection represented + * by this pooled connection. This variable may initially be null, + * in which case {@link #getUnderlying} will return a new + * connection and set this variable. This variable is mutually + * exclusive with {@link #_txConn} and is always null for + * connections inside a transaction. + */ + Connection _underlying; + + + /** + * If this connection is part of a global transaction, this + * object identifies the transaction. The transaction's + * underlying JDBC connection is exposed through this object and + * {@link #_underlying} is null. If this connection is closed, + * then the connection has been timedout. Commit/rollback will + * always set this variable to null. + */ + private TxConnection _txConn; + + + /** + * The client connection last handed to the application. If the + * application calls {@link #getConnection} again, we should hand + * out a new client connection and render the previous one closed. + */ + // No longer in use, see _clientId + //private ClientConnection _clientConn; + + + /** + * An event listener can be registered and notified when the + * client connection has been closed by the application or a + * fatal error rendered it unuseable. + */ + private ConnectionEventListener _listener; + + + /** + * The resource manager is used to share connections within the + * same transaction. + */ + private XADataSourceImpl _resManager; + + + /** + * This is an identifier we hand to the client connection when we + * create it. When the client connection asks for the underlying + * connection, we compare the identifiers. If since that point we + * created a new client connection, we regard an old client + * connection as discarded and do not hand it the underlying + * connection. + * <p> + * Previously, when a new client connection was created, we used + * a reference to the old one to terminate it. This proved to + * not work well, since the client connection could never be + * finalized. + */ + private int _clientId = 1; + + + /** + * Construct a new XA/pooled connection with the underlying JDBC + * connection suitable for this driver only. This is a one to one + * mapping between this connection and the underlying connection. + * The underlying connection is only provided for pooled + * connections. XA connections are suspect of being enlisted with + * a global transaction which might already bear an underlying + * connection. If not, one will be created later on. + */ + XAConnectionImpl( XADataSourceImpl resManager, + Connection underlying ) + { + _underlying = underlying; + _resManager = resManager; + } + + + public synchronized void close() + throws SQLException + { + // This is our indication that this connection has been + // closed programmatically. + if ( _resManager == null ) + throw new SQLException( "This connection has been closed" ); + + // The client connection is no longer useable. + /* Deprecated: see _clientId + if ( _clientConn != null ) + _clientConn.terminate(); + */ + _clientId = -1; + + // The underlying connection is closed and this connection + // is no longer useable. This method can be called any number + // of times (e.g. we use it in finalizer). We do not handle + // transactions, we just kill the connection. + try { + if ( _underlying != null ) { + _underlying.commit(); + _underlying.close(); + } else if ( _txConn != null ) { + try { + end( _txConn.xid, TMSUCCESS ); + } catch ( XAException except ) { } + } + } finally { + _resManager = null; + _underlying = null; + _txConn = null; + _listener = null; + } + } + + + public XAResource getXAResource() + { + // The connection acts as it's own resource manager + return this; + } + + + public synchronized void addConnectionEventListener( ConnectionEventListener listener ) + { + if ( listener == null ) + throw new NullPointerException( "XAConnection: Argument 'listener' is null" ); + if ( _listener != null ) + throw new IllegalStateException( "XAConnection: Only one listener supported per connection" ); + _listener = listener; + } + + + public synchronized void removeConnectionEventListener( ConnectionEventListener listener ) + { + if ( listener == null ) + throw new NullPointerException( "XAConnection: Argument 'listener' is null" ); + if ( _listener == null || _listener != listener ) + throw new IllegalStateException( "XAConnection: Listener never registered with this pooled connection" ); + _listener = null; + } + + + public synchronized java.sql.Connection getConnection() + throws SQLException + { + // If this pooled connection has been closed, throw an exception. + if ( _resManager == null ) + throw new SQLException( "This connection has been closed" ); + + // If getConnection() was called before and the underlying + // connection was not closed, we take it away from the previous + // recieved as per the PooledConnection design. + /* Deprecated: see _clientId + if ( _clientConn != null ) + _clientConn.terminate(); + */ + + // If we are handling an underlying connection, we commit the + // old transaction and are ready to work for a new one. + // If we are part of a global transaction we hope that end/ + // start were called properly, but we're not longer in that + // transaction. + if ( _underlying != null ) { + try { + _underlying.commit(); + } catch ( SQLException except ) { + ConnectionEvent event; + + if ( _listener != null ) { + event = new ConnectionEvent( this, except ); + _listener.connectionErrorOccurred( event ); + } + } + } + + // Create a new ClientConnection which will be returned to the + // application. The ClientConnection cannot be closed directly + // and cannot manage it's own transactions. + /* Deprecated: see _clientId + _clientConn = new ClientConnection( this ); + return _clientConn; + */ + return new ClientConnection( this, ++_clientId ); + } + + + /** + * Called by {@link ClientConnection} to notify that the application + * has attempted to close the connection. After this call, the client + * connection is no longer useable and this pooled connection can be + * reused. The event listener is notified immediately. + * + * @param clientId The {@link ClientConnection} identifier + */ + synchronized void notifyClose( int clientId ) + { + ConnectionEvent event; + + // ClientConnection has been closed, we dissociated it from + // the underlying connection and notify any listener that this + // pooled connection can be reused. + /* Deprecated: see clientId + _clientConn.terminate(); + _clientConn = null; + */ + // We have to expect being called by a ClientConnection that we + // no longer regard as valid. That's acceptable, we just ignore. + if ( clientId != _clientId ) + return; + + // If we are handling an underlying connection, we commit the + // old transaction and are ready to work for a new one. + // If we are part of a global transaction we hope that end/ + // start were called properly. + if ( _underlying != null ) { + try { + _underlying.commit(); + } catch ( SQLException except ) { + if ( _listener != null ) { + event = new ConnectionEvent( this, except ); + _listener.connectionErrorOccurred( event ); + } + return; + } + } + // Notify the listener. + if ( _listener != null ) { + event = new ConnectionEvent( this ); + _listener.connectionClosed( event ); + } + } + + + /** + * Called by {@link ClientConnection} to notify that an error + * occured with the underlying connection. If the error is + * critical, the underlying connection is closed and the listener + * is notified. + * + * @param clientId The {@link ClientConnection} identifier + * @param except The exception raised by the underlying connection + */ + synchronized void notifyError( int clientId, SQLException except ) + { + ConnectionEvent event; + + if ( clientId != _clientId ) + return; + + // If the connection is not two-phase commit we cannot determine + // whether the error is critical, we just return. If the connection + // is two phase commit, but the error is not critical, we return. + if ( _underlying != null ) { + if ( ! ( _underlying instanceof TwoPhaseConnection ) || + ! ( (TwoPhaseConnection) _underlying ).isCriticalError( except ) ) + return; + if ( _txConn.conn == null || + ! ( _txConn.conn instanceof TwoPhaseConnection ) || + ! ( (TwoPhaseConnection) _txConn.conn ).isCriticalError( except ) ) + return; + } + + // The client connection is no longer useable, the underlying + // connection (if used) is closed, the TxConnection (if used) + // is rolledback and this connection dies (but close() may + // still be called). + ++_clientId; + if ( _underlying != null ) { + try { + _underlying.close(); + } catch ( SQLException e2 ) { + // Ignore that, we know there's an error. + } + _underlying = null; + } else if ( _txConn != null ) { + try { + end( _txConn.xid, TMFAIL ); + } catch ( XAException e2 ) { + // Ignore that, we know there's an error. + } + _txConn = null; + } + + // Notify the listener. + if ( _listener != null ) { + event = new ConnectionEvent( this, except ); + _listener.connectionErrorOccurred( event ); + } + } + + + protected void finalize() + throws Throwable + { + // We are no longer referenced by anyone (including the + // connection pool). Time to close down. + close(); + } + + + public String toString() + { + if ( _underlying != null ) + return "XAConnection: " + _underlying; + else + return "XAConnection: unused"; + } + + + public synchronized void start( Xid xid, int flags ) + throws XAException + { + // General checks. + if ( xid == null ) + throw new XAException( XAException.XAER_INVAL ); + if ( _txConn != null ) + throw new XAException( XAException.XAER_OUTSIDE ); + + synchronized ( _resManager ) { + if ( flags == TMNOFLAGS ) { + // Starting a new transaction. First, make sure it is + // not shared with any other connection (need to join + // for that). + if ( _resManager.getTxConnection( xid ) != null ) + throw new XAException( XAException.XAER_DUPID ); + + // Create a new TxConnection to describe this + // connection in the context of a transaction and + // register it with the resource manager so it can + // be shared. + try { + _txConn = new TxConnection(); + if ( _underlying != null ) { + _txConn.conn = _underlying; + _underlying = null; + } else + _txConn.conn = _resManager.newConnection(); + _txConn.xid = xid; + _txConn.count = 1; + _txConn.started = System.currentTimeMillis(); + _txConn.timeout = _txConn.started + ( _resManager.getTransactionTimeout() * 1000 ); + _resManager.setTxConnection( xid, _txConn ); + } catch ( SQLException except ) { + // If error occured at this point, we can only + // report it as resource manager error. + if ( _resManager.getLogWriter() != null ) + _resManager.getLogWriter().println( "XAConnection: failed to begin a transaction: " + except ); + throw new XAException( XAException.XAER_RMERR ); + } + + try { + _txConn.conn.setAutoCommit( false ); + try { + if ( _resManager.isolationLevel() != Connection.TRANSACTION_NONE ) + _txConn.conn.setTransactionIsolation( _resManager.isolationLevel() ); + } catch ( SQLException e ) { + // The underlying driver might not support this + // isolation level that we use by default. + } + if ( _txConn.conn instanceof TwoPhaseConnection ) + ( (TwoPhaseConnection) _txConn.conn ).enableSQLTransactions( false ); + } catch ( SQLException except ) { + // If error occured at this point, we can only + // report it as resource manager error. + if ( _resManager.getLogWriter() != null ) + _resManager.getLogWriter().println( "XAConnection: failed to begin a transaction: " + except ); + throw new XAException( XAException.XAER_RMERR ); + } + } else if ( flags == TMJOIN || flags == TMRESUME ) { + // We are joining another transaction with an + // existing TxConnection. + _txConn = _resManager.getTxConnection( xid ); + if ( _txConn == null ) + throw new XAException( XAException.XAER_INVAL ); + + // Update the number of XAConnections sharing this + // transaction connection. + if ( flags == TMJOIN && _txConn.count == 0 ) + throw new XAException( XAException.XAER_PROTO ); + ++_txConn.count; + + // If we already have an underlying connection (as we can + // expect to), we should release that underlying connection + // and make it available to the resource manager. + if ( _underlying != null ) { + _resManager.releaseConnection( _underlying ); + _underlying = null; + } + } else + // No other flags supported in start(). + throw new XAException( XAException.XAER_INVAL ); + } + } + + + public synchronized void end( Xid xid, int flags ) + throws XAException + { + // General checks. + if ( xid == null ) + throw new XAException( XAException.XAER_INVAL ); + // Note: we could get end with success or failure even it + // we were previously excluded from the transaction. + if ( _txConn == null && flags == TMSUSPEND ) + throw new XAException( XAException.XAER_NOTA ); + + synchronized ( _resManager ) { + if ( flags == TMSUCCESS || flags == TMFAIL) { + // We are now leaving a transaction we started or + // joined before. We can expect any of prepare/ + // commit/rollback to be called next, so TxConnection + // is still valid. + + // If we were suspended from the transaction, we'll + // join it for the duration of this operation. + // Make sure the reference count reaches zero by the + // time we get to prepare. + if ( _txConn == null ) { + _txConn = _resManager.getTxConnection( xid ); + if ( _txConn == null ) + throw new XAException( XAException.XAER_NOTA ); + } else { + if ( _txConn.xid != null && ! _txConn.xid.equals( xid ) ) + throw new XAException( XAException.XAER_NOTA ); + --_txConn.count; + } + + // If transaction failed, we can rollback the + // transaction and release the underlying connection. + // We can expect all other resources to recieved the + // same end notification. We don't expect forget to happen. + if ( flags == TMFAIL && _txConn.conn != null ) { + try { + if ( _txConn.conn instanceof TwoPhaseConnection ) + ( (TwoPhaseConnection) _txConn.conn ).enableSQLTransactions( true ); + _txConn.conn.rollback(); + _resManager.releaseConnection( _txConn.conn ); + } catch ( SQLException except ) { + // There is a problem with the underlying + // connection, but it was not added to the poll. + } + _resManager.setTxConnection( _txConn.xid, null ); + _txConn.conn = null; + _txConn.xid = null; + } + + if ( flags == TMSUCCESS) { + // We should be looking for a new transaction. + // Next thing we might be participating in a new + // transaction while the current one is being + // rolled back. + _txConn = null; + } + } else if ( flags == TMSUSPEND ) { + // We no longer take part in this transaction. + // Possibly we'll be asked to resume later on, but + // right now we have to forget about the transaction + // and the underlying connection. + --_txConn.count; + _txConn = null; + } else + // No other flags supported in end(). + throw new XAException( XAException.XAER_INVAL ); + } + } + + + public synchronized void forget( Xid xid ) + throws XAException + { + TxConnection txConn; + + // General checks. + if ( xid == null ) + throw new XAException( XAException.XAER_INVAL ); + synchronized ( _resManager ) { + // We have to forget about the transaction, meaning the + // transaction no longer exists for this or any other + // connection. We might be called multiple times. + txConn = _resManager.setTxConnection( xid, null ); + if ( _txConn == txConn ) + _txConn = null; + if ( txConn != null ) { + if ( txConn.conn != null ) { + _resManager.releaseConnection( txConn.conn ); + txConn.conn = null; + } + txConn.xid = null; + } + } + } + + + public synchronized int prepare( Xid xid ) + throws XAException + { + TxConnection txConn; + + // General checks. + if ( xid == null ) + throw new XAException( XAException.XAER_INVAL ); + + synchronized ( _resManager ) { + // Technically, prepare may be called for any connection, + // not just this one. + txConn = _resManager.getTxConnection( xid ); + if ( txConn == null ) + throw new XAException( XAException.XAER_NOTA ); + + // This is an error and should never happen. All other + // parties in the transaction should have left it before. + if ( txConn.count > 0 ) + throw new XAException( XAException.XAER_PROTO ); + + // If the transaction failed, we have to force a rollback. + // We track the case of failure due to a timeout. + if ( txConn.timedOut ) + throw new XAException( XAException.XA_RBTIMEOUT ); + if ( txConn.conn == null ) + throw new XAException( XAException.XA_RBROLLBACK ); + + // Since there is no preparation mechanism in a generic + // JDBC driver, we only test for read-only transaction + // but do not commit at this point. + try { + txConn.prepared = true; + if ( txConn.conn instanceof TwoPhaseConnection ) { + // For 2pc connection we ask it to prepare and determine + // whether it's commiting or read-only. If a rollback + // exception happens, we report it. + try { + if ( ( (TwoPhaseConnection) txConn.conn ).prepare() ) + return XA_OK; + else { + txConn.readOnly = true; + return XA_RDONLY; + } + } catch ( SQLException except ) { + throw new XAException( XAException.XA_RBROLLBACK ); + } + } else { + // For standard connection we cannot prepare, we can + // only guess if it's read only. + if ( txConn.conn.isReadOnly() ) { + txConn.readOnly = true; + return XA_RDONLY; + } + return XA_OK; + } + } catch ( SQLException except ) { + try { + // Fatal error in the connection, kill it. + txConn.conn.close(); + } catch ( SQLException e ) { } + txConn.conn = null; + if ( _resManager.getLogWriter() != null ) + _resManager.getLogWriter().println( "XAConnection: failed to commit a transaction: " + except ); + // If we cannot commit the transaction, force a rollback. + throw new XAException( XAException.XA_RBROLLBACK ); + } + } + } + + + public Xid[] recover( int flags ) + throws XAException + { + synchronized ( _resManager ) { + return _resManager.getTxRecover(); + } + } + + + public synchronized void commit( Xid xid, boolean onePhase ) + throws XAException + { + TxConnection txConn; + + // General checks. + if ( xid == null ) + throw new XAException( XAException.XAER_INVAL ); + + synchronized ( _resManager ) { + // Technically, commit may be called for any connection, + // not just this one. + txConn = _resManager.getTxConnection( xid ); + if ( txConn == null ) + throw new XAException( XAException.XAER_NOTA ); + + // If the transaction failed, we have to force + // a rollback. + if ( txConn.conn == null ) + throw new XAException( XAException.XA_RBROLLBACK ); + + // If connection has been prepared and is read-only, + // nothing to do at this stage. + if ( txConn.readOnly ) + return; + + // This must be a one-phase commite, or the connection + // should have been prepared before. + if ( onePhase || txConn.prepared ) { + try { + // Prevent multiple commit attempts. + txConn.readOnly = true; + if ( txConn.conn instanceof TwoPhaseConnection ) + ( (TwoPhaseConnection) txConn.conn ).enableSQLTransactions( true ); + txConn.conn.commit(); + } catch ( SQLException except ) { + try { + // Unknown error in the connection, better kill it. + txConn.conn.close(); + } catch ( SQLException e ) { } + txConn.conn = null; + if ( _resManager.getLogWriter() != null ) + _resManager.getLogWriter().println( "XAConnection: failed to commit a transaction: " + except ); + // If we cannot commit the transaction, a heuristic tollback. + throw new XAException( XAException.XA_HEURRB ); + } + } else { + // 2pc we should have prepared before. + if ( ! txConn.prepared ) + throw new XAException( XAException.XAER_PROTO ); + } + } + } + + + public synchronized void rollback( Xid xid ) + throws XAException + { + TxConnection txConn; + + + // General checks. + if ( xid == null ) + throw new XAException( XAException.XAER_INVAL ); + + synchronized ( _resManager ) { + // Technically, rollback may be called for any connection, + // not just this one. + txConn = _resManager.getTxConnection( xid ); + if ( txConn == null ) + throw new XAException( XAException.XAER_NOTA ); + + // If connection has been prepared and is read-only, + // nothing to do at this stage. If connection has + // been terminated any other way, nothing to do + // either. + if ( txConn.readOnly || txConn.conn == null ) + return; + + try { + txConn.prepared = false; + if ( txConn.conn instanceof TwoPhaseConnection ) + ( (TwoPhaseConnection) txConn.conn ).enableSQLTransactions( true ); + txConn.conn.rollback(); + } catch ( SQLException except ) { + try { + // Unknown error in the connection, better kill it. + txConn.conn.close(); + } catch ( SQLException e ) { } + txConn.conn = null; + if ( _resManager.getLogWriter() != null ) + _resManager.getLogWriter().println( "XAConnection: failed to rollback a transaction: " + except ); + // If we cannot commit the transaction, a heuristic tollback. + throw new XAException( XAException.XA_RBROLLBACK ); + } finally { + forget( xid ); + } + } + } + + + public synchronized boolean isSameRM( XAResource xaRes ) + throws XAException + { + // Two resource managers are equal if they produce equivalent + // connection (i.e. same database, same user). If the two are + // equivalent they would share a transaction by joining. + if ( xaRes == null || ! ( xaRes instanceof XAConnectionImpl ) ) + return false; + if ( _resManager.equals( ( (XAConnectionImpl) xaRes )._resManager ) ) + return true; + return false; + } + + + public synchronized boolean setTransactionTimeout( int seconds ) + throws XAException + { + if ( seconds < 0 ) + throw new XAException( XAException.XAER_INVAL ); + // Zero resets to the default for all transactions. + if ( seconds == 0 ) + seconds = _resManager.getTransactionTimeout(); + // If a transaction has started, change it's timeout to the new value. + if ( _txConn != null ) { + _txConn.timeout = _txConn.started + ( seconds * 1000 ); + return true; + } + return false; + } + + + public int getTransactionTimeout() + { + long timeout; + + if ( _txConn == null ) + return 0; + return (int) ( _txConn.timeout - _txConn.started ) / 1000; + } + + + /** + * Returns true if this connection is inside a global transaction. + * If the connection is inside a global transaction it will not + * allow commit/rollback directly from the {@link + * java.sql.Connection} interface. + */ + boolean insideGlobalTx() + { + return ( _txConn != null ); + } + + + /** + * Called to obtain the underlying connections. If this connection + * is part of a transaction, the transction's underlying connection + * is returned, or an exception is thrown if the connection was + * terminated due to timeout. If this connection is not part of a + * transaction, a non-transactional connection is returned. + * + * @param clientId The {@link ClientConnection} identifier + */ + Connection getUnderlying( int clientId ) + throws SQLException + { + // If we were notified of the client closing, or have been + // requested to have a new client connection since then, + // the client id will not match to that of the caller. + // We use that to decide that the caller has been closed. + if ( clientId != _clientId ) + throw new SQLException( "This application connection has been closed" ); + + if ( _txConn != null ) { + if ( _txConn.timedOut ) + throw new SQLException( "The transaction has timed out and has been rolledback and closed" ); + if ( _txConn.conn == null ) + throw new SQLException( "The transaction has been terminated and this connection has been closed" ); + return _txConn.conn; + } + if ( _underlying == null ) { + _underlying = _resManager.newConnection(); + _underlying.setAutoCommit( true ); + } + return _underlying; + } + + +} + + + diff --git a/src/interfaces/jdbc/org/postgresql/xa/XADataSourceImpl.java b/src/interfaces/jdbc/org/postgresql/xa/XADataSourceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b1e3f4fa0a493fc34b097c782c50f63fec8b06bb --- /dev/null +++ b/src/interfaces/jdbc/org/postgresql/xa/XADataSourceImpl.java @@ -0,0 +1,459 @@ +/** + * Redistribution and use of this software and associated documentation + * ("Software"), with or without modification, are permitted provided + * that the following conditions are met: + * + * 1. Redistributions of source code must retain copyright + * statements and notices. Redistributions must also contain a + * copy of this document. + * + * 2. Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. The name "Exolab" must not be used to endorse or promote + * products derived from this Software without prior written + * permission of Exoffice Technologies. For written permission, + * please contact info@exolab.org. + * + * 4. Products derived from this Software may not be called "Exolab" + * nor may "Exolab" appear in their names without prior written + * permission of Exoffice Technologies. Exolab is a registered + * trademark of Exoffice Technologies. + * + * 5. Due credit should be given to the Exolab Project + * (http://www.exolab.org/). + * + * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved. + * + * $Id: XADataSourceImpl.java,v 1.1 2000/04/17 20:07:56 peter Exp $ + */ + + +package org.postgresql.xa; + + +import java.io.Serializable; +import java.io.PrintWriter; +import java.util.Hashtable; +import java.util.Vector; +import java.util.Stack; +import java.util.Enumeration; +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; +import javax.sql.PooledConnection; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import javax.transaction.xa.Xid; + + + +/** + * Implements a JDBC 2.0 {@link XADataSource} for any JDBC driver + * with JNDI persistance support. The base implementation is actually + * provided by a different {@link DataSource} class; although this is + * the super class, it only provides the pooling and XA specific + * implementation. + * + * + * @author <a href="arkin@exoffice.com">Assaf Arkin</a> + * @version 1.0 + */ +public abstract class XADataSourceImpl + implements DataSource, ConnectionPoolDataSource, + XADataSource, Serializable, Runnable +{ + + + /** + * Maps underlying JDBC connections into global transaction Xids. + */ + private transient Hashtable _txConnections = new Hashtable(); + + + /** + * This is a pool of free underlying JDBC connections. If two + * XA connections are used in the same transaction, the second + * one will make its underlying JDBC connection available to + * the pool. This is not a real connection pool, only a marginal + * efficiency solution for dealing with shared transactions. + */ + private transient Stack _pool = new Stack(); + + + /** + * A background deamon thread terminating connections that have + * timed out. + */ + private transient Thread _background; + + + /** + * The default timeout for all new transactions. + */ + private int _txTimeout = DEFAULT_TX_TIMEOUT; + + + /** + * The default timeout for all new transactions is 10 seconds. + */ + public final static int DEFAULT_TX_TIMEOUT = 10; + + + + + /** + * Implementation details: + * If two XAConnections are associated with the same transaction + * (one with a start the other with a join) they must use the + * same underlying JDBC connection. They lookup the underlying + * JDBC connection based on the transaction's Xid in the + * originating XADataSource. + * + * Currently the XADataSource must be the exact same object, + * this should be changed so all XADataSources that are equal + * share a table of all enlisted connections + * + * To test is two connections should fall under the same + * transaction we match the resource managers by comparing the + * database/user they fall under using a comparison of the + * XADataSource properties. + */ + + + public XADataSourceImpl() + { + super(); + + // Create a background thread that will track transactions + // that timeout, abort them and release the underlying + // connections to the pool. + _background = new Thread( this, "XADataSource Timeout Daemon" ); + _background.setPriority( Thread.MIN_PRIORITY ); + _background.setDaemon( true ); + _background.start(); + } + + + public XAConnection getXAConnection() + throws SQLException + { + // Construct a new XAConnection with no underlying connection. + // When a JDBC method requires an underlying connection, one + // will be created. We don't create the underlying connection + // beforehand, as it might be coming from an existing + // transaction. + return new XAConnectionImpl( this, null ); + } + + + public XAConnection getXAConnection( String user, String password ) + throws SQLException + { + // Since we create the connection on-demand with newConnection + // or obtain it from a transaction, we cannot support XA + // connections with a caller specified user name. + throw new SQLException( "XAConnection does not support connections with caller specified user name" ); + } + + + public PooledConnection getPooledConnection() + throws SQLException + { + // Construct a new pooled connection and an underlying JDBC + // connection to go along with it. + return new XAConnectionImpl( this, getConnection() ); + } + + + public PooledConnection getPooledConnection( String user, String password ) + throws SQLException + { + // Construct a new pooled connection and an underlying JDBC + // connection to go along with it. + return new XAConnectionImpl( this, getConnection( user, password ) ); + } + + + /** + * Returns the default timeout for all transactions. + */ + public int getTransactionTimeout() + { + return _txTimeout; + } + + + /** + * This method is defined in the interface and implemented in the + * derived class, we re-define it just to make sure it does not + * throw an {@link SQLException} and that we do not need to + * catch one. + */ + public abstract java.io.PrintWriter getLogWriter(); + + + /** + * Sets the default timeout for all transactions. The timeout is + * specified in seconds. Use zero for the default timeout. Calling + * this method does not affect transactions in progress. + * + * @param seconds The timeout in seconds + */ + public void setTransactionTimeout( int seconds ) + { + if ( seconds <= 0 ) + _txTimeout = DEFAULT_TX_TIMEOUT; + else + _txTimeout = seconds; + _background.interrupt(); + } + + + /** + * Returns an underlying connection for the global transaction, + * if one has been associated before. + * + * @param xid The transaction Xid + * @return A connection associated with that transaction, or null + */ + TxConnection getTxConnection( Xid xid ) + { + return (TxConnection) _txConnections.get( xid ); + } + + + /** + * Associates the global transaction with an underlying connection, + * or dissociate it when null is passed. + * + * @param xid The transaction Xid + * @param conn The connection to associate, null to dissociate + */ + TxConnection setTxConnection( Xid xid, TxConnection txConn ) + { + if ( txConn == null ) + return (TxConnection) _txConnections.remove( xid ); + else + return (TxConnection) _txConnections.put( xid, txConn ); + } + + + /** + * Release an unused connection back to the pool. If an XA + * connection has been asked to join an existing transaction, + * it will no longer use it's own connection and make it available + * to newly created connections. + * + * @param conn An open connection that is no longer in use + */ + void releaseConnection( Connection conn ) + { + _pool.push( conn ); + } + + + /** + * Creates a new underlying connection. Used by XA connection + * that lost it's underlying connection when joining a + * transaction and is now asked to produce a new connection. + * + * @return An open connection ready for use + * @throws SQLException An error occured trying to open + * a connection + */ + Connection newConnection() + throws SQLException + { + Connection conn; + + // Check in the pool first. + if ( ! _pool.empty() ) { + conn = (Connection) _pool.pop(); + return conn; + } + return getConnection(); + } + + + /** + * XXX Not fully implemented yet and no code to really + * test it. + */ + Xid[] getTxRecover() + { + Vector list; + Enumeration enum; + TxConnection txConn; + + list = new Vector(); + enum = _txConnections.elements(); + while ( enum.hasMoreElements() ) { + txConn = (TxConnection) enum.nextElement(); + if ( txConn.conn != null && txConn.prepared ) + list.add( txConn.xid ); + } + return (Xid[]) list.toArray(); + } + + + /** + * Returns the transaction isolation level to use with all newly + * created transactions, or {@link Connection#TRANSACTION_NONE} + * if using the driver's default isolation level. + */ + public int isolationLevel() + { + return Connection.TRANSACTION_NONE; + } + + + public void run() + { + Enumeration enum; + int reduce; + long timeout; + TxConnection txConn; + + while ( true ) { + // Go to sleep for the duration of a transaction + // timeout. This mean transactions will timeout on average + // at _txTimeout * 1.5. + try { + Thread.sleep( _txTimeout * 1000 ); + } catch ( InterruptedException except ) { + } + + try { + // Check to see if there are any pooled connections + // we can release. We release 10% of the pooled + // connections each time, so in a heavy loaded + // environment we don't get to release that many, but + // as load goes down we do. These are not actually + // pooled connections, but connections that happen to + // get in and out of a transaction, not that many. + reduce = _pool.size() - ( _pool.size() / 10 ) - 1; + if ( reduce >= 0 && _pool.size() > reduce ) { + if ( getLogWriter() != null ) + getLogWriter().println( "DataSource " + toString() + + ": Reducing internal connection pool size from " + + _pool.size() + " to " + reduce ); + while ( _pool.size() > reduce ) { + try { + ( (Connection) _pool.pop() ).close(); + } catch ( SQLException except ) { } + } + } + } catch ( Exception except ) { } + + // Look for all connections inside a transaction that + // should have timed out by now. + timeout = System.currentTimeMillis(); + enum = _txConnections.elements(); + while ( enum.hasMoreElements() ) { + txConn = (TxConnection) enum.nextElement(); + // If the transaction timed out, we roll it back and + // invalidate it, but do not remove it from the transaction + // list yet. We wait for the next iteration, minimizing the + // chance of a NOTA exception. + if ( txConn.conn == null ) { + _txConnections.remove( txConn.xid ); + // Chose not to use an iterator so we must + // re-enumerate the list after removing + // an element from it. + enum = _txConnections.elements(); + } else if ( txConn.timeout < timeout ) { + + try { + Connection underlying; + + synchronized ( txConn ) { + if ( txConn.conn == null ) + continue; + if ( getLogWriter() != null ) + getLogWriter().println( "DataSource " + toString() + + ": Transaction timed out and being aborted: " + + txConn.xid ); + // Remove the connection from the transaction + // association. XAConnection will now have + // no underlying connection and attempt to + // create a new one. + underlying = txConn.conn; + txConn.conn = null; + txConn.timedOut = true; + + // Rollback the underlying connection to + // abort the transaction and release the + // underlying connection to the pool. + try { + underlying.rollback(); + releaseConnection( underlying ); + } catch ( SQLException except ) { + if ( getLogWriter() != null ) + getLogWriter().println( "DataSource " + toString() + + ": Error aborting timed out transaction: " + except ); + try { + underlying.close(); + } catch ( SQLException e2 ) { } + } + } + } catch ( Exception except ) { } + + } + } + } + } + + + + public void debug( PrintWriter writer ) + { + Enumeration enum; + TxConnection txConn; + StringBuffer buffer; + + writer.println( "Debug info for XADataSource:" ); + enum = _txConnections.elements(); + if ( ! enum.hasMoreElements() ) + writer.println( "Empty" ); + while ( enum.hasMoreElements() ) { + buffer = new StringBuffer(); + txConn = (TxConnection) enum.nextElement(); + buffer.append( "TxConnection " ); + if ( txConn.xid != null ) + buffer.append( txConn.xid ); + if ( txConn.conn != null ) + buffer.append( ' ' ).append( txConn.conn ); + buffer.append( " count: " ).append( txConn.count ); + if ( txConn.prepared ) + buffer.append( " prepared" ); + if ( txConn.timedOut ) + buffer.append( " timed-out" ); + if ( txConn.readOnly ) + buffer.append( " read-only" ); + writer.println( buffer.toString() ); + } + enum = _pool.elements(); + while ( enum.hasMoreElements() ) + writer.println( "Pooled underlying: " + enum.nextElement().toString() ); + } + + +}