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