1 /*
2  * For work developed by the HSQL Development Group:
3  *
4  * Copyright (c) 2001-2016, The HSQL Development Group
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * Redistributions of source code must retain the above copyright notice, this
11  * list of conditions and the following disclaimer.
12  *
13  * Redistributions in binary form must reproduce the above copyright notice,
14  * this list of conditions and the following disclaimer in the documentation
15  * and/or other materials provided with the distribution.
16  *
17  * Neither the name of the HSQL Development Group nor the names of its
18  * contributors may be used to endorse or promote products derived from this
19  * software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
25  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  *
34  *
35  * For work originally developed by the Hypersonic SQL Group:
36  *
37  * Copyright (c) 1995-2000, The Hypersonic SQL Group.
38  * All rights reserved.
39  *
40  * Redistribution and use in source and binary forms, with or without
41  * modification, are permitted provided that the following conditions are met:
42  *
43  * Redistributions of source code must retain the above copyright notice, this
44  * list of conditions and the following disclaimer.
45  *
46  * Redistributions in binary form must reproduce the above copyright notice,
47  * this list of conditions and the following disclaimer in the documentation
48  * and/or other materials provided with the distribution.
49  *
50  * Neither the name of the Hypersonic SQL Group nor the names of its
51  * contributors may be used to endorse or promote products derived from this
52  * software without specific prior written permission.
53  *
54  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57  * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
58  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
59  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
60  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
61  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
62  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
63  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65  *
66  * This software consists of voluntary contributions made by many individuals
67  * on behalf of the Hypersonic SQL Group.
68  */
69 
70 
71 package org.hsqldb.server;
72 
73 import java.io.BufferedInputStream;
74 import java.io.ByteArrayInputStream;
75 import java.io.DataInputStream;
76 import java.io.EOFException;
77 import java.io.IOException;
78 import java.net.Socket;
79 import java.net.SocketException;
80 import java.util.concurrent.atomic.AtomicInteger;
81 
82 import org.hsqldb.ClientConnection;
83 import org.hsqldb.ColumnBase;
84 import org.hsqldb.DatabaseManager;
85 import org.hsqldb.HsqlException;
86 import org.hsqldb.Session;
87 import org.hsqldb.StatementTypes;
88 import org.hsqldb.error.Error;
89 import org.hsqldb.error.ErrorCode;
90 import org.hsqldb.lib.DataOutputStream;
91 import org.hsqldb.navigator.RowSetNavigator;
92 import org.hsqldb.persist.HsqlDatabaseProperties;
93 import org.hsqldb.resources.ResourceBundleHandler;
94 import org.hsqldb.result.Result;
95 import org.hsqldb.result.ResultConstants;
96 import org.hsqldb.result.ResultMetaData;
97 import org.hsqldb.result.ResultProperties;
98 import org.hsqldb.rowio.RowInputBinary;
99 import org.hsqldb.rowio.RowOutputBinary;
100 import org.hsqldb.rowio.RowOutputInterface;
101 import org.hsqldb.types.Type;
102 
103 // fredt@users 20020215 - patch 461556 by paul-h@users - server factory
104 // fredt@users 20020424 - patch 1.7.0 by fredt - shutdown without exit
105 // fredt@users 20021002 - patch 1.7.1 by fredt - changed notification method
106 // fredt@users 20030618 - patch 1.7.2 by fredt - changed read/write methods
107 // fredt@users 20091013 - move set session to null suggested by Otto Joyner
108 
109 /**
110  *  All ServerConnection objects are listed in a Set in server
111  *  and removed by this class when closed.<p>
112  *
113  *  When the database or server is shutdown, the signalClose() method is called
114  *  for all current ServerConnection instances. This will call the private
115  *  close() method. If the ServerConnection thread itself has caused the
116  *  shutdown it returns the result of the operation to the client.
117  *  (fredt@users)<p>
118  *
119  * ODBC support added for version 2.0.0 by Blaine Simpson.<p>
120  *
121  * @author Blaine Simpson (unsaved@users dot sourceforge.net
122  * @author Fred Toussi (fredt@users dot sourceforge.net)
123  * @version 2.3.4
124  * @since 1.6.2
125  */
126 class ServerConnection implements Runnable {
127 
128     boolean                  keepAlive;
129     private String           user;
130     int                      dbID;
131     int                      dbIndex;
132     private volatile Session session;
133     private Socket           socket;
134     private Server           server;
135     private DataInputStream  dataInput;
136     private DataOutputStream dataOutput;
137     private int              mThread;
138     static final int         BUFFER_SIZE = 0x1000;
139     final byte[]             mainBuffer  = new byte[BUFFER_SIZE];
140     RowOutputInterface       rowOut;
141     RowInputBinary           rowIn;
142     Thread                   runnerThread;
143 
144     //
145     private static AtomicInteger mCurrentThread = new AtomicInteger(0);
146 
147     //
148     protected static String TEXTBANNER_PART1 = null;
149     protected static String TEXTBANNER_PART2 = null;
150 
151     static {
152         int serverBundleHandle = ResourceBundleHandler.getBundleHandle(
153             "org_hsqldb_server_Server_messages", null);
154 
155         if (serverBundleHandle < 0) {
156             throw new RuntimeException(
157                 "MISSING Resource Bundle.  See source code");
158 
159             // This will be caught before prod release.
160             // Not necessary to localize message.
161         }
162 
163         TEXTBANNER_PART1 = ResourceBundleHandler.getString(serverBundleHandle,
164                 "textbanner.part1");
165         TEXTBANNER_PART2 = ResourceBundleHandler.getString(serverBundleHandle,
166                 "textbanner.part2");
167 
168         if (TEXTBANNER_PART1 == null || TEXTBANNER_PART2 == null) {
169             throw new RuntimeException(
170                 "MISSING Resource Bundle msg definition.  See source code");
171 
172             // This will be caught before prod release.
173             // Not necessary to localize message.
174         }
175     }
176 
177     /**
178      * Creates a new ServerConnection to the specified Server on the
179      * specified socket.
180      *
181      * @param socket the network socket on which Server communication
182      *      takes place
183      * @param server the Server instance to which the object
184      *      represents a connection
185      */
ServerConnection(Socket socket, Server server)186     ServerConnection(Socket socket, Server server) {
187 
188         RowOutputBinary rowOutTemp = new RowOutputBinary(mainBuffer);
189 
190         rowIn  = new RowInputBinary(rowOutTemp);
191         rowOut = rowOutTemp;
192 
193         //
194         Thread runnerThread;
195 
196         this.socket = socket;
197         this.server = server;
198         mThread     = mCurrentThread.getAndIncrement();
199 
200         synchronized (server.serverConnSet) {
201             server.serverConnSet.add(this);
202         }
203     }
204 
205     /**
206      * Signals this object to close, including exiting the thread running
207      * the request handling loop
208      */
signalClose()209     void signalClose() {
210 
211         keepAlive = false;
212 
213         if (Thread.currentThread().equals(runnerThread)) {
214             Result resultOut = Result.updateZeroResult;
215 
216             try {
217                 resultOut.write(session, dataOutput, rowOut);
218             } catch (Throwable t) {}
219         }
220 
221         close();
222     }
223 
224     /**
225      * Closes this connection.
226      */
close()227     private void close() {
228 
229         if (session != null) {
230             session.close();
231 
232             session = null;
233         }
234 
235         // fredt@user - closing the socket is to stop this thread
236         synchronized (this) {
237             try {
238                 if (socket != null) {
239                     socket.close();
240 
241                     socket = null;
242                 }
243             } catch (IOException e) {}
244 
245             socket = null;
246         }
247 
248         synchronized (server.serverConnSet) {
249             server.serverConnSet.remove(this);
250         }
251 
252         try {
253             runnerThread.setContextClassLoader(null);
254         } catch (Throwable t) {}
255     }
256 
257     /**
258      * Initializes this connection.
259      * <p>
260      * Will return (not throw) if fail to initialize the connection.
261      * </p>
262      */
init()263     private void init() {
264 
265         runnerThread = Thread.currentThread();
266         keepAlive    = true;
267 
268         try {
269             socket.setTcpNoDelay(true);
270 
271             dataInput = new DataInputStream(
272                 new BufferedInputStream(socket.getInputStream()));
273             dataOutput = new DataOutputStream(socket.getOutputStream());
274 
275             int firstInt = handshake();
276 
277             switch (streamProtocol) {
278 
279                 case HSQL_STREAM_PROTOCOL :
280                     if (firstInt
281                             != ClientConnection
282                                 .NETWORK_COMPATIBILITY_VERSION_INT) {
283                         if (firstInt == -1900000) {
284                             firstInt = -2000000;
285                         }
286 
287                         String verString =
288                             ClientConnection.toNetCompVersionString(firstInt);
289 
290                         throw Error.error(
291                             null, ErrorCode.SERVER_VERSIONS_INCOMPATIBLE, 0,
292                             new String[] {
293                             verString, HsqlDatabaseProperties.THIS_VERSION
294                         });
295                     }
296 
297                     int msgType = dataInput.readByte();
298 
299                     receiveResult(msgType);
300                     break;
301 
302                 case ODBC_STREAM_PROTOCOL :
303                     odbcConnect(firstInt);
304                     break;
305 
306                 default :
307 
308                     // Protocol detection failures should already have been
309                     // handled.
310                     keepAlive = false;
311             }
312         } catch (Exception e) {
313 
314             // Only "unexpected" failures are caught here.
315             // Expected failures will have been handled (by sending feedback
316             // to user-- with an output Result for normal protocols), then
317             // continuing.
318             StringBuffer sb = new StringBuffer(mThread
319                                                + ":Failed to connect client.");
320 
321             if (user != null) {
322                 sb.append("  User '" + user + "'.");
323             }
324 
325             server.printWithThread(sb.toString() + "  Stack trace follows.");
326             server.printStackTrace(e);
327         }
328     }
329 
330     private static class CleanExit extends Exception {}
331 
332     private static class ClientFailure extends Exception {
333 
334         private String clientMessage = null;
335 
ClientFailure(String ourMessage, String clientMessage)336         public ClientFailure(String ourMessage, String clientMessage) {
337 
338             super(ourMessage);
339 
340             this.clientMessage = clientMessage;
341         }
342 
getClientMessage()343         public String getClientMessage() {
344             return clientMessage;
345         }
346     }
347 
348     private CleanExit cleanExit = new CleanExit();
349 
receiveResult(int resultMode)350     private void receiveResult(int resultMode) throws CleanExit, IOException {
351 
352         boolean terminate = false;
353         Result resultIn = Result.newResult(session, resultMode, dataInput,
354                                            rowIn);
355 
356         resultIn.readLobResults(session, dataInput, rowIn);
357         server.printRequest(mThread, resultIn);
358 
359         Result resultOut = null;
360 
361         switch (resultIn.getType()) {
362 
363             case ResultConstants.CONNECT : {
364                 resultOut = setDatabase(resultIn);
365 
366                 break;
367             }
368             case ResultConstants.SQLCANCEL : {
369                 resultOut = cancelStatement(resultIn);
370                 terminate = true;
371 
372                 break;
373             }
374             case ResultConstants.DISCONNECT : {
375                 resultOut = Result.updateZeroResult;
376                 terminate = true;
377 
378                 break;
379             }
380             case ResultConstants.RESETSESSION : {
381                 session.resetSession();
382 
383                 resultOut = Result.updateZeroResult;
384 
385                 break;
386             }
387             case ResultConstants.EXECUTE_INVALID : {
388                 resultOut =
389                     Result.newErrorResult(Error.error(ErrorCode.X_07502));
390 
391                 break;
392             }
393             default : {
394                 resultOut = session.execute(resultIn);
395 
396                 break;
397             }
398         }
399 
400         resultOut.write(session, dataOutput, rowOut);
401         rowOut.reset(mainBuffer);
402         rowIn.resetRow(mainBuffer.length);
403 
404         if (terminate) {
405             throw cleanExit;
406         }
407     }
408 
409     private OdbcPacketOutputStream outPacket = null;
410 
receiveOdbcPacket(char inC)411     private void receiveOdbcPacket(char inC) throws IOException, CleanExit {
412 
413         /*
414          * The driver's notion of the transaction state, I (no) or T (yes),
415          * corresponds precisely inversely to our server-side Session
416          * autoCommit setting.
417          * If the user/app runs in non-autocommit mode and says to run a
418          * COMMIT followed by an INSERT, the driver will handle the user/app's
419          * facade of autocommittedness, and will send the server <OL>
420          *   <LI>COMMIT (which will cause us to set session.setAutoCommit(true)
421          *   <LI>BEGIN (which will cause us to set session.setAutoCommit(false)
422          *   <LI>INSERT...
423          * </OL>
424          */
425         char    c;
426         boolean sendReadyForQuery = false;
427         String  psHandle, portalHandle, handle, dataString, tmpStr;
428 
429         // Statement which must be executed after the primary statement, but
430         // before sending the ReadyForQuery Z packet.
431         String                interposedStatement = null;
432         Result                r, rOut;
433         int                   paramCount, lastSemi;
434         OdbcPreparedStatement odbcPs;
435         StatementPortal       portal;
436         ResultMetaData        pmd;
437         OdbcPacketInputStream inPacket = null;
438         Type[]                colTypes;
439         PgType[]              pgTypes;
440 
441         try {
442             inPacket = OdbcPacketInputStream.newOdbcPacketInputStream(inC,
443                     dataInput);
444 
445             server.printWithThread("Got op (" + inPacket.packetType + ')');
446             server.printWithThread("Got packet length of "
447                                    + inPacket.available()
448                                    + " + type byte + 4 size header");
449 
450             if (inPacket.available() >= 1000000000) {
451                 throw new IOException("Insane packet length: "
452                                       + inPacket.available()
453                                       + " + type byte + 4 size header");
454             }
455         } catch (SocketException se) {
456             server.printWithThread("Ungraceful client exit: " + se);
457 
458             throw cleanExit;    // not "clean", but handled
459         } catch (IOException ioe) {
460             server.printWithThread("Fatal ODBC protocol failure: " + ioe);
461 
462             try {
463                 OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_FATAL,
464                                      ioe.toString(), "08P01", dataOutput);
465 
466                 // Code here means Protocol Violation
467             } catch (Exception e) {
468 
469                 // We just make an honest effort to notify the client
470             }
471 
472             throw cleanExit;    // not "clean", but handled
473         }
474 
475         /**
476          * ODBC Service State Machine  (the remainder of this method)
477          */
478         switch (odbcCommMode) {
479 
480             case OdbcUtil.ODBC_EXT_RECOVER_MODE :
481                 if (inPacket.packetType != 'S') {
482                     if (server.isTrace()) {
483                         server.printWithThread("Ignoring a '"
484                                                + inPacket.packetType + "'");
485                     }
486 
487                     return;
488                 }
489 
490                 odbcCommMode = OdbcUtil.ODBC_EXTENDED_MODE;
491 
492                 server.printWithThread(
493                     "EXTENDED comm session being recovered");
494 
495                 // Now the main switch will handle the Sync packet carefully
496                 // the same as if there were no recovery.
497                 break;
498 
499             case OdbcUtil.ODBC_SIMPLE_MODE :
500                 switch (inPacket.packetType) {
501 
502                     case 'P' :
503 
504                     // This is the only way to make this switch, according
505                     // to docs, but that does not allow for intermixing of
506                     // static and prepared statement (selects or other).
507                     // Therefore we allow all of the following, which works
508                     // great.
509                     case 'H' :
510                     case 'S' :
511                     case 'D' :
512                     case 'B' :
513                     case 'E' :
514                     case 'C' :
515                         odbcCommMode = OdbcUtil.ODBC_EXTENDED_MODE;
516 
517                         server.printWithThread(
518                             "Switching mode from SIMPLE to EXTENDED");
519 
520                     // Do not detect unexpected ops here.
521                     // In that case, leave the mode as it is, and the main
522                     // switch below will handle appropriately.
523                 }
524                 break;
525 
526             case OdbcUtil.ODBC_EXTENDED_MODE :
527                 switch (inPacket.packetType) {
528 
529                     case 'Q' :
530                         odbcCommMode = OdbcUtil.ODBC_SIMPLE_MODE;
531 
532                         server.printWithThread(
533                             "Switching mode from EXTENDED to SIMPLE");
534 
535                     // Do not detect unexpected ops here.
536                     // In that case, leave the mode as it is, and the main
537                     // switch below will handle appropriately.
538                 }
539                 break;
540 
541             default :
542                 throw new RuntimeException("Unexpected ODBC comm mode value: "
543                                            + odbcCommMode);
544         }
545 
546         outPacket.reset();
547 
548         try {
549 
550             // Every switch case must either throw or break.
551             // For cases which break
552             //   The packet will always be checked to make sure all bytes have
553             //   been consumed.
554             //   Set boolean sendReadyForQuery to send a Z/ReadyForQuery packet
555             //   to client.
556             // DO NOT return early.  If you need to abort, that is exceptional
557             // behavior and you should throw an Exception.
558             MAIN_ODBC_COMM_SWITCH:
559             switch (inPacket.packetType) {
560 
561                 case 'Q' :                                    // Query packet
562                     String sql = inPacket.readString();
563 
564                     // We don't ask for the null terminator
565                     /* **********************************************
566                      * These first few cases handle the driver's implicit handling
567                      * of transactions. */
568                     if (sql.startsWith("BEGIN;") || sql.equals("BEGIN")) {
569                         /*
570                          * We may get here because of Driver client trying to
571                          * manage transactions implicitly; or because user/app.
572                          * has really issued a "BEGIN" command.
573                          * In the first case, we don't need to run the
574                          * corresponding START TRANSACTION command, since the
575                          * HyperSQL engine does this automatically, and can tell
576                          * when it is needed far better than the client; however
577                          * we do use this fact to update our Session autocommit
578                          * state to match the client's notion.
579                          * We ignore the latter case, because real HyperSQL
580                          * user/apps will use "START TRANSACTION", not "BEGIN".
581                          * Therefore, we just update autocommit state and run no
582                          * other command against the engine.
583                          */
584                         sql = sql.equals("BEGIN") ? null
585                                                   : sql.substring(
586                                                       "BEGIN;".length());
587 
588                         server.printWithThread(
589                             "ODBC Trans started.  Session AutoCommit -> F");
590 
591                         try {
592                             session.setAutoCommit(false);
593                         } catch (HsqlException he) {
594                             throw new RecoverableOdbcFailure(
595                                 "Failed to change transaction state: "
596                                 + he.getMessage(), he.getSQLState());
597                         }
598 
599                         // Now just placate the driver
600                         outPacket.write("BEGIN");
601                         outPacket.xmit('C', dataOutput);
602 
603                         if (sql == null) {
604                             sendReadyForQuery = true;
605 
606                             break;
607                         }
608                     }
609 
610                     if (sql.startsWith("SAVEPOINT ") && sql.indexOf(';') > 0) {
611                         int firstSemi = sql.indexOf(';');
612 
613                         server.printWithThread(
614                             "Interposing BEFORE primary statement: "
615                             + sql.substring(0, firstSemi));
616                         odbcExecDirect(sql.substring(0, firstSemi));
617 
618                         sql = sql.substring(firstSemi + 1);
619                     }
620 
621                     lastSemi = sql.lastIndexOf(';');
622 
623                     if (lastSemi > 0) {
624                         String suffix = sql.substring(lastSemi + 1);
625 
626                         if (suffix.startsWith("RELEASE ")) {
627                             interposedStatement = suffix;
628                             sql                 = sql.substring(0, lastSemi);
629                         }
630                     }
631 
632                     /** ******************************************* */
633                     String normalized = sql.trim().toLowerCase();
634 
635                     if (server.isTrace()) {
636                         server.printWithThread("Received query (" + sql + ')');
637                     }
638 
639                     /*
640                      * BEWARE:  We aren't supporting multiple result-sets from a
641                      * compound statement.  Plus, a general requirement is, the
642                      * entire compound statement may return just one result set.
643                      * I don't have time to check how it works elsewhere, but here,
644                      * and for now, the Rowset-generating statement (SELECT, etc.)
645                      * must be first in order for us to detect that we need to
646                      * return a result set.
647                      * If we do parse out the component statement here, the states
648                      * set above apply to all executions, and only one Z packet
649                      * should be sent at the very end.
650                      *
651                      * I find that the Driver can't handle compound statements
652                      * which mix resultset + non-resultset statements (even in
653                      * SIMPLE mode), so we are more capable than our client is.
654                      */
655                     if (normalized.startsWith("select current_schema()")) {
656                         server.printWithThread(
657                             "Implement 'select current_schema() emulation!");
658 
659                         throw new RecoverableOdbcFailure(
660                             "current_schema() not supported yet", "0A000");
661                     }
662 
663                     if (normalized.startsWith("select n.nspname,")) {
664 
665                         // Executed by psqlodbc after every user-specified query.
666                         server.printWithThread(
667                             "Swallowing 'select n.nspname,...'");
668                         outPacket.writeShort(1);              // Num cols.
669                         outPacket.write("oid");
670                         outPacket.writeInt(201);
671                         outPacket.writeShort(1);
672                         outPacket.writeInt(23);
673                         outPacket.writeShort(4);
674                         outPacket.writeInt(-1);
675                         outPacket.writeShort(0);
676                         outPacket.xmit('T', dataOutput);      // Xmit Row Definition
677 
678                         // This query returns no rows.  typenam "lo"??
679                         outPacket.write("SELECT");
680                         outPacket.xmit('C', dataOutput);
681 
682                         sendReadyForQuery = true;
683 
684                         break;
685                     }
686 
687                     if (normalized.startsWith(
688                             "select oid, typbasetype from")) {
689 
690                         // Executed by psqlodbc immediately after connecting.
691                         server.printWithThread(
692                             "Simulating 'select oid, typbasetype...'");
693                         /*
694                          * This query is run as "a hack to get the oid of our
695                          * large object oid type.
696                          */
697                         outPacket.writeShort(2);              // Num cols.
698                         outPacket.write("oid");               // Col. name
699                         outPacket.writeInt(101);              // table ID
700                         outPacket.writeShort(102);            // column id
701                         outPacket.writeInt(26);               // Datatype ID  [adtid]
702                         outPacket.writeShort(4);              // Datatype size  [adtsize]
703                         outPacket.writeInt(-1);               // Var size [atttypmod]
704                         outPacket.writeShort(0);              // text "format code"
705                         outPacket.write("typbasetype");       // Col. name
706                         outPacket.writeInt(101);              // table ID
707                         outPacket.writeShort(103);            // column id
708                         outPacket.writeInt(26);               // Datatype ID  [adtid]
709                         outPacket.writeShort(4);              // Datatype size  [adtsize]
710                         outPacket.writeInt(-1);               // Var size [atttypmod]
711                         outPacket.writeShort(0);              // text "format code"
712                         outPacket.xmit('T', dataOutput);      // sending a Tuple (row)
713 
714                         // This query returns no rows.  typenam "lo"??
715                         outPacket.write("SELECT");
716                         outPacket.xmit('C', dataOutput);
717 
718                         sendReadyForQuery = true;
719 
720                         break;
721                     }
722 
723                     if (normalized.startsWith("select ")) {
724                         server.printWithThread(
725                             "Performing a real non-prepared SELECT...");
726 
727                         r = Result.newExecuteDirectRequest();
728 
729                         r.setPrepareOrExecuteProperties(
730                             sql, 0, 0, StatementTypes.RETURN_RESULT, 0,
731                             ResultProperties.defaultPropsValue,
732                             java.sql.Statement.NO_GENERATED_KEYS, null, null);
733 
734                         rOut = session.execute(r);
735 
736                         switch (rOut.getType()) {
737 
738                             case ResultConstants.DATA :
739                                 break;
740 
741                             case ResultConstants.ERROR :
742                                 throw new RecoverableOdbcFailure(rOut);
743                             default :
744                                 throw new RecoverableOdbcFailure(
745                                     "Output Result from Query execution is of "
746                                     + "unexpected type: " + rOut.getType());
747                         }
748 
749                         // See Result.newDataHeadResult() for what we have here
750                         // .metaData, .navigator
751                         RowSetNavigator navigator = rOut.getNavigator();
752                         ResultMetaData  md        = rOut.metaData;
753 
754                         if (md == null) {
755                             throw new RecoverableOdbcFailure(
756                                 "Failed to get metadata for query results");
757                         }
758 
759                         int      columnCount = md.getColumnCount();
760                         String[] colLabels   = md.getGeneratedColumnNames();
761 
762                         colTypes = md.columnTypes;
763                         pgTypes  = new PgType[columnCount];
764 
765                         for (int i = 0; i < pgTypes.length; i++) {
766                             pgTypes[i] = PgType.getPgType(colTypes[i],
767                                                           md.isTableColumn(i));
768                         }
769 
770                         // fredt : colLabels may not contain some column names
771                         // colDefs is used when no label is present:
772                         // SELECT TABLECOL AS COLLABLE has both name and label
773                         // SELECT TABLECOL has name 'TABLECOL'
774                         // SELECT 2 AS CONST has label 'CONST'
775                         ColumnBase[] colDefs = md.columns;
776 
777                         // Num cols.
778                         outPacket.writeShort(columnCount);
779 
780                         for (int i = 0; i < columnCount; i++) {
781 
782                             // col name
783                             if (colLabels[i] != null) {
784                                 outPacket.write(colLabels[i]);
785                             } else {
786                                 outPacket.write(colDefs[i].getNameString());
787                             }
788 
789                             // table ID  [relid]:
790                             outPacket.writeInt(OdbcUtil.getTableOidForColumn(i,
791                                     md));
792 
793                             // column id  [attid]
794                             outPacket.writeShort(OdbcUtil.getIdForColumn(i,
795                                     md));
796                             outPacket.writeInt(pgTypes[i].getOid());
797 
798                             // Datatype size  [adtsize]
799                             outPacket.writeShort(pgTypes[i].getTypeWidth());
800                             outPacket.writeInt(pgTypes[i].getLPConstraint());
801 
802                             // Var size [atttypmod]
803                             // This is the size constraint integer
804                             // like VARCHAR(12) or DECIMAL(4).
805                             // -1 if none specified for this column.
806                             outPacket.writeShort(0);
807 
808                             // format code, 0 = text column, 1 = binary column,
809                             // but entirely ignored by our driver.
810                             // Would only be non-0 if a 'B' command requested it.
811                         }
812 
813                         outPacket.xmit('T', dataOutput);      // Xmit Row Definition
814 
815                         int rowNum = 0;
816 
817                         while (navigator.next()) {
818                             rowNum++;
819 
820                             Object[] rowData = navigator.getCurrent();
821 
822                             // Row.getData().  Don't know why *Data.getCurrent()
823                             //                 method returns Object instead of O[].
824                             //  TODO:  Remove the assertion here:
825                             if (rowData == null) {
826                                 throw new RecoverableOdbcFailure("Null row?");
827                             }
828 
829                             if (rowData.length < columnCount) {
830                                 throw new RecoverableOdbcFailure(
831                                     "Data element mismatch. " + columnCount
832                                     + " metadata cols, yet " + rowData.length
833                                     + " data elements for row " + rowNum);
834                             }
835 
836                             //server.printWithThread("Row " + rowNum + " has "
837                             //+ rowData.length + " elements");
838                             outPacket.writeShort(columnCount);
839 
840                             // This field is just swallowed by PG ODBC
841                             // client, but OdbcUtil.validated by psql.
842                             for (int i = 0; i < columnCount; i++) {
843                                 if (rowData[i] == null) {
844                                     /*
845                                     server.printWithThread("R" + rowNum + "C"
846                                         + (i+1) + " => [null]");
847                                     */
848                                     outPacket.writeInt(-1);
849                                 } else {
850                                     dataString =
851                                         pgTypes[i].valueString(rowData[i]);
852 
853                                     outPacket.writeSized(dataString);
854 
855                                     if (server.isTrace()) {
856                                         server.printWithThread(
857                                             "R" + rowNum + "C" + (i + 1)
858                                             + " => ("
859                                             + rowData[i].getClass().getName()
860                                             + ") [" + dataString + ']');
861                                     }
862                                 }
863                             }
864 
865                             outPacket.xmit('D', dataOutput);
866                         }
867 
868                         outPacket.write("SELECT");
869                         outPacket.xmit('C', dataOutput);
870 
871                         sendReadyForQuery = true;
872 
873                         break;
874                     }
875 
876                     if (normalized.startsWith("deallocate \"")
877                             && normalized.charAt(normalized.length() - 1)
878                                == '"') {
879                         tmpStr = sql.trim().substring(
880                             "deallocate \"".length()).trim();
881 
882                         // Must use "sql" directly since name is case-sensitive
883                         handle = tmpStr.substring(0, tmpStr.length() - 1);
884                         odbcPs = (OdbcPreparedStatement) sessionOdbcPsMap.get(
885                             handle);
886 
887                         if (odbcPs != null) {
888                             odbcPs.close();
889                         }
890 
891                         portal =
892                             (StatementPortal) sessionOdbcPortalMap.get(handle);
893 
894                         if (portal != null) {
895                             portal.close();
896                         }
897 
898                         if (odbcPs == null && portal == null) {
899                             /*
900                             throw new RecoverableOdbcFailure(null,
901                                 "No object present for handle: " + handle, "08P01");
902                             Driver does not handle state change correctly, so
903                             for now we just issue a warning:
904                             OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR,
905                                 "No object present for handle: " + handle,
906                                 dataOutput);
907                             TODO:  Retest this.  May have been side-effect of
908                                    other problems.
909                             */
910                             server.printWithThread(
911                                 "Ignoring bad 'DEALLOCATE' cmd");
912                         }
913 
914                         if (server.isTrace()) {
915                             server.printWithThread("Deallocated PS/Portal '"
916                                                    + handle + "'");
917                         }
918 
919                         outPacket.write("DEALLOCATE");
920                         outPacket.xmit('C', dataOutput);
921 
922                         sendReadyForQuery = true;
923 
924                         break;
925                     }
926 
927                     if (normalized.startsWith("set client_encoding to ")) {
928                         server.printWithThread("Stubbing EXECDIR for: " + sql);
929                         outPacket.write("SET");
930                         outPacket.xmit('C', dataOutput);
931 
932                         sendReadyForQuery = true;
933 
934                         break;
935                     }
936 
937                     // Case below is non-String-matched Qs:
938                     server.printWithThread("Performing a real EXECDIRECT...");
939                     odbcExecDirect(sql);
940 
941                     sendReadyForQuery = true;
942                     break;
943 
944                 case 'X' :                                    // Terminate packet
945                     if (sessionOdbcPsMap.size()
946                             > (sessionOdbcPsMap.containsKey("") ? 1
947                                                                 : 0)) {
948                         server.printWithThread("Client left "
949                                                + sessionOdbcPsMap.size()
950                                                + " PS objects open");
951                     }
952 
953                     if (sessionOdbcPortalMap.size()
954                             > (sessionOdbcPortalMap.containsKey("") ? 1
955                                                                     : 0)) {
956                         server.printWithThread("Client left "
957                                                + sessionOdbcPortalMap.size()
958                                                + " Portal objects open");
959                     }
960 
961                     OdbcUtil.validateInputPacketSize(inPacket);
962 
963                     throw cleanExit;
964                 case 'H' :                                    // Flush packet
965 
966                     // No-op.  It is impossible to cache while supporting multiple
967                     // ps and portal objects, so there is nothing for a Flush to
968                     // do.  There isn't even a reply to a Flush packet.
969                     break;
970 
971                 case 'S' :                                    // Sync packet
972 
973                     // Special case for Sync packets.
974                     // To facilitate recovery, we do not abort in case of problems.
975                     if (session.isAutoCommit()) {
976                         try {
977 
978                             // I don't see how this can be useful.  If we ran DML, it
979                             // will have autocommitted.  If we have just switched to
980                             // autoCommit mode, then according to spec we must have
981                             // executed an implicit commit then.
982                             server.printWithThread(
983                                 "Silly implicit commit by Sync");
984                             session.commit(true);
985 
986                             // TODO:  Find out if chain param should be T or F.
987                         } catch (HsqlException he) {
988                             server.printWithThread("Implicit commit failed: "
989                                                    + he);
990                             OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR,
991                                                  "Implicit commit failed",
992                                                  he.getSQLState(), dataOutput);
993                         }
994                     }
995 
996                     sendReadyForQuery = true;
997                     break;
998 
999                 case 'P' :                                    // Parse packet
1000                     psHandle = inPacket.readString();
1001 
1002                     String query = OdbcUtil.revertMungledPreparedQuery(
1003                         inPacket.readString());
1004 
1005                     paramCount = inPacket.readUnsignedShort();
1006 
1007                     for (int i = 0; i < paramCount; i++) {
1008                         if (inPacket.readInt() != 0) {
1009                             throw new RecoverableOdbcFailure(
1010                                 null,
1011                                 "Parameter-type OID specifiers not supported yet",
1012                                 "0A000");
1013                         }
1014                     }
1015 
1016                     if (server.isTrace()) {
1017                         server.printWithThread(
1018                             "Received Prepare request for query (" + query
1019                             + ") with handle '" + psHandle + "'");
1020                     }
1021 
1022                     if (psHandle.length() > 0
1023                             && sessionOdbcPsMap.containsKey(psHandle)) {
1024                         throw new RecoverableOdbcFailure(
1025                             null,
1026                             "PS handle '" + psHandle + "' already in use.  "
1027                             + "You must close it before recreating", "08P01");
1028                     }
1029 
1030                     new OdbcPreparedStatement(psHandle, query,
1031                                               sessionOdbcPsMap, session);
1032                     outPacket.xmit('1', dataOutput);
1033                     break;
1034 
1035                 case 'D' :                                    // Describe packet
1036                     c      = inPacket.readByteChar();
1037                     handle = inPacket.readString();
1038                     odbcPs = null;
1039                     portal = null;
1040 
1041                     if (c == 'S') {
1042                         odbcPs = (OdbcPreparedStatement) sessionOdbcPsMap.get(
1043                             handle);
1044                     } else if (c == 'P') {
1045                         portal =
1046                             (StatementPortal) sessionOdbcPortalMap.get(handle);
1047                     } else {
1048                         throw new RecoverableOdbcFailure(
1049                             null,
1050                             "Description packet request type invalid: " + c,
1051                             "08P01");
1052                     }
1053 
1054                     if (server.isTrace()) {
1055                         server.printWithThread("Received Describe request for "
1056                                                + c + " of  handle '" + handle
1057                                                + "'");
1058                     }
1059 
1060                     if (odbcPs == null && portal == null) {
1061                         throw new RecoverableOdbcFailure(
1062                             null,
1063                             "No object present for " + c + " handle: "
1064                             + handle, "08P01");
1065                     }
1066 
1067                     Result ackResult = (odbcPs == null) ? portal.ackResult
1068                                                         : odbcPs.ackResult;
1069 
1070                     pmd        = ackResult.parameterMetaData;
1071                     paramCount = pmd.getColumnCount();
1072 
1073                     Type[] paramTypes = pmd.getParameterTypes();
1074 
1075                     if (paramCount != paramTypes.length) {
1076                         throw new RecoverableOdbcFailure(
1077                             "Parameter count mismatch.  Count of "
1078                             + paramCount + " reported, but there are "
1079                             + paramTypes.length + " param md objects");
1080                     }
1081 
1082                     if (c == 'S') {
1083                         outPacket.writeShort(paramCount);
1084 
1085                         for (int i = 0; i < paramTypes.length; i++) {
1086                             outPacket.writeInt(
1087                                 PgType.getPgType(
1088                                     paramTypes[i], true).getOid());
1089 
1090                             // TODO:  Determine whether parameter typing works
1091                             // better for Strings when try to match table column
1092                             // or not.  2nd param to getPgType().
1093                         }
1094 
1095                         outPacket.xmit('t', dataOutput);
1096 
1097                         // ParameterDescription packet
1098                     }
1099 
1100                     ResultMetaData md = ackResult.metaData;
1101 
1102                     if (md.getColumnCount() < 1) {
1103                         if (server.isTrace()) {
1104                             server.printWithThread(
1105                                 "Non-rowset query so returning NoData packet");
1106                         }
1107 
1108                         // Send NoData packet because no columnar output from
1109                         // this statement.
1110                         outPacket.xmit('n', dataOutput);
1111 
1112                         break;
1113                     }
1114 
1115                     // TODO:
1116                     // May need to pass the extra BIGINT pseudo-column for
1117                     // updatable-row or other purposes.  In that case, it may
1118                     // make sense to use getExtendedColumnCount(), etc.
1119                     String[] colNames = md.getGeneratedColumnNames();
1120 
1121                     if (md.getColumnCount() != colNames.length) {
1122                         throw new RecoverableOdbcFailure(
1123                             "Couldn't get all column names: "
1124                             + md.getColumnCount() + " cols. but only got "
1125                             + colNames.length + " col. names");
1126                     }
1127 
1128                     colTypes = md.columnTypes;
1129                     pgTypes  = new PgType[colNames.length];
1130 
1131                     ColumnBase[] colDefs = md.columns;
1132 
1133                     for (int i = 0; i < pgTypes.length; i++) {
1134                         pgTypes[i] = PgType.getPgType(colTypes[i],
1135                                                       md.isTableColumn(i));
1136                     }
1137 
1138                     if (colNames.length != colDefs.length) {
1139                         throw new RecoverableOdbcFailure(
1140                             "Col data mismatch.  " + colDefs.length
1141                             + " col instances but " + colNames.length
1142                             + " col names");
1143                     }
1144 
1145                     outPacket.writeShort(colNames.length);    // Num cols.
1146 
1147                     for (int i = 0; i < colNames.length; i++) {
1148                         outPacket.write(colNames[i]);         // Col. name
1149 
1150                         // table ID  [relid]:
1151                         outPacket.writeInt(OdbcUtil.getTableOidForColumn(i,
1152                                 md));
1153 
1154                         // column id  [attid]
1155                         outPacket.writeShort(OdbcUtil.getIdForColumn(i, md));
1156                         outPacket.writeInt(pgTypes[i].getOid());
1157 
1158                         // Datatype size  [adtsize]
1159                         outPacket.writeShort(pgTypes[i].getTypeWidth());
1160                         outPacket.writeInt(pgTypes[i].getLPConstraint());
1161 
1162                         // Var size [atttypmod]
1163                         // This is the size constraint integer
1164                         // like VARCHAR(12) or DECIMAL(4).
1165                         // -1 if none specified for this column.
1166                         outPacket.writeShort(0);
1167 
1168                         // format code, 0 = text column, 1 = binary column,
1169                         // but entirely ignored by our driver.
1170                         // Would only be non-0 if a 'B' command requested it.
1171                     }
1172 
1173                     outPacket.xmit('T', dataOutput);          // Xmit Row Definition
1174                     break;
1175 
1176                 case 'B' :                                    // Bind packet
1177                     portalHandle = inPacket.readString();
1178                     psHandle     = inPacket.readString();
1179 
1180                     int       paramFormatCount = inPacket.readUnsignedShort();
1181                     boolean[] paramBinary      = new boolean[paramFormatCount];
1182 
1183                     for (int i = 0; i < paramFormatCount; i++) {
1184                         paramBinary[i] = inPacket.readUnsignedShort() != 0;
1185 
1186                         if (server.isTrace() && paramBinary[i]) {
1187                             server.printWithThread("Binary param #" + i);
1188                         }
1189                     }
1190 
1191                     paramCount = inPacket.readUnsignedShort();
1192 
1193                     Object[] paramVals = new Object[paramCount];
1194 
1195                     for (int i = 0; i < paramVals.length; i++) {
1196                         if (i < paramBinary.length && paramBinary[i]) {
1197                             paramVals[i] = inPacket.readSizedBinaryData();
1198                         } else {
1199                             paramVals[i] = inPacket.readSizedString();
1200                         }
1201                     }
1202 
1203                     int outFormatCount = inPacket.readUnsignedShort();
1204 
1205                     for (int i = 0; i < outFormatCount; i++) {
1206                         if (inPacket.readUnsignedShort() != 0) {
1207                             throw new RecoverableOdbcFailure(
1208                                 null, "Binary output values not supported",
1209                                 "0A000");
1210                         }
1211                     }
1212 
1213                     if (server.isTrace()) {
1214                         server.printWithThread(
1215                             "Received Bind request to make Portal from ("
1216                             + psHandle + ")' with handle '" + portalHandle
1217                             + "'");
1218                     }
1219 
1220                     odbcPs =
1221                         (OdbcPreparedStatement) sessionOdbcPsMap.get(psHandle);
1222 
1223                     if (odbcPs == null) {
1224                         throw new RecoverableOdbcFailure(
1225                             null,
1226                             "No object present for PS handle: " + psHandle,
1227                             "08P01");
1228                     }
1229 
1230                     if (portalHandle.length() > 0
1231                             && sessionOdbcPortalMap.containsKey(
1232                                 portalHandle)) {
1233                         throw new RecoverableOdbcFailure(
1234                             null,
1235                             "Portal handle '" + portalHandle
1236                             + "' already in use.  "
1237                             + "You must close it before recreating", "08P01");
1238                     }
1239 
1240                     pmd = odbcPs.ackResult.parameterMetaData;
1241 
1242                     if (paramCount != pmd.getColumnCount()) {
1243                         throw new RecoverableOdbcFailure(
1244                             null,
1245                             "Client didn't specify all "
1246                             + pmd.getColumnCount() + " parameters ("
1247                             + paramCount + ')', "08P01");
1248                     }
1249 
1250                     new StatementPortal(portalHandle, odbcPs, paramVals,
1251                                         sessionOdbcPortalMap);
1252                     outPacket.xmit('2', dataOutput);
1253                     break;
1254 
1255                 case 'E' :                                    // Execute packet
1256                     portalHandle = inPacket.readString();
1257 
1258                     int fetchRows = inPacket.readInt();
1259 
1260                     if (server.isTrace()) {
1261                         server.printWithThread("Received Exec request for "
1262                                                + fetchRows
1263                                                + " rows from portal handle '"
1264                                                + portalHandle + "'");
1265                     }
1266 
1267                     portal = (StatementPortal) sessionOdbcPortalMap.get(
1268                         portalHandle);
1269 
1270                     if (portal == null) {
1271                         throw new RecoverableOdbcFailure(
1272                             null,
1273                             "No object present for Portal handle: "
1274                             + portalHandle, "08P01");
1275                     }
1276 
1277                     // result properties means readonly, not holdable
1278                     portal.bindResult.setPreparedExecuteProperties(
1279                         portal.parameters, fetchRows, 0, 0, 0);
1280 
1281                     // 0 for maxRows means unlimited.  Same for fetchRows.
1282                     rOut = session.execute(portal.bindResult);
1283 
1284                     switch (rOut.getType()) {
1285 
1286                         case ResultConstants.UPDATECOUNT :
1287                             outPacket.write(
1288                                 OdbcUtil.echoBackReplyString(
1289                                     portal.lcQuery, rOut.getUpdateCount()));
1290                             outPacket.xmit('C', dataOutput);
1291 
1292                             // end of rows (B or D packets)
1293                             // This keeps session.autoUpdate in sync with client's
1294                             // notion of transaction state.
1295                             if (portal.lcQuery.equals("commit")
1296                                     || portal.lcQuery.startsWith("commit ")
1297                                     || portal.lcQuery.equals("rollback")
1298                                     || portal.lcQuery.startsWith(
1299                                         "rollback ")) {
1300                                 try {
1301                                     session.setAutoCommit(true);
1302                                 } catch (HsqlException he) {
1303                                     throw new RecoverableOdbcFailure(
1304                                         "Failed to change transaction state: "
1305                                         + he.getMessage(), he.getSQLState());
1306                                 }
1307                             }
1308                             break MAIN_ODBC_COMM_SWITCH;
1309 
1310                         case ResultConstants.DATA :
1311                             break;
1312 
1313                         case ResultConstants.ERROR :
1314                             throw new RecoverableOdbcFailure(rOut);
1315                         default :
1316                             throw new RecoverableOdbcFailure(
1317                                 "Output Result from Portal execution is of "
1318                                 + "unexpected type: " + rOut.getType());
1319                     }
1320 
1321                     // See Result.newDataHeadResult() for what we have here
1322                     // .metaData, .navigator
1323                     RowSetNavigator navigator = rOut.getNavigator();
1324                     int             rowNum    = 0;
1325                     int colCount = portal.ackResult.metaData.getColumnCount();
1326 
1327                     while (navigator.next()) {
1328                         rowNum++;
1329 
1330                         Object[] rowData = navigator.getCurrent();
1331 
1332                         if (rowData == null) {
1333                             throw new RecoverableOdbcFailure("Null row?");
1334                         }
1335 
1336                         if (rowData.length < colCount) {
1337                             throw new RecoverableOdbcFailure(
1338                                 "Data element mismatch. " + colCount
1339                                 + " metadata cols, yet " + rowData.length
1340                                 + " data elements for row " + rowNum);
1341                         }
1342 
1343                         //server.printWithThread("Row " + rowNum + " has "
1344                         //+ rowData.length + " elements");
1345                         outPacket.writeShort(colCount);
1346 
1347                         // This field is just swallowed by PG ODBC
1348                         // client, but validated by psql.
1349                         colTypes = portal.ackResult.metaData.columnTypes;
1350                         pgTypes  = new PgType[colCount];
1351 
1352                         for (int i = 0; i < pgTypes.length; i++) {
1353                             pgTypes[i] = PgType.getPgType(
1354                                 colTypes[i],
1355                                 portal.ackResult.metaData.isTableColumn(i));
1356                         }
1357 
1358                         for (int i = 0; i < colCount; i++) {
1359                             if (rowData[i] == null) {
1360                                 /*
1361                                 server.printWithThread("R" + rowNum + "C"
1362                                     + (i+1) + " => [null]");
1363                                 */
1364                                 outPacket.writeInt(-1);
1365                             } else {
1366                                 dataString =
1367                                     pgTypes[i].valueString(rowData[i]);
1368 
1369                                 outPacket.writeSized(dataString);
1370 
1371                                 if (server.isTrace()) {
1372                                     server.printWithThread(
1373                                         "R" + rowNum + "C" + (i + 1) + " => ("
1374                                         + rowData[i].getClass().getName()
1375                                         + ") [" + dataString + ']');
1376                                 }
1377                             }
1378                         }
1379 
1380                         outPacket.xmit('D', dataOutput);
1381                     }
1382 
1383                     if (navigator.afterLast()) {
1384                         outPacket.write("SELECT");
1385                         outPacket.xmit('C', dataOutput);
1386 
1387                         // end of rows (B or D packets)
1388                     } else {
1389                         outPacket.xmit('s', dataOutput);
1390                     }
1391 
1392                     // N.b., we return.
1393                     // You might think that this completion of an EXTENDED sequence
1394                     // would end in ReadyForQuery/Z, but no.
1395                     break;
1396 
1397                 case 'C' :                                    // Close packet
1398                     c      = inPacket.readByteChar();
1399                     handle = inPacket.readString();
1400                     odbcPs = null;
1401                     portal = null;
1402 
1403                     if (c == 'S') {
1404                         odbcPs = (OdbcPreparedStatement) sessionOdbcPsMap.get(
1405                             handle);
1406 
1407                         if (odbcPs != null) {
1408                             odbcPs.close();
1409                         }
1410                     } else if (c == 'P') {
1411                         portal =
1412                             (StatementPortal) sessionOdbcPortalMap.get(handle);
1413 
1414                         if (portal != null) {
1415                             portal.close();
1416                         }
1417                     } else {
1418                         throw new RecoverableOdbcFailure(
1419                             null,
1420                             "Description packet request type invalid: " + c,
1421                             "08P01");
1422                     }
1423 
1424                     // TODO:  Try sending a warning to client for both == null.
1425                     // Broke things earlier, but that may have been due to
1426                     // other problems.
1427                     if (server.isTrace()) {
1428                         server.printWithThread("Closed " + c + " '" + handle
1429                                                + "'? "
1430                                                + (odbcPs != null
1431                                                   || portal != null));
1432                     }
1433 
1434                     outPacket.xmit('3', dataOutput);
1435                     break;
1436 
1437                 default :
1438                     throw new RecoverableOdbcFailure(
1439                         null,
1440                         "Unsupported operation type (" + inPacket.packetType
1441                         + ')', "0A000");
1442             }
1443 
1444             OdbcUtil.validateInputPacketSize(inPacket);
1445 
1446             if (interposedStatement != null) {
1447                 server.printWithThread("Interposing AFTER primary statement: "
1448                                        + interposedStatement);
1449                 odbcExecDirect(interposedStatement);
1450             }
1451 
1452             if (sendReadyForQuery) {
1453                 outPacket.reset();
1454 
1455                 // The reset is unnecessary now.  For safety in case somebody
1456                 // codes something above which may abort processing of a
1457                 // packet before xmit.
1458                 outPacket.writeByte(session.isAutoCommit() ? 'I'
1459                                                            : 'T');
1460                 outPacket.xmit('Z', dataOutput);
1461             }
1462         } catch (RecoverableOdbcFailure rf) {
1463             Result errorResult = rf.getErrorResult();
1464 
1465             if (errorResult == null) {
1466                 String stateCode = rf.getSqlStateCode();
1467                 String svrMsg    = rf.toString();
1468                 String cliMsg    = rf.getClientMessage();
1469 
1470                 if (server.isTrace()) {
1471                     server.printWithThread(svrMsg);
1472                 }
1473 
1474                 if (cliMsg != null) {
1475                     OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR, cliMsg,
1476                                          stateCode, dataOutput);
1477                 }
1478             } else {
1479                 if (server.isTrace()) {
1480                     server.printWithThread("Result object error: "
1481                                            + errorResult.getMainString());
1482                 }
1483 
1484                 // This class of error is not considered a Server problem, so
1485                 // we don't log on the server side.
1486                 OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR,
1487                                      errorResult.getMainString(),
1488                                      errorResult.getSubString(), dataOutput);
1489             }
1490 
1491             switch (odbcCommMode) {
1492 
1493                 case OdbcUtil.ODBC_SIMPLE_MODE :
1494                     outPacket.reset();                        /// transaction status = Error
1495                     outPacket.writeByte('E');                 /// transaction status = Error
1496 
1497                     // TODO:  Consider keeping this state until the session
1498                     // is either committed or rolled back.
1499                     // (Right now we just return 'E' here, then revert to
1500                     // I or T).
1501                     outPacket.xmit('Z', dataOutput);
1502                     break;
1503 
1504                 case OdbcUtil.ODBC_EXTENDED_MODE :
1505                     odbcCommMode = OdbcUtil.ODBC_EXT_RECOVER_MODE;
1506 
1507                     server.printWithThread("Reverting to EXT_RECOVER mode");
1508                     break;
1509             }
1510         }
1511     }
1512 
1513     /**
1514      * Initializes this connection and runs the request handling
1515      * loop until closed.
1516      */
run()1517     public void run() {
1518 
1519         int msgType;
1520 
1521         init();
1522 
1523         if (session != null) {
1524             try {
1525                 while (keepAlive) {
1526                     msgType = dataInput.readByte();
1527 
1528                     if (msgType < ResultConstants.MODE_UPPER_LIMIT) {
1529                         receiveResult(msgType);
1530                     } else {
1531                         receiveOdbcPacket((char) msgType);
1532                     }
1533                 }
1534             } catch (CleanExit ce) {
1535                 keepAlive = false;
1536             } catch (IOException e) {
1537 
1538                 // fredt - is thrown when connection drops
1539                 server.printWithThread(mThread + ":disconnected " + user);
1540             } catch (HsqlException e) {
1541 
1542                 // fredt - is thrown in unforeseen circumstances
1543                 if (keepAlive) {
1544                     server.printStackTrace(e);
1545                 }
1546             } catch (Throwable e) {
1547 
1548                 // fredt - is thrown in unforeseen circumstances
1549                 if (keepAlive) {
1550                     server.printStackTrace(e);
1551                 }
1552             }
1553         }
1554 
1555         close();
1556     }
1557 
setDatabase(Result resultIn)1558     private Result setDatabase(Result resultIn) {
1559 
1560         try {
1561             String databaseName = resultIn.getDatabaseName();
1562 
1563             dbIndex = server.getDBIndex(databaseName);
1564             dbID    = server.dbID[dbIndex];
1565             user    = resultIn.getMainString();
1566 
1567             if (!server.isSilent()) {
1568                 server.printWithThread(mThread + ":Trying to connect user '"
1569                                        + user + "' to DB (" + databaseName
1570                                        + ')');
1571             }
1572 
1573             session = DatabaseManager.newSession(dbID, user,
1574                                                  resultIn.getSubString(),
1575                                                  resultIn.getZoneString(),
1576                                                  resultIn.getUpdateCount());
1577 
1578             if (!server.isSilent()) {
1579                 server.printWithThread(mThread + ":Connected user '" + user
1580                                        + "'");
1581             }
1582 
1583             return Result.newConnectionAcknowledgeResponse(session);
1584         } catch (HsqlException e) {
1585             session = null;
1586 
1587             return Result.newErrorResult(e);
1588         } catch (RuntimeException e) {
1589             session = null;
1590 
1591             return Result.newErrorResult(e);
1592         }
1593     }
1594 
cancelStatement(Result resultIn)1595     private Result cancelStatement(Result resultIn) {
1596 
1597         try {
1598             dbID = resultIn.getDatabaseId();
1599 
1600             long sessionId = resultIn.getSessionId();
1601 
1602             session = DatabaseManager.getSession(dbID, sessionId);
1603 
1604             if (!server.isSilent()) {
1605                 server.printWithThread(mThread
1606                                        + ":Trying to cancel statement "
1607                                        + " to DB (" + dbID + ')');
1608             }
1609 
1610             return session.cancel(resultIn);
1611         } catch (HsqlException e) {
1612             session = null;
1613 
1614             return Result.updateZeroResult;
1615         } catch (Throwable t) {
1616             session = null;
1617 
1618             return Result.updateZeroResult;
1619         }
1620     }
1621 
1622     /**
1623      * Retrieves the thread name to be used  when
1624      * this object is the Runnable object of a Thread.
1625      *
1626      * @return the thread name to be used  when this object is the Runnable
1627      * object of a Thread.
1628      */
getConnectionThreadName()1629     String getConnectionThreadName() {
1630         return "HSQLDB Connection @" + Integer.toString(hashCode(), 16);
1631     }
1632 
1633     /**
1634      * Don't want this too high, or users may give up before seeing the
1635      * banner.  Can't be too low or we could close a valid but slow
1636      * client connection.
1637      */
1638     public static long MAX_WAIT_FOR_CLIENT_DATA   = 1000;    // ms.
1639     public static long CLIENT_DATA_POLLING_PERIOD = 100;     // ms.
1640 
1641     /**
1642      * The only known case where a connection attempt will get stuck is
1643      * if client connects with hsqls to a https server; or
1644      * hsql to a http server.
1645      * All other client X server combinations are handled gracefully.
1646      * <P/>
1647      * If returns (a.o.t. throws), then state variable streamProtocol will
1648      * be set.
1649      *
1650      * @return int read as first thing off of stream
1651      */
handshake()1652     public int handshake() throws IOException {
1653 
1654         long clientDataDeadline = new java.util.Date().getTime()
1655                                   + MAX_WAIT_FOR_CLIENT_DATA;
1656 
1657         if (!(socket instanceof javax.net.ssl.SSLSocket)) {
1658 
1659             // available() does not work for SSL socket input stream
1660             do {
1661                 try {
1662                     Thread.sleep(CLIENT_DATA_POLLING_PERIOD);
1663                 } catch (InterruptedException ie) {}
1664             } while (dataInput.available() < 5
1665                      && new java.util.Date().getTime() < clientDataDeadline);
1666 
1667             // Old HSQLDB clients will send resultType byte + 4 length bytes
1668             // New HSQLDB clients will send NCV int + above = 9 bytes
1669             // ODBC clients will send a much larger StartupPacket
1670             if (dataInput.available() < 1) {
1671                 dataOutput.write(
1672                     (TEXTBANNER_PART1
1673                      + ClientConnection.NETWORK_COMPATIBILITY_VERSION
1674                      + TEXTBANNER_PART2 + '\n').getBytes());
1675                 dataOutput.flush();
1676 
1677                 throw Error.error(ErrorCode.SERVER_UNKNOWN_CLIENT);
1678             }
1679         }
1680 
1681         int firstInt = dataInput.readInt();
1682 
1683         switch (firstInt >> 24) {
1684 
1685             case 80 :    // Empirically
1686                 server.print(
1687                     "Rejected attempt from client using hsql HTTP protocol");
1688 
1689                 return 0;
1690 
1691             case 0 :
1692 
1693                 // For ODBC protocol, this is the first byte of a 4-byte int
1694                 // size.  The size can never be large enough that the first
1695                 // byte will be non-zero.
1696                 streamProtocol = ODBC_STREAM_PROTOCOL;
1697                 break;
1698 
1699             default :
1700                 streamProtocol = HSQL_STREAM_PROTOCOL;
1701 
1702             // HSQL protocol client
1703         }
1704 
1705         return firstInt;
1706     }
1707 
odbcConnect(int firstInt)1708     private void odbcConnect(int firstInt) throws IOException {
1709 
1710         /* Until client receives teh ReadyForQuery packet at the end of this
1711          * method, we (the server) initiate all packet exchanges. */
1712         int major = dataInput.readUnsignedShort();
1713         int minor = dataInput.readUnsignedShort();
1714 
1715         // Can just return to fail, until the value of "session" is set below.
1716         if (major == 1 && minor == 7) {
1717 
1718             // This is what old HyperSQL versions always send
1719             // TODO:  Consider sending client a 1.8-compatible SQLException
1720             server.print("A pre-version 2.0 client attempted to connect.  "
1721                          + "We rejected them.");
1722 
1723             return;
1724         }
1725 
1726         if (major == 1234 && minor == 5679) {
1727 
1728             // No reason to pay any attention to the size header in this case.
1729             dataOutput.writeByte('N');    // SSL not supported yet
1730 
1731             // TODO:  Implement SSL here (and reply with 'S')
1732             odbcConnect(dataInput.readInt());
1733 
1734             return;
1735         }
1736 
1737         if (major == 1234 && minor == 5678) {
1738 
1739             // No reason to pay any attention to the size header in this case.
1740             if (firstInt != 16) {
1741                 server.print(
1742                     "ODBC cancellation request sent wrong packet length: "
1743                     + firstInt);
1744             }
1745 
1746             server.print(
1747                 "Got an ODBC cancellation request for thread ID "
1748                 + dataInput.readInt() + ", but we don't support "
1749                 + "OOB cancellation yet.  "
1750                 + "Ignoring this request and closing the connection.");
1751 
1752             // N.b.,  Spec says to NOT reply to client in this case.
1753             return;
1754         }
1755 
1756         server.printWithThread("ODBC client connected.  "
1757                                + "ODBC Protocol Compatibility Version "
1758                                + major + '.' + minor);
1759 
1760         OdbcPacketInputStream inPacket =
1761             OdbcPacketInputStream.newOdbcPacketInputStream('\0', dataInput,
1762                 firstInt - 8);
1763 
1764         // - 4 for size of firstInt - 2 for major - 2 for minor
1765         java.util.Map stringPairs = inPacket.readStringPairs();
1766 
1767         if (server.isTrace()) {
1768             server.print("String Pairs from ODBC client: " + stringPairs);
1769         }
1770 
1771         try {
1772             try {
1773                 OdbcUtil.validateInputPacketSize(inPacket);
1774             } catch (RecoverableOdbcFailure rf) {
1775 
1776                 // In this case, we do not treat it as recoverable
1777                 throw new ClientFailure(rf.toString(), rf.getClientMessage());
1778             }
1779 
1780             inPacket.close();
1781 
1782             if (!stringPairs.containsKey("database")) {
1783                 throw new ClientFailure("Client did not identify database",
1784                                         "Target database not identified");
1785             }
1786 
1787             if (!stringPairs.containsKey("user")) {
1788                 throw new ClientFailure("Client did not identify user",
1789                                         "Target account not identified");
1790             }
1791 
1792             String databaseName = (String) stringPairs.get("database");
1793 
1794             user = (String) stringPairs.get("user");
1795 
1796             if (databaseName.equals("/")) {
1797 
1798                 // Work-around because ODBC doesn't allow "" for Database name
1799                 databaseName = "";
1800             }
1801 
1802             /* Unencoded/unsalted authentication */
1803             dataOutput.writeByte('R');
1804             dataOutput.writeInt(8);    //size
1805             dataOutput.writeInt(OdbcUtil.ODBC_AUTH_REQ_PASSWORD);
1806             dataOutput.flush();
1807 
1808             // areq of auth. mode.
1809             char c = '\0';
1810 
1811             try {
1812                 c = (char) dataInput.readByte();
1813             } catch (EOFException eofe) {
1814                 server.printWithThread(
1815                     "Looks like we got a goofy psql no-auth attempt.  "
1816                     + "Will probably retry properly very shortly");
1817 
1818                 return;
1819             }
1820 
1821             if (c != 'p') {
1822                 throw new ClientFailure(
1823                     "Expected password prefix 'p', " + "but got '" + c + "'",
1824                     "Password value not prefixed with 'p'");
1825             }
1826 
1827             int len = dataInput.readInt() - 5;
1828 
1829             // Is password len after -4 for count int -1 for null term
1830             if (len < 0) {
1831                 throw new ClientFailure(
1832                     "Client submitted invalid password length " + len,
1833                     "Invalid password length " + len);
1834             }
1835 
1836             String password = ServerConnection.readNullTermdUTF(len,
1837                 dataInput);
1838 
1839             dbIndex = server.getDBIndex(databaseName);
1840             dbID    = server.dbID[dbIndex];
1841 
1842             if (!server.isSilent()) {
1843                 server.printWithThread(mThread + ":Trying to connect user '"
1844                                        + user + "' to DB (" + databaseName
1845                                        + ')');
1846             }
1847 
1848             try {
1849                 session = DatabaseManager.newSession(dbID, user, password,
1850                                                      null, 0);
1851 
1852                 // TODO:  Find out what updateCount, the last para, is for:
1853                 //                                   resultIn.getUpdateCount());
1854             } catch (Exception e) {
1855                 throw new ClientFailure("User name or password denied: " + e,
1856                                         "Login attempt rejected");
1857             }
1858         } catch (ClientFailure cf) {
1859             server.print(cf.toString());
1860 
1861             // Code below means CONNECTION FAILURE
1862             OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_FATAL,
1863                                  cf.getClientMessage(), "08006", dataOutput);
1864 
1865             return;
1866         }
1867 
1868         outPacket = OdbcPacketOutputStream.newOdbcPacketOutputStream();
1869 
1870         outPacket.writeInt(OdbcUtil.ODBC_AUTH_REQ_OK);    //success
1871         outPacket.xmit('R', dataOutput);                  // Notify client of success
1872 
1873         for (int i = 0; i < OdbcUtil.hardcodedParams.length; i++) {
1874             OdbcUtil.writeParam(OdbcUtil.hardcodedParams[i][0],
1875                                 OdbcUtil.hardcodedParams[i][1], dataOutput);
1876         }
1877 
1878         // If/when we implement OOB cancellation, we would send the
1879         // Session identifier and key here, with a 'K' packet.
1880         outPacket.writeByte('I');           // Trans. status = Not in transaction
1881         outPacket.xmit('Z', dataOutput);    // Notify client of success
1882 
1883         // This ReadyForQuery turns over responsibility to initiate packet
1884         // exchanges to the client.
1885         OdbcUtil.alertClient(
1886             OdbcUtil.ODBC_SEVERITY_INFO,
1887             "MHello\nYou have connected to HyperSQL ODBC Server", dataOutput);
1888         dataOutput.flush();
1889     }
1890 
1891     private java.util.Map sessionOdbcPsMap     = new java.util.HashMap();
1892     private java.util.Map sessionOdbcPortalMap = new java.util.HashMap();
1893 
1894     /**
1895      * Read String directly from dataInput.
1896      *
1897      * @param reqLength Required length
1898      */
readNullTermdUTF(int reqLength, java.io.InputStream istream)1899     private static String readNullTermdUTF(int reqLength,
1900                                            java.io.InputStream istream)
1901                                            throws IOException {
1902 
1903         /* Would be MUCH easier to do this with Java6's String
1904          * encoding/decoding operations */
1905         int    bytesRead = 0;
1906         byte[] ba        = new byte[reqLength + 3];
1907 
1908         ba[0] = (byte) (reqLength >>> 8);
1909         ba[1] = (byte) reqLength;
1910 
1911         while (bytesRead < reqLength + 1) {
1912             bytesRead += istream.read(ba, 2 + bytesRead,
1913                                       reqLength + 1 - bytesRead);
1914         }
1915 
1916         if (ba[ba.length - 1] != 0) {
1917             throw new IOException("String not null-terminated");
1918         }
1919 
1920         for (int i = 2; i < ba.length - 1; i++) {
1921             if (ba[i] == 0) {
1922                 throw new RuntimeException("Null internal to String at offset "
1923                                            + (i - 2));
1924             }
1925         }
1926 
1927         java.io.DataInputStream dis =
1928             new java.io.DataInputStream(new ByteArrayInputStream(ba));
1929         String s = dis.readUTF();
1930 
1931         //String s = java.io.DataInputStream.readUTF(dis);
1932         // TODO:  Test the previous two to see if one works better for
1933         // high-order characters.
1934         dis.close();
1935 
1936         return s;
1937     }
1938 
1939     // Tentative state variable
1940     private int      streamProtocol            = UNDEFINED_STREAM_PROTOCOL;
1941     static final int UNDEFINED_STREAM_PROTOCOL = 0;
1942     static final int HSQL_STREAM_PROTOCOL      = 1;
1943     static final int ODBC_STREAM_PROTOCOL      = 2;
1944     int              odbcCommMode              = OdbcUtil.ODBC_SIMPLE_MODE;
1945 
odbcExecDirect(String inStatement)1946     private void odbcExecDirect(String inStatement)
1947     throws RecoverableOdbcFailure, IOException {
1948 
1949         String statement = inStatement;
1950         String norm      = statement.trim().toLowerCase();
1951 
1952         if (norm.startsWith("release ")
1953                 && !norm.startsWith("release savepoint")) {
1954             server.printWithThread(
1955                 "Transmogrifying 'RELEASE ...' to 'RELEASE SAVEPOINT...");
1956 
1957             statement = statement.trim().substring(0, "release ".length())
1958                         + "SAVEPOINT "
1959                         + statement.trim().substring("release ".length());
1960         }
1961 
1962         Result r = Result.newExecuteDirectRequest();
1963 
1964         r.setPrepareOrExecuteProperties(
1965             statement, 0, 0, StatementTypes.RETURN_COUNT, 0,
1966             ResultProperties.defaultPropsValue,
1967             ResultConstants.RETURN_NO_GENERATED_KEYS, null, null);
1968 
1969         Result rOut = session.execute(r);
1970 
1971         switch (rOut.getType()) {
1972 
1973             case ResultConstants.UPDATECOUNT :
1974                 break;
1975 
1976             case ResultConstants.ERROR :
1977                 throw new RecoverableOdbcFailure(rOut);
1978             default :
1979                 throw new RecoverableOdbcFailure(
1980                     "Output Result from execution is of "
1981                     + "unexpected type: " + rOut.getType());
1982         }
1983 
1984         outPacket.reset();
1985         outPacket.write(OdbcUtil.echoBackReplyString(norm,
1986                 rOut.getUpdateCount()));
1987 
1988         // This keeps session.autoUpdate in sync with client's notion
1989         // of transaction state.
1990         outPacket.xmit('C', dataOutput);
1991 
1992         if (norm.equals("commit") || norm.startsWith("commit ")
1993                 || norm.equals("rollback") || norm.startsWith("rollback ")) {
1994             try {
1995                 session.setAutoCommit(true);
1996             } catch (HsqlException he) {
1997                 throw new RecoverableOdbcFailure(
1998                     "Failed to change transaction state: " + he.getMessage(),
1999                     he.getSQLState());
2000             }
2001         }
2002     }
2003 }
2004