Commit 35a945f2 authored by Bruce Momjian's avatar Bruce Momjian

New stuff from Peter Mount for jdbc.

parent c37adac7
...@@ -123,7 +123,7 @@ public class psql ...@@ -123,7 +123,7 @@ public class psql
if(rs.wasNull()) if(rs.wasNull())
System.out.print("{null}"+(i<cols?"\t":"\n")); System.out.print("{null}"+(i<cols?"\t":"\n"));
else else
System.out.print(rs.getObject(i).toString()+(i<cols?"\t":"\n")); System.out.print(o.toString()+(i<cols?"\t":"\n"));
} }
} }
......
...@@ -34,6 +34,9 @@ public class Connection implements java.sql.Connection ...@@ -34,6 +34,9 @@ public class Connection implements java.sql.Connection
// This is set by postgresql.Statement.setMaxRows() // This is set by postgresql.Statement.setMaxRows()
protected int maxrows = 0; // maximum no. of rows; 0 = unlimited protected int maxrows = 0; // maximum no. of rows; 0 = unlimited
// This is a cache of the DatabaseMetaData instance for this connection
protected DatabaseMetaData metadata;
private String PG_HOST; private String PG_HOST;
private int PG_PORT; private int PG_PORT;
private String PG_USER; private String PG_USER;
...@@ -44,17 +47,6 @@ public class Connection implements java.sql.Connection ...@@ -44,17 +47,6 @@ public class Connection implements java.sql.Connection
public boolean CONNECTION_OK = true; public boolean CONNECTION_OK = true;
public boolean CONNECTION_BAD = false; public boolean CONNECTION_BAD = false;
//private static final int STARTUP_LEN = 288; // Length of a startup packet
// These are defined in src/include/libpq/pqcomm.h
//private int STARTUP_CODE = STARTUP_USER;
//private static final int STARTUP_USER = 7; // User auth
//private static final int STARTUP_KRB4 = 10; // Kerberos 4 (unused)
//private static final int STARTUP_KRB5 = 11; // Kerberos 5 (unused)
//private static final int STARTUP_HBA = 12; // Host Based
//private static final int STARTUP_NONE = 13; // Unauthenticated (unused)
//private static final int STARTUP_PASS = 14; // Password auth
private boolean autoCommit = true; private boolean autoCommit = true;
private boolean readOnly = false; private boolean readOnly = false;
...@@ -88,12 +80,6 @@ public class Connection implements java.sql.Connection ...@@ -88,12 +80,6 @@ public class Connection implements java.sql.Connection
// be across all connections, which could be to different backends. // be across all connections, which could be to different backends.
protected Hashtable fieldCache = new Hashtable(); protected Hashtable fieldCache = new Hashtable();
// This is used by Field to cache oid -> names.
// It's here, because it's shared across this connection only.
// Hence it cannot be static within the Field class, because it would then
// be across all connections, which could be to different backends.
protected Hashtable fieldCache = new Hashtable();
/** /**
* This is the current date style of the backend * This is the current date style of the backend
*/ */
...@@ -150,8 +136,6 @@ public class Connection implements java.sql.Connection ...@@ -150,8 +136,6 @@ public class Connection implements java.sql.Connection
*/ */
public Connection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException public Connection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException
{ {
//int len = STARTUP_LEN; // Length of a startup packet
// Throw an exception if the user or password properties are missing // Throw an exception if the user or password properties are missing
// This occasionally occurs when the client uses the properties version // This occasionally occurs when the client uses the properties version
// of getConnection(), and is a common question on the email lists // of getConnection(), and is a common question on the email lists
...@@ -169,30 +153,15 @@ public class Connection implements java.sql.Connection ...@@ -169,30 +153,15 @@ public class Connection implements java.sql.Connection
PG_HOST = new String(host); PG_HOST = new String(host);
PG_STATUS = CONNECTION_BAD; PG_STATUS = CONNECTION_BAD;
// Pre 6.3 code
// This handles the auth property. Any value begining with p enables
// password authentication, while anything begining with i enables
// ident (RFC 1413) authentication. Any other values default to trust.
//
// Also, the postgresql.auth system property can be used to change the
// local default, if the auth property is not present.
//
//String auth = info.getProperty("auth",System.getProperty("postgresql.auth","trust")).toLowerCase();
//if(auth.startsWith("p")) {
//// Password authentication
//STARTUP_CODE=STARTUP_PASS;
//} else if(auth.startsWith("i")) {
//// Ident (RFC 1413) authentication
//STARTUP_CODE=STARTUP_HBA;
//} else {
//// Anything else defaults to trust authentication
//STARTUP_CODE=STARTUP_USER;
//}
// Now make the initial connection // Now make the initial connection
try try
{ {
pg_stream = new PG_Stream(host, port); pg_stream = new PG_Stream(host, port);
} catch (ConnectException cex) {
// Added by Peter Mount <peter@retep.org.uk>
// ConnectException is thrown when the connection cannot be made.
// we trap this an return a more meaningful message for the end user
throw new SQLException ("Connection refused. Check that the hostname and port is correct, and that the postmaster is running with the -i flag, which enables TCP/IP networking.");
} catch (IOException e) { } catch (IOException e) {
throw new SQLException ("Connection failed: " + e.toString()); throw new SQLException ("Connection failed: " + e.toString());
} }
...@@ -200,30 +169,17 @@ public class Connection implements java.sql.Connection ...@@ -200,30 +169,17 @@ public class Connection implements java.sql.Connection
// Now we need to construct and send a startup packet // Now we need to construct and send a startup packet
try try
{ {
// Pre 6.3 code
//pg_stream.SendInteger(len, 4); len -= 4;
//pg_stream.SendInteger(STARTUP_CODE, 4); len -= 4;
//pg_stream.Send(database.getBytes(), 64); len -= 64;
//pg_stream.Send(PG_USER.getBytes(), len);
//
//// Send the password packet if required
//if(STARTUP_CODE == STARTUP_PASS) {
//len=STARTUP_LEN;
//pg_stream.SendInteger(len, 4); len -= 4;
//pg_stream.SendInteger(STARTUP_PASS, 4); len -= 4;
//pg_stream.Send(PG_USER.getBytes(), PG_USER.length());
//len-=PG_USER.length();
//pg_stream.SendInteger(0,1); len -= 1;
//pg_stream.Send(PG_PASSWORD.getBytes(), len);
//}
// Ver 6.3 code // Ver 6.3 code
pg_stream.SendInteger(4+4+SM_DATABASE+SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY,4); pg_stream.SendInteger(4+4+SM_DATABASE+SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY,4);
pg_stream.SendInteger(PG_PROTOCOL_LATEST_MAJOR,2); pg_stream.SendInteger(PG_PROTOCOL_LATEST_MAJOR,2);
pg_stream.SendInteger(PG_PROTOCOL_LATEST_MINOR,2); pg_stream.SendInteger(PG_PROTOCOL_LATEST_MINOR,2);
pg_stream.Send(database.getBytes(),SM_DATABASE); pg_stream.Send(database.getBytes(),SM_DATABASE);
// This last send includes the unused fields
pg_stream.Send(PG_USER.getBytes(),SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY); pg_stream.Send(PG_USER.getBytes(),SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY);
// The last send includes the unused fields
// now flush the startup packets to the backend
pg_stream.flush();
// Now get the response from the backend, either an error message // Now get the response from the backend, either an error message
// or an authentication request // or an authentication request
...@@ -233,6 +189,12 @@ public class Connection implements java.sql.Connection ...@@ -233,6 +189,12 @@ public class Connection implements java.sql.Connection
switch(beresp) switch(beresp)
{ {
case 'E': case 'E':
// An error occured, so pass the error message to the
// user.
//
// The most common one to be thrown here is:
// "User authentication failed"
//
throw new SQLException(pg_stream.ReceiveString(4096)); throw new SQLException(pg_stream.ReceiveString(4096));
case 'R': case 'R':
...@@ -267,7 +229,7 @@ public class Connection implements java.sql.Connection ...@@ -267,7 +229,7 @@ public class Connection implements java.sql.Connection
pg_stream.SendInteger(5+PG_PASSWORD.length(),4); pg_stream.SendInteger(5+PG_PASSWORD.length(),4);
pg_stream.Send(PG_PASSWORD.getBytes()); pg_stream.Send(PG_PASSWORD.getBytes());
pg_stream.SendInteger(0,1); pg_stream.SendInteger(0,1);
//pg_stream.SendPacket(PG_PASSWORD.getBytes()); pg_stream.flush();
break; break;
case AUTH_REQ_CRYPT: case AUTH_REQ_CRYPT:
...@@ -276,11 +238,11 @@ public class Connection implements java.sql.Connection ...@@ -276,11 +238,11 @@ public class Connection implements java.sql.Connection
pg_stream.SendInteger(5+crypted.length(),4); pg_stream.SendInteger(5+crypted.length(),4);
pg_stream.Send(crypted.getBytes()); pg_stream.Send(crypted.getBytes());
pg_stream.SendInteger(0,1); pg_stream.SendInteger(0,1);
//pg_stream.SendPacket(UnixCrypt.crypt(salt,PG_PASSWORD).getBytes()); pg_stream.flush();
break; break;
default: default:
throw new SQLException("Authentication type "+areq+" not supported"); throw new SQLException("Authentication type "+areq+" not supported. Check that you have configured the pg_hba.conf file to include the client's IP address or Subnet, and is using a supported authentication scheme.");
} }
break; break;
...@@ -511,7 +473,9 @@ public class Connection implements java.sql.Connection ...@@ -511,7 +473,9 @@ public class Connection implements java.sql.Connection
*/ */
public java.sql.DatabaseMetaData getMetaData() throws SQLException public java.sql.DatabaseMetaData getMetaData() throws SQLException
{ {
return new DatabaseMetaData(this); if(metadata==null)
metadata = new DatabaseMetaData(this);
return metadata;
} }
/** /**
...@@ -631,8 +595,6 @@ public class Connection implements java.sql.Connection ...@@ -631,8 +595,6 @@ public class Connection implements java.sql.Connection
*/ */
public void addWarning(String msg) public void addWarning(String msg)
{ {
//PrintStream log = DriverManager.getLogStream();
//if(log!=null)
DriverManager.println(msg); DriverManager.println(msg);
// Add the warning to the chain // Add the warning to the chain
...@@ -691,6 +653,7 @@ public class Connection implements java.sql.Connection ...@@ -691,6 +653,7 @@ public class Connection implements java.sql.Connection
buf = sql.getBytes(); buf = sql.getBytes();
pg_stream.Send(buf); pg_stream.Send(buf);
pg_stream.SendChar(0); pg_stream.SendChar(0);
pg_stream.flush();
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("I/O Error: " + e.toString()); throw new SQLException("I/O Error: " + e.toString());
} }
...@@ -726,6 +689,7 @@ public class Connection implements java.sql.Connection ...@@ -726,6 +689,7 @@ public class Connection implements java.sql.Connection
pg_stream.SendChar('Q'); pg_stream.SendChar('Q');
pg_stream.SendChar(' '); pg_stream.SendChar(' ');
pg_stream.SendChar(0); pg_stream.SendChar(0);
pg_stream.flush();
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("I/O Error: " + e.toString()); throw new SQLException("I/O Error: " + e.toString());
} }
...@@ -964,6 +928,8 @@ public class Connection implements java.sql.Connection ...@@ -964,6 +928,8 @@ public class Connection implements java.sql.Connection
return ((Serialize)o).fetch(Integer.parseInt(value)); return ((Serialize)o).fetch(Integer.parseInt(value));
} }
} catch(SQLException sx) { } catch(SQLException sx) {
// rethrow the exception. Done because we capture any others next
sx.fillInStackTrace();
throw sx; throw sx;
} catch(Exception ex) { } catch(Exception ex) {
throw new SQLException("Failed to create object for "+type+": "+ex); throw new SQLException("Failed to create object for "+type+": "+ex);
...@@ -999,14 +965,17 @@ public class Connection implements java.sql.Connection ...@@ -999,14 +965,17 @@ public class Connection implements java.sql.Connection
// If so, then call it's fetch method. // If so, then call it's fetch method.
if(x instanceof Serialize) if(x instanceof Serialize)
return ((Serialize)x).store(o); return ((Serialize)x).store(o);
// Thow an exception because the type is unknown
throw new SQLException("The object could not be stored. Check that any tables required have already been created in the database.");
} catch(SQLException sx) { } catch(SQLException sx) {
// rethrow the exception. Done because we capture any others next
sx.fillInStackTrace();
throw sx; throw sx;
} catch(Exception ex) { } catch(Exception ex) {
throw new SQLException("Failed to store object: "+ex); throw new SQLException("Failed to store object: "+ex);
} }
// should never be reached
return 0;
} }
/** /**
...@@ -1045,10 +1014,12 @@ public class Connection implements java.sql.Connection ...@@ -1045,10 +1014,12 @@ public class Connection implements java.sql.Connection
private static final String defaultObjectTypes[][] = { private static final String defaultObjectTypes[][] = {
{"box", "postgresql.geometric.PGbox"}, {"box", "postgresql.geometric.PGbox"},
{"circle", "postgresql.geometric.PGcircle"}, {"circle", "postgresql.geometric.PGcircle"},
{"line", "postgresql.geometric.PGline"},
{"lseg", "postgresql.geometric.PGlseg"}, {"lseg", "postgresql.geometric.PGlseg"},
{"path", "postgresql.geometric.PGpath"}, {"path", "postgresql.geometric.PGpath"},
{"point", "postgresql.geometric.PGpoint"}, {"point", "postgresql.geometric.PGpoint"},
{"polygon", "postgresql.geometric.PGpolygon"} {"polygon", "postgresql.geometric.PGpolygon"},
{"money", "postgresql.util.PGmoney"}
}; };
// This initialises the objectTypes hashtable // This initialises the objectTypes hashtable
......
...@@ -37,6 +37,9 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -37,6 +37,9 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
static final int iInt4Oid = 23; // OID for int4 static final int iInt4Oid = 23; // OID for int4
static final int VARHDRSZ = 4; // length 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) public DatabaseMetaData(Connection conn)
{ {
this.connection = conn; this.connection = conn;
...@@ -170,7 +173,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -170,7 +173,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
*/ */
public String getDatabaseProductVersion() throws SQLException public String getDatabaseProductVersion() throws SQLException
{ {
return ("6.3"); return ("6.4");
} }
/** /**
...@@ -1473,39 +1476,37 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1473,39 +1476,37 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
Field f[] = new Field[8]; Field f[] = new Field[8];
ResultSet r; // ResultSet for the SQL query that we need to do ResultSet r; // ResultSet for the SQL query that we need to do
Vector v = new Vector(); // The new ResultSet tuple stuff Vector v = new Vector(); // The new ResultSet tuple stuff
String remarks = new String("no remarks");
f[0] = new Field(connection, new String("PROCEDURE_CAT"), iVarcharOid, 32); byte remarks[] = defaultRemarks;
f[1] = new Field(connection, new String("PROCEDURE_SCHEM"), iVarcharOid, 32);
f[2] = new Field(connection, new String("PROCEDURE_NAME"), iVarcharOid, 32); f[0] = new Field(connection, "PROCEDURE_CAT", iVarcharOid, 32);
f[3] = null; f[1] = new Field(connection, "PROCEDURE_SCHEM", iVarcharOid, 32);
f[4] = null; f[2] = new Field(connection, "PROCEDURE_NAME", iVarcharOid, 32);
f[5] = null; f[3] = f[4] = f[5] = null; // reserved, must be null for now
f[6] = new Field(connection, new String("REMARKS"), iVarcharOid, 8192); f[6] = new Field(connection, "REMARKS", iVarcharOid, 8192);
f[7] = new Field(connection, new String("PROCEDURE_TYPE"), iInt2Oid, 2); f[7] = new Field(connection, "PROCEDURE_TYPE", iInt2Oid, 2);
r = connection.ExecSQL("select proname, proretset from pg_proc order by proname");
if (r.getColumnCount() != 2 || r.getTupleCount() <= 1) // If the pattern is null, then set it to the default
throw new SQLException("Unexpected return from query for procedure list"); if(procedureNamePattern==null)
procedureNamePattern="%";
r = connection.ExecSQL("select proname, proretset from pg_proc where proname like '"+procedureNamePattern.toLowerCase()+"' order by proname");
while (r.next()) while (r.next())
{ {
byte[][] tuple = new byte[8][0]; byte[][] tuple = new byte[8][0];
String name = r.getString(1);
remarks = new String("no remarks");
boolean retset = r.getBoolean(2);
tuple[0] = null; // Catalog name tuple[0] = null; // Catalog name
tuple[1] = null; // Schema name tuple[1] = null; // Schema name
tuple[2] = name.getBytes(); // Procedure name tuple[2] = r.getBytes(1); // Procedure name
tuple[3] = null; // Reserved tuple[3] = tuple[4] = tuple[5] = null; // Reserved
tuple[4] = null; // Reserved tuple[6] = remarks; // Remarks
tuple[5] = null; // Reserved
tuple[6] = remarks.getBytes(); // Remarks if (r.getBoolean(2))
tuple[7] = new byte[1]; tuple[7] = Integer.toString(java.sql.DatabaseMetaData.procedureReturnsResult).getBytes();
if (retset)
tuple[7][0] = (byte)java.sql.DatabaseMetaData.procedureReturnsResult;
else else
tuple[7][0] = (byte)java.sql.DatabaseMetaData.procedureNoResult; tuple[7] = Integer.toString(java.sql.DatabaseMetaData.procedureNoResult).getBytes();
v.addElement(tuple); v.addElement(tuple);
} }
return new ResultSet(connection, f, v, "OK", 1); return new ResultSet(connection, f, v, "OK", 1);
...@@ -1559,6 +1560,12 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1559,6 +1560,12 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
// Implementation note: This is required for Borland's JBuilder to work // 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 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. // for now, this returns an empty result set.
Field f[] = new Field[13]; Field f[] = new Field[13];
ResultSet r; // ResultSet for the SQL query that we need to do ResultSet r; // ResultSet for the SQL query that we need to do
...@@ -1578,6 +1585,8 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1578,6 +1585,8 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
f[11] = new Field(connection, new String("NULLABLE"), iInt2Oid, 2); f[11] = new Field(connection, new String("NULLABLE"), iInt2Oid, 2);
f[12] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); f[12] = new Field(connection, new String("REMARKS"), iVarcharOid, 32);
// add query loop here
return new ResultSet(connection, f, v, "OK", 1); return new ResultSet(connection, f, v, "OK", 1);
} }
...@@ -1620,6 +1629,9 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1620,6 +1629,9 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
if(types==null) if(types==null)
types = defaultTableTypes; types = defaultTableTypes;
if(tableNamePattern==null)
tableNamePattern="%";
// the field descriptors for the new ResultSet // the field descriptors for the new ResultSet
Field f[] = new Field[5]; Field f[] = new Field[5];
ResultSet r; // ResultSet for the SQL query that we need to do ResultSet r; // ResultSet for the SQL query that we need to do
...@@ -1632,7 +1644,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1632,7 +1644,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
f[4] = new Field(connection, new String("REMARKS"), iVarcharOid, 32); f[4] = new Field(connection, new String("REMARKS"), iVarcharOid, 32);
// Now form the query // Now form the query
StringBuffer sql = new StringBuffer("select relname,oid from pg_class where "); StringBuffer sql = new StringBuffer("select relname,oid from pg_class where (");
boolean notFirst=false; boolean notFirst=false;
for(int i=0;i<types.length;i++) { for(int i=0;i<types.length;i++) {
if(notFirst) if(notFirst)
...@@ -1644,32 +1656,35 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1644,32 +1656,35 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
} }
} }
// 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 // Now run the query
r = connection.ExecSQL(sql.toString()); r = connection.ExecSQL(sql.toString());
if (r.getColumnCount() != 2) byte remarks[];
throw new SQLException("Unexpected return from query for table list");
while (r.next()) while (r.next())
{ {
byte[][] tuple = new byte[5][0]; byte[][] tuple = new byte[5][0];
String name = r.getString(1);
String remarks = new String("no remarks");
// Fetch the description for the table (if any) // Fetch the description for the table (if any)
ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(2)); ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(2));
if(dr.getTupleCount()==1) { if(dr.getTupleCount()==1) {
dr.next(); dr.next();
remarks=dr.getString(1); remarks = dr.getBytes(1);
} } else
remarks = defaultRemarks;
dr.close(); dr.close();
tuple[0] = null; // Catalog name tuple[0] = null; // Catalog name
tuple[1] = null; // Schema name tuple[1] = null; // Schema name
tuple[2] = name.getBytes(); // Table name tuple[2] = r.getBytes(1); // Table name
tuple[3] = null; // Table type tuple[3] = null; // Table type
tuple[4] = remarks.getBytes(); // Remarks tuple[4] = remarks; // Remarks
v.addElement(tuple); v.addElement(tuple);
} }
r.close(); r.close();
...@@ -1848,28 +1863,34 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1848,28 +1863,34 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
f[16] = new Field(connection, new String("ORDINAL_POSITION"), iInt4Oid,4); f[16] = new Field(connection, new String("ORDINAL_POSITION"), iInt4Oid,4);
f[17] = new Field(connection, new String("IS_NULLABLE"), iVarcharOid, 32); 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 // Now form the query
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+"' and a.attname like '"+columnNamePattern+"' and a.attnum>0 order by c.relname,a.attnum"); // 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()) { while(r.next()) {
byte[][] tuple = new byte[18][0]; byte[][] tuple = new byte[18][0];
String name = r.getString(1);
String remarks = new String("no remarks");
String columnSize;
// Fetch the description for the table (if any) // Fetch the description for the table (if any)
ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(1)); ResultSet dr = connection.ExecSQL("select description from pg_description where objoid="+r.getInt(1));
if(dr.getTupleCount()==1) { if(dr.getTupleCount()==1) {
dr.next(); dr.next();
remarks=dr.getString(1); tuple[11] = dr.getBytes(1);
} } else
tuple[11] = defaultRemarks;
dr.close(); dr.close();
tuple[0] = "".getBytes(); // Catalog name tuple[0] = "".getBytes(); // Catalog name
tuple[1] = "".getBytes(); // Schema name tuple[1] = "".getBytes(); // Schema name
tuple[2] = r.getString(2).getBytes(); // Table name tuple[2] = r.getBytes(2); // Table name
tuple[3] = r.getString(3).getBytes(); // Column name tuple[3] = r.getBytes(3); // Column name
dr = connection.ExecSQL("select typname from pg_type where oid = "+r.getString(4)); dr = connection.ExecSQL("select typname from pg_type where oid = "+r.getString(4));
dr.next(); dr.next();
...@@ -1878,15 +1899,15 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1878,15 +1899,15 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
tuple[4] = Integer.toString(Field.getSQLType(typname)).getBytes(); // Data type tuple[4] = Integer.toString(Field.getSQLType(typname)).getBytes(); // Data type
tuple[5] = typname.getBytes(); // Type name tuple[5] = typname.getBytes(); // Type name
// Column size
// Looking at the psql source, // Looking at the psql source,
// I think the length of a varchar as specified when the table was created // 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) // should be extracted from atttypmod which contains this length + sizeof(int32)
if (typname.equals("bpchar") || typname.equals("varchar")) { if (typname.equals("bpchar") || typname.equals("varchar")) {
int atttypmod = r.getInt(8); int atttypmod = r.getInt(8);
columnSize = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0); tuple[6] = Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0).getBytes();
} else } else
columnSize = r.getString(7); tuple[6] = r.getBytes(7);
tuple[6] = columnSize.getBytes(); // Column size
tuple[7] = null; // Buffer length tuple[7] = null; // Buffer length
...@@ -1894,8 +1915,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1894,8 +1915,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal tuple[9] = "10".getBytes(); // Num Prec Radix - assume decimal
// tuple[10] is below // tuple[10] is below
// tuple[11] is above
tuple[11] = remarks.getBytes(); // Remarks
tuple[12] = null; // column default tuple[12] = null; // column default
...@@ -1904,7 +1924,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1904,7 +1924,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
tuple[15] = tuple[6]; // char octet length tuple[15] = tuple[6]; // char octet length
tuple[16] = r.getString(5).getBytes(); // ordinal position tuple[16] = r.getBytes(5); // ordinal position
String nullFlag = r.getString(6); String nullFlag = r.getString(6);
tuple[10] = Integer.toString(nullFlag.equals("f")?java.sql.DatabaseMetaData.columnNullable:java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable tuple[10] = Integer.toString(nullFlag.equals("f")?java.sql.DatabaseMetaData.columnNullable:java.sql.DatabaseMetaData.columnNoNulls).getBytes(); // Nullable
...@@ -1948,6 +1968,14 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1948,6 +1968,14 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
Field f[] = new Field[8]; Field f[] = new Field[8];
Vector v = new Vector(); 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[0] = new Field(connection,new String("TABLE_CAT"),iVarcharOid,32);
f[1] = new Field(connection,new String("TABLE_SCHEM"),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[2] = new Field(connection,new String("TABLE_NAME"),iVarcharOid,32);
...@@ -1958,11 +1986,13 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -1958,11 +1986,13 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
f[7] = new Field(connection,new String("IS_GRANTABLE"),iVarcharOid,32); f[7] = new Field(connection,new String("IS_GRANTABLE"),iVarcharOid,32);
// This is taken direct from the psql source // This is taken direct from the psql source
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 ORDER BY relname"); 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()) { while(r.next()) {
byte[][] tuple = new byte[8][0]; byte[][] tuple = new byte[8][0];
tuple[0] = tuple[1]= "".getBytes(); tuple[0] = tuple[1]= "".getBytes();
DriverManager.println("relname=\""+r.getString(1)+"\" relacl=\""+r.getString(2)+"\""); 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); //v.addElement(tuple);
} }
...@@ -2122,7 +2152,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -2122,7 +2152,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
"'1' as KEY_SEQ,"+ // -- fake it as a String for now "'1' as KEY_SEQ,"+ // -- fake it as a String for now
"t.typname as PK_NAME " + "t.typname as PK_NAME " +
" FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a, pg_type t " + " FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a, pg_type t " +
" WHERE relkind = 'r' " + // -- not indices " WHERE bc.relkind = 'r' " + // -- not indices
" and bc.relname ~ '"+table+"'" + " and bc.relname ~ '"+table+"'" +
" and i.indrelid = bc.oid" + " and i.indrelid = bc.oid" +
" and i.indexrelid = ic.oid" + " and i.indexrelid = ic.oid" +
...@@ -2379,22 +2409,30 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -2379,22 +2409,30 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
f[16] = new Field(connection, new String("SQL_DATETIME_SUB"), 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); 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()) { while(rs.next()) {
byte[][] tuple = new byte[18][]; byte[][] tuple = new byte[18][];
String typname=rs.getString(1); String typname=rs.getString(1);
tuple[0] = typname.getBytes(); tuple[0] = typname.getBytes();
tuple[1] = Integer.toString(Field.getSQLType(typname)).getBytes(); tuple[1] = Integer.toString(Field.getSQLType(typname)).getBytes();
tuple[2] = "9".getBytes(); // for now tuple[2] = b9; // for now
tuple[6] = Integer.toString(typeNoNulls).getBytes(); // for now tuple[6] = bnn; // for now
tuple[7] = "f".getBytes(); // false for now - not case sensitive tuple[7] = bf; // false for now - not case sensitive
tuple[8] = Integer.toString(typeSearchable).getBytes(); tuple[8] = bts;
tuple[9] = "f".getBytes(); // false for now - it's signed tuple[9] = bf; // false for now - it's signed
tuple[10] = "f".getBytes(); // false for now - must handle money tuple[10] = bf; // false for now - must handle money
tuple[11] = "f".getBytes(); // false for now - handle autoincrement tuple[11] = bf; // false for now - handle autoincrement
// 12 - LOCAL_TYPE_NAME is null // 12 - LOCAL_TYPE_NAME is null
// 13 & 14 ? // 13 & 14 ?
// 15 & 16 are unused so we return null // 15 & 16 are unused so we return null
tuple[17] = "10".getBytes(); // everything is base 10 tuple[17] = b10; // everything is base 10
v.addElement(tuple); v.addElement(tuple);
} }
rs.close(); rs.close();
...@@ -2431,7 +2469,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData ...@@ -2431,7 +2469,7 @@ public class DatabaseMetaData implements java.sql.DatabaseMetaData
* within index; zero when TYPE is tableIndexStatistic * within index; zero when TYPE is tableIndexStatistic
* <LI><B>COLUMN_NAME</B> String => column name; null when TYPE is * <LI><B>COLUMN_NAME</B> String => column name; null when TYPE is
* tableIndexStatistic * tableIndexStatistic
* <LI><B>ASC_OR_DESC</B> String => column sort sequence, "A" => ascending, * <LI><B>ASC_OR_DESC</B> String => column sort sequence, "A" => ascending
* "D" => descending, may be null if sort sequence is not supported; * "D" => descending, may be null if sort sequence is not supported;
* null when TYPE is tableIndexStatistic * null when TYPE is tableIndexStatistic
* <LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then * <LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then
......
...@@ -27,7 +27,7 @@ public class Driver implements java.sql.Driver ...@@ -27,7 +27,7 @@ public class Driver implements java.sql.Driver
// These should be in sync with the backend that the driver was // These should be in sync with the backend that the driver was
// distributed with // distributed with
static final int MAJORVERSION = 6; static final int MAJORVERSION = 6;
static final int MINORVERSION = 3; static final int MINORVERSION = 4;
static static
{ {
......
...@@ -20,7 +20,16 @@ public class PG_Stream ...@@ -20,7 +20,16 @@ public class PG_Stream
{ {
private Socket connection; private Socket connection;
private InputStream pg_input; private InputStream pg_input;
private OutputStream pg_output; private BufferedOutputStream pg_output;
// This is the error message returned when an EOF occurs
private static final String EOF_MSG = "The backend has broken the connection. Possibly the action you have attempted has caused it to close.";
// This is the error message returned when an IOException occurs
private static final String IOE_MSG = "IOError while reading from backend: ";
// This is the error message returned when flushing the stream.
private static final String FLUSH_MSG = "Error flushing output: ";
/** /**
* Constructor: Connect to the PostgreSQL back end and return * Constructor: Connect to the PostgreSQL back end and return
...@@ -33,8 +42,13 @@ public class PG_Stream ...@@ -33,8 +42,13 @@ public class PG_Stream
public PG_Stream(String host, int port) throws IOException public PG_Stream(String host, int port) throws IOException
{ {
connection = new Socket(host, port); connection = new Socket(host, port);
// Submitted by Jason Venner <jason@idiom.com> adds a 10x speed
// improvement on FreeBSD machines (caused by a bug in their TCP Stack)
connection.setTcpNoDelay(true);
pg_input = connection.getInputStream(); pg_input = connection.getInputStream();
pg_output = connection.getOutputStream(); pg_output = new BufferedOutputStream(connection.getOutputStream());
} }
/** /**
...@@ -45,7 +59,6 @@ public class PG_Stream ...@@ -45,7 +59,6 @@ public class PG_Stream
*/ */
public void SendChar(int val) throws IOException public void SendChar(int val) throws IOException
{ {
//pg_output.write(val);
byte b[] = new byte[1]; byte b[] = new byte[1];
b[0] = (byte)val; b[0] = (byte)val;
pg_output.write(b); pg_output.write(b);
...@@ -165,9 +178,9 @@ public class PG_Stream ...@@ -165,9 +178,9 @@ public class PG_Stream
try try
{ {
c = pg_input.read(); c = pg_input.read();
if (c < 0) throw new IOException("EOF"); if (c < 0) throw new IOException(EOF_MSG);
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("Error reading from backend: " + e.toString()); throw new SQLException(IOE_MSG + e.toString());
} }
return c; return c;
} }
...@@ -190,11 +203,11 @@ public class PG_Stream ...@@ -190,11 +203,11 @@ public class PG_Stream
int b = pg_input.read(); int b = pg_input.read();
if (b < 0) if (b < 0)
throw new IOException("EOF"); throw new IOException(EOF_MSG);
n = n | (b << (8 * i)) ; n = n | (b << (8 * i)) ;
} }
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("Error reading from backend: " + e.toString()); throw new SQLException(IOE_MSG + e.toString());
} }
return n; return n;
} }
...@@ -217,11 +230,11 @@ public class PG_Stream ...@@ -217,11 +230,11 @@ public class PG_Stream
int b = pg_input.read(); int b = pg_input.read();
if (b < 0) if (b < 0)
throw new IOException("EOF"); throw new IOException(EOF_MSG);
n = b | (n << 8); n = b | (n << 8);
} }
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("Error reading from backend: " + e.toString()); throw new SQLException(IOE_MSG + e.toString());
} }
return n; return n;
} }
...@@ -246,7 +259,7 @@ public class PG_Stream ...@@ -246,7 +259,7 @@ public class PG_Stream
{ {
int c = pg_input.read(); int c = pg_input.read();
if (c < 0) if (c < 0)
throw new IOException("EOF"); throw new IOException(EOF_MSG);
else if (c == 0) else if (c == 0)
break; break;
else else
...@@ -255,7 +268,7 @@ public class PG_Stream ...@@ -255,7 +268,7 @@ public class PG_Stream
if (s >= maxsiz) if (s >= maxsiz)
throw new IOException("Too Much Data"); throw new IOException("Too Much Data");
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("Error reading from backend: " + e.toString()); throw new SQLException(IOE_MSG + e.toString());
} }
String v = new String(rst, 0, s); String v = new String(rst, 0, s);
return v; return v;
...@@ -314,20 +327,7 @@ public class PG_Stream ...@@ -314,20 +327,7 @@ public class PG_Stream
private byte[] Receive(int siz) throws SQLException private byte[] Receive(int siz) throws SQLException
{ {
byte[] answer = new byte[siz]; byte[] answer = new byte[siz];
int s = 0; Receive(answer,0,siz);
try
{
while (s < siz)
{
int w = pg_input.read(answer, s, siz - s);
if (w < 0)
throw new IOException("EOF");
s += w;
}
} catch (IOException e) {
throw new SQLException("Error reading from backend: " + e.toString());
}
return answer; return answer;
} }
...@@ -349,11 +349,11 @@ public class PG_Stream ...@@ -349,11 +349,11 @@ public class PG_Stream
{ {
int w = pg_input.read(b, off+s, siz - s); int w = pg_input.read(b, off+s, siz - s);
if (w < 0) if (w < 0)
throw new IOException("EOF"); throw new IOException(EOF_MSG);
s += w; s += w;
} }
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("Error reading from backend: " + e.toString()); throw new SQLException(IOE_MSG + e.toString());
} }
} }
...@@ -367,7 +367,7 @@ public class PG_Stream ...@@ -367,7 +367,7 @@ public class PG_Stream
try { try {
pg_output.flush(); pg_output.flush();
} catch (IOException e) { } catch (IOException e) {
throw new SQLException("Error flushing output: " + e.toString()); throw new SQLException(FLUSH_MSG + e.toString());
} }
} }
......
...@@ -309,23 +309,20 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta ...@@ -309,23 +309,20 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta
{ {
SimpleDateFormat df = new SimpleDateFormat("''"+connection.getDateStyle()+"''"); SimpleDateFormat df = new SimpleDateFormat("''"+connection.getDateStyle()+"''");
// Ideally the following should work: set(parameterIndex, df.format(x));
//
// set(parameterIndex, df.format(x)); // The above is how the date should be handled.
// //
// however, SimpleDateFormat seems to format a date to the previous // However, in JDK's prior to 1.1.6 (confirmed with the
// day. So a fix (for now) is to add a day before formatting. // Linux jdk1.1.3 and the Win95 JRE1.1.5), SimpleDateFormat seems
// This needs more people to confirm this is really happening, or // to format a date to the previous day. So the fix is to add a day
// possibly for us to implement our own formatting code. // before formatting.
// //
// I've tested this with the Linux jdk1.1.3 and the Win95 JRE1.1.5 // PS: 86400000 is one day
// //
set(parameterIndex, df.format(new java.util.Date(x.getTime()+DAY))); //set(parameterIndex, df.format(new java.util.Date(x.getTime()+86400000)));
} }
// This equates to 1 day
private static final int DAY = 86400000;
/** /**
* Set a parameter to a java.sql.Time value. The driver converts * 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. * this to a SQL TIME value when it sends it to the database.
......
...@@ -714,6 +714,12 @@ public class ResultSet implements java.sql.ResultSet ...@@ -714,6 +714,12 @@ public class ResultSet implements java.sql.ResultSet
throw new SQLException("Column index out of range"); throw new SQLException("Column index out of range");
field = fields[columnIndex - 1]; 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()) switch (field.getSQLType())
{ {
case Types.BIT: case Types.BIT:
......
...@@ -121,11 +121,7 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData ...@@ -121,11 +121,7 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData
{ {
String type_name = getField(column).getTypeName(); String type_name = getField(column).getTypeName();
if (type_name.equals("cash")) return type_name.equals("cash") || type_name.equals("money");
return true;
if (type_name.equals("money"))
return true;
return false;
} }
/** /**
...@@ -214,11 +210,14 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData ...@@ -214,11 +210,14 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData
* *
* @param column the first column is 1, the second is 2, etc. * @param column the first column is 1, the second is 2, etc.
* @return the column name * @return the column name
* @exception SQLException if a databvase access error occurs * @exception SQLException if a database access error occurs
*/ */
public String getColumnName(int column) throws SQLException public String getColumnName(int column) throws SQLException
{ {
return getField(column).name; Field f = getField(column);
if(f!=null)
return f.name;
return "field"+column;
} }
/** /**
...@@ -233,13 +232,7 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData ...@@ -233,13 +232,7 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData
*/ */
public String getSchemaName(int column) throws SQLException public String getSchemaName(int column) throws SQLException
{ {
String table_name = getTableName(column);
// If the table name is invalid, so are we.
if (table_name.equals(""))
return ""; return "";
return ""; // Ok, so I don't know how to
// do this as yet.
} }
/** /**
...@@ -328,12 +321,7 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData ...@@ -328,12 +321,7 @@ public class ResultSetMetaData implements java.sql.ResultSetMetaData
*/ */
public String getCatalogName(int column) throws SQLException public String getCatalogName(int column) throws SQLException
{ {
String table_name = getTableName(column);
if (table_name.equals(""))
return ""; return "";
return ""; // As with getSchemaName(), this
// is just the start of it.
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment