From a9983ab4149991ac03506cc7985ee0a4bf6aba34 Mon Sep 17 00:00:00 2001 From: Barry Lind Date: Thu, 29 May 2003 03:21:32 +0000 Subject: [PATCH] Initial attempt to integrate in V3 protocol support. This is still a work in progress, although all RTs pass using the V3 protocol on a 7.4 database and also pass using the V2 protocol on a 7.3 database. SSL support is known not to work. Modified Files: jdbc/org/postgresql/PGConnection.java jdbc/org/postgresql/errors.properties jdbc/org/postgresql/core/BaseConnection.java jdbc/org/postgresql/core/Encoding.java jdbc/org/postgresql/core/Field.java jdbc/org/postgresql/core/PGStream.java jdbc/org/postgresql/core/QueryExecutor.java jdbc/org/postgresql/core/StartupPacket.java jdbc/org/postgresql/fastpath/Fastpath.java jdbc/org/postgresql/fastpath/FastpathArg.java jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java jdbc/org/postgresql/test/jdbc2/BlobTest.java jdbc/org/postgresql/test/jdbc2/CallableStmtTest.java jdbc/org/postgresql/test/jdbc2/MiscTest.java jdbc/org/postgresql/test/jdbc3/Jdbc3TestSuite.java --- .../jdbc/org/postgresql/PGConnection.java | 4 +- .../jdbc/org/postgresql/core/BaseConnection.java | 5 +- .../jdbc/org/postgresql/core/Encoding.java | 13 +- src/interfaces/jdbc/org/postgresql/core/Field.java | 5 +- .../jdbc/org/postgresql/core/PGStream.java | 57 ++- .../jdbc/org/postgresql/core/QueryExecutor.java | 255 ++++++++++++- .../jdbc/org/postgresql/core/StartupPacket.java | 27 +- .../jdbc/org/postgresql/errors.properties | 1 + .../jdbc/org/postgresql/fastpath/Fastpath.java | 125 ++++++- .../jdbc/org/postgresql/fastpath/FastpathArg.java | 14 +- .../postgresql/jdbc1/AbstractJdbc1Connection.java | 406 ++++++++++++++++++--- .../jdbc/org/postgresql/test/jdbc2/BlobTest.java | 4 +- .../postgresql/test/jdbc2/CallableStmtTest.java | 4 - .../jdbc/org/postgresql/test/jdbc2/MiscTest.java | 3 +- 14 files changed, 841 insertions(+), 82 deletions(-) diff --git a/src/interfaces/jdbc/org/postgresql/PGConnection.java b/src/interfaces/jdbc/org/postgresql/PGConnection.java index e2eb81dc44..7abda337ee 100644 --- a/src/interfaces/jdbc/org/postgresql/PGConnection.java +++ b/src/interfaces/jdbc/org/postgresql/PGConnection.java @@ -9,15 +9,13 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/Attic/PGConnection.java,v 1.5 2003/04/14 10:39:51 davec Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/Attic/PGConnection.java,v 1.6 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ package org.postgresql; import java.sql.*; -import java.util.Properties; -import java.util.Vector; import org.postgresql.core.Encoding; import org.postgresql.fastpath.Fastpath; import org.postgresql.largeobject.LargeObjectManager; diff --git a/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java b/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java index d2c56ddb03..30a4ba909a 100644 --- a/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java +++ b/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java @@ -6,14 +6,13 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseConnection.java,v 1.2 2003/04/13 04:10:07 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseConnection.java,v 1.3 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ package org.postgresql.core; import java.sql.DatabaseMetaData; -import java.sql.ResultSet; import java.sql.Statement; import java.sql.SQLException; import org.postgresql.PGConnection; @@ -32,6 +31,8 @@ public interface BaseConnection extends PGConnection public Encoding getEncoding() throws SQLException; public DatabaseMetaData getMetaData() throws SQLException; public Object getObject(String type, String value) throws SQLException; + public int getPGProtocolVersionMajor(); + public int getPGProtocolVersionMinor(); public PGStream getPGStream(); public String getPGType(int oid) throws SQLException; public int getPGType(String pgTypeName) throws SQLException; diff --git a/src/interfaces/jdbc/org/postgresql/core/Encoding.java b/src/interfaces/jdbc/org/postgresql/core/Encoding.java index 1aa888c3bd..73ccde041d 100644 --- a/src/interfaces/jdbc/org/postgresql/core/Encoding.java +++ b/src/interfaces/jdbc/org/postgresql/core/Encoding.java @@ -6,7 +6,7 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/Encoding.java,v 1.10 2003/03/07 18:39:41 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/Encoding.java,v 1.11 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ @@ -140,15 +140,22 @@ public class Encoding */ public byte[] encode(String s) throws SQLException { + byte[] l_return; try { if (encoding == null) { - return s.getBytes(); + l_return = s.getBytes(); } else { - return s.getBytes(encoding); + l_return = s.getBytes(encoding); + } + //Don't return null, return an empty byte[] instead + if (l_return == null) { + return new byte[0]; + } else { + return l_return; } } catch (UnsupportedEncodingException e) diff --git a/src/interfaces/jdbc/org/postgresql/core/Field.java b/src/interfaces/jdbc/org/postgresql/core/Field.java index 71d9dee904..debb4b1485 100644 --- a/src/interfaces/jdbc/org/postgresql/core/Field.java +++ b/src/interfaces/jdbc/org/postgresql/core/Field.java @@ -6,17 +6,14 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/Field.java,v 1.1 2003/03/07 18:39:41 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/Field.java,v 1.2 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ package org.postgresql.core; -import java.lang.*; import java.sql.*; -import java.util.*; import org.postgresql.core.BaseConnection; -import org.postgresql.util.PSQLException; /* */ diff --git a/src/interfaces/jdbc/org/postgresql/core/PGStream.java b/src/interfaces/jdbc/org/postgresql/core/PGStream.java index bbe96d9a42..016859f9fd 100644 --- a/src/interfaces/jdbc/org/postgresql/core/PGStream.java +++ b/src/interfaces/jdbc/org/postgresql/core/PGStream.java @@ -7,7 +7,7 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/PGStream.java,v 1.1 2003/03/07 18:39:41 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/PGStream.java,v 1.2 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ @@ -85,6 +85,25 @@ public class PGStream } /* + * Sends an integer to the back end + * + * @param val the integer to be sent + * @param siz the length of the integer in bytes (size of structure) + * @exception IOException if an I/O error occurs + */ + public void SendIntegerR(int val, int siz) throws IOException + { + byte[] buf = new byte[siz]; + + for (int i = 0; i < siz; i++) + { + buf[i] = (byte)(val & 0xff); + val >>= 8; + } + Send(buf); + } + + /* * Send an array of bytes to the backend * * @param buf The array of bytes to be sent @@ -273,7 +292,39 @@ public class PGStream * an array of strings * @exception SQLException if a data I/O error occurs */ - public byte[][] ReceiveTuple(int nf, boolean bin) throws SQLException + public byte[][] ReceiveTupleV3(int nf, boolean bin) throws SQLException + { + //TODO: use l_msgSize + int l_msgSize = ReceiveIntegerR(4); + int i; + int l_nf = ReceiveIntegerR(2); + byte[][] answer = new byte[l_nf][0]; + + for (i = 0 ; i < l_nf ; ++i) + { + int l_size = ReceiveIntegerR(4); + boolean isNull = l_size == -1; + if (isNull) + answer[i] = null; + else + { + answer[i] = Receive(l_size); + } + } + return answer; + } + + /* + * Read a tuple from the back end. A tuple is a two dimensional + * array of bytes + * + * @param nf the number of fields expected + * @param bin true if the tuple is a binary tuple + * @return null if the current response has no more tuples, otherwise + * an array of strings + * @exception SQLException if a data I/O error occurs + */ + public byte[][] ReceiveTupleV2(int nf, boolean bin) throws SQLException { int i, bim = (nf + 7) / 8; byte[] bitmask = Receive(bim); @@ -313,7 +364,7 @@ public class PGStream * @return array of bytes received * @exception SQLException if a data I/O error occurs */ - private byte[] Receive(int siz) throws SQLException + public byte[] Receive(int siz) throws SQLException { byte[] answer = new byte[siz]; Receive(answer, 0, siz); diff --git a/src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java b/src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java index 11f795c597..5b030e75e6 100644 --- a/src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java +++ b/src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java @@ -6,7 +6,7 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/QueryExecutor.java,v 1.21 2003/05/07 03:03:30 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/QueryExecutor.java,v 1.22 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ @@ -15,10 +15,8 @@ package org.postgresql.core; import java.util.Vector; import java.io.IOException; import java.sql.*; +import org.postgresql.Driver; import org.postgresql.util.PSQLException; -import org.postgresql.jdbc1.AbstractJdbc1Connection; -import org.postgresql.jdbc1.AbstractJdbc1ResultSet; -import org.postgresql.jdbc1.AbstractJdbc1Statement; public class QueryExecutor { @@ -96,6 +94,123 @@ public class QueryExecutor */ private BaseResultSet execute() throws SQLException { + if (connection.getPGProtocolVersionMajor() == 3) { + if (Driver.logDebug) + Driver.debug("Using Protocol Version3 to send query"); + return executeV3(); + } else { + if (Driver.logDebug) + Driver.debug("Using Protocol Version2 to send query"); + return executeV2(); + } + } + + private BaseResultSet executeV3() throws SQLException + { + + StringBuffer errorMessage = null; + + if (pgStream == null) + { + throw new PSQLException("postgresql.con.closed"); + } + + synchronized (pgStream) + { + + sendQueryV3(); + + int c; + boolean l_endQuery = false; + while (!l_endQuery) + { + c = pgStream.ReceiveChar(); + switch (c) + { + case 'A': // Asynchronous Notify + int pid = pgStream.ReceiveInteger(4); + String msg = pgStream.ReceiveString(connection.getEncoding()); + connection.addNotification(new org.postgresql.core.Notification(msg, pid)); + break; + case 'B': // Binary Data Transfer + receiveTupleV3(true); + break; + case 'C': // Command Status + receiveCommandStatusV3(); + break; + case 'D': // Text Data Transfer + receiveTupleV3(false); + break; + case 'E': // Error Message + + // it's possible to get more than one error message for a query + // see libpq comments wrt backend closing a connection + // so, append messages to a string buffer and keep processing + // check at the bottom to see if we need to throw an exception + + if ( errorMessage == null ) + errorMessage = new StringBuffer(); + + int l_elen = pgStream.ReceiveIntegerR(4); + errorMessage.append(connection.getEncoding().decode(pgStream.Receive(l_elen-4))); + // keep processing + break; + case 'I': // Empty Query + int t = pgStream.ReceiveChar(); + break; + case 'N': // Error Notification + int l_nlen = pgStream.ReceiveIntegerR(4); + statement.addWarning(connection.getEncoding().decode(pgStream.Receive(l_nlen-4))); + break; + case 'P': // Portal Name + String pname = pgStream.ReceiveString(connection.getEncoding()); + break; + case 'S': + //TODO: handle parameter status messages + int l_len = pgStream.ReceiveIntegerR(4); + String l_pStatus = connection.getEncoding().decode(pgStream.Receive(l_len-4)); + if (Driver.logDebug) + Driver.debug("ParameterStatus="+ l_pStatus); + break; + case 'T': // MetaData Field Description + receiveFieldsV3(); + break; + case 'Z': + // read ReadyForQuery + //TODO: use size better + if (pgStream.ReceiveIntegerR(4) != 5) throw new PSQLException("postgresql.con.setup"); + //TODO: handle transaction status + char l_tStatus = (char)pgStream.ReceiveChar(); + l_endQuery = true; + break; + default: + throw new PSQLException("postgresql.con.type", + new Character((char) c)); + } + + } + + // did we get an error during this query? + if ( errorMessage != null ) + throw new SQLException( errorMessage.toString() ); + + + //if an existing result set was passed in reuse it, else + //create a new one + if (rs != null) + { + rs.reInit(fields, tuples, status, update_count, insert_oid, binaryCursor); + } + else + { + rs = statement.createResultSet(fields, tuples, status, update_count, insert_oid, binaryCursor); + } + return rs; + } + } + + private BaseResultSet executeV2() throws SQLException + { StringBuffer errorMessage = null; @@ -107,7 +222,7 @@ public class QueryExecutor synchronized (pgStream) { - sendQuery(); + sendQueryV2(); int c; boolean l_endQuery = false; @@ -123,13 +238,13 @@ public class QueryExecutor connection.addNotification(new org.postgresql.core.Notification(msg, pid)); break; case 'B': // Binary Data Transfer - receiveTuple(true); + receiveTupleV2(true); break; case 'C': // Command Status - receiveCommandStatus(); + receiveCommandStatusV2(); break; case 'D': // Text Data Transfer - receiveTuple(false); + receiveTupleV2(false); break; case 'E': // Error Message @@ -154,7 +269,7 @@ public class QueryExecutor String pname = pgStream.ReceiveString(connection.getEncoding()); break; case 'T': // MetaData Field Description - receiveFields(); + receiveFieldsV2(); break; case 'Z': l_endQuery = true; @@ -188,7 +303,48 @@ public class QueryExecutor /* * Send a query to the backend. */ - private void sendQuery() throws SQLException + private void sendQueryV3() throws SQLException + { + for ( int i = 0; i < m_binds.length ; i++ ) + { + if ( m_binds[i] == null ) + throw new PSQLException("postgresql.prep.param", new Integer(i + 1)); + } + try + { + byte[][] l_parts = new byte[(m_binds.length*2)+1][]; + int j = 0; + int l_msgSize = 4; + Encoding l_encoding = connection.getEncoding(); + pgStream.SendChar('Q'); + for (int i = 0 ; i < m_binds.length ; ++i) + { + l_parts[j] = l_encoding.encode(m_sqlFrags[i]); + l_msgSize += l_parts[j].length; + j++; + l_parts[j] = l_encoding.encode(m_binds[i].toString()); + l_msgSize += l_parts[j].length; + j++; + } + l_parts[j] = l_encoding.encode(m_sqlFrags[m_binds.length]); + l_msgSize += l_parts[j].length; + pgStream.SendInteger(l_msgSize+1,4); + for (int k = 0; k < l_parts.length; k++) { + pgStream.Send(l_parts[k]); + } + pgStream.SendChar(0); + pgStream.flush(); + } + catch (IOException e) + { + throw new PSQLException("postgresql.con.ioerror", e); + } + } + + /* + * Send a query to the backend. + */ + private void sendQueryV2() throws SQLException { for ( int i = 0; i < m_binds.length ; i++ ) { @@ -220,11 +376,27 @@ public class QueryExecutor * * @param isBinary set if the tuple should be treated as binary data */ - private void receiveTuple(boolean isBinary) throws SQLException + private void receiveTupleV3(boolean isBinary) throws SQLException { if (fields == null) throw new PSQLException("postgresql.con.tuple"); - Object tuple = pgStream.ReceiveTuple(fields.length, isBinary); + Object tuple = pgStream.ReceiveTupleV3(fields.length, isBinary); + if (isBinary) + binaryCursor = true; + if (maxRows == 0 || tuples.size() < maxRows) + tuples.addElement(tuple); + } + + /* + * Receive a tuple from the backend. + * + * @param isBinary set if the tuple should be treated as binary data + */ + private void receiveTupleV2(boolean isBinary) throws SQLException + { + if (fields == null) + throw new PSQLException("postgresql.con.tuple"); + Object tuple = pgStream.ReceiveTupleV2(fields.length, isBinary); if (isBinary) binaryCursor = true; if (maxRows == 0 || tuples.size() < maxRows) @@ -234,7 +406,36 @@ public class QueryExecutor /* * Receive command status from the backend. */ - private void receiveCommandStatus() throws SQLException + private void receiveCommandStatusV3() throws SQLException + { + //TODO: better handle the msg len + int l_len = pgStream.ReceiveIntegerR(4); + //read l_len -5 bytes (-4 for l_len and -1 for trailing \0) + status = connection.getEncoding().decode(pgStream.Receive(l_len-5)); + //now read and discard the trailing \0 + pgStream.Receive(1); + try + { + // Now handle the update count correctly. + if (status.startsWith("INSERT") || status.startsWith("UPDATE") || status.startsWith("DELETE") || status.startsWith("MOVE")) + { + update_count = Integer.parseInt(status.substring(1 + status.lastIndexOf(' '))); + } + if (status.startsWith("INSERT")) + { + insert_oid = Long.parseLong(status.substring(1 + status.indexOf(' '), + status.lastIndexOf(' '))); + } + } + catch (NumberFormatException nfe) + { + throw new PSQLException("postgresql.con.fathom", status); + } + } + /* + * Receive command status from the backend. + */ + private void receiveCommandStatusV2() throws SQLException { status = pgStream.ReceiveString(connection.getEncoding()); @@ -261,7 +462,33 @@ public class QueryExecutor /* * Receive the field descriptions from the back end. */ - private void receiveFields() throws SQLException + private void receiveFieldsV3() throws SQLException + { + //TODO: use the msgSize + //TODO: use the tableOid, and tablePosition + if (fields != null) + throw new PSQLException("postgresql.con.multres"); + int l_msgSize = pgStream.ReceiveIntegerR(4); + int size = pgStream.ReceiveIntegerR(2); + fields = new Field[size]; + + for (int i = 0; i < fields.length; i++) + { + String typeName = pgStream.ReceiveString(connection.getEncoding()); + int tableOid = pgStream.ReceiveIntegerR(4); + int tablePosition = pgStream.ReceiveIntegerR(2); + int typeOid = pgStream.ReceiveIntegerR(4); + int typeLength = pgStream.ReceiveIntegerR(2); + int typeModifier = pgStream.ReceiveIntegerR(4); + int formatType = pgStream.ReceiveIntegerR(2); + //TODO: use the extra values coming back + fields[i] = new Field(connection, typeName, typeOid, typeLength, typeModifier); + } + } + /* + * Receive the field descriptions from the back end. + */ + private void receiveFieldsV2() throws SQLException { if (fields != null) throw new PSQLException("postgresql.con.multres"); diff --git a/src/interfaces/jdbc/org/postgresql/core/StartupPacket.java b/src/interfaces/jdbc/org/postgresql/core/StartupPacket.java index 74ad6ffc39..dd93991123 100644 --- a/src/interfaces/jdbc/org/postgresql/core/StartupPacket.java +++ b/src/interfaces/jdbc/org/postgresql/core/StartupPacket.java @@ -5,7 +5,7 @@ import java.io.IOException; /** * Sent to the backend to initialize a newly created connection. * - * $Id: StartupPacket.java,v 1.3 2003/03/07 18:39:42 barry Exp $ + * $Id: StartupPacket.java,v 1.4 2003/05/29 03:21:32 barry Exp $ */ public class StartupPacket @@ -31,6 +31,31 @@ public class StartupPacket public void writeTo(PGStream stream) throws IOException { + if (protocolMajor == 3) { + v3WriteTo(stream); + } else { + v2WriteTo(stream); + } + } + + public void v3WriteTo(PGStream stream) throws IOException + { + stream.SendInteger(4 + 4 + "user".length() + 1 + user.length() + 1 + "database".length() +1 + database.length() + 1 + 1, 4); + stream.SendInteger(protocolMajor, 2); + stream.SendInteger(protocolMinor, 2); + stream.Send("user".getBytes()); + stream.SendChar(0); + stream.Send(user.getBytes()); + stream.SendChar(0); + stream.Send("database".getBytes()); + stream.SendChar(0); + stream.Send(database.getBytes()); + stream.SendChar(0); + stream.SendChar(0); + } + + public void v2WriteTo(PGStream stream) throws IOException + { stream.SendInteger(4 + 4 + SM_DATABASE + SM_USER + SM_OPTIONS + SM_UNUSED + SM_TTY, 4); stream.SendInteger(protocolMajor, 2); stream.SendInteger(protocolMinor, 2); diff --git a/src/interfaces/jdbc/org/postgresql/errors.properties b/src/interfaces/jdbc/org/postgresql/errors.properties index 9a6c556101..d75ca97e9d 100644 --- a/src/interfaces/jdbc/org/postgresql/errors.properties +++ b/src/interfaces/jdbc/org/postgresql/errors.properties @@ -18,6 +18,7 @@ postgresql.con.misc:A connection error has occurred: {0} postgresql.con.multres:Cannot handle multiple result groups. postgresql.con.pass:The password property is missing. It is mandatory. postgresql.con.refused:Connection refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections. +postgresql.con.scm:SCM credentials authentication is not supported by this driver. postgresql.con.setup:Protocol error. Session setup failed. postgresql.con.sslfail:An error occured while getting setting up the SSL connection. postgresql.con.sslnotsupported:The server does not support SSL diff --git a/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java b/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java index 1e094e15cf..b9abb6eacc 100644 --- a/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java +++ b/src/interfaces/jdbc/org/postgresql/fastpath/Fastpath.java @@ -6,7 +6,7 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/fastpath/Attic/Fastpath.java,v 1.12 2003/03/07 18:39:42 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/fastpath/Attic/Fastpath.java,v 1.13 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ @@ -62,6 +62,129 @@ public class Fastpath */ public Object fastpath(int fnid, boolean resulttype, FastpathArg[] args) throws SQLException { + if (conn.haveMinimumServerVersion("7.4")) { + return fastpathV3(fnid, resulttype, args); + } else { + return fastpathV2(fnid, resulttype, args); + } + } + + private Object fastpathV3(int fnid, boolean resulttype, FastpathArg[] args) throws SQLException + { + // added Oct 7 1998 to give us thread safety + synchronized (stream) + { + // send the function call + try + { + int l_msgLen = 0; + l_msgLen += 16; + for (int i=0;i < args.length;i++) + l_msgLen += args[i].sendSize(); + + stream.SendChar('F'); + stream.SendInteger(l_msgLen,4); + stream.SendInteger(fnid, 4); + stream.SendInteger(1,2); + stream.SendInteger(1,2); + stream.SendInteger(args.length,2); + + for (int i = 0;i < args.length;i++) + args[i].send(stream); + + stream.SendInteger(1,2); + + // 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 + + // Now loop, reading the results + Object result = null; // our result + StringBuffer errorMessage = null; + int c; + boolean l_endQuery = false; + while (!l_endQuery) + { + c = stream.ReceiveChar(); + + switch (c) + { + case 'A': // Asynchronous Notify + int pid = stream.ReceiveInteger(4); + String msg = stream.ReceiveString(conn.getEncoding()); + conn.addNotification(new org.postgresql.core.Notification(msg, pid)); + break; + //------------------------------ + // Error message returned + case 'E': + if ( errorMessage == null ) + errorMessage = new StringBuffer(); + + int l_elen = stream.ReceiveIntegerR(4); + errorMessage.append(conn.getEncoding().decode(stream.Receive(l_elen-4))); + break; + //------------------------------ + // Notice from backend + case 'N': + int l_nlen = stream.ReceiveIntegerR(4); + conn.addWarning(conn.getEncoding().decode(stream.Receive(l_nlen-4))); + break; + + case 'V': + int l_msgLen = stream.ReceiveIntegerR(4); + int l_valueLen = stream.ReceiveIntegerR(4); + + if (l_valueLen == -1) + { + //null value + } + else if (l_valueLen == 0) + { + result = new byte[0]; + } + else + { + // Return an Integer if + if (resulttype) + result = new Integer(stream.ReceiveIntegerR(l_valueLen)); + else + { + byte buf[] = new byte[l_valueLen]; + stream.Receive(buf, 0, l_valueLen); + result = buf; + } + } + break; + + case 'Z': + //TODO: use size better + if (stream.ReceiveIntegerR(4) != 5) throw new PSQLException("postgresql.con.setup"); + //TODO: handle transaction status + char l_tStatus = (char)stream.ReceiveChar(); + l_endQuery = true; + break; + + default: + throw new PSQLException("postgresql.fp.protocol", new Character((char)c)); + } + } + + if ( errorMessage != null ) + throw new PSQLException("postgresql.fp.error", errorMessage.toString()); + + return result; + } + } + + private Object fastpathV2(int fnid, boolean resulttype, FastpathArg[] args) throws SQLException + { // added Oct 7 1998 to give us thread safety synchronized (stream) { diff --git a/src/interfaces/jdbc/org/postgresql/fastpath/FastpathArg.java b/src/interfaces/jdbc/org/postgresql/fastpath/FastpathArg.java index 7e59ce2387..0cc8ff6757 100644 --- a/src/interfaces/jdbc/org/postgresql/fastpath/FastpathArg.java +++ b/src/interfaces/jdbc/org/postgresql/fastpath/FastpathArg.java @@ -7,7 +7,7 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/fastpath/Attic/FastpathArg.java,v 1.4 2003/03/07 18:39:42 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/fastpath/Attic/FastpathArg.java,v 1.5 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ @@ -100,5 +100,17 @@ public class FastpathArg s.Send(bytes); } } + + protected int sendSize() + { + if (type) + { + return 8; + } + else + { + return 4+bytes.length; + } + } } diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java index bf7027ef56..c6718f8b05 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java @@ -9,7 +9,7 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Connection.java,v 1.18 2003/03/19 04:06:20 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Connection.java,v 1.19 2003/05/29 03:21:32 barry Exp $ * *------------------------------------------------------------------------- */ @@ -76,11 +76,10 @@ public abstract class AbstractJdbc1Connection implements BaseConnection private String this_url; private String cursor = null; // The positioned update cursor name - // These are new for v6.3, they determine the current protocol versions - // supported by this version of the driver. They are defined in - // src/include/libpq/pqcomm.h - protected static final int PG_PROTOCOL_LATEST_MAJOR = 2; - protected static final int PG_PROTOCOL_LATEST_MINOR = 0; + private int PGProtocolVersionMajor = 2; + private int PGProtocolVersionMinor = 0; + public int getPGProtocolVersionMajor() { return PGProtocolVersionMajor; } + public int getPGProtocolVersionMinor() { return PGProtocolVersionMinor; } private static final int AUTH_REQ_OK = 0; private static final int AUTH_REQ_KRB4 = 1; @@ -88,6 +87,7 @@ public abstract class AbstractJdbc1Connection implements BaseConnection private static final int AUTH_REQ_PASSWORD = 3; private static final int AUTH_REQ_CRYPT = 4; private static final int AUTH_REQ_MD5 = 5; + private static final int AUTH_REQ_SCM = 6; // These are used to cache oids, PGTypes and SQLTypes @@ -140,7 +140,7 @@ public abstract class AbstractJdbc1Connection implements BaseConnection PG_HOST = host; PG_STATUS = CONNECTION_BAD; - if (info.getProperty("ssl") != null && this_driver.sslEnabled()) + if (info.getProperty("ssl") != null && Driver.sslEnabled()) { useSSL = true; } @@ -207,6 +207,327 @@ public abstract class AbstractJdbc1Connection implements BaseConnection throw new PSQLException ("postgresql.con.failed", e); } + //Now do the protocol work + if (haveMinimumCompatibleVersion("7.4")) { + openConnectionV3(host,port,info,database,url,d,password); + } else { + openConnectionV2(host,port,info,database,url,d,password); + } + } + + private void openConnectionV3(String p_host, int p_port, Properties p_info, String p_database, String p_url, Driver p_d, String p_password) throws SQLException + { + PGProtocolVersionMajor = 3; + if (Driver.logDebug) + Driver.debug("Using Protocol Version3"); + + // Now we need to construct and send an ssl startup packet + try + { + if (useSSL) { + if (Driver.logDebug) + Driver.debug("Asking server if it supports ssl"); + pgStream.SendInteger(8,4); + pgStream.SendInteger(80877103,4); + + // now flush the ssl packets to the backend + pgStream.flush(); + + // Now get the response from the backend, either an error message + // or an authentication request + int beresp = pgStream.ReceiveChar(); + if (Driver.logDebug) + Driver.debug("Server response was (S=Yes,N=No): "+(char)beresp); + switch (beresp) + { + 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 PSQLException("postgresql.con.misc", pgStream.ReceiveString(encoding)); + + case 'N': + // Server does not support ssl + throw new PSQLException("postgresql.con.sslnotsupported"); + + case 'S': + // Server supports ssl + if (Driver.logDebug) + Driver.debug("server does support ssl"); + Driver.makeSSL(pgStream); + break; + + default: + throw new PSQLException("postgresql.con.sslfail"); + } + } + } + catch (IOException e) + { + throw new PSQLException("postgresql.con.failed", e); + } + + + // Now we need to construct and send a startup packet + try + { + new StartupPacket(PGProtocolVersionMajor, + PGProtocolVersionMinor, + PG_USER, + p_database).writeTo(pgStream); + + // now flush the startup packets to the backend + pgStream.flush(); + + // Now get the response from the backend, either an error message + // or an authentication request + int areq = -1; // must have a value here + do + { + int beresp = pgStream.ReceiveChar(); + String salt = null; + byte [] md5Salt = new byte[4]; + switch (beresp) + { + 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" + // + int l_elen = pgStream.ReceiveIntegerR(4); + if (l_elen > 30000) { + //if the error length is > than 30000 we assume this is really a v2 protocol + //server so try again with a v2 connection + //need to create a new connection and try again + try + { + pgStream = new PGStream(p_host, p_port); + } + catch (ConnectException cex) + { + // Added by Peter Mount + // ConnectException is thrown when the connection cannot be made. + // we trap this an return a more meaningful message for the end user + throw new PSQLException ("postgresql.con.refused"); + } + catch (IOException e) + { + throw new PSQLException ("postgresql.con.failed", e); + } + openConnectionV2(p_host, p_port, p_info, p_database, p_url, p_d, p_password); + return; + } + throw new PSQLException("postgresql.con.misc",encoding.decode(pgStream.Receive(l_elen-4))); + + case 'R': + // Get the message length + int l_msgLen = pgStream.ReceiveIntegerR(4); + // Get the type of request + areq = pgStream.ReceiveIntegerR(4); + // Get the crypt password salt if there is one + if (areq == AUTH_REQ_CRYPT) + { + byte[] rst = new byte[2]; + rst[0] = (byte)pgStream.ReceiveChar(); + rst[1] = (byte)pgStream.ReceiveChar(); + salt = new String(rst, 0, 2); + if (Driver.logDebug) + Driver.debug("Crypt salt=" + salt); + } + + // Or get the md5 password salt if there is one + if (areq == AUTH_REQ_MD5) + { + + md5Salt[0] = (byte)pgStream.ReceiveChar(); + md5Salt[1] = (byte)pgStream.ReceiveChar(); + md5Salt[2] = (byte)pgStream.ReceiveChar(); + md5Salt[3] = (byte)pgStream.ReceiveChar(); + salt = new String(md5Salt, 0, 4); + if (Driver.logDebug) + Driver.debug("MD5 salt=" + salt); + } + + // now send the auth packet + switch (areq) + { + case AUTH_REQ_OK: + break; + + case AUTH_REQ_KRB4: + if (Driver.logDebug) + Driver.debug("postgresql: KRB4"); + throw new PSQLException("postgresql.con.kerb4"); + + case AUTH_REQ_KRB5: + if (Driver.logDebug) + Driver.debug("postgresql: KRB5"); + throw new PSQLException("postgresql.con.kerb5"); + + case AUTH_REQ_SCM: + if (Driver.logDebug) + Driver.debug("postgresql: SCM"); + throw new PSQLException("postgresql.con.scm"); + + + case AUTH_REQ_PASSWORD: + if (Driver.logDebug) + Driver.debug("postgresql: PASSWORD"); + pgStream.SendChar('p'); + pgStream.SendInteger(5 + p_password.length(), 4); + pgStream.Send(p_password.getBytes()); + pgStream.SendChar(0); + pgStream.flush(); + break; + + case AUTH_REQ_CRYPT: + if (Driver.logDebug) + Driver.debug("postgresql: CRYPT"); + String crypted = UnixCrypt.crypt(salt, p_password); + pgStream.SendChar('p'); + pgStream.SendInteger(5 + crypted.length(), 4); + pgStream.Send(crypted.getBytes()); + pgStream.SendChar(0); + pgStream.flush(); + break; + + case AUTH_REQ_MD5: + if (Driver.logDebug) + Driver.debug("postgresql: MD5"); + byte[] digest = MD5Digest.encode(PG_USER, p_password, md5Salt); + pgStream.SendChar('p'); + pgStream.SendInteger(5 + digest.length, 4); + pgStream.Send(digest); + pgStream.SendChar(0); + pgStream.flush(); + break; + + default: + throw new PSQLException("postgresql.con.auth", new Integer(areq)); + } + break; + + default: + throw new PSQLException("postgresql.con.authfail"); + } + } + while (areq != AUTH_REQ_OK); + + } + catch (IOException e) + { + throw new PSQLException("postgresql.con.failed", e); + } + + int beresp; + do + { + beresp = pgStream.ReceiveChar(); + switch (beresp) + { + case 'Z': + //ready for query + break; + case 'K': + int l_msgLen = pgStream.ReceiveIntegerR(4); + if (l_msgLen != 12) throw new PSQLException("postgresql.con.setup"); + pid = pgStream.ReceiveIntegerR(4); + ckey = pgStream.ReceiveIntegerR(4); + break; + case 'E': + int l_elen = pgStream.ReceiveIntegerR(4); + throw new PSQLException("postgresql.con.backend",encoding.decode(pgStream.Receive(l_elen-4))); + case 'N': + int l_nlen = pgStream.ReceiveIntegerR(4); + addWarning(encoding.decode(pgStream.Receive(l_nlen-4))); + break; + case 'S': + //TODO: handle parameter status messages + int l_len = pgStream.ReceiveIntegerR(4); + String l_pStatus = encoding.decode(pgStream.Receive(l_len-4)); + if (Driver.logDebug) + Driver.debug("ParameterStatus="+ l_pStatus); + break; + default: + if (Driver.logDebug) + Driver.debug("invalid state="+ (char)beresp); + throw new PSQLException("postgresql.con.setup"); + } + } + while (beresp != 'Z'); + // read ReadyForQuery + if (pgStream.ReceiveIntegerR(4) != 5) throw new PSQLException("postgresql.con.setup"); + //TODO: handle transaction status + char l_tStatus = (char)pgStream.ReceiveChar(); + + // "pg_encoding_to_char(1)" will return 'EUC_JP' for a backend compiled with multibyte, + // otherwise it's hardcoded to 'SQL_ASCII'. + // If the backend doesn't know about multibyte we can't assume anything about the encoding + // used, so we denote this with 'UNKNOWN'. + //Note: begining with 7.2 we should be using pg_client_encoding() which + //is new in 7.2. However it isn't easy to conditionally call this new + //function, since we don't yet have the information as to what server + //version we are talking to. Thus we will continue to call + //getdatabaseencoding() until we drop support for 7.1 and older versions + //or until someone comes up with a conditional way to run one or + //the other function depending on server version that doesn't require + //two round trips to the server per connection + + final String encodingQuery = + "case when pg_encoding_to_char(1) = 'SQL_ASCII' then 'UNKNOWN' else getdatabaseencoding() end"; + + // Set datestyle and fetch db encoding in a single call, to avoid making + // more than one round trip to the backend during connection startup. + + + BaseResultSet resultSet + = execSQL("set datestyle to 'ISO'; select version(), " + encodingQuery + ";"); + + if (! resultSet.next()) + { + throw new PSQLException("postgresql.con.failed", "failed getting backend encoding"); + } + String version = resultSet.getString(1); + dbVersionNumber = extractVersionNumber(version); + + String dbEncoding = resultSet.getString(2); + encoding = Encoding.getEncoding(dbEncoding, p_info.getProperty("charSet")); + //In 7.3 we are forced to do a second roundtrip to handle the case + //where a database may not be running in autocommit mode + //jdbc by default assumes autocommit is on until setAutoCommit(false) + //is called. Therefore we need to ensure a new connection is + //initialized to autocommit on. + //We also set the client encoding so that the driver only needs + //to deal with utf8. We can only do this in 7.3 because multibyte + //support is now always included + if (haveMinimumServerVersion("7.3")) + { + BaseResultSet acRset = + //TODO: if protocol V3 we can set the client encoding in startup + execSQL("set client_encoding = 'UNICODE'"); + //set encoding to be unicode + encoding = Encoding.getEncoding("UNICODE", null); + + } + + // Initialise object handling + initObjectTypes(); + + // Mark the connection as ok, and cleanup + PG_STATUS = CONNECTION_OK; + } + + private void openConnectionV2(String host, int port, Properties info, String database, String url, Driver d, String password) throws SQLException + { + PGProtocolVersionMajor = 2; + if (Driver.logDebug) + Driver.debug("Using Protocol Version2"); + // Now we need to construct and send an ssl startup packet try { @@ -260,8 +581,8 @@ public abstract class AbstractJdbc1Connection implements BaseConnection // Now we need to construct and send a startup packet try { - new StartupPacket(PG_PROTOCOL_LATEST_MAJOR, - PG_PROTOCOL_LATEST_MINOR, + new StartupPacket(PGProtocolVersionMajor, + PGProtocolVersionMinor, PG_USER, database).writeTo(pgStream); @@ -486,7 +807,6 @@ public abstract class AbstractJdbc1Connection implements BaseConnection PG_STATUS = CONNECTION_OK; } - /* * Return the instance of org.postgresql.Driver * that created this connection @@ -776,6 +1096,35 @@ public abstract class AbstractJdbc1Connection implements BaseConnection */ public void close() throws SQLException { + if (haveMinimumCompatibleVersion("7.4")) { + closeV3(); + } else { + closeV2(); + } + } + + public void closeV3() throws SQLException + { + if (pgStream != null) + { + try + { + pgStream.SendChar('X'); + pgStream.SendInteger(0,4); + pgStream.flush(); + pgStream.close(); + } + catch (IOException e) + {} + finally + { + pgStream = null; + } + } + } + + public void closeV2() throws SQLException + { if (pgStream != null) { try @@ -887,29 +1236,11 @@ public abstract class AbstractJdbc1Connection implements BaseConnection return ; if (autoCommit) { - if (haveMinimumServerVersion("7.3")) - { - //We do the select to ensure a transaction is in process - //before we do the commit to avoid warning messages - //from issuing a commit without a transaction in process - //NOTE this is done in two network roundtrips to work around - //a server bug in 7.3 where the select wouldn't actually start - //a new transaction if in the same command as the commit - execSQL("select 1;"); - execSQL("commit; set autocommit = on;"); - } - else - { execSQL("end"); - } } else { - if (haveMinimumServerVersion("7.3")) - { - execSQL("set autocommit = off; " + getIsolationLevelSQL()); - } - else if (haveMinimumServerVersion("7.1")) + if (haveMinimumServerVersion("7.1")) { execSQL("begin;" + getIsolationLevelSQL()); } @@ -948,11 +1279,8 @@ public abstract class AbstractJdbc1Connection implements BaseConnection { if (autoCommit) return ; - if (haveMinimumServerVersion("7.3")) - { - execSQL("commit; " + getIsolationLevelSQL()); - } - else if (haveMinimumServerVersion("7.1")) + //TODO: delay starting new transaction until first command + if (haveMinimumServerVersion("7.1")) { execSQL("commit;begin;" + getIsolationLevelSQL()); } @@ -976,14 +1304,8 @@ public abstract class AbstractJdbc1Connection implements BaseConnection { if (autoCommit) return ; - if (haveMinimumServerVersion("7.3")) - { - //we don't automatically start a transaction - //but let the server functionality automatically start - //one when the first statement is executed - execSQL("rollback; " + getIsolationLevelSQL()); - } - else if (haveMinimumServerVersion("7.1")) + //TODO: delay starting transaction until first command + if (haveMinimumServerVersion("7.1")) { execSQL("rollback; begin;" + getIsolationLevelSQL()); } diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/BlobTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/BlobTest.java index 7a33ae5392..021c1e3201 100644 --- a/src/interfaces/jdbc/org/postgresql/test/jdbc2/BlobTest.java +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/BlobTest.java @@ -8,7 +8,7 @@ import java.sql.*; import org.postgresql.largeobject.*; /* - * $Id: BlobTest.java,v 1.7 2002/08/14 20:35:40 barry Exp $ + * $Id: BlobTest.java,v 1.8 2003/05/29 03:21:32 barry Exp $ * * Some simple tests based on problems reported by users. Hopefully these will * help prevent previous problems from re-occuring ;-) @@ -188,7 +188,7 @@ public class BlobTest extends TestCase result = result && f == -1 && b == -1; if (!result) - System.out.println("\nBlob compare failed at " + c + " of " + blob.size()); + assertTrue("Blob compare failed at " + c + " of " + blob.size(), false); blob.close(); fis.close(); diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/CallableStmtTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/CallableStmtTest.java index eb05fa97ab..7426c8f0c6 100644 --- a/src/interfaces/jdbc/org/postgresql/test/jdbc2/CallableStmtTest.java +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/CallableStmtTest.java @@ -56,7 +56,6 @@ public class CallableStmtTest extends TestCase public void testGetDouble () throws Throwable { - // System.out.println ("Testing CallableStmt Types.DOUBLE"); CallableStatement call = con.prepareCall (func + pkgName + "getDouble (?) }"); call.setDouble (2, (double)3.04); call.registerOutParameter (1, Types.DOUBLE); @@ -67,7 +66,6 @@ public class CallableStmtTest extends TestCase public void testGetInt () throws Throwable { - // System.out.println ("Testing CallableStmt Types.INTEGER"); CallableStatement call = con.prepareCall (func + pkgName + "getInt (?) }"); call.setInt (2, 4); call.registerOutParameter (1, Types.INTEGER); @@ -78,7 +76,6 @@ public class CallableStmtTest extends TestCase public void testGetNumeric () throws Throwable { - // System.out.println ("Testing CallableStmt Types.NUMERIC"); CallableStatement call = con.prepareCall (func + pkgName + "getNumeric (?) }"); call.setBigDecimal (2, new java.math.BigDecimal(4)); call.registerOutParameter (1, Types.NUMERIC); @@ -90,7 +87,6 @@ public class CallableStmtTest extends TestCase public void testGetString () throws Throwable { - // System.out.println ("Testing CallableStmt Types.VARCHAR"); CallableStatement call = con.prepareCall (func + pkgName + "getString (?) }"); call.setString (2, "foo"); call.registerOutParameter (1, Types.VARCHAR); diff --git a/src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java b/src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java index 025f8c0e72..8c95b0e39f 100644 --- a/src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java +++ b/src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java @@ -5,7 +5,7 @@ import junit.framework.TestCase; import java.sql.*; /* - * $Id: MiscTest.java,v 1.8 2002/09/06 21:23:06 momjian Exp $ + * $Id: MiscTest.java,v 1.9 2003/05/29 03:21:32 barry Exp $ * * Some simple tests based on problems reported by users. Hopefully these will * help prevent previous problems from re-occuring ;-) @@ -79,7 +79,6 @@ public class MiscTest extends TestCase public void xtestLocking() { - System.out.println("testing lock"); try { Connection con = TestUtil.openDB(); -- 2.11.0