Connection.java 30.1 KB
Newer Older
Peter Mount's avatar
Peter Mount committed
1 2 3 4 5 6 7 8 9 10 11 12
package org.postgresql;

import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import org.postgresql.Field;
import org.postgresql.fastpath.*;
import org.postgresql.largeobject.*;
import org.postgresql.util.*;

/**
13
 * $Id: Connection.java,v 1.16 2001/06/01 20:57:58 momjian Exp $
Peter Mount's avatar
Peter Mount committed
14 15 16 17 18 19 20 21 22
 *
 * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or
 * JDBC2 versions of the Connection class.
 *
 */
public abstract class Connection
{
  // This is the network stream associated with this connection
  public PG_Stream pg_stream;
23

Peter Mount's avatar
Peter Mount committed
24
  // This is set by org.postgresql.Statement.setMaxRows()
25
  //public int maxrows = 0;		// maximum no. of rows; 0 = unlimited
26

Peter Mount's avatar
Peter Mount committed
27 28 29 30 31 32
  private String PG_HOST;
  private int PG_PORT;
  private String PG_USER;
  private String PG_PASSWORD;
  private String PG_DATABASE;
  private boolean PG_STATUS;
33 34 35 36 37 38 39 40

  /**
   *  The encoding to use for this connection.
   *  If <b>null</b>, the encoding has not been specified by the
   *  user, and the default encoding for the platform should be
   *  used.
   */
  private String encoding;
41

Peter Mount's avatar
Peter Mount committed
42 43
  public boolean CONNECTION_OK = true;
  public boolean CONNECTION_BAD = false;
44

Peter Mount's avatar
Peter Mount committed
45 46
  public boolean autoCommit = true;
  public boolean readOnly = false;
47

Peter Mount's avatar
Peter Mount committed
48 49 50
  public Driver this_driver;
  private String this_url;
  private String cursor = null;	// The positioned update cursor name
51

Peter Mount's avatar
Peter Mount committed
52 53 54 55 56 57 58 59 60 61
  // 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 static final int SM_DATABASE	= 64;
  private static final int SM_USER	= 32;
  private static final int SM_OPTIONS	= 64;
  private static final int SM_UNUSED	= 64;
  private static final int SM_TTY	= 64;
62

Peter Mount's avatar
Peter Mount committed
63 64 65 66 67
  private static final int AUTH_REQ_OK       = 0;
  private static final int AUTH_REQ_KRB4     = 1;
  private static final int AUTH_REQ_KRB5     = 2;
  private static final int AUTH_REQ_PASSWORD = 3;
  private static final int AUTH_REQ_CRYPT    = 4;
68

Peter Mount's avatar
Peter Mount committed
69 70
  // New for 6.3, salt value for crypt authorisation
  private String salt;
71

Peter Mount's avatar
Peter Mount committed
72 73 74 75 76
  // 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.
  public Hashtable fieldCache = new Hashtable();
77

Peter Mount's avatar
Peter Mount committed
78 79
  // Now handle notices as warnings, so things like "show" now work
  public SQLWarning firstWarning = null;
80

Peter Mount's avatar
Peter Mount committed
81 82 83
    // The PID an cancellation key we get from the backend process
    public int pid;
    public int ckey;
84 85

    // This receive_sbuf should be used by the different methods
86 87
    // that call pg_stream.ReceiveString() in this Connection, so
    // so we avoid uneccesary new allocations.
88
    byte receive_sbuf[] = new byte[8192];
89

Peter Mount's avatar
Peter Mount committed
90 91 92 93 94 95
    /**
     * This is called by Class.forName() from within org.postgresql.Driver
     */
    public Connection()
    {
    }
96

Peter Mount's avatar
Peter Mount committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
    /**
     * This method actually opens the connection. It is called by Driver.
     *
     * @param host the hostname of the database back end
     * @param port the port number of the postmaster process
     * @param info a Properties[] thing of the user and password
     * @param database the database to connect to
     * @param u the URL of the connection
     * @param d the Driver instantation of the connection
     * @return a valid connection profile
     * @exception SQLException if a database access error occurs
     */
    protected void openConnection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException
    {
    // Throw an exception if the user or password properties are missing
    // This occasionally occurs when the client uses the properties version
    // of getConnection(), and is a common question on the email lists
    if(info.getProperty("user")==null)
      throw new PSQLException("postgresql.con.user");
    if(info.getProperty("password")==null)
      throw new PSQLException("postgresql.con.pass");
118

Peter Mount's avatar
Peter Mount committed
119
    this_driver = d;
120 121 122 123
    this_url = url;
    PG_DATABASE = database;
    PG_PASSWORD = info.getProperty("password");
    PG_USER = info.getProperty("user");
Peter Mount's avatar
Peter Mount committed
124
    PG_PORT = port;
125
    PG_HOST = host;
Peter Mount's avatar
Peter Mount committed
126
    PG_STATUS = CONNECTION_BAD;
127

Peter Mount's avatar
Peter Mount committed
128 129 130 131 132 133 134 135 136 137 138 139
    // Now make the initial connection
    try
      {
	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 PSQLException ("postgresql.con.refused");
      } catch (IOException e) {
	throw new PSQLException ("postgresql.con.failed",e);
      }
140

Peter Mount's avatar
Peter Mount committed
141 142 143 144 145 146 147 148
      // Now we need to construct and send a startup packet
      try
	{
	  // Ver 6.3 code
	  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_MINOR,2);
	  pg_stream.Send(database.getBytes(),SM_DATABASE);
149

Peter Mount's avatar
Peter Mount committed
150 151
	  // This last send includes the unused fields
	  pg_stream.Send(PG_USER.getBytes(),SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY);
152

Peter Mount's avatar
Peter Mount committed
153 154
	  // now flush the startup packets to the backend
	  pg_stream.flush();
155

Peter Mount's avatar
Peter Mount committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169
	  // 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 = pg_stream.ReceiveChar();
	    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"
		//
170
		throw new SQLException(pg_stream.ReceiveString
171
                                       (receive_sbuf, 4096, getEncoding()));
172

Peter Mount's avatar
Peter Mount committed
173 174 175
	      case 'R':
		// Get the type of request
		areq = pg_stream.ReceiveIntegerR(4);
176

Peter Mount's avatar
Peter Mount committed
177 178 179 180 181 182 183 184
		// Get the password salt if there is one
		if(areq == AUTH_REQ_CRYPT) {
		  byte[] rst = new byte[2];
		  rst[0] = (byte)pg_stream.ReceiveChar();
		  rst[1] = (byte)pg_stream.ReceiveChar();
		  salt = new String(rst,0,2);
		  DriverManager.println("Salt="+salt);
		}
185

Peter Mount's avatar
Peter Mount committed
186 187 188 189 190
		// now send the auth packet
		switch(areq)
		  {
		  case AUTH_REQ_OK:
		    break;
191

Peter Mount's avatar
Peter Mount committed
192 193 194
		  case AUTH_REQ_KRB4:
		    DriverManager.println("postgresql: KRB4");
		    throw new PSQLException("postgresql.con.kerb4");
195

Peter Mount's avatar
Peter Mount committed
196 197 198
		  case AUTH_REQ_KRB5:
		    DriverManager.println("postgresql: KRB5");
		    throw new PSQLException("postgresql.con.kerb5");
199

Peter Mount's avatar
Peter Mount committed
200 201 202 203 204 205 206
		  case AUTH_REQ_PASSWORD:
		    DriverManager.println("postgresql: PASSWORD");
		    pg_stream.SendInteger(5+PG_PASSWORD.length(),4);
		    pg_stream.Send(PG_PASSWORD.getBytes());
		    pg_stream.SendInteger(0,1);
		    pg_stream.flush();
		    break;
207

Peter Mount's avatar
Peter Mount committed
208 209 210 211 212 213 214 215
		  case AUTH_REQ_CRYPT:
		    DriverManager.println("postgresql: CRYPT");
		    String crypted = UnixCrypt.crypt(salt,PG_PASSWORD);
		    pg_stream.SendInteger(5+crypted.length(),4);
		    pg_stream.Send(crypted.getBytes());
		    pg_stream.SendInteger(0,1);
		    pg_stream.flush();
		    break;
216

Peter Mount's avatar
Peter Mount committed
217 218 219 220
		  default:
		    throw new PSQLException("postgresql.con.auth",new Integer(areq));
		  }
		break;
221

Peter Mount's avatar
Peter Mount committed
222 223 224 225
	      default:
		throw new PSQLException("postgresql.con.authfail");
	      }
	    } while(areq != AUTH_REQ_OK);
226

Peter Mount's avatar
Peter Mount committed
227 228 229
	} catch (IOException e) {
	  throw new PSQLException("postgresql.con.failed",e);
	}
230

Peter Mount's avatar
Peter Mount committed
231 232 233 234 235 236 237 238 239 240

      // As of protocol version 2.0, we should now receive the cancellation key and the pid
      int beresp = pg_stream.ReceiveChar();
      switch(beresp) {
        case 'K':
          pid = pg_stream.ReceiveInteger(4);
          ckey = pg_stream.ReceiveInteger(4);
          break;
	case 'E':
	case 'N':
241
           throw new SQLException(pg_stream.ReceiveString
242
                                  (receive_sbuf, 4096, getEncoding()));
Peter Mount's avatar
Peter Mount committed
243 244 245 246 247 248 249 250 251 252 253
        default:
          throw new PSQLException("postgresql.con.setup");
      }

      // Expect ReadyForQuery packet
      beresp = pg_stream.ReceiveChar();
      switch(beresp) {
        case 'Z':
	   break;
	case 'E':
	case 'N':
254
           throw new SQLException(pg_stream.ReceiveString(receive_sbuf, 4096, getEncoding()));
Peter Mount's avatar
Peter Mount committed
255 256 257 258 259 260 261 262 263 264 265
        default:
          throw new PSQLException("postgresql.con.setup");
      }

      // Originally we issued a SHOW DATESTYLE statement to find the databases default
      // datestyle. However, this caused some problems with timestamps, so in 6.5, we
      // went the way of ODBC, and set the connection to ISO.
      //
      // This may cause some clients to break when they assume anything other than ISO,
      // but then - they should be using the proper methods ;-)
      //
266
      // We also ask the DB for certain properties (i.e. DatabaseEncoding at this time)
Peter Mount's avatar
Peter Mount committed
267 268
      //
      firstWarning = null;
269

270 271
      java.sql.ResultSet initrset = ExecSQL("set datestyle to 'ISO'; " +
        "select case when pg_encoding_to_char(1) = 'SQL_ASCII' then 'UNKNOWN' else getdatabaseencoding() end");
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

      String dbEncoding = null;
      //retrieve DB properties
      if(initrset.next()) {

        //handle DatabaseEncoding
        dbEncoding = initrset.getString(1);
        //convert from the PostgreSQL name to the Java name
        if (dbEncoding.equals("SQL_ASCII")) {
          dbEncoding = "ASCII";
        } else if (dbEncoding.equals("UNICODE")) {
          dbEncoding = "UTF8";
        } else if (dbEncoding.equals("LATIN1")) {
          dbEncoding = "ISO8859_1";
        } else if (dbEncoding.equals("LATIN2")) {
          dbEncoding = "ISO8859_2";
        } else if (dbEncoding.equals("LATIN3")) {
          dbEncoding = "ISO8859_3";
        } else if (dbEncoding.equals("LATIN4")) {
          dbEncoding = "ISO8859_4";
        } else if (dbEncoding.equals("LATIN5")) {
          dbEncoding = "ISO8859_5";
        } else if (dbEncoding.equals("LATIN6")) {
          dbEncoding = "ISO8859_6";
        } else if (dbEncoding.equals("LATIN7")) {
          dbEncoding = "ISO8859_7";
        } else if (dbEncoding.equals("LATIN8")) {
          dbEncoding = "ISO8859_8";
        } else if (dbEncoding.equals("LATIN9")) {
          dbEncoding = "ISO8859_9";
        } else if (dbEncoding.equals("EUC_JP")) {
          dbEncoding = "EUC_JP";
        } else if (dbEncoding.equals("EUC_CN")) {
          dbEncoding = "EUC_CN";
        } else if (dbEncoding.equals("EUC_KR")) {
          dbEncoding = "EUC_KR";
        } else if (dbEncoding.equals("EUC_TW")) {
          dbEncoding = "EUC_TW";
        } else if (dbEncoding.equals("KOI8")) {
311 312 313 314 315 316 317 318 319 320
	  // try first if KOI8_U is present, it's a superset of KOI8_R
	    try {
        	dbEncoding = "KOI8_U";
		"test".getBytes(dbEncoding);
	    }
	    catch(UnsupportedEncodingException uee) {
	    // well, KOI8_U is still not in standard JDK, falling back to KOI8_R :(
        	dbEncoding = "KOI8_R";
	    }

321 322
        } else if (dbEncoding.equals("WIN")) {
          dbEncoding = "Cp1252";
323 324 325 326 327
        } else if (dbEncoding.equals("UNKNOWN")) {
          //This isn't a multibyte database so we don't have an encoding to use
          //We leave dbEncoding null which will cause the default encoding for the
          //JVM to be used
          dbEncoding = null;
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
        } else {
          dbEncoding = null;
        }
      }


      //Set the encoding for this connection
      //Since the encoding could be specified or obtained from the DB we use the
      //following order:
      //  1.  passed as a property
      //  2.  value from DB if supported by current JVM
      //  3.  default for JVM (leave encoding null)
      String passedEncoding = info.getProperty("charSet");  // could be null

      if (passedEncoding != null) {
        encoding = passedEncoding;
      } else {
        if (dbEncoding != null) {
          //test DB encoding
          try {
            "TEST".getBytes(dbEncoding);
            //no error the encoding is supported by the current JVM
            encoding = dbEncoding;
          } catch (UnsupportedEncodingException uee) {
            //dbEncoding is not supported by the current JVM
            encoding = null;
          }
        } else {
          encoding = null;
        }
      }
359

Peter Mount's avatar
Peter Mount committed
360 361
      // Initialise object handling
      initObjectTypes();
362

Peter Mount's avatar
Peter Mount committed
363 364 365 366
      // Mark the connection as ok, and cleanup
      firstWarning = null;
      PG_STATUS = CONNECTION_OK;
    }
367

Peter Mount's avatar
Peter Mount committed
368 369 370
    // These methods used to be in the main Connection implementation. As they
    // are common to all implementations (JDBC1 or 2), they are placed here.
    // This should make it easy to maintain the two specifications.
371

Peter Mount's avatar
Peter Mount committed
372 373 374 375 376 377 378
    /**
     * This adds a warning to the warning chain.
     * @param msg message to add
     */
    public void addWarning(String msg)
    {
	DriverManager.println(msg);
379

Peter Mount's avatar
Peter Mount committed
380 381 382 383 384
	// Add the warning to the chain
	if(firstWarning!=null)
	    firstWarning.setNextWarning(new SQLWarning(msg));
	else
	    firstWarning = new SQLWarning(msg);
385

Peter Mount's avatar
Peter Mount committed
386
	// Now check for some specific messages
387

Peter Mount's avatar
Peter Mount committed
388 389 390 391 392 393 394
	// This is obsolete in 6.5, but I've left it in here so if we need to use this
	// technique again, we'll know where to place it.
	//
	// This is generated by the SQL "show datestyle"
	//if(msg.startsWith("NOTICE:") && msg.indexOf("DateStyle")>0) {
	//// 13 is the length off "DateStyle is "
	//msg = msg.substring(msg.indexOf("DateStyle is ")+13);
395
	//
Peter Mount's avatar
Peter Mount committed
396 397 398 399 400
	//for(int i=0;i<dateStyles.length;i+=2)
	//if(msg.startsWith(dateStyles[i]))
	//currentDateStyle=i+1; // this is the index of the format
	//}
    }
401

Peter Mount's avatar
Peter Mount committed
402 403 404 405 406 407 408 409 410 411 412
    /**
     * Send a query to the backend.  Returns one of the ResultSet
     * objects.
     *
     * <B>Note:</B> there does not seem to be any method currently
     * in existance to return the update count.
     *
     * @param sql the SQL statement to be executed
     * @return a ResultSet holding the results
     * @exception SQLException if a database error occurs
     */
413
    public java.sql.ResultSet ExecSQL(String sql) throws SQLException
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
    {
      return ExecSQL(sql,null);
    }

    /**
     * Send a query to the backend.  Returns one of the ResultSet
     * objects.
     *
     * <B>Note:</B> there does not seem to be any method currently
     * in existance to return the update count.
     *
     * @param sql the SQL statement to be executed
     * @param stat The Statement associated with this query (may be null)
     * @return a ResultSet holding the results
     * @exception SQLException if a database error occurs
     */
    public java.sql.ResultSet ExecSQL(String sql,java.sql.Statement stat) throws SQLException
Peter Mount's avatar
Peter Mount committed
431
    {
432 433 434 435 436
      // added Jan 30 2001 to correct maxrows per statement
      int maxrows=0;
      if(stat!=null)
        maxrows=stat.getMaxRows();

Peter Mount's avatar
Peter Mount committed
437 438
	// added Oct 7 1998 to give us thread safety.
	synchronized(pg_stream) {
439 440 441 442 443
 	    // Deallocate all resources in the stream associated
  	    // with a previous request.
  	    // This will let the driver reuse byte arrays that has already
  	    // been allocated instead of allocating new ones in order
  	    // to gain performance improvements.
444 445 446 447
  	    // PM 17/01/01: Commented out due to race bug. See comments in
            // PG_Stream
            //pg_stream.deallocate();

Peter Mount's avatar
Peter Mount committed
448 449
	    Field[] fields = null;
	    Vector tuples = new Vector();
450
	    byte[] buf = null;
Peter Mount's avatar
Peter Mount committed
451 452 453
	    int fqp = 0;
	    boolean hfr = false;
	    String recv_status = null, msg;
454 455
	    int update_count = 1;
	    int insert_oid = 0;
Peter Mount's avatar
Peter Mount committed
456
	    SQLException final_error = null;
457

458 459 460 461
	    // Commented out as the backend can now handle queries
	    // larger than 8K. Peter June 6 2000
	    //if (sql.length() > 8192)
	    //throw new PSQLException("postgresql.con.toolong",sql);
462 463 464 465 466 467 468 469 470 471 472 473

        if (getEncoding() == null)
            buf = sql.getBytes();
        else {
            try {
                buf = sql.getBytes(getEncoding());
            } catch (UnsupportedEncodingException unse) {
                 throw new PSQLException("postgresql.con.encoding",
                                        unse);
            }
        }

Peter Mount's avatar
Peter Mount committed
474 475 476
	    try
		{
		    pg_stream.SendChar('Q');
477
		    pg_stream.Send(buf);
Peter Mount's avatar
Peter Mount committed
478 479 480 481 482
		    pg_stream.SendChar(0);
		    pg_stream.flush();
		} catch (IOException e) {
		    throw new PSQLException("postgresql.con.ioerror",e);
		}
483

Peter Mount's avatar
Peter Mount committed
484 485 486
	    while (!hfr || fqp > 0)
		{
		    Object tup=null;	// holds rows as they are recieved
487

Peter Mount's avatar
Peter Mount committed
488
		    int c = pg_stream.ReceiveChar();
489

Peter Mount's avatar
Peter Mount committed
490 491 492 493
		    switch (c)
			{
			case 'A':	// Asynchronous Notify
			    pid = pg_stream.ReceiveInteger(4);
494
			    msg = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
Peter Mount's avatar
Peter Mount committed
495 496 497 498 499 500 501 502 503 504
			    break;
			case 'B':	// Binary Data Transfer
			    if (fields == null)
				throw new PSQLException("postgresql.con.tuple");
			    tup = pg_stream.ReceiveTuple(fields.length, true);
			    // This implements Statement.setMaxRows()
			    if(maxrows==0 || tuples.size()<maxrows)
				tuples.addElement(tup);
			    break;
			case 'C':	// Command Status
505
			    recv_status = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
506

Peter Mount's avatar
Peter Mount committed
507
				// Now handle the update count correctly.
508
				if(recv_status.startsWith("INSERT") || recv_status.startsWith("UPDATE") || recv_status.startsWith("DELETE")) {
Peter Mount's avatar
Peter Mount committed
509 510 511 512 513
					try {
						update_count = Integer.parseInt(recv_status.substring(1+recv_status.lastIndexOf(' ')));
					} catch(NumberFormatException nfe) {
						throw new PSQLException("postgresql.con.fathom",recv_status);
					}
514 515 516 517 518 519 520
					if(recv_status.startsWith("INSERT")) {
					    try {
						insert_oid = Integer.parseInt(recv_status.substring(1+recv_status.indexOf(' '),recv_status.lastIndexOf(' ')));
					    } catch(NumberFormatException nfe) {
						throw new PSQLException("postgresql.con.fathom",recv_status);
					    }
					}
Peter Mount's avatar
Peter Mount committed
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
				}
			    if (fields != null)
				hfr = true;
			    else
				{
				    try
					{
					    pg_stream.SendChar('Q');
					    pg_stream.SendChar(' ');
					    pg_stream.SendChar(0);
					    pg_stream.flush();
					} catch (IOException e) {
					    throw new PSQLException("postgresql.con.ioerror",e);
					}
				    fqp++;
				}
			    break;
			case 'D':	// Text Data Transfer
			    if (fields == null)
				throw new PSQLException("postgresql.con.tuple");
			    tup = pg_stream.ReceiveTuple(fields.length, false);
			    // This implements Statement.setMaxRows()
			    if(maxrows==0 || tuples.size()<maxrows)
				tuples.addElement(tup);
			    break;
			case 'E':	// Error Message
547
			    msg = pg_stream.ReceiveString(receive_sbuf,4096,getEncoding());
Peter Mount's avatar
Peter Mount committed
548 549 550 551 552
			    final_error = new SQLException(msg);
			    hfr = true;
			    break;
			case 'I':	// Empty Query
			    int t = pg_stream.ReceiveChar();
553

Peter Mount's avatar
Peter Mount committed
554 555 556 557 558 559 560 561
			    if (t != 0)
				throw new PSQLException("postgresql.con.garbled");
			    if (fqp > 0)
				fqp--;
			    if (fqp == 0)
				hfr = true;
			    break;
			case 'N':	// Error Notification
562
			    addWarning(pg_stream.ReceiveString(receive_sbuf,4096,getEncoding()));
Peter Mount's avatar
Peter Mount committed
563 564
			    break;
			case 'P':	// Portal Name
565
			    String pname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
Peter Mount's avatar
Peter Mount committed
566 567 568 569 570 571 572 573 574 575 576 577 578 579
			    break;
			case 'T':	// MetaData Field Description
			    if (fields != null)
				throw new PSQLException("postgresql.con.multres");
			    fields = ReceiveFields();
			    break;
			case 'Z':       // backend ready for query, ignore for now :-)
			    break;
			default:
			    throw new PSQLException("postgresql.con.type",new Character((char)c));
			}
		}
	    if (final_error != null)
		throw final_error;
580

581
	    return getResultSet(this, stat, fields, tuples, recv_status, update_count, insert_oid);
Peter Mount's avatar
Peter Mount committed
582 583 584 585 586 587 588 589 590 591 592 593 594
	}
    }

    /**
     * Receive the field descriptions from the back end
     *
     * @return an array of the Field object describing the fields
     * @exception SQLException if a database error occurs
     */
    private Field[] ReceiveFields() throws SQLException
    {
	int nf = pg_stream.ReceiveIntegerR(2), i;
	Field[] fields = new Field[nf];
595

Peter Mount's avatar
Peter Mount committed
596 597
	for (i = 0 ; i < nf ; ++i)
	    {
598
		String typname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
Peter Mount's avatar
Peter Mount committed
599 600 601 602 603 604 605
		int typid = pg_stream.ReceiveIntegerR(4);
		int typlen = pg_stream.ReceiveIntegerR(2);
		int typmod = pg_stream.ReceiveIntegerR(4);
		fields[i] = new Field(this, typname, typid, typlen, typmod);
	    }
	return fields;
    }
606

Peter Mount's avatar
Peter Mount committed
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
    /**
     * In SQL, a result table can be retrieved through a cursor that
     * is named.  The current row of a result can be updated or deleted
     * using a positioned update/delete statement that references the
     * cursor name.
     *
     * We support one cursor per connection.
     *
     * setCursorName sets the cursor name.
     *
     * @param cursor the cursor name
     * @exception SQLException if a database access error occurs
     */
    public void setCursorName(String cursor) throws SQLException
    {
	this.cursor = cursor;
    }
624

Peter Mount's avatar
Peter Mount committed
625 626 627 628 629 630 631 632 633 634
    /**
     * getCursorName gets the cursor name.
     *
     * @return the current cursor name
     * @exception SQLException if a database access error occurs
     */
    public String getCursorName() throws SQLException
    {
	return cursor;
    }
635

Peter Mount's avatar
Peter Mount committed
636 637 638 639 640 641 642 643 644 645 646 647 648
    /**
     * We are required to bring back certain information by
     * the DatabaseMetaData class.  These functions do that.
     *
     * Method getURL() brings back the URL (good job we saved it)
     *
     * @return the url
     * @exception SQLException just in case...
     */
    public String getURL() throws SQLException
    {
	return this_url;
    }
649

Peter Mount's avatar
Peter Mount committed
650 651 652 653 654 655 656 657 658 659 660
    /**
     * Method getUserName() brings back the User Name (again, we
     * saved it)
     *
     * @return the user name
     * @exception SQLException just in case...
     */
    public String getUserName() throws SQLException
    {
	return PG_USER;
    }
661 662 663

    /**
     *  Get the character encoding to use for this connection.
664
     *  @return the encoding to use, or <b>null</b> for the
665 666 667 668 669
     *  default encoding.
     */
    public String getEncoding() throws SQLException {
        return encoding;
    }
670

Peter Mount's avatar
Peter Mount committed
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
    /**
     * This returns the Fastpath API for the current connection.
     *
     * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
     * functions on the org.postgresql backend itself.
     *
     * <p>It is primarily used by the LargeObject API
     *
     * <p>The best way to use this is as follows:
     *
     * <p><pre>
     * import org.postgresql.fastpath.*;
     * ...
     * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI();
     * </pre>
     *
     * <p>where myconn is an open Connection to org.postgresql.
     *
     * @return Fastpath object allowing access to functions on the org.postgresql
     * backend.
     * @exception SQLException by Fastpath when initialising for first time
     */
    public Fastpath getFastpathAPI() throws SQLException
    {
	if(fastpath==null)
	    fastpath = new Fastpath(this,pg_stream);
	return fastpath;
    }
699

Peter Mount's avatar
Peter Mount committed
700 701
    // This holds a reference to the Fastpath API if already open
    private Fastpath fastpath = null;
702

Peter Mount's avatar
Peter Mount committed
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
    /**
     * This returns the LargeObject API for the current connection.
     *
     * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
     * functions on the org.postgresql backend itself.
     *
     * <p>The best way to use this is as follows:
     *
     * <p><pre>
     * import org.postgresql.largeobject.*;
     * ...
     * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI();
     * </pre>
     *
     * <p>where myconn is an open Connection to org.postgresql.
     *
     * @return LargeObject object that implements the API
     * @exception SQLException by LargeObject when initialising for first time
     */
    public LargeObjectManager getLargeObjectAPI() throws SQLException
    {
	if(largeobject==null)
	    largeobject = new LargeObjectManager(this);
	return largeobject;
    }
728

Peter Mount's avatar
Peter Mount committed
729 730
    // This holds a reference to the LargeObject API if already open
    private LargeObjectManager largeobject = null;
731

Peter Mount's avatar
Peter Mount committed
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
    /**
     * This method is used internally to return an object based around
     * org.postgresql's more unique data types.
     *
     * <p>It uses an internal Hashtable to get the handling class. If the
     * type is not supported, then an instance of org.postgresql.util.PGobject
     * is returned.
     *
     * You can use the getValue() or setValue() methods to handle the returned
     * object. Custom objects can have their own methods.
     *
     * In 6.4, this is extended to use the org.postgresql.util.Serialize class to
     * allow the Serialization of Java Objects into the database without using
     * Blobs. Refer to that class for details on how this new feature works.
     *
     * @return PGobject for this type, and set to value
     * @exception SQLException if value is not correct for this type
     * @see org.postgresql.util.Serialize
     */
    public Object getObject(String type,String value) throws SQLException
    {
	try {
	    Object o = objectTypes.get(type);
755

Peter Mount's avatar
Peter Mount committed
756 757 758 759 760 761 762 763
	    // If o is null, then the type is unknown, so check to see if type
	    // is an actual table name. If it does, see if a Class is known that
	    // can handle it
	    if(o == null) {
		Serialize ser = new Serialize(this,type);
		objectTypes.put(type,ser);
		return ser.fetch(Integer.parseInt(value));
	    }
764

Peter Mount's avatar
Peter Mount committed
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
	    // If o is not null, and it is a String, then its a class name that
	    // extends PGobject.
	    //
	    // This is used to implement the org.postgresql unique types (like lseg,
	    // point, etc).
	    if(o instanceof String) {
		// 6.3 style extending PG_Object
		PGobject obj = null;
		obj = (PGobject)(Class.forName((String)o).newInstance());
		obj.setType(type);
		obj.setValue(value);
		return (Object)obj;
	    } else {
		// If it's an object, it should be an instance of our Serialize class
		// If so, then call it's fetch method.
		if(o instanceof Serialize)
		    return ((Serialize)o).fetch(Integer.parseInt(value));
	    }
	} catch(SQLException sx) {
	    // rethrow the exception. Done because we capture any others next
	    sx.fillInStackTrace();
	    throw sx;
	} catch(Exception ex) {
	    throw new PSQLException("postgresql.con.creobj",type,ex);
	}
790

Peter Mount's avatar
Peter Mount committed
791 792 793
	// should never be reached
	return null;
    }
794

Peter Mount's avatar
Peter Mount committed
795 796 797 798 799 800 801 802 803 804 805 806
    /**
     * This stores an object into the database.
     * @param o Object to store
     * @return OID of the new rectord
     * @exception SQLException if value is not correct for this type
     * @see org.postgresql.util.Serialize
     */
    public int putObject(Object o) throws SQLException
    {
	try {
	    String type = o.getClass().getName();
	    Object x = objectTypes.get(type);
807

Peter Mount's avatar
Peter Mount committed
808 809 810 811 812 813 814 815
	    // If x is null, then the type is unknown, so check to see if type
	    // is an actual table name. If it does, see if a Class is known that
	    // can handle it
	    if(x == null) {
		Serialize ser = new Serialize(this,type);
		objectTypes.put(type,ser);
		return ser.store(o);
	    }
816

Peter Mount's avatar
Peter Mount committed
817 818 819 820
	    // If it's an object, it should be an instance of our Serialize class
	    // If so, then call it's fetch method.
	    if(x instanceof Serialize)
		return ((Serialize)x).store(o);
821

Peter Mount's avatar
Peter Mount committed
822 823
	    // Thow an exception because the type is unknown
	    throw new PSQLException("postgresql.con.strobj");
824

Peter Mount's avatar
Peter Mount committed
825 826 827 828 829 830 831 832
	} catch(SQLException sx) {
	    // rethrow the exception. Done because we capture any others next
	    sx.fillInStackTrace();
	    throw sx;
	} catch(Exception ex) {
	    throw new PSQLException("postgresql.con.strobjex",ex);
	}
    }
833

Peter Mount's avatar
Peter Mount committed
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
    /**
     * This allows client code to add a handler for one of org.postgresql's
     * more unique data types.
     *
     * <p><b>NOTE:</b> This is not part of JDBC, but an extension.
     *
     * <p>The best way to use this is as follows:
     *
     * <p><pre>
     * ...
     * ((org.postgresql.Connection)myconn).addDataType("mytype","my.class.name");
     * ...
     * </pre>
     *
     * <p>where myconn is an open Connection to org.postgresql.
     *
     * <p>The handling class must extend org.postgresql.util.PGobject
     *
     * @see org.postgresql.util.PGobject
     */
    public void addDataType(String type,String name)
    {
	objectTypes.put(type,name);
    }
858

Peter Mount's avatar
Peter Mount committed
859 860
    // This holds the available types
    private Hashtable objectTypes = new Hashtable();
861

Peter Mount's avatar
Peter Mount committed
862 863 864 865 866 867
    // This array contains the types that are supported as standard.
    //
    // The first entry is the types name on the database, the second
    // the full class name of the handling class.
    //
    private static final String defaultObjectTypes[][] = {
868 869 870 871 872 873 874 875
	{"box",		"org.postgresql.geometric.PGbox"},
	{"circle",	"org.postgresql.geometric.PGcircle"},
	{"line",	"org.postgresql.geometric.PGline"},
	{"lseg",	"org.postgresql.geometric.PGlseg"},
	{"path",	"org.postgresql.geometric.PGpath"},
	{"point",	"org.postgresql.geometric.PGpoint"},
	{"polygon",	"org.postgresql.geometric.PGpolygon"},
	{"money",	"org.postgresql.util.PGmoney"}
Peter Mount's avatar
Peter Mount committed
876
    };
877

Peter Mount's avatar
Peter Mount committed
878 879 880 881 882 883
    // This initialises the objectTypes hashtable
    private void initObjectTypes()
    {
	for(int i=0;i<defaultObjectTypes.length;i++)
	    objectTypes.put(defaultObjectTypes[i][0],defaultObjectTypes[i][1]);
    }
884

Peter Mount's avatar
Peter Mount committed
885 886
    // These are required by other common classes
    public abstract java.sql.Statement createStatement() throws SQLException;
887

Peter Mount's avatar
Peter Mount committed
888 889 890 891
    /**
     * This returns a resultset. It must be overridden, so that the correct
     * version (from jdbc1 or jdbc2) are returned.
     */
892
    protected abstract java.sql.ResultSet getResultSet(org.postgresql.Connection conn,java.sql.Statement stat, Field[] fields, Vector tuples, String status, int updateCount,int insertOID) throws SQLException;
893

Peter Mount's avatar
Peter Mount committed
894
    public abstract void close() throws SQLException;
895

Peter Mount's avatar
Peter Mount committed
896 897 898 899 900 901 902 903 904 905 906 907
    /**
     * Overides finalize(). If called, it closes the connection.
     *
     * This was done at the request of Rachel Greenham
     * <rachel@enlarion.demon.co.uk> who hit a problem where multiple
     * clients didn't close the connection, and once a fortnight enough
     * clients were open to kill the org.postgres server.
     */
    public void finalize() throws Throwable
    {
	close();
    }
908

Peter Mount's avatar
Peter Mount committed
909 910 911 912
    /**
     * This is an attempt to implement SQL Escape clauses
     */
    public String EscapeSQL(String sql) {
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
      //if (DEBUG) { System.out.println ("parseSQLEscapes called"); }

      // If we find a "{d", assume we have a date escape.
      //
      // Since the date escape syntax is very close to the
      // native Postgres date format, we just remove the escape
      // delimiters.
      //
      // This implementation could use some optimization, but it has
      // worked in practice for two years of solid use.
      int index = sql.indexOf("{d");
      while (index != -1) {
        //System.out.println ("escape found at index: " + index);
        StringBuffer buf = new StringBuffer(sql);
        buf.setCharAt(index, ' ');
        buf.setCharAt(index + 1, ' ');
        buf.setCharAt(sql.indexOf('}', index), ' ');
        sql = new String(buf);
        index = sql.indexOf("{d");
      }
      //System.out.println ("modified SQL: " + sql);
      return sql;
Peter Mount's avatar
Peter Mount committed
935
    }
936

Peter Mount's avatar
Peter Mount committed
937
}