]> granicus.if.org Git - postgresql/blob - src/interfaces/jdbc/org/postgresql/Connection.java
Tue Jan 30 22:24:00 GMT 2001 peter@retep.org.uk
[postgresql] / src / interfaces / jdbc / org / postgresql / Connection.java
1 package org.postgresql;
2
3 import java.io.*;
4 import java.net.*;
5 import java.sql.*;
6 import java.util.*;
7 import org.postgresql.Field;
8 import org.postgresql.fastpath.*;
9 import org.postgresql.largeobject.*;
10 import org.postgresql.util.*;
11
12 /**
13  * $Id: Connection.java,v 1.14 2001/01/31 08:26:01 peter Exp $
14  *
15  * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or
16  * JDBC2 versions of the Connection class.
17  *
18  */
19 public abstract class Connection
20 {
21   // This is the network stream associated with this connection
22   public PG_Stream pg_stream;
23
24   // This is set by org.postgresql.Statement.setMaxRows()
25   //public int maxrows = 0;             // maximum no. of rows; 0 = unlimited
26
27   private String PG_HOST;
28   private int PG_PORT;
29   private String PG_USER;
30   private String PG_PASSWORD;
31   private String PG_DATABASE;
32   private boolean PG_STATUS;
33
34   /**
35    *  The encoding to use for this connection.
36    *  If <b>null</b>, the encoding has not been specified by the
37    *  user, and the default encoding for the platform should be
38    *  used.
39    */
40   private String encoding;
41
42   public boolean CONNECTION_OK = true;
43   public boolean CONNECTION_BAD = false;
44
45   public boolean autoCommit = true;
46   public boolean readOnly = false;
47
48   public Driver this_driver;
49   private String this_url;
50   private String cursor = null; // The positioned update cursor name
51
52   // These are new for v6.3, they determine the current protocol versions
53   // supported by this version of the driver. They are defined in
54   // src/include/libpq/pqcomm.h
55   protected static final int PG_PROTOCOL_LATEST_MAJOR = 2;
56   protected static final int PG_PROTOCOL_LATEST_MINOR = 0;
57   private static final int SM_DATABASE  = 64;
58   private static final int SM_USER      = 32;
59   private static final int SM_OPTIONS   = 64;
60   private static final int SM_UNUSED    = 64;
61   private static final int SM_TTY       = 64;
62
63   private static final int AUTH_REQ_OK       = 0;
64   private static final int AUTH_REQ_KRB4     = 1;
65   private static final int AUTH_REQ_KRB5     = 2;
66   private static final int AUTH_REQ_PASSWORD = 3;
67   private static final int AUTH_REQ_CRYPT    = 4;
68
69   // New for 6.3, salt value for crypt authorisation
70   private String salt;
71
72   // This is used by Field to cache oid -> names.
73   // It's here, because it's shared across this connection only.
74   // Hence it cannot be static within the Field class, because it would then
75   // be across all connections, which could be to different backends.
76   public Hashtable fieldCache = new Hashtable();
77
78   // Now handle notices as warnings, so things like "show" now work
79   public SQLWarning firstWarning = null;
80
81     // The PID an cancellation key we get from the backend process
82     public int pid;
83     public int ckey;
84
85     // This receive_sbuf should be used by the different methods
86     // that call pg_stream.ReceiveString() in this Connection, so
87     // so we avoid uneccesary new allocations.
88     byte receive_sbuf[] = new byte[8192];
89
90     /**
91      * This is called by Class.forName() from within org.postgresql.Driver
92      */
93     public Connection()
94     {
95     }
96
97     /**
98      * This method actually opens the connection. It is called by Driver.
99      *
100      * @param host the hostname of the database back end
101      * @param port the port number of the postmaster process
102      * @param info a Properties[] thing of the user and password
103      * @param database the database to connect to
104      * @param u the URL of the connection
105      * @param d the Driver instantation of the connection
106      * @return a valid connection profile
107      * @exception SQLException if a database access error occurs
108      */
109     protected void openConnection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException
110     {
111     // Throw an exception if the user or password properties are missing
112     // This occasionally occurs when the client uses the properties version
113     // of getConnection(), and is a common question on the email lists
114     if(info.getProperty("user")==null)
115       throw new PSQLException("postgresql.con.user");
116     if(info.getProperty("password")==null)
117       throw new PSQLException("postgresql.con.pass");
118
119     this_driver = d;
120     this_url = url;
121     PG_DATABASE = database;
122     PG_PASSWORD = info.getProperty("password");
123     PG_USER = info.getProperty("user");
124     PG_PORT = port;
125     PG_HOST = host;
126     PG_STATUS = CONNECTION_BAD;
127
128     // Now make the initial connection
129     try
130       {
131         pg_stream = new PG_Stream(host, port);
132       } catch (ConnectException cex) {
133         // Added by Peter Mount <peter@retep.org.uk>
134         // ConnectException is thrown when the connection cannot be made.
135         // we trap this an return a more meaningful message for the end user
136         throw new PSQLException ("postgresql.con.refused");
137       } catch (IOException e) {
138         throw new PSQLException ("postgresql.con.failed",e);
139       }
140
141       // Now we need to construct and send a startup packet
142       try
143         {
144           // Ver 6.3 code
145           pg_stream.SendInteger(4+4+SM_DATABASE+SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY,4);
146           pg_stream.SendInteger(PG_PROTOCOL_LATEST_MAJOR,2);
147           pg_stream.SendInteger(PG_PROTOCOL_LATEST_MINOR,2);
148           pg_stream.Send(database.getBytes(),SM_DATABASE);
149
150           // This last send includes the unused fields
151           pg_stream.Send(PG_USER.getBytes(),SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY);
152
153           // now flush the startup packets to the backend
154           pg_stream.flush();
155
156           // Now get the response from the backend, either an error message
157           // or an authentication request
158           int areq = -1; // must have a value here
159           do {
160             int beresp = pg_stream.ReceiveChar();
161             switch(beresp)
162               {
163               case 'E':
164                 // An error occured, so pass the error message to the
165                 // user.
166                 //
167                 // The most common one to be thrown here is:
168                 // "User authentication failed"
169                 //
170                 throw new SQLException(pg_stream.ReceiveString
171                                        (receive_sbuf, 4096, getEncoding()));
172
173               case 'R':
174                 // Get the type of request
175                 areq = pg_stream.ReceiveIntegerR(4);
176
177                 // Get the password salt if there is one
178                 if(areq == AUTH_REQ_CRYPT) {
179                   byte[] rst = new byte[2];
180                   rst[0] = (byte)pg_stream.ReceiveChar();
181                   rst[1] = (byte)pg_stream.ReceiveChar();
182                   salt = new String(rst,0,2);
183                   DriverManager.println("Salt="+salt);
184                 }
185
186                 // now send the auth packet
187                 switch(areq)
188                   {
189                   case AUTH_REQ_OK:
190                     break;
191
192                   case AUTH_REQ_KRB4:
193                     DriverManager.println("postgresql: KRB4");
194                     throw new PSQLException("postgresql.con.kerb4");
195
196                   case AUTH_REQ_KRB5:
197                     DriverManager.println("postgresql: KRB5");
198                     throw new PSQLException("postgresql.con.kerb5");
199
200                   case AUTH_REQ_PASSWORD:
201                     DriverManager.println("postgresql: PASSWORD");
202                     pg_stream.SendInteger(5+PG_PASSWORD.length(),4);
203                     pg_stream.Send(PG_PASSWORD.getBytes());
204                     pg_stream.SendInteger(0,1);
205                     pg_stream.flush();
206                     break;
207
208                   case AUTH_REQ_CRYPT:
209                     DriverManager.println("postgresql: CRYPT");
210                     String crypted = UnixCrypt.crypt(salt,PG_PASSWORD);
211                     pg_stream.SendInteger(5+crypted.length(),4);
212                     pg_stream.Send(crypted.getBytes());
213                     pg_stream.SendInteger(0,1);
214                     pg_stream.flush();
215                     break;
216
217                   default:
218                     throw new PSQLException("postgresql.con.auth",new Integer(areq));
219                   }
220                 break;
221
222               default:
223                 throw new PSQLException("postgresql.con.authfail");
224               }
225             } while(areq != AUTH_REQ_OK);
226
227         } catch (IOException e) {
228           throw new PSQLException("postgresql.con.failed",e);
229         }
230
231
232       // As of protocol version 2.0, we should now receive the cancellation key and the pid
233       int beresp = pg_stream.ReceiveChar();
234       switch(beresp) {
235         case 'K':
236           pid = pg_stream.ReceiveInteger(4);
237           ckey = pg_stream.ReceiveInteger(4);
238           break;
239         case 'E':
240         case 'N':
241            throw new SQLException(pg_stream.ReceiveString
242                                   (receive_sbuf, 4096, getEncoding()));
243         default:
244           throw new PSQLException("postgresql.con.setup");
245       }
246
247       // Expect ReadyForQuery packet
248       beresp = pg_stream.ReceiveChar();
249       switch(beresp) {
250         case 'Z':
251            break;
252         case 'E':
253         case 'N':
254            throw new SQLException(pg_stream.ReceiveString(receive_sbuf, 4096, getEncoding()));
255         default:
256           throw new PSQLException("postgresql.con.setup");
257       }
258
259       // Originally we issued a SHOW DATESTYLE statement to find the databases default
260       // datestyle. However, this caused some problems with timestamps, so in 6.5, we
261       // went the way of ODBC, and set the connection to ISO.
262       //
263       // This may cause some clients to break when they assume anything other than ISO,
264       // but then - they should be using the proper methods ;-)
265       //
266       // We also ask the DB for certain properties (i.e. DatabaseEncoding at this time)
267       //
268       firstWarning = null;
269
270       java.sql.ResultSet initrset = ExecSQL("set datestyle to 'ISO'; select getdatabaseencoding()");
271
272       String dbEncoding = null;
273       //retrieve DB properties
274       if(initrset.next()) {
275
276         //handle DatabaseEncoding
277         dbEncoding = initrset.getString(1);
278         //convert from the PostgreSQL name to the Java name
279         if (dbEncoding.equals("SQL_ASCII")) {
280           dbEncoding = "ASCII";
281         } else if (dbEncoding.equals("UNICODE")) {
282           dbEncoding = "UTF8";
283         } else if (dbEncoding.equals("LATIN1")) {
284           dbEncoding = "ISO8859_1";
285         } else if (dbEncoding.equals("LATIN2")) {
286           dbEncoding = "ISO8859_2";
287         } else if (dbEncoding.equals("LATIN3")) {
288           dbEncoding = "ISO8859_3";
289         } else if (dbEncoding.equals("LATIN4")) {
290           dbEncoding = "ISO8859_4";
291         } else if (dbEncoding.equals("LATIN5")) {
292           dbEncoding = "ISO8859_5";
293         } else if (dbEncoding.equals("LATIN6")) {
294           dbEncoding = "ISO8859_6";
295         } else if (dbEncoding.equals("LATIN7")) {
296           dbEncoding = "ISO8859_7";
297         } else if (dbEncoding.equals("LATIN8")) {
298           dbEncoding = "ISO8859_8";
299         } else if (dbEncoding.equals("LATIN9")) {
300           dbEncoding = "ISO8859_9";
301         } else if (dbEncoding.equals("EUC_JP")) {
302           dbEncoding = "EUC_JP";
303         } else if (dbEncoding.equals("EUC_CN")) {
304           dbEncoding = "EUC_CN";
305         } else if (dbEncoding.equals("EUC_KR")) {
306           dbEncoding = "EUC_KR";
307         } else if (dbEncoding.equals("EUC_TW")) {
308           dbEncoding = "EUC_TW";
309         } else if (dbEncoding.equals("KOI8")) {
310           dbEncoding = "KOI8_R";
311         } else if (dbEncoding.equals("WIN")) {
312           dbEncoding = "Cp1252";
313         } else {
314           dbEncoding = null;
315         }
316       }
317
318
319       //Set the encoding for this connection
320       //Since the encoding could be specified or obtained from the DB we use the
321       //following order:
322       //  1.  passed as a property
323       //  2.  value from DB if supported by current JVM
324       //  3.  default for JVM (leave encoding null)
325       String passedEncoding = info.getProperty("charSet");  // could be null
326
327       if (passedEncoding != null) {
328         encoding = passedEncoding;
329       } else {
330         if (dbEncoding != null) {
331           //test DB encoding
332           try {
333             "TEST".getBytes(dbEncoding);
334             //no error the encoding is supported by the current JVM
335             encoding = dbEncoding;
336           } catch (UnsupportedEncodingException uee) {
337             //dbEncoding is not supported by the current JVM
338             encoding = null;
339           }
340         } else {
341           encoding = null;
342         }
343       }
344
345       // Initialise object handling
346       initObjectTypes();
347
348       // Mark the connection as ok, and cleanup
349       firstWarning = null;
350       PG_STATUS = CONNECTION_OK;
351     }
352
353     // These methods used to be in the main Connection implementation. As they
354     // are common to all implementations (JDBC1 or 2), they are placed here.
355     // This should make it easy to maintain the two specifications.
356
357     /**
358      * This adds a warning to the warning chain.
359      * @param msg message to add
360      */
361     public void addWarning(String msg)
362     {
363         DriverManager.println(msg);
364
365         // Add the warning to the chain
366         if(firstWarning!=null)
367             firstWarning.setNextWarning(new SQLWarning(msg));
368         else
369             firstWarning = new SQLWarning(msg);
370
371         // Now check for some specific messages
372
373         // This is obsolete in 6.5, but I've left it in here so if we need to use this
374         // technique again, we'll know where to place it.
375         //
376         // This is generated by the SQL "show datestyle"
377         //if(msg.startsWith("NOTICE:") && msg.indexOf("DateStyle")>0) {
378         //// 13 is the length off "DateStyle is "
379         //msg = msg.substring(msg.indexOf("DateStyle is ")+13);
380         //
381         //for(int i=0;i<dateStyles.length;i+=2)
382         //if(msg.startsWith(dateStyles[i]))
383         //currentDateStyle=i+1; // this is the index of the format
384         //}
385     }
386
387     /**
388      * Send a query to the backend.  Returns one of the ResultSet
389      * objects.
390      *
391      * <B>Note:</B> there does not seem to be any method currently
392      * in existance to return the update count.
393      *
394      * @param sql the SQL statement to be executed
395      * @return a ResultSet holding the results
396      * @exception SQLException if a database error occurs
397      */
398     public java.sql.ResultSet ExecSQL(String sql) throws SQLException
399     {
400       return ExecSQL(sql,null);
401     }
402
403     /**
404      * Send a query to the backend.  Returns one of the ResultSet
405      * objects.
406      *
407      * <B>Note:</B> there does not seem to be any method currently
408      * in existance to return the update count.
409      *
410      * @param sql the SQL statement to be executed
411      * @param stat The Statement associated with this query (may be null)
412      * @return a ResultSet holding the results
413      * @exception SQLException if a database error occurs
414      */
415     public java.sql.ResultSet ExecSQL(String sql,java.sql.Statement stat) throws SQLException
416     {
417       // added Jan 30 2001 to correct maxrows per statement
418       int maxrows=0;
419       if(stat!=null)
420         maxrows=stat.getMaxRows();
421
422         // added Oct 7 1998 to give us thread safety.
423         synchronized(pg_stream) {
424             // Deallocate all resources in the stream associated
425             // with a previous request.
426             // This will let the driver reuse byte arrays that has already
427             // been allocated instead of allocating new ones in order
428             // to gain performance improvements.
429             // PM 17/01/01: Commented out due to race bug. See comments in
430             // PG_Stream
431             //pg_stream.deallocate();
432
433             Field[] fields = null;
434             Vector tuples = new Vector();
435             byte[] buf = null;
436             int fqp = 0;
437             boolean hfr = false;
438             String recv_status = null, msg;
439             int update_count = 1;
440             int insert_oid = 0;
441             SQLException final_error = null;
442
443             // Commented out as the backend can now handle queries
444             // larger than 8K. Peter June 6 2000
445             //if (sql.length() > 8192)
446             //throw new PSQLException("postgresql.con.toolong",sql);
447
448         if (getEncoding() == null)
449             buf = sql.getBytes();
450         else {
451             try {
452                 buf = sql.getBytes(getEncoding());
453             } catch (UnsupportedEncodingException unse) {
454                  throw new PSQLException("postgresql.con.encoding",
455                                         unse);
456             }
457         }
458
459             try
460                 {
461                     pg_stream.SendChar('Q');
462                     pg_stream.Send(buf);
463                     pg_stream.SendChar(0);
464                     pg_stream.flush();
465                 } catch (IOException e) {
466                     throw new PSQLException("postgresql.con.ioerror",e);
467                 }
468
469             while (!hfr || fqp > 0)
470                 {
471                     Object tup=null;    // holds rows as they are recieved
472
473                     int c = pg_stream.ReceiveChar();
474
475                     switch (c)
476                         {
477                         case 'A':       // Asynchronous Notify
478                             pid = pg_stream.ReceiveInteger(4);
479                             msg = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
480                             break;
481                         case 'B':       // Binary Data Transfer
482                             if (fields == null)
483                                 throw new PSQLException("postgresql.con.tuple");
484                             tup = pg_stream.ReceiveTuple(fields.length, true);
485                             // This implements Statement.setMaxRows()
486                             if(maxrows==0 || tuples.size()<maxrows)
487                                 tuples.addElement(tup);
488                             break;
489                         case 'C':       // Command Status
490                             recv_status = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
491
492                                 // Now handle the update count correctly.
493                                 if(recv_status.startsWith("INSERT") || recv_status.startsWith("UPDATE") || recv_status.startsWith("DELETE")) {
494                                         try {
495                                                 update_count = Integer.parseInt(recv_status.substring(1+recv_status.lastIndexOf(' ')));
496                                         } catch(NumberFormatException nfe) {
497                                                 throw new PSQLException("postgresql.con.fathom",recv_status);
498                                         }
499                                         if(recv_status.startsWith("INSERT")) {
500                                             try {
501                                                 insert_oid = Integer.parseInt(recv_status.substring(1+recv_status.indexOf(' '),recv_status.lastIndexOf(' ')));
502                                             } catch(NumberFormatException nfe) {
503                                                 throw new PSQLException("postgresql.con.fathom",recv_status);
504                                             }
505                                         }
506                                 }
507                             if (fields != null)
508                                 hfr = true;
509                             else
510                                 {
511                                     try
512                                         {
513                                             pg_stream.SendChar('Q');
514                                             pg_stream.SendChar(' ');
515                                             pg_stream.SendChar(0);
516                                             pg_stream.flush();
517                                         } catch (IOException e) {
518                                             throw new PSQLException("postgresql.con.ioerror",e);
519                                         }
520                                     fqp++;
521                                 }
522                             break;
523                         case 'D':       // Text Data Transfer
524                             if (fields == null)
525                                 throw new PSQLException("postgresql.con.tuple");
526                             tup = pg_stream.ReceiveTuple(fields.length, false);
527                             // This implements Statement.setMaxRows()
528                             if(maxrows==0 || tuples.size()<maxrows)
529                                 tuples.addElement(tup);
530                             break;
531                         case 'E':       // Error Message
532                             msg = pg_stream.ReceiveString(receive_sbuf,4096,getEncoding());
533                             final_error = new SQLException(msg);
534                             hfr = true;
535                             break;
536                         case 'I':       // Empty Query
537                             int t = pg_stream.ReceiveChar();
538
539                             if (t != 0)
540                                 throw new PSQLException("postgresql.con.garbled");
541                             if (fqp > 0)
542                                 fqp--;
543                             if (fqp == 0)
544                                 hfr = true;
545                             break;
546                         case 'N':       // Error Notification
547                             addWarning(pg_stream.ReceiveString(receive_sbuf,4096,getEncoding()));
548                             break;
549                         case 'P':       // Portal Name
550                             String pname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
551                             break;
552                         case 'T':       // MetaData Field Description
553                             if (fields != null)
554                                 throw new PSQLException("postgresql.con.multres");
555                             fields = ReceiveFields();
556                             break;
557                         case 'Z':       // backend ready for query, ignore for now :-)
558                             break;
559                         default:
560                             throw new PSQLException("postgresql.con.type",new Character((char)c));
561                         }
562                 }
563             if (final_error != null)
564                 throw final_error;
565
566             return getResultSet(this, stat, fields, tuples, recv_status, update_count, insert_oid);
567         }
568     }
569
570     /**
571      * Receive the field descriptions from the back end
572      *
573      * @return an array of the Field object describing the fields
574      * @exception SQLException if a database error occurs
575      */
576     private Field[] ReceiveFields() throws SQLException
577     {
578         int nf = pg_stream.ReceiveIntegerR(2), i;
579         Field[] fields = new Field[nf];
580
581         for (i = 0 ; i < nf ; ++i)
582             {
583                 String typname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
584                 int typid = pg_stream.ReceiveIntegerR(4);
585                 int typlen = pg_stream.ReceiveIntegerR(2);
586                 int typmod = pg_stream.ReceiveIntegerR(4);
587                 fields[i] = new Field(this, typname, typid, typlen, typmod);
588             }
589         return fields;
590     }
591
592     /**
593      * In SQL, a result table can be retrieved through a cursor that
594      * is named.  The current row of a result can be updated or deleted
595      * using a positioned update/delete statement that references the
596      * cursor name.
597      *
598      * We support one cursor per connection.
599      *
600      * setCursorName sets the cursor name.
601      *
602      * @param cursor the cursor name
603      * @exception SQLException if a database access error occurs
604      */
605     public void setCursorName(String cursor) throws SQLException
606     {
607         this.cursor = cursor;
608     }
609
610     /**
611      * getCursorName gets the cursor name.
612      *
613      * @return the current cursor name
614      * @exception SQLException if a database access error occurs
615      */
616     public String getCursorName() throws SQLException
617     {
618         return cursor;
619     }
620
621     /**
622      * We are required to bring back certain information by
623      * the DatabaseMetaData class.  These functions do that.
624      *
625      * Method getURL() brings back the URL (good job we saved it)
626      *
627      * @return the url
628      * @exception SQLException just in case...
629      */
630     public String getURL() throws SQLException
631     {
632         return this_url;
633     }
634
635     /**
636      * Method getUserName() brings back the User Name (again, we
637      * saved it)
638      *
639      * @return the user name
640      * @exception SQLException just in case...
641      */
642     public String getUserName() throws SQLException
643     {
644         return PG_USER;
645     }
646
647     /**
648      *  Get the character encoding to use for this connection.
649      *  @return the encoding to use, or <b>null</b> for the
650      *  default encoding.
651      */
652     public String getEncoding() throws SQLException {
653         return encoding;
654     }
655
656     /**
657      * This returns the Fastpath API for the current connection.
658      *
659      * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
660      * functions on the org.postgresql backend itself.
661      *
662      * <p>It is primarily used by the LargeObject API
663      *
664      * <p>The best way to use this is as follows:
665      *
666      * <p><pre>
667      * import org.postgresql.fastpath.*;
668      * ...
669      * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI();
670      * </pre>
671      *
672      * <p>where myconn is an open Connection to org.postgresql.
673      *
674      * @return Fastpath object allowing access to functions on the org.postgresql
675      * backend.
676      * @exception SQLException by Fastpath when initialising for first time
677      */
678     public Fastpath getFastpathAPI() throws SQLException
679     {
680         if(fastpath==null)
681             fastpath = new Fastpath(this,pg_stream);
682         return fastpath;
683     }
684
685     // This holds a reference to the Fastpath API if already open
686     private Fastpath fastpath = null;
687
688     /**
689      * This returns the LargeObject API for the current connection.
690      *
691      * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
692      * functions on the org.postgresql backend itself.
693      *
694      * <p>The best way to use this is as follows:
695      *
696      * <p><pre>
697      * import org.postgresql.largeobject.*;
698      * ...
699      * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI();
700      * </pre>
701      *
702      * <p>where myconn is an open Connection to org.postgresql.
703      *
704      * @return LargeObject object that implements the API
705      * @exception SQLException by LargeObject when initialising for first time
706      */
707     public LargeObjectManager getLargeObjectAPI() throws SQLException
708     {
709         if(largeobject==null)
710             largeobject = new LargeObjectManager(this);
711         return largeobject;
712     }
713
714     // This holds a reference to the LargeObject API if already open
715     private LargeObjectManager largeobject = null;
716
717     /**
718      * This method is used internally to return an object based around
719      * org.postgresql's more unique data types.
720      *
721      * <p>It uses an internal Hashtable to get the handling class. If the
722      * type is not supported, then an instance of org.postgresql.util.PGobject
723      * is returned.
724      *
725      * You can use the getValue() or setValue() methods to handle the returned
726      * object. Custom objects can have their own methods.
727      *
728      * In 6.4, this is extended to use the org.postgresql.util.Serialize class to
729      * allow the Serialization of Java Objects into the database without using
730      * Blobs. Refer to that class for details on how this new feature works.
731      *
732      * @return PGobject for this type, and set to value
733      * @exception SQLException if value is not correct for this type
734      * @see org.postgresql.util.Serialize
735      */
736     public Object getObject(String type,String value) throws SQLException
737     {
738         try {
739             Object o = objectTypes.get(type);
740
741             // If o is null, then the type is unknown, so check to see if type
742             // is an actual table name. If it does, see if a Class is known that
743             // can handle it
744             if(o == null) {
745                 Serialize ser = new Serialize(this,type);
746                 objectTypes.put(type,ser);
747                 return ser.fetch(Integer.parseInt(value));
748             }
749
750             // If o is not null, and it is a String, then its a class name that
751             // extends PGobject.
752             //
753             // This is used to implement the org.postgresql unique types (like lseg,
754             // point, etc).
755             if(o instanceof String) {
756                 // 6.3 style extending PG_Object
757                 PGobject obj = null;
758                 obj = (PGobject)(Class.forName((String)o).newInstance());
759                 obj.setType(type);
760                 obj.setValue(value);
761                 return (Object)obj;
762             } else {
763                 // If it's an object, it should be an instance of our Serialize class
764                 // If so, then call it's fetch method.
765                 if(o instanceof Serialize)
766                     return ((Serialize)o).fetch(Integer.parseInt(value));
767             }
768         } catch(SQLException sx) {
769             // rethrow the exception. Done because we capture any others next
770             sx.fillInStackTrace();
771             throw sx;
772         } catch(Exception ex) {
773             throw new PSQLException("postgresql.con.creobj",type,ex);
774         }
775
776         // should never be reached
777         return null;
778     }
779
780     /**
781      * This stores an object into the database.
782      * @param o Object to store
783      * @return OID of the new rectord
784      * @exception SQLException if value is not correct for this type
785      * @see org.postgresql.util.Serialize
786      */
787     public int putObject(Object o) throws SQLException
788     {
789         try {
790             String type = o.getClass().getName();
791             Object x = objectTypes.get(type);
792
793             // If x is null, then the type is unknown, so check to see if type
794             // is an actual table name. If it does, see if a Class is known that
795             // can handle it
796             if(x == null) {
797                 Serialize ser = new Serialize(this,type);
798                 objectTypes.put(type,ser);
799                 return ser.store(o);
800             }
801
802             // If it's an object, it should be an instance of our Serialize class
803             // If so, then call it's fetch method.
804             if(x instanceof Serialize)
805                 return ((Serialize)x).store(o);
806
807             // Thow an exception because the type is unknown
808             throw new PSQLException("postgresql.con.strobj");
809
810         } catch(SQLException sx) {
811             // rethrow the exception. Done because we capture any others next
812             sx.fillInStackTrace();
813             throw sx;
814         } catch(Exception ex) {
815             throw new PSQLException("postgresql.con.strobjex",ex);
816         }
817     }
818
819     /**
820      * This allows client code to add a handler for one of org.postgresql's
821      * more unique data types.
822      *
823      * <p><b>NOTE:</b> This is not part of JDBC, but an extension.
824      *
825      * <p>The best way to use this is as follows:
826      *
827      * <p><pre>
828      * ...
829      * ((org.postgresql.Connection)myconn).addDataType("mytype","my.class.name");
830      * ...
831      * </pre>
832      *
833      * <p>where myconn is an open Connection to org.postgresql.
834      *
835      * <p>The handling class must extend org.postgresql.util.PGobject
836      *
837      * @see org.postgresql.util.PGobject
838      */
839     public void addDataType(String type,String name)
840     {
841         objectTypes.put(type,name);
842     }
843
844     // This holds the available types
845     private Hashtable objectTypes = new Hashtable();
846
847     // This array contains the types that are supported as standard.
848     //
849     // The first entry is the types name on the database, the second
850     // the full class name of the handling class.
851     //
852     private static final String defaultObjectTypes[][] = {
853         {"box",         "org.postgresql.geometric.PGbox"},
854         {"circle",      "org.postgresql.geometric.PGcircle"},
855         {"line",        "org.postgresql.geometric.PGline"},
856         {"lseg",        "org.postgresql.geometric.PGlseg"},
857         {"path",        "org.postgresql.geometric.PGpath"},
858         {"point",       "org.postgresql.geometric.PGpoint"},
859         {"polygon",     "org.postgresql.geometric.PGpolygon"},
860         {"money",       "org.postgresql.util.PGmoney"}
861     };
862
863     // This initialises the objectTypes hashtable
864     private void initObjectTypes()
865     {
866         for(int i=0;i<defaultObjectTypes.length;i++)
867             objectTypes.put(defaultObjectTypes[i][0],defaultObjectTypes[i][1]);
868     }
869
870     // These are required by other common classes
871     public abstract java.sql.Statement createStatement() throws SQLException;
872
873     /**
874      * This returns a resultset. It must be overridden, so that the correct
875      * version (from jdbc1 or jdbc2) are returned.
876      */
877     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;
878
879     public abstract void close() throws SQLException;
880
881     /**
882      * Overides finalize(). If called, it closes the connection.
883      *
884      * This was done at the request of Rachel Greenham
885      * <rachel@enlarion.demon.co.uk> who hit a problem where multiple
886      * clients didn't close the connection, and once a fortnight enough
887      * clients were open to kill the org.postgres server.
888      */
889     public void finalize() throws Throwable
890     {
891         close();
892     }
893
894     /**
895      * This is an attempt to implement SQL Escape clauses
896      */
897     public String EscapeSQL(String sql) {
898       //if (DEBUG) { System.out.println ("parseSQLEscapes called"); }
899
900       // If we find a "{d", assume we have a date escape.
901       //
902       // Since the date escape syntax is very close to the
903       // native Postgres date format, we just remove the escape
904       // delimiters.
905       //
906       // This implementation could use some optimization, but it has
907       // worked in practice for two years of solid use.
908       int index = sql.indexOf("{d");
909       while (index != -1) {
910         //System.out.println ("escape found at index: " + index);
911         StringBuffer buf = new StringBuffer(sql);
912         buf.setCharAt(index, ' ');
913         buf.setCharAt(index + 1, ' ');
914         buf.setCharAt(sql.indexOf('}', index), ' ');
915         sql = new String(buf);
916         index = sql.indexOf("{d");
917       }
918       //System.out.println ("modified SQL: " + sql);
919       return sql;
920     }
921
922 }