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