1 package org.postgresql;
7 import org.postgresql.Field;
8 import org.postgresql.fastpath.*;
9 import org.postgresql.largeobject.*;
10 import org.postgresql.util.*;
13 * $Id: Connection.java,v 1.14 2001/01/31 08:26:01 peter Exp $
15 * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or
16 * JDBC2 versions of the Connection class.
19 public abstract class Connection
21 // This is the network stream associated with this connection
22 public PG_Stream pg_stream;
24 // This is set by org.postgresql.Statement.setMaxRows()
25 //public int maxrows = 0; // maximum no. of rows; 0 = unlimited
27 private String PG_HOST;
29 private String PG_USER;
30 private String PG_PASSWORD;
31 private String PG_DATABASE;
32 private boolean PG_STATUS;
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
40 private String encoding;
42 public boolean CONNECTION_OK = true;
43 public boolean CONNECTION_BAD = false;
45 public boolean autoCommit = true;
46 public boolean readOnly = false;
48 public Driver this_driver;
49 private String this_url;
50 private String cursor = null; // The positioned update cursor name
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;
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;
69 // New for 6.3, salt value for crypt authorisation
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();
78 // Now handle notices as warnings, so things like "show" now work
79 public SQLWarning firstWarning = null;
81 // The PID an cancellation key we get from the backend process
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];
91 * This is called by Class.forName() from within org.postgresql.Driver
98 * This method actually opens the connection. It is called by Driver.
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
109 protected void openConnection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException
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");
121 PG_DATABASE = database;
122 PG_PASSWORD = info.getProperty("password");
123 PG_USER = info.getProperty("user");
126 PG_STATUS = CONNECTION_BAD;
128 // Now make the initial connection
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);
141 // Now we need to construct and send a startup packet
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);
150 // This last send includes the unused fields
151 pg_stream.Send(PG_USER.getBytes(),SM_USER+SM_OPTIONS+SM_UNUSED+SM_TTY);
153 // now flush the startup packets to the backend
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
160 int beresp = pg_stream.ReceiveChar();
164 // An error occured, so pass the error message to the
167 // The most common one to be thrown here is:
168 // "User authentication failed"
170 throw new SQLException(pg_stream.ReceiveString
171 (receive_sbuf, 4096, getEncoding()));
174 // Get the type of request
175 areq = pg_stream.ReceiveIntegerR(4);
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);
186 // now send the auth packet
193 DriverManager.println("postgresql: KRB4");
194 throw new PSQLException("postgresql.con.kerb4");
197 DriverManager.println("postgresql: KRB5");
198 throw new PSQLException("postgresql.con.kerb5");
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);
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);
218 throw new PSQLException("postgresql.con.auth",new Integer(areq));
223 throw new PSQLException("postgresql.con.authfail");
225 } while(areq != AUTH_REQ_OK);
227 } catch (IOException e) {
228 throw new PSQLException("postgresql.con.failed",e);
232 // As of protocol version 2.0, we should now receive the cancellation key and the pid
233 int beresp = pg_stream.ReceiveChar();
236 pid = pg_stream.ReceiveInteger(4);
237 ckey = pg_stream.ReceiveInteger(4);
241 throw new SQLException(pg_stream.ReceiveString
242 (receive_sbuf, 4096, getEncoding()));
244 throw new PSQLException("postgresql.con.setup");
247 // Expect ReadyForQuery packet
248 beresp = pg_stream.ReceiveChar();
254 throw new SQLException(pg_stream.ReceiveString(receive_sbuf, 4096, getEncoding()));
256 throw new PSQLException("postgresql.con.setup");
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.
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 ;-)
266 // We also ask the DB for certain properties (i.e. DatabaseEncoding at this time)
270 java.sql.ResultSet initrset = ExecSQL("set datestyle to 'ISO'; select getdatabaseencoding()");
272 String dbEncoding = null;
273 //retrieve DB properties
274 if(initrset.next()) {
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")) {
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";
319 //Set the encoding for this connection
320 //Since the encoding could be specified or obtained from the DB we use the
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
327 if (passedEncoding != null) {
328 encoding = passedEncoding;
330 if (dbEncoding != null) {
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
345 // Initialise object handling
348 // Mark the connection as ok, and cleanup
350 PG_STATUS = CONNECTION_OK;
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.
358 * This adds a warning to the warning chain.
359 * @param msg message to add
361 public void addWarning(String msg)
363 DriverManager.println(msg);
365 // Add the warning to the chain
366 if(firstWarning!=null)
367 firstWarning.setNextWarning(new SQLWarning(msg));
369 firstWarning = new SQLWarning(msg);
371 // Now check for some specific messages
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.
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);
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
388 * Send a query to the backend. Returns one of the ResultSet
391 * <B>Note:</B> there does not seem to be any method currently
392 * in existance to return the update count.
394 * @param sql the SQL statement to be executed
395 * @return a ResultSet holding the results
396 * @exception SQLException if a database error occurs
398 public java.sql.ResultSet ExecSQL(String sql) throws SQLException
400 return ExecSQL(sql,null);
404 * Send a query to the backend. Returns one of the ResultSet
407 * <B>Note:</B> there does not seem to be any method currently
408 * in existance to return the update count.
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
415 public java.sql.ResultSet ExecSQL(String sql,java.sql.Statement stat) throws SQLException
417 // added Jan 30 2001 to correct maxrows per statement
420 maxrows=stat.getMaxRows();
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
431 //pg_stream.deallocate();
433 Field[] fields = null;
434 Vector tuples = new Vector();
438 String recv_status = null, msg;
439 int update_count = 1;
441 SQLException final_error = null;
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);
448 if (getEncoding() == null)
449 buf = sql.getBytes();
452 buf = sql.getBytes(getEncoding());
453 } catch (UnsupportedEncodingException unse) {
454 throw new PSQLException("postgresql.con.encoding",
461 pg_stream.SendChar('Q');
463 pg_stream.SendChar(0);
465 } catch (IOException e) {
466 throw new PSQLException("postgresql.con.ioerror",e);
469 while (!hfr || fqp > 0)
471 Object tup=null; // holds rows as they are recieved
473 int c = pg_stream.ReceiveChar();
477 case 'A': // Asynchronous Notify
478 pid = pg_stream.ReceiveInteger(4);
479 msg = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
481 case 'B': // Binary Data Transfer
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);
489 case 'C': // Command Status
490 recv_status = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
492 // Now handle the update count correctly.
493 if(recv_status.startsWith("INSERT") || recv_status.startsWith("UPDATE") || recv_status.startsWith("DELETE")) {
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);
499 if(recv_status.startsWith("INSERT")) {
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);
513 pg_stream.SendChar('Q');
514 pg_stream.SendChar(' ');
515 pg_stream.SendChar(0);
517 } catch (IOException e) {
518 throw new PSQLException("postgresql.con.ioerror",e);
523 case 'D': // Text Data Transfer
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);
531 case 'E': // Error Message
532 msg = pg_stream.ReceiveString(receive_sbuf,4096,getEncoding());
533 final_error = new SQLException(msg);
536 case 'I': // Empty Query
537 int t = pg_stream.ReceiveChar();
540 throw new PSQLException("postgresql.con.garbled");
546 case 'N': // Error Notification
547 addWarning(pg_stream.ReceiveString(receive_sbuf,4096,getEncoding()));
549 case 'P': // Portal Name
550 String pname = pg_stream.ReceiveString(receive_sbuf,8192,getEncoding());
552 case 'T': // MetaData Field Description
554 throw new PSQLException("postgresql.con.multres");
555 fields = ReceiveFields();
557 case 'Z': // backend ready for query, ignore for now :-)
560 throw new PSQLException("postgresql.con.type",new Character((char)c));
563 if (final_error != null)
566 return getResultSet(this, stat, fields, tuples, recv_status, update_count, insert_oid);
571 * Receive the field descriptions from the back end
573 * @return an array of the Field object describing the fields
574 * @exception SQLException if a database error occurs
576 private Field[] ReceiveFields() throws SQLException
578 int nf = pg_stream.ReceiveIntegerR(2), i;
579 Field[] fields = new Field[nf];
581 for (i = 0 ; i < nf ; ++i)
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);
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
598 * We support one cursor per connection.
600 * setCursorName sets the cursor name.
602 * @param cursor the cursor name
603 * @exception SQLException if a database access error occurs
605 public void setCursorName(String cursor) throws SQLException
607 this.cursor = cursor;
611 * getCursorName gets the cursor name.
613 * @return the current cursor name
614 * @exception SQLException if a database access error occurs
616 public String getCursorName() throws SQLException
622 * We are required to bring back certain information by
623 * the DatabaseMetaData class. These functions do that.
625 * Method getURL() brings back the URL (good job we saved it)
628 * @exception SQLException just in case...
630 public String getURL() throws SQLException
636 * Method getUserName() brings back the User Name (again, we
639 * @return the user name
640 * @exception SQLException just in case...
642 public String getUserName() throws SQLException
648 * Get the character encoding to use for this connection.
649 * @return the encoding to use, or <b>null</b> for the
652 public String getEncoding() throws SQLException {
657 * This returns the Fastpath API for the current connection.
659 * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
660 * functions on the org.postgresql backend itself.
662 * <p>It is primarily used by the LargeObject API
664 * <p>The best way to use this is as follows:
667 * import org.postgresql.fastpath.*;
669 * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI();
672 * <p>where myconn is an open Connection to org.postgresql.
674 * @return Fastpath object allowing access to functions on the org.postgresql
676 * @exception SQLException by Fastpath when initialising for first time
678 public Fastpath getFastpathAPI() throws SQLException
681 fastpath = new Fastpath(this,pg_stream);
685 // This holds a reference to the Fastpath API if already open
686 private Fastpath fastpath = null;
689 * This returns the LargeObject API for the current connection.
691 * <p><b>NOTE:</b> This is not part of JDBC, but allows access to
692 * functions on the org.postgresql backend itself.
694 * <p>The best way to use this is as follows:
697 * import org.postgresql.largeobject.*;
699 * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI();
702 * <p>where myconn is an open Connection to org.postgresql.
704 * @return LargeObject object that implements the API
705 * @exception SQLException by LargeObject when initialising for first time
707 public LargeObjectManager getLargeObjectAPI() throws SQLException
709 if(largeobject==null)
710 largeobject = new LargeObjectManager(this);
714 // This holds a reference to the LargeObject API if already open
715 private LargeObjectManager largeobject = null;
718 * This method is used internally to return an object based around
719 * org.postgresql's more unique data types.
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
725 * You can use the getValue() or setValue() methods to handle the returned
726 * object. Custom objects can have their own methods.
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.
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
736 public Object getObject(String type,String value) throws SQLException
739 Object o = objectTypes.get(type);
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
745 Serialize ser = new Serialize(this,type);
746 objectTypes.put(type,ser);
747 return ser.fetch(Integer.parseInt(value));
750 // If o is not null, and it is a String, then its a class name that
753 // This is used to implement the org.postgresql unique types (like lseg,
755 if(o instanceof String) {
756 // 6.3 style extending PG_Object
758 obj = (PGobject)(Class.forName((String)o).newInstance());
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));
768 } catch(SQLException sx) {
769 // rethrow the exception. Done because we capture any others next
770 sx.fillInStackTrace();
772 } catch(Exception ex) {
773 throw new PSQLException("postgresql.con.creobj",type,ex);
776 // should never be reached
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
787 public int putObject(Object o) throws SQLException
790 String type = o.getClass().getName();
791 Object x = objectTypes.get(type);
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
797 Serialize ser = new Serialize(this,type);
798 objectTypes.put(type,ser);
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);
807 // Thow an exception because the type is unknown
808 throw new PSQLException("postgresql.con.strobj");
810 } catch(SQLException sx) {
811 // rethrow the exception. Done because we capture any others next
812 sx.fillInStackTrace();
814 } catch(Exception ex) {
815 throw new PSQLException("postgresql.con.strobjex",ex);
820 * This allows client code to add a handler for one of org.postgresql's
821 * more unique data types.
823 * <p><b>NOTE:</b> This is not part of JDBC, but an extension.
825 * <p>The best way to use this is as follows:
829 * ((org.postgresql.Connection)myconn).addDataType("mytype","my.class.name");
833 * <p>where myconn is an open Connection to org.postgresql.
835 * <p>The handling class must extend org.postgresql.util.PGobject
837 * @see org.postgresql.util.PGobject
839 public void addDataType(String type,String name)
841 objectTypes.put(type,name);
844 // This holds the available types
845 private Hashtable objectTypes = new Hashtable();
847 // This array contains the types that are supported as standard.
849 // The first entry is the types name on the database, the second
850 // the full class name of the handling class.
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"}
863 // This initialises the objectTypes hashtable
864 private void initObjectTypes()
866 for(int i=0;i<defaultObjectTypes.length;i++)
867 objectTypes.put(defaultObjectTypes[i][0],defaultObjectTypes[i][1]);
870 // These are required by other common classes
871 public abstract java.sql.Statement createStatement() throws SQLException;
874 * This returns a resultset. It must be overridden, so that the correct
875 * version (from jdbc1 or jdbc2) are returned.
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;
879 public abstract void close() throws SQLException;
882 * Overides finalize(). If called, it closes the connection.
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.
889 public void finalize() throws Throwable
895 * This is an attempt to implement SQL Escape clauses
897 public String EscapeSQL(String sql) {
898 //if (DEBUG) { System.out.println ("parseSQLEscapes called"); }
900 // If we find a "{d", assume we have a date escape.
902 // Since the date escape syntax is very close to the
903 // native Postgres date format, we just remove the escape
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");
918 //System.out.println ("modified SQL: " + sql);