Connection.java 28.8 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.12 2001/01/18 14:50:14 peter 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 25
  // This is set by org.postgresql.Statement.setMaxRows()
  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 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 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
      java.sql.ResultSet initrset = ExecSQL("set datestyle to 'ISO'; select getdatabaseencoding()");

      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")) {
          dbEncoding = "KOI8_R";
        } else if (dbEncoding.equals("WIN")) {
          dbEncoding = "Cp1252";
        } 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;
        }
      }
344

Peter Mount's avatar
Peter Mount committed
345 346
      // Initialise object handling
      initObjectTypes();
347

Peter Mount's avatar
Peter Mount committed
348 349 350 351
      // Mark the connection as ok, and cleanup
      firstWarning = null;
      PG_STATUS = CONNECTION_OK;
    }
352

Peter Mount's avatar
Peter Mount committed
353 354 355
    // 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.
356

Peter Mount's avatar
Peter Mount committed
357 358 359 360 361 362 363
    /**
     * This adds a warning to the warning chain.
     * @param msg message to add
     */
    public void addWarning(String msg)
    {
	DriverManager.println(msg);
364

Peter Mount's avatar
Peter Mount committed
365 366 367 368 369
	// Add the warning to the chain
	if(firstWarning!=null)
	    firstWarning.setNextWarning(new SQLWarning(msg));
	else
	    firstWarning = new SQLWarning(msg);
370

Peter Mount's avatar
Peter Mount committed
371
	// Now check for some specific messages
372

Peter Mount's avatar
Peter Mount committed
373 374 375 376 377 378 379
	// 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);
380
	//
Peter Mount's avatar
Peter Mount committed
381 382 383 384 385
	//for(int i=0;i<dateStyles.length;i+=2)
	//if(msg.startsWith(dateStyles[i]))
	//currentDateStyle=i+1; // this is the index of the format
	//}
    }
386

Peter Mount's avatar
Peter Mount committed
387 388 389 390 391 392 393 394 395 396 397
    /**
     * 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
     */
398
    public java.sql.ResultSet ExecSQL(String sql) throws SQLException
Peter Mount's avatar
Peter Mount committed
399 400 401
    {
	// added Oct 7 1998 to give us thread safety.
	synchronized(pg_stream) {
402 403 404 405 406
 	    // 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.
407 408 409 410
  	    // 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
411 412
	    Field[] fields = null;
	    Vector tuples = new Vector();
413
	    byte[] buf = null;
Peter Mount's avatar
Peter Mount committed
414 415 416
	    int fqp = 0;
	    boolean hfr = false;
	    String recv_status = null, msg;
417 418
	    int update_count = 1;
	    int insert_oid = 0;
Peter Mount's avatar
Peter Mount committed
419
	    SQLException final_error = null;
420

421 422 423 424
	    // 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);
425 426 427 428 429 430 431 432 433 434 435 436

        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
437 438 439
	    try
		{
		    pg_stream.SendChar('Q');
440
		    pg_stream.Send(buf);
Peter Mount's avatar
Peter Mount committed
441 442 443 444 445
		    pg_stream.SendChar(0);
		    pg_stream.flush();
		} catch (IOException e) {
		    throw new PSQLException("postgresql.con.ioerror",e);
		}
446

Peter Mount's avatar
Peter Mount committed
447 448 449
	    while (!hfr || fqp > 0)
		{
		    Object tup=null;	// holds rows as they are recieved
450

Peter Mount's avatar
Peter Mount committed
451
		    int c = pg_stream.ReceiveChar();
452

Peter Mount's avatar
Peter Mount committed
453 454 455 456
		    switch (c)
			{
			case 'A':	// Asynchronous Notify
			    pid = pg_stream.ReceiveInteger(4);
457
			    msg = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
Peter Mount's avatar
Peter Mount committed
458 459 460 461 462 463 464 465 466 467
			    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
468
			    recv_status = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
469

Peter Mount's avatar
Peter Mount committed
470
				// Now handle the update count correctly.
471
				if(recv_status.startsWith("INSERT") || recv_status.startsWith("UPDATE") || recv_status.startsWith("DELETE")) {
Peter Mount's avatar
Peter Mount committed
472 473 474 475 476
					try {
						update_count = Integer.parseInt(recv_status.substring(1+recv_status.lastIndexOf(' ')));
					} catch(NumberFormatException nfe) {
						throw new PSQLException("postgresql.con.fathom",recv_status);
					}
477 478 479 480 481 482 483
					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
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
				}
			    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
510
			    msg = pg_stream.ReceiveString(receive_sbuf,4096,getEncoding());
Peter Mount's avatar
Peter Mount committed
511 512 513 514 515
			    final_error = new SQLException(msg);
			    hfr = true;
			    break;
			case 'I':	// Empty Query
			    int t = pg_stream.ReceiveChar();
516

Peter Mount's avatar
Peter Mount committed
517 518 519 520 521 522 523 524
			    if (t != 0)
				throw new PSQLException("postgresql.con.garbled");
			    if (fqp > 0)
				fqp--;
			    if (fqp == 0)
				hfr = true;
			    break;
			case 'N':	// Error Notification
525
			    addWarning(pg_stream.ReceiveString(receive_sbuf,4096,getEncoding()));
Peter Mount's avatar
Peter Mount committed
526 527
			    break;
			case 'P':	// Portal Name
528
			    String pname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
Peter Mount's avatar
Peter Mount committed
529 530 531 532 533 534 535 536 537 538 539 540 541 542
			    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;
543

544
	    return getResultSet(this, fields, tuples, recv_status, update_count, insert_oid);
Peter Mount's avatar
Peter Mount committed
545 546 547 548 549 550 551 552 553 554 555 556 557
	}
    }

    /**
     * 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];
558

Peter Mount's avatar
Peter Mount committed
559 560
	for (i = 0 ; i < nf ; ++i)
	    {
561
		String typname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
Peter Mount's avatar
Peter Mount committed
562 563 564 565 566 567 568
		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;
    }
569

Peter Mount's avatar
Peter Mount committed
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
    /**
     * 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;
    }
587

Peter Mount's avatar
Peter Mount committed
588 589 590 591 592 593 594 595 596 597
    /**
     * 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;
    }
598

Peter Mount's avatar
Peter Mount committed
599 600 601 602 603 604 605 606 607 608 609 610 611
    /**
     * 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;
    }
612

Peter Mount's avatar
Peter Mount committed
613 614 615 616 617 618 619 620 621 622 623
    /**
     * 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;
    }
624 625 626

    /**
     *  Get the character encoding to use for this connection.
627
     *  @return the encoding to use, or <b>null</b> for the
628 629 630 631 632
     *  default encoding.
     */
    public String getEncoding() throws SQLException {
        return encoding;
    }
633

Peter Mount's avatar
Peter Mount committed
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
    /**
     * 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;
    }
662

Peter Mount's avatar
Peter Mount committed
663 664
    // This holds a reference to the Fastpath API if already open
    private Fastpath fastpath = null;
665

Peter Mount's avatar
Peter Mount committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
    /**
     * 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;
    }
691

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

Peter Mount's avatar
Peter Mount committed
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
    /**
     * 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);
718

Peter Mount's avatar
Peter Mount committed
719 720 721 722 723 724 725 726
	    // 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));
	    }
727

Peter Mount's avatar
Peter Mount committed
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
	    // 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);
	}
753

Peter Mount's avatar
Peter Mount committed
754 755 756
	// should never be reached
	return null;
    }
757

Peter Mount's avatar
Peter Mount committed
758 759 760 761 762 763 764 765 766 767 768 769
    /**
     * 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);
770

Peter Mount's avatar
Peter Mount committed
771 772 773 774 775 776 777 778
	    // 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);
	    }
779

Peter Mount's avatar
Peter Mount committed
780 781 782 783
	    // 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);
784

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

Peter Mount's avatar
Peter Mount committed
788 789 790 791 792 793 794 795
	} 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);
	}
    }
796

Peter Mount's avatar
Peter Mount committed
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
    /**
     * 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);
    }
821

Peter Mount's avatar
Peter Mount committed
822 823
    // This holds the available types
    private Hashtable objectTypes = new Hashtable();
824

Peter Mount's avatar
Peter Mount committed
825 826 827 828 829 830
    // 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[][] = {
831 832 833 834 835 836 837 838
	{"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
839
    };
840

Peter Mount's avatar
Peter Mount committed
841 842 843 844 845 846
    // This initialises the objectTypes hashtable
    private void initObjectTypes()
    {
	for(int i=0;i<defaultObjectTypes.length;i++)
	    objectTypes.put(defaultObjectTypes[i][0],defaultObjectTypes[i][1]);
    }
847

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

Peter Mount's avatar
Peter Mount committed
851 852 853 854
    /**
     * This returns a resultset. It must be overridden, so that the correct
     * version (from jdbc1 or jdbc2) are returned.
     */
855
    protected abstract java.sql.ResultSet getResultSet(org.postgresql.Connection conn, Field[] fields, Vector tuples, String status, int updateCount,int insertOID) throws SQLException;
856

Peter Mount's avatar
Peter Mount committed
857
    public abstract void close() throws SQLException;
858

Peter Mount's avatar
Peter Mount committed
859 860 861 862 863 864 865 866 867 868 869 870
    /**
     * 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();
    }
871

Peter Mount's avatar
Peter Mount committed
872 873 874 875
    /**
     * This is an attempt to implement SQL Escape clauses
     */
    public String EscapeSQL(String sql) {
876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
      //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
898
    }
899

Peter Mount's avatar
Peter Mount committed
900
}