mirror of
https://github.com/postgres/postgres.git
synced 2025-09-03 15:22:11 +03:00
581 lines
14 KiB
Java
581 lines
14 KiB
Java
package org.postgresql;
|
|
|
|
import java.io.*;
|
|
import java.lang.*;
|
|
import java.net.*;
|
|
import java.util.*;
|
|
import java.sql.*;
|
|
import org.postgresql.*;
|
|
import org.postgresql.util.*;
|
|
|
|
/**
|
|
* @version 1.0 15-APR-1997
|
|
*
|
|
* This class is used by Connection & PGlobj for communicating with the
|
|
* backend.
|
|
*
|
|
* @see java.sql.Connection
|
|
*/
|
|
// This class handles all the Streamed I/O for a org.postgresql connection
|
|
public class PG_Stream
|
|
{
|
|
private Socket connection;
|
|
private InputStream pg_input;
|
|
private BufferedOutputStream pg_output;
|
|
|
|
BytePoolDim1 bytePoolDim1 = new BytePoolDim1();
|
|
BytePoolDim2 bytePoolDim2 = new BytePoolDim2();
|
|
|
|
/**
|
|
* Constructor: Connect to the PostgreSQL back end and return
|
|
* a stream connection.
|
|
*
|
|
* @param host the hostname to connect to
|
|
* @param port the port number that the postmaster is sitting on
|
|
* @exception IOException if an IOException occurs below it.
|
|
*/
|
|
public PG_Stream(String host, int port) throws IOException
|
|
{
|
|
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);
|
|
|
|
// Buffer sizes submitted by Sverre H Huseby <sverrehu@online.no>
|
|
pg_input = new BufferedInputStream(connection.getInputStream(), 8192);
|
|
pg_output = new BufferedOutputStream(connection.getOutputStream(), 8192);
|
|
}
|
|
|
|
/**
|
|
* Sends a single character to the back end
|
|
*
|
|
* @param val the character to be sent
|
|
* @exception IOException if an I/O error occurs
|
|
*/
|
|
public void SendChar(int val) throws IOException
|
|
{
|
|
// Original code
|
|
//byte b[] = new byte[1];
|
|
//b[0] = (byte)val;
|
|
//pg_output.write(b);
|
|
|
|
// Optimised version by Sverre H. Huseby Aug 22 1999 Applied Sep 13 1999
|
|
pg_output.write((byte)val);
|
|
}
|
|
|
|
/**
|
|
* 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 SendInteger(int val, int siz) throws IOException
|
|
{
|
|
byte[] buf = bytePoolDim1.allocByte(siz);
|
|
|
|
while (siz-- > 0)
|
|
{
|
|
buf[siz] = (byte)(val & 0xff);
|
|
val >>= 8;
|
|
}
|
|
Send(buf);
|
|
}
|
|
|
|
/**
|
|
* Sends an integer to the back end in reverse order.
|
|
*
|
|
* This is required when the backend uses the routines in the
|
|
* src/backend/libpq/pqcomprim.c module.
|
|
*
|
|
* As time goes by, this should become obsolete.
|
|
*
|
|
* @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 SendIntegerReverse(int val, int siz) throws IOException
|
|
{
|
|
byte[] buf = bytePoolDim1.allocByte(siz);
|
|
int p=0;
|
|
while (siz-- > 0)
|
|
{
|
|
buf[p++] = (byte)(val & 0xff);
|
|
val >>= 8;
|
|
}
|
|
Send(buf);
|
|
}
|
|
|
|
/**
|
|
* Send an array of bytes to the backend
|
|
*
|
|
* @param buf The array of bytes to be sent
|
|
* @exception IOException if an I/O error occurs
|
|
*/
|
|
public void Send(byte buf[]) throws IOException
|
|
{
|
|
pg_output.write(buf);
|
|
}
|
|
|
|
/**
|
|
* Send an exact array of bytes to the backend - if the length
|
|
* has not been reached, send nulls until it has.
|
|
*
|
|
* @param buf the array of bytes to be sent
|
|
* @param siz the number of bytes to be sent
|
|
* @exception IOException if an I/O error occurs
|
|
*/
|
|
public void Send(byte buf[], int siz) throws IOException
|
|
{
|
|
Send(buf,0,siz);
|
|
}
|
|
|
|
/**
|
|
* Send an exact array of bytes to the backend - if the length
|
|
* has not been reached, send nulls until it has.
|
|
*
|
|
* @param buf the array of bytes to be sent
|
|
* @param off offset in the array to start sending from
|
|
* @param siz the number of bytes to be sent
|
|
* @exception IOException if an I/O error occurs
|
|
*/
|
|
public void Send(byte buf[], int off, int siz) throws IOException
|
|
{
|
|
int i;
|
|
|
|
pg_output.write(buf, off, ((buf.length-off) < siz ? (buf.length-off) : siz));
|
|
if((buf.length-off) < siz)
|
|
{
|
|
for (i = buf.length-off ; i < siz ; ++i)
|
|
{
|
|
pg_output.write(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends a packet, prefixed with the packet's length
|
|
* @param buf buffer to send
|
|
* @exception SQLException if an I/O Error returns
|
|
*/
|
|
public void SendPacket(byte[] buf) throws IOException
|
|
{
|
|
SendInteger(buf.length+4,4);
|
|
Send(buf);
|
|
}
|
|
|
|
/**
|
|
* Receives a single character from the backend
|
|
*
|
|
* @return the character received
|
|
* @exception SQLException if an I/O Error returns
|
|
*/
|
|
public int ReceiveChar() throws SQLException
|
|
{
|
|
int c = 0;
|
|
|
|
try
|
|
{
|
|
c = pg_input.read();
|
|
if (c < 0) throw new PSQLException("postgresql.stream.eof");
|
|
} catch (IOException e) {
|
|
throw new PSQLException("postgresql.stream.ioerror",e);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* Receives an integer from the backend
|
|
*
|
|
* @param siz length of the integer in bytes
|
|
* @return the integer received from the backend
|
|
* @exception SQLException if an I/O error occurs
|
|
*/
|
|
public int ReceiveInteger(int siz) throws SQLException
|
|
{
|
|
int n = 0;
|
|
|
|
try
|
|
{
|
|
for (int i = 0 ; i < siz ; i++)
|
|
{
|
|
int b = pg_input.read();
|
|
|
|
if (b < 0)
|
|
throw new PSQLException("postgresql.stream.eof");
|
|
n = n | (b << (8 * i)) ;
|
|
}
|
|
} catch (IOException e) {
|
|
throw new PSQLException("postgresql.stream.ioerror",e);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Receives an integer from the backend
|
|
*
|
|
* @param siz length of the integer in bytes
|
|
* @return the integer received from the backend
|
|
* @exception SQLException if an I/O error occurs
|
|
*/
|
|
public int ReceiveIntegerR(int siz) throws SQLException
|
|
{
|
|
int n = 0;
|
|
|
|
try
|
|
{
|
|
for (int i = 0 ; i < siz ; i++)
|
|
{
|
|
int b = pg_input.read();
|
|
|
|
if (b < 0)
|
|
throw new PSQLException("postgresql.stream.eof");
|
|
n = b | (n << 8);
|
|
}
|
|
} catch (IOException e) {
|
|
throw new PSQLException("postgresql.stream.ioerror",e);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Receives a null-terminated string from the backend. Maximum of
|
|
* maxsiz bytes - if we don't see a null, then we assume something
|
|
* has gone wrong.
|
|
*
|
|
* @param maxsiz maximum length of string
|
|
* @return string from back end
|
|
* @exception SQLException if an I/O error occurs
|
|
*/
|
|
public String ReceiveString(int maxsiz) throws SQLException
|
|
{
|
|
byte[] rst = bytePoolDim1.allocByte(maxsiz);
|
|
return ReceiveString(rst, maxsiz, null);
|
|
}
|
|
|
|
/**
|
|
* Receives a null-terminated string from the backend. Maximum of
|
|
* maxsiz bytes - if we don't see a null, then we assume something
|
|
* has gone wrong.
|
|
*
|
|
* @param maxsiz maximum length of string
|
|
* @param encoding the charset encoding to use.
|
|
* @param maxsiz maximum length of string in bytes
|
|
* @return string from back end
|
|
* @exception SQLException if an I/O error occurs
|
|
*/
|
|
public String ReceiveString(int maxsiz, String encoding) throws SQLException
|
|
{
|
|
byte[] rst = bytePoolDim1.allocByte(maxsiz);
|
|
return ReceiveString(rst, maxsiz, encoding);
|
|
}
|
|
|
|
/**
|
|
* Receives a null-terminated string from the backend. Maximum of
|
|
* maxsiz bytes - if we don't see a null, then we assume something
|
|
* has gone wrong.
|
|
*
|
|
* @param rst byte array to read the String into. rst.length must
|
|
* equal to or greater than maxsize.
|
|
* @param maxsiz maximum length of string in bytes
|
|
* @param encoding the charset encoding to use.
|
|
* @return string from back end
|
|
* @exception SQLException if an I/O error occurs
|
|
*/
|
|
public String ReceiveString(byte rst[], int maxsiz, String encoding)
|
|
throws SQLException
|
|
{
|
|
int s = 0;
|
|
|
|
try
|
|
{
|
|
while (s < maxsiz)
|
|
{
|
|
int c = pg_input.read();
|
|
if (c < 0)
|
|
throw new PSQLException("postgresql.stream.eof");
|
|
else if (c == 0) {
|
|
rst[s] = 0;
|
|
break;
|
|
} else
|
|
rst[s++] = (byte)c;
|
|
}
|
|
if (s >= maxsiz)
|
|
throw new PSQLException("postgresql.stream.toomuch");
|
|
} catch (IOException e) {
|
|
throw new PSQLException("postgresql.stream.ioerror",e);
|
|
}
|
|
String v = null;
|
|
if (encoding == null)
|
|
v = new String(rst, 0, s);
|
|
else {
|
|
try {
|
|
v = new String(rst, 0, s, encoding);
|
|
} catch (UnsupportedEncodingException unse) {
|
|
throw new PSQLException("postgresql.stream.encoding", unse);
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* 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[][] ReceiveTuple(int nf, boolean bin) throws SQLException
|
|
{
|
|
int i, bim = (nf + 7)/8;
|
|
byte[] bitmask = Receive(bim);
|
|
byte[][] answer = bytePoolDim2.allocByte(nf);
|
|
|
|
int whichbit = 0x80;
|
|
int whichbyte = 0;
|
|
|
|
for (i = 0 ; i < nf ; ++i)
|
|
{
|
|
boolean isNull = ((bitmask[whichbyte] & whichbit) == 0);
|
|
whichbit >>= 1;
|
|
if (whichbit == 0)
|
|
{
|
|
++whichbyte;
|
|
whichbit = 0x80;
|
|
}
|
|
if (isNull)
|
|
answer[i] = null;
|
|
else
|
|
{
|
|
int len = ReceiveIntegerR(4);
|
|
if (!bin)
|
|
len -= 4;
|
|
if (len < 0)
|
|
len = 0;
|
|
answer[i] = Receive(len);
|
|
}
|
|
}
|
|
return answer;
|
|
}
|
|
|
|
/**
|
|
* Reads in a given number of bytes from the backend
|
|
*
|
|
* @param siz number of bytes to read
|
|
* @return array of bytes received
|
|
* @exception SQLException if a data I/O error occurs
|
|
*/
|
|
private byte[] Receive(int siz) throws SQLException
|
|
{
|
|
byte[] answer = bytePoolDim1.allocByte(siz);
|
|
Receive(answer,0,siz);
|
|
return answer;
|
|
}
|
|
|
|
/**
|
|
* Reads in a given number of bytes from the backend
|
|
*
|
|
* @param buf buffer to store result
|
|
* @param off offset in buffer
|
|
* @param siz number of bytes to read
|
|
* @exception SQLException if a data I/O error occurs
|
|
*/
|
|
public void Receive(byte[] b,int off,int siz) throws SQLException
|
|
{
|
|
int s = 0;
|
|
|
|
try
|
|
{
|
|
while (s < siz)
|
|
{
|
|
int w = pg_input.read(b, off+s, siz - s);
|
|
if (w < 0)
|
|
throw new PSQLException("postgresql.stream.eof");
|
|
s += w;
|
|
}
|
|
} catch (IOException e) {
|
|
throw new PSQLException("postgresql.stream.ioerror",e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This flushes any pending output to the backend. It is used primarily
|
|
* by the Fastpath code.
|
|
* @exception SQLException if an I/O error occurs
|
|
*/
|
|
public void flush() throws SQLException
|
|
{
|
|
try {
|
|
pg_output.flush();
|
|
} catch (IOException e) {
|
|
throw new PSQLException("postgresql.stream.flush",e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the connection
|
|
*
|
|
* @exception IOException if a IO Error occurs
|
|
*/
|
|
public void close() throws IOException
|
|
{
|
|
pg_output.write("X\0".getBytes());
|
|
pg_output.flush();
|
|
pg_output.close();
|
|
pg_input.close();
|
|
connection.close();
|
|
}
|
|
|
|
/**
|
|
* Deallocate all resources that has been associated with any previous
|
|
* query.
|
|
*/
|
|
public void deallocate(){
|
|
bytePoolDim1.deallocate();
|
|
bytePoolDim2.deallocate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A simple and fast object pool implementation that can pool objects
|
|
* of any type. This implementation is not thread safe, it is up to the users
|
|
* of this class to assure thread safety.
|
|
*/
|
|
class ObjectPool {
|
|
int cursize = 0;
|
|
int maxsize = 16;
|
|
Object arr[] = new Object[maxsize];
|
|
|
|
public void add(Object o){
|
|
if(cursize >= maxsize){
|
|
Object newarr[] = new Object[maxsize*2];
|
|
System.arraycopy(arr, 0, newarr, 0, maxsize);
|
|
maxsize = maxsize * 2;
|
|
arr = newarr;
|
|
}
|
|
arr[cursize++] = o;
|
|
}
|
|
|
|
public Object remove(){
|
|
return arr[--cursize];
|
|
}
|
|
public boolean isEmpty(){
|
|
return cursize == 0;
|
|
}
|
|
public int size(){
|
|
return cursize;
|
|
}
|
|
public void addAll(ObjectPool pool){
|
|
int srcsize = pool.size();
|
|
if(srcsize == 0)
|
|
return;
|
|
int totalsize = srcsize + cursize;
|
|
if(totalsize > maxsize){
|
|
Object newarr[] = new Object[totalsize*2];
|
|
System.arraycopy(arr, 0, newarr, 0, cursize);
|
|
maxsize = maxsize = totalsize * 2;
|
|
arr = newarr;
|
|
}
|
|
System.arraycopy(pool.arr, 0, arr, cursize, srcsize);
|
|
cursize = totalsize;
|
|
}
|
|
public void clear(){
|
|
cursize = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A simple and efficient class to pool one dimensional byte arrays
|
|
* of different sizes.
|
|
*/
|
|
class BytePoolDim1 {
|
|
int maxsize = 256;
|
|
ObjectPool notusemap[] = new ObjectPool[maxsize];
|
|
ObjectPool inusemap[] = new ObjectPool[maxsize];
|
|
byte binit[][] = new byte[maxsize][0];
|
|
|
|
public BytePoolDim1(){
|
|
for(int i = 0; i < maxsize; i++){
|
|
binit[i] = new byte[i];
|
|
inusemap[i] = new ObjectPool();
|
|
notusemap[i] = new ObjectPool();
|
|
}
|
|
}
|
|
|
|
public byte[] allocByte(int size){
|
|
if(size > maxsize){
|
|
return new byte[size];
|
|
}
|
|
|
|
ObjectPool not_usel = notusemap[size];
|
|
ObjectPool in_usel = inusemap[size];
|
|
byte b[] = null;
|
|
|
|
if(!not_usel.isEmpty()) {
|
|
Object o = not_usel.remove();
|
|
b = (byte[]) o;
|
|
} else
|
|
b = new byte[size];
|
|
in_usel.add(b);
|
|
|
|
return b;
|
|
}
|
|
|
|
public void deallocate(){
|
|
for(int i = 0; i < maxsize; i++){
|
|
notusemap[i].addAll(inusemap[i]);
|
|
inusemap[i].clear();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* A simple and efficient class to pool two dimensional byte arrays
|
|
* of different sizes.
|
|
*/
|
|
class BytePoolDim2 {
|
|
int maxsize = 32;
|
|
ObjectPool notusemap[] = new ObjectPool[maxsize];
|
|
ObjectPool inusemap[] = new ObjectPool[maxsize];
|
|
|
|
public BytePoolDim2(){
|
|
for(int i = 0; i < maxsize; i++){
|
|
inusemap[i] = new ObjectPool();
|
|
notusemap[i] = new ObjectPool();
|
|
}
|
|
}
|
|
|
|
public byte[][] allocByte(int size){
|
|
if(size > maxsize){
|
|
return new byte[size][0];
|
|
}
|
|
ObjectPool not_usel = notusemap[size];
|
|
ObjectPool in_usel = inusemap[size];
|
|
|
|
byte b[][] = null;
|
|
|
|
if(!not_usel.isEmpty()) {
|
|
Object o = not_usel.remove();
|
|
b = (byte[][]) o;
|
|
} else
|
|
b = new byte[size][0];
|
|
in_usel.add(b);
|
|
return b;
|
|
}
|
|
|
|
public void deallocate(){
|
|
for(int i = 0; i < maxsize; i++){
|
|
notusemap[i].addAll(inusemap[i]);
|
|
inusemap[i].clear();
|
|
}
|
|
}
|
|
}
|
|
|