All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hsqldb.server.ServerConnection Maven / Gradle / Ivy

There is a newer version: 2.7.4
Show newest version
/*
 * For work developed by the HSQL Development Group:
 *
 * Copyright (c) 2001-2020, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *
 * For work originally developed by the Hypersonic SQL Group:
 *
 * Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 */


package org.hsqldb.server;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.atomic.AtomicLong;

import org.hsqldb.ClientConnection;
import org.hsqldb.ColumnBase;
import org.hsqldb.DatabaseManager;
import org.hsqldb.HsqlException;
import org.hsqldb.Session;
import org.hsqldb.StatementTypes;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.DataOutputStream;
import org.hsqldb.navigator.RowSetNavigator;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.resources.ResourceBundleHandler;
import org.hsqldb.result.Result;
import org.hsqldb.result.ResultConstants;
import org.hsqldb.result.ResultMetaData;
import org.hsqldb.result.ResultProperties;
import org.hsqldb.rowio.RowInputBinary;
import org.hsqldb.rowio.RowOutputBinary;
import org.hsqldb.rowio.RowOutputInterface;
import org.hsqldb.types.Type;

// fredt@users 20020215 - patch 461556 by paul-h@users - server factory
// fredt@users 20020424 - patch 1.7.0 by fredt - shutdown without exit
// fredt@users 20021002 - patch 1.7.1 by fredt - changed notification method
// fredt@users 20030618 - patch 1.7.2 by fredt - changed read/write methods
// fredt@users 20091013 - move set session to null suggested by Otto Joyner

/**
 *  All ServerConnection objects are listed in a Set in server
 *  and removed by this class when closed.

* * When the database or server is shutdown, the signalClose() method is called * for all current ServerConnection instances. This will call the private * close() method. If the ServerConnection thread itself has caused the * shutdown it returns the result of the operation to the client. * (fredt@users)

* * ODBC support added for version 2.0.0 by Blaine Simpson.

* * ODBC support updated for version 2.5.x. (fredt@users)

* * @author Blaine Simpson (unsaved@users dot sourceforge.net * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.5.1 * @since 1.6.2 */ class ServerConnection implements Runnable { boolean keepAlive; private String user; int dbID; int dbIndex; private volatile Session session; private Socket socket; private Server server; private DataInputStream dataInput; private DataOutputStream dataOutput; private long mThread; static final int BUFFER_SIZE = 0x1000; final byte[] mainBuffer = new byte[BUFFER_SIZE]; RowOutputInterface rowOut; RowInputBinary rowIn; Thread runnerThread; InResultProcessor processor; // private static AtomicLong mCurrentThread = new AtomicLong(0); // protected static String TEXTBANNER_PART1 = null; protected static String TEXTBANNER_PART2 = null; static { int serverBundleHandle = ResourceBundleHandler.getBundleHandle( "org_hsqldb_server_Server_messages", null); if (serverBundleHandle < 0) { throw new RuntimeException( "MISSING Resource Bundle. See source code"); // This will be caught before prod release. // Not necessary to localize message. } TEXTBANNER_PART1 = ResourceBundleHandler.getString(serverBundleHandle, "textbanner.part1"); TEXTBANNER_PART2 = ResourceBundleHandler.getString(serverBundleHandle, "textbanner.part2"); if (TEXTBANNER_PART1 == null || TEXTBANNER_PART2 == null) { throw new RuntimeException( "MISSING Resource Bundle msg definition. See source code"); // This will be caught before prod release. // Not necessary to localize message. } } /** * Creates a new ServerConnection to the specified Server on the * specified socket. * * @param socket the network socket on which Server communication * takes place * @param server the Server instance to which the object * represents a connection */ ServerConnection(Socket socket, Server server) { RowOutputBinary rowOutTemp = new RowOutputBinary(mainBuffer); rowIn = new RowInputBinary(rowOutTemp); rowOut = rowOutTemp; // this.socket = socket; this.server = server; this.mThread = mCurrentThread.getAndIncrement(); synchronized (server.serverConnSet) { server.serverConnSet.add(this); } } /** * Signals this object to close, including exiting the thread running * the request handling loop */ void signalClose() { keepAlive = false; if (Thread.currentThread().equals(runnerThread)) { Result resultOut = Result.updateZeroResult; try { resultOut.write(session, dataOutput, rowOut); } catch (Throwable t) {} } close(); } /** * Closes this connection. */ private void close() { if (session != null) { session.close(); session = null; } // fredt@user - closing the socket is to stop this thread synchronized (this) { try { if (socket != null) { socket.close(); socket = null; } } catch (IOException e) {} socket = null; } synchronized (server.serverConnSet) { server.serverConnSet.remove(this); } try { runnerThread.setContextClassLoader(null); } catch (Throwable t) {} } /** * Initializes this connection. *

* Will return (not throw) if fail to initialize the connection. *

*/ private void init() { runnerThread = Thread.currentThread(); keepAlive = true; try { socket.setTcpNoDelay(true); dataInput = new DataInputStream( new BufferedInputStream(socket.getInputStream(), 16 * 1024)); dataOutput = new DataOutputStream(socket.getOutputStream()); int firstInt = handshake(); switch (streamProtocol) { case HSQL_STREAM_PROTOCOL : int msgType = dataInput.readByte(); processor.receiveConnection(msgType); break; case ODBC_STREAM_PROTOCOL : processor.receiveConnection(firstInt); break; default : // Protocol detection failures should already have been // handled. keepAlive = false; } } catch (Exception e) { // Only "unexpected" failures are caught here. // Expected failures will have been handled (by sending feedback // to user-- with an output Result for normal protocols), then // continuing. StringBuilder sb = new StringBuilder(String.valueOf(mThread)); sb.append(":Failed to connect client."); if (user != null) { sb.append(" User '").append(user).append("'."); } sb.append(" Stack trace follows."); server.printWithThread(sb.toString()); server.printStackTrace(e); } } private static class ClientFailure extends Exception { private String clientMessage = null; public ClientFailure(String ourMessage, String clientMessage) { super(ourMessage); this.clientMessage = clientMessage; } public String getClientMessage() { return clientMessage; } } private CleanExit cleanExit = new CleanExit(); private void receiveResult(int resultMode) throws CleanExit, IOException { boolean terminate = false; Result resultIn = Result.newResult(session, resultMode, dataInput, rowIn); resultIn.readLobResults(session, dataInput); server.printRequest(mThread, resultIn); Result resultOut = null; switch (resultIn.getType()) { case ResultConstants.CONNECT : { resultOut = setDatabase(resultIn); break; } case ResultConstants.SQLCANCEL : { resultOut = cancelStatement(resultIn); terminate = true; break; } case ResultConstants.DISCONNECT : { resultOut = Result.updateZeroResult; terminate = true; break; } case ResultConstants.RESETSESSION : { session.resetSession(); resultOut = Result.updateZeroResult; break; } case ResultConstants.EXECUTE_INVALID : { resultOut = Result.newErrorResult(Error.error(ErrorCode.X_07502)); break; } default : { resultOut = session.execute(resultIn); break; } } resultOut.write(session, dataOutput, rowOut); rowOut.reset(mainBuffer); rowIn.resetRow(mainBuffer.length); if (terminate) { throw cleanExit; } } private OdbcPacketOutputStream outPacket = null; private void receiveOdbcPacket(char inC) throws IOException, CleanExit { /* * The driver's notion of the transaction state, I (no) or T (yes), * corresponds precisely inversely to our server-side Session * autoCommit setting. * If the user/app runs in non-autocommit mode and says to run a * COMMIT followed by an INSERT, the driver will handle the user/app's * facade of autocommittedness, and will send the server
    *
  1. COMMIT (which will cause us to set session.setAutoCommit(true) *
  2. BEGIN (which will cause us to set session.setAutoCommit(false) *
  3. INSERT... *
*/ char c; boolean sendReadyForQuery = false; String psHandle, portalHandle, handle, dataString, tmpStr; // Statement which must be executed after the primary statement, but // before sending the ReadyForQuery Z packet. String interposedStatement = null; Result r, rOut; int paramCount, lastSemi; OdbcPreparedStatement odbcPs; StatementPortal portal; ResultMetaData pmd; OdbcPacketInputStream inPacket = null; Type[] colTypes; PgType[] pgTypes; try { inPacket = OdbcPacketInputStream.newOdbcPacketInputStream(inC, dataInput); server.printWithThread("Got op (" + inPacket.packetType + ')'); server.printWithThread("Got packet length of " + inPacket.available() + " + type byte + 4 size header"); if (inPacket.available() >= 1000000000) { throw new IOException("Insane packet length: " + inPacket.available() + " + type byte + 4 size header"); } } catch (SocketException se) { server.printWithThread("Ungraceful client exit: " + se); throw cleanExit; // not "clean", but handled } catch (IOException ioe) { server.printWithThread("Fatal ODBC protocol failure: " + ioe); try { OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_FATAL, ioe.toString(), "08P01", dataOutput); // Code here means Protocol Violation } catch (Exception e) { // We just make an honest effort to notify the client } throw cleanExit; // not "clean", but handled } /** * ODBC Service State Machine (the remainder of this method) */ switch (odbcCommMode) { case OdbcUtil.ODBC_EXT_RECOVER_MODE : if (inPacket.packetType != 'S') { if (server.isTrace()) { server.printWithThread("Ignoring a '" + inPacket.packetType + "'"); } return; } odbcCommMode = OdbcUtil.ODBC_EXTENDED_MODE; server.printWithThread( "EXTENDED comm session being recovered"); // Now the main switch will handle the Sync packet carefully // the same as if there were no recovery. break; case OdbcUtil.ODBC_SIMPLE_MODE : switch (inPacket.packetType) { case 'P' : // This is the only way to make this switch, according // to docs, but that does not allow for intermixing of // static and prepared statement (selects or other). // Therefore we allow all of the following, which works // great. case 'H' : case 'S' : case 'D' : case 'B' : case 'E' : case 'C' : odbcCommMode = OdbcUtil.ODBC_EXTENDED_MODE; server.printWithThread( "Switching mode from SIMPLE to EXTENDED"); // Do not detect unexpected ops here. // In that case, leave the mode as it is, and the main // switch below will handle appropriately. } break; case OdbcUtil.ODBC_EXTENDED_MODE : switch (inPacket.packetType) { case 'Q' : odbcCommMode = OdbcUtil.ODBC_SIMPLE_MODE; server.printWithThread( "Switching mode from EXTENDED to SIMPLE"); // Do not detect unexpected ops here. // In that case, leave the mode as it is, and the main // switch below will handle appropriately. } break; default : throw new RuntimeException("Unexpected ODBC comm mode value: " + odbcCommMode); } outPacket.reset(); try { // Every switch case must either throw or break. // For cases which break // The packet will always be checked to make sure all bytes have // been consumed. // Set boolean sendReadyForQuery to send a Z/ReadyForQuery packet // to client. // DO NOT return early. If you need to abort, that is exceptional // behavior and you should throw an Exception. MAIN_ODBC_COMM_SWITCH: switch (inPacket.packetType) { case 'Q' : // Query packet String sql = inPacket.readString(); // We don't ask for the null terminator /* ********************************************** * These first few cases handle the driver's implicit handling * of transactions. */ if (sql.startsWith("BEGIN;") || sql.equals("BEGIN")) { /* * We may get here because of Driver client trying to * manage transactions implicitly; or because user/app. * has really issued a "BEGIN" command. * In the first case, we don't need to run the * corresponding START TRANSACTION command, since the * HyperSQL engine does this automatically, and can tell * when it is needed far better than the client; however * we do use this fact to update our Session autocommit * state to match the client's notion. * We ignore the latter case, because real HyperSQL * user/apps will use "START TRANSACTION", not "BEGIN". * Therefore, we just update autocommit state and run no * other command against the engine. */ sql = sql.equals("BEGIN") ? null : sql.substring( "BEGIN;".length()); server.printWithThread( "ODBC Trans started. Session AutoCommit -> F"); try { session.setAutoCommit(false); } catch (HsqlException he) { throw new RecoverableOdbcFailure( "Failed to change transaction state: " + he.getMessage(), he.getSQLState()); } // Now just placate the driver outPacket.write("BEGIN"); outPacket.xmit('C', dataOutput); if (sql == null) { sendReadyForQuery = true; break; } } if (sql.startsWith("SAVEPOINT ") && sql.indexOf(';') > 0) { int firstSemi = sql.indexOf(';'); server.printWithThread( "Interposing BEFORE primary statement: " + sql.substring(0, firstSemi)); odbcExecDirect(sql.substring(0, firstSemi)); sql = sql.substring(firstSemi + 1); } lastSemi = sql.lastIndexOf(';'); if (lastSemi > 0) { String suffix = sql.substring(lastSemi + 1); if (suffix.startsWith("RELEASE ")) { interposedStatement = suffix; sql = sql.substring(0, lastSemi); } if (suffix.startsWith("show transaction_isolation")) { interposedStatement = null; sql = "values session_isolation_level()"; } } /** ******************************************* */ String normalized = sql.trim().toLowerCase(); if (server.isTrace()) { server.printWithThread("Received query (" + sql + ')'); } /* * BEWARE: We aren't supporting multiple result-sets from a * compound statement. Plus, a general requirement is, the * entire compound statement may return just one result set. * I don't have time to check how it works elsewhere, but here, * and for now, the Rowset-generating statement (SELECT, etc.) * must be first in order for us to detect that we need to * return a result set. * If we do parse out the component statement here, the states * set above apply to all executions, and only one Z packet * should be sent at the very end. * * I find that the Driver can't handle compound statements * which mix resultset + non-resultset statements (even in * SIMPLE mode), so we are more capable than our client is. */ if (normalized.startsWith("select current_schema()")) { //ok in PGS compatibility mode // but transalate for other modes sql = "values current_schema()"; normalized = sql; } if (normalized.startsWith("show ")) { sql = "values " + sql.substring("show ".length()); normalized = sql; } if (normalized.startsWith("values n.nspname,")) { // Executed by psqlodbc after every user-specified query. server.printWithThread( "Swallowing 'select n.nspname,...'"); outPacket.writeShort(1); // Num cols. outPacket.write("oid"); outPacket.writeInt(201); outPacket.writeShort(1); outPacket.writeInt(23); outPacket.writeShort(4); outPacket.writeInt(-1); outPacket.writeShort(0); outPacket.xmit('T', dataOutput); // Xmit Row Definition // This query returns no rows. typenam "lo"?? outPacket.write("SELECT"); outPacket.xmit('C', dataOutput); sendReadyForQuery = true; break; } if (normalized.startsWith( "select oid, typbasetype from")) { // Executed by psqlodbc immediately after connecting. server.printWithThread( "Simulating 'select oid, typbasetype...'"); /* * This query is run as "a hack to get the oid of our * large object oid type. */ outPacket.writeShort(2); // Num cols. outPacket.write("oid"); // Col. name outPacket.writeInt(101); // table ID outPacket.writeShort(102); // column id outPacket.writeInt(26); // Datatype ID [adtid] outPacket.writeShort(4); // Datatype size [adtsize] outPacket.writeInt(-1); // Var size [atttypmod] outPacket.writeShort(0); // text "format code" outPacket.write("typbasetype"); // Col. name outPacket.writeInt(101); // table ID outPacket.writeShort(103); // column id outPacket.writeInt(26); // Datatype ID [adtid] outPacket.writeShort(4); // Datatype size [adtsize] outPacket.writeInt(-1); // Var size [atttypmod] outPacket.writeShort(0); // text "format code" outPacket.xmit('T', dataOutput); // sending a Tuple (row) // This query returns no rows. typenam "lo"?? outPacket.write("SELECT"); outPacket.xmit('C', dataOutput); sendReadyForQuery = true; break; } if (normalized.startsWith("select ") || normalized.startsWith("values ")) { server.printWithThread( "Performing a real non-prepared SELECT..."); r = Result.newExecuteDirectRequest(); r.setPrepareOrExecuteProperties( sql, 0, 0, StatementTypes.RETURN_RESULT, 0, ResultProperties.defaultPropsValue, java.sql.Statement.NO_GENERATED_KEYS, null, null); server.printWithThread(sql); rOut = session.execute(r); switch (rOut.getType()) { case ResultConstants.DATA : break; case ResultConstants.ERROR : throw new RecoverableOdbcFailure(rOut); default : throw new RecoverableOdbcFailure( "Output Result from Query execution is of " + "unexpected type: " + rOut.getType()); } // See Result.newDataHeadResult() for what we have here // .metaData, .navigator RowSetNavigator navigator = rOut.getNavigator(); ResultMetaData md = rOut.metaData; if (md == null) { throw new RecoverableOdbcFailure( "Failed to get metadata for query results"); } int columnCount = md.getColumnCount(); String[] colLabels = md.getGeneratedColumnNames(); colTypes = md.columnTypes; pgTypes = new PgType[columnCount]; for (int i = 0; i < pgTypes.length; i++) { pgTypes[i] = PgType.getPgType(colTypes[i]); } // fredt : colLabels may not contain some column names // colDefs is used when no label is present: // SELECT TABLECOL AS COLLABLE has both name and label // SELECT TABLECOL has name 'TABLECOL' // SELECT 2 AS CONST has label 'CONST' ColumnBase[] colDefs = md.columns; // Num cols. outPacket.writeShort(columnCount); for (int i = 0; i < columnCount; i++) { // col name if (colLabels[i] != null) { outPacket.write(colLabels[i]); } else { outPacket.write(colDefs[i].getNameString()); } // table ID [relid]: outPacket.writeInt(OdbcUtil.getTableOidForColumn(i, md)); // column id [attid] outPacket.writeShort(OdbcUtil.getIdForColumn(i, md)); outPacket.writeInt(pgTypes[i].getOid()); // Datatype size [adtsize] outPacket.writeShort(pgTypes[i].getTypeWidth()); outPacket.writeInt(pgTypes[i].getLPConstraint()); // Var size [atttypmod] // This is the size constraint integer // like VARCHAR(12) or DECIMAL(4). // -1 if none specified for this column. outPacket.writeShort(0); // format code, 0 = text column, 1 = binary column, // but entirely ignored by our driver. // Would only be non-0 if a 'B' command requested it. } outPacket.xmit('T', dataOutput); // Xmit Row Definition int rowNum = 0; while (navigator.next()) { rowNum++; Object[] rowData = navigator.getCurrent(); // Row.getData(). Don't know why *Data.getCurrent() // method returns Object instead of O[]. // TODO: Remove the assertion here: if (rowData == null) { throw new RecoverableOdbcFailure("Null row?"); } if (rowData.length < columnCount) { throw new RecoverableOdbcFailure( "Data element mismatch. " + columnCount + " metadata cols, yet " + rowData.length + " data elements for row " + rowNum); } //server.printWithThread("Row " + rowNum + " has " //+ rowData.length + " elements"); outPacket.writeShort(columnCount); // This field is just swallowed by PG ODBC // client, but OdbcUtil.validated by psql. for (int i = 0; i < columnCount; i++) { if (rowData[i] == null) { /* server.printWithThread("R" + rowNum + "C" + (i+1) + " => [null]"); */ outPacket.writeInt(-1); } else { dataString = pgTypes[i].valueString(rowData[i]); outPacket.writeSized(dataString); if (server.isTrace()) { server.printWithThread( "R" + rowNum + "C" + (i + 1) + " => (" + rowData[i].getClass().getName() + ") [" + dataString + ']'); } } } outPacket.xmit('D', dataOutput); } outPacket.write("SELECT"); outPacket.xmit('C', dataOutput); sendReadyForQuery = true; break; } if (normalized.startsWith("deallocate \"") && normalized.charAt(normalized.length() - 1) == '"') { tmpStr = sql.trim().substring( "deallocate \"".length()).trim(); // Must use "sql" directly since name is case-sensitive handle = tmpStr.substring(0, tmpStr.length() - 1); odbcPs = (OdbcPreparedStatement) sessionOdbcPsMap.get( handle); if (odbcPs != null) { odbcPs.close(); } portal = (StatementPortal) sessionOdbcPortalMap.get(handle); if (portal != null) { portal.close(); } if (odbcPs == null && portal == null) { /* throw new RecoverableOdbcFailure(null, "No object present for handle: " + handle, "08P01"); Driver does not handle state change correctly, so for now we just issue a warning: OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR, "No object present for handle: " + handle, dataOutput); TODO: Retest this. May have been side-effect of other problems. */ server.printWithThread( "Ignoring bad 'DEALLOCATE' cmd"); } if (server.isTrace()) { server.printWithThread("Deallocated PS/Portal '" + handle + "'"); } outPacket.write("DEALLOCATE"); outPacket.xmit('C', dataOutput); sendReadyForQuery = true; break; } if (normalized.startsWith("set client_encoding to ")) { server.printWithThread("Stubbing EXECDIR for: " + sql); outPacket.write("SET"); outPacket.xmit('C', dataOutput); sendReadyForQuery = true; break; } // Case below is non-String-matched Qs: server.printWithThread("Performing a real EXECDIRECT..."); odbcExecDirect(sql); sendReadyForQuery = true; break; case 'X' : // Terminate packet if (sessionOdbcPsMap.size() > (sessionOdbcPsMap.containsKey("") ? 1 : 0)) { server.printWithThread("Client left " + sessionOdbcPsMap.size() + " PS objects open"); } if (sessionOdbcPortalMap.size() > (sessionOdbcPortalMap.containsKey("") ? 1 : 0)) { server.printWithThread("Client left " + sessionOdbcPortalMap.size() + " Portal objects open"); } OdbcUtil.validateInputPacketSize(inPacket); throw cleanExit; case 'H' : // Flush packet // No-op. It is impossible to cache while supporting multiple // ps and portal objects, so there is nothing for a Flush to // do. There isn't even a reply to a Flush packet. break; case 'S' : // Sync packet // Special case for Sync packets. // To facilitate recovery, we do not abort in case of problems. if (session.isAutoCommit()) { try { // I don't see how this can be useful. If we ran DML, it // will have autocommitted. If we have just switched to // autoCommit mode, then according to spec we must have // executed an implicit commit then. server.printWithThread( "Silly implicit commit by Sync"); session.commit(true); // TODO: Find out if chain param should be T or F. } catch (HsqlException he) { server.printWithThread("Implicit commit failed: " + he); OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR, "Implicit commit failed", he.getSQLState(), dataOutput); } } sendReadyForQuery = true; break; case 'P' : // Parse packet psHandle = inPacket.readString(); String query = OdbcUtil.revertMungledPreparedQuery( inPacket.readString()); paramCount = inPacket.readUnsignedShort(); for (int i = 0; i < paramCount; i++) { if (inPacket.readInt() != 0) { throw new RecoverableOdbcFailure( null, "Parameter-type OID specifiers not supported yet", "0A000"); } } if (server.isTrace()) { server.printWithThread( "Received Prepare request for query (" + query + ") with handle '" + psHandle + "'"); } if (psHandle.length() > 0 && sessionOdbcPsMap.containsKey(psHandle)) { throw new RecoverableOdbcFailure( null, "PS handle '" + psHandle + "' already in use. " + "You must close it before recreating", "08P01"); } new OdbcPreparedStatement(psHandle, query, sessionOdbcPsMap, session); outPacket.xmit('1', dataOutput); break; case 'D' : // Describe packet c = inPacket.readByteChar(); handle = inPacket.readString(); odbcPs = null; portal = null; if (c == 'S') { odbcPs = (OdbcPreparedStatement) sessionOdbcPsMap.get( handle); } else if (c == 'P') { portal = (StatementPortal) sessionOdbcPortalMap.get(handle); } else { throw new RecoverableOdbcFailure( null, "Description packet request type invalid: " + c, "08P01"); } if (server.isTrace()) { server.printWithThread("Received Describe request for " + c + " of handle '" + handle + "'"); } if (odbcPs == null && portal == null) { throw new RecoverableOdbcFailure( null, "No object present for " + c + " handle: " + handle, "08P01"); } Result ackResult = (odbcPs == null) ? portal.ackResult : odbcPs.ackResult; pmd = ackResult.parameterMetaData; paramCount = pmd.getColumnCount(); Type[] paramTypes = pmd.getParameterTypes(); if (paramCount != paramTypes.length) { throw new RecoverableOdbcFailure( "Parameter count mismatch. Count of " + paramCount + " reported, but there are " + paramTypes.length + " param md objects"); } if (c == 'S') { outPacket.writeShort(paramCount); for (int i = 0; i < paramTypes.length; i++) { outPacket.writeInt( PgType.getPgType(paramTypes[i]).getOid()); // TODO: Determine whether parameter typing works // better for Strings when try to match table column // or not. 2nd param to getPgType(). } outPacket.xmit('t', dataOutput); // ParameterDescription packet } ResultMetaData md = ackResult.metaData; if (md.getColumnCount() < 1) { if (server.isTrace()) { server.printWithThread( "Non-rowset query so returning NoData packet"); } // Send NoData packet because no columnar output from // this statement. outPacket.xmit('n', dataOutput); break; } // TODO: // May need to pass the extra BIGINT pseudo-column for // updatable-row or other purposes. In that case, it may // make sense to use getExtendedColumnCount(), etc. String[] colNames = md.getGeneratedColumnNames(); if (md.getColumnCount() != colNames.length) { throw new RecoverableOdbcFailure( "Couldn't get all column names: " + md.getColumnCount() + " cols. but only got " + colNames.length + " col. names"); } colTypes = md.columnTypes; pgTypes = new PgType[colNames.length]; ColumnBase[] colDefs = md.columns; for (int i = 0; i < pgTypes.length; i++) { pgTypes[i] = PgType.getPgType(colTypes[i]); } if (colNames.length != colDefs.length) { throw new RecoverableOdbcFailure( "Col data mismatch. " + colDefs.length + " col instances but " + colNames.length + " col names"); } outPacket.writeShort(colNames.length); // Num cols. for (int i = 0; i < colNames.length; i++) { outPacket.write(colNames[i]); // Col. name // table ID [relid]: outPacket.writeInt(OdbcUtil.getTableOidForColumn(i, md)); // column id [attid] outPacket.writeShort(OdbcUtil.getIdForColumn(i, md)); outPacket.writeInt(pgTypes[i].getOid()); // Datatype size [adtsize] outPacket.writeShort(pgTypes[i].getTypeWidth()); outPacket.writeInt(pgTypes[i].getLPConstraint()); // Var size [atttypmod] // This is the size constraint integer // like VARCHAR(12) or DECIMAL(4). // -1 if none specified for this column. outPacket.writeShort(0); // format code, 0 = text column, 1 = binary column, // but entirely ignored by our driver. // Would only be non-0 if a 'B' command requested it. } outPacket.xmit('T', dataOutput); // Xmit Row Definition break; case 'B' : // Bind packet portalHandle = inPacket.readString(); psHandle = inPacket.readString(); int paramFormatCount = inPacket.readUnsignedShort(); boolean[] paramBinary = new boolean[paramFormatCount]; for (int i = 0; i < paramFormatCount; i++) { paramBinary[i] = inPacket.readUnsignedShort() != 0; if (server.isTrace() && paramBinary[i]) { server.printWithThread("Binary param #" + i); } } paramCount = inPacket.readUnsignedShort(); Object[] paramVals = new Object[paramCount]; for (int i = 0; i < paramVals.length; i++) { if (i < paramBinary.length && paramBinary[i]) { paramVals[i] = inPacket.readSizedBinaryData(); } else { paramVals[i] = inPacket.readSizedString(); } } int outFormatCount = inPacket.readUnsignedShort(); for (int i = 0; i < outFormatCount; i++) { if (inPacket.readUnsignedShort() != 0) { throw new RecoverableOdbcFailure( null, "Binary output values not supported", "0A000"); } } if (server.isTrace()) { server.printWithThread( "Received Bind request to make Portal from (" + psHandle + ")' with handle '" + portalHandle + "'"); } odbcPs = (OdbcPreparedStatement) sessionOdbcPsMap.get(psHandle); if (odbcPs == null) { throw new RecoverableOdbcFailure( null, "No object present for PS handle: " + psHandle, "08P01"); } if (portalHandle.length() > 0 && sessionOdbcPortalMap.containsKey( portalHandle)) { throw new RecoverableOdbcFailure( null, "Portal handle '" + portalHandle + "' already in use. " + "You must close it before recreating", "08P01"); } pmd = odbcPs.ackResult.parameterMetaData; if (paramCount != pmd.getColumnCount()) { throw new RecoverableOdbcFailure( null, "Client didn't specify all " + pmd.getColumnCount() + " parameters (" + paramCount + ')', "08P01"); } new StatementPortal(portalHandle, odbcPs, paramVals, sessionOdbcPortalMap); outPacket.xmit('2', dataOutput); break; case 'E' : // Execute packet portalHandle = inPacket.readString(); int fetchRows = inPacket.readInt(); if (server.isTrace()) { server.printWithThread("Received Exec request for " + fetchRows + " rows from portal handle '" + portalHandle + "'"); } portal = (StatementPortal) sessionOdbcPortalMap.get( portalHandle); if (portal == null) { throw new RecoverableOdbcFailure( null, "No object present for Portal handle: " + portalHandle, "08P01"); } // result properties means readonly, not holdable portal.bindResult.setPreparedExecuteProperties( portal.parameters, fetchRows, 0, 0, 0); // 0 for maxRows means unlimited. Same for fetchRows. rOut = session.execute(portal.bindResult); switch (rOut.getType()) { case ResultConstants.UPDATECOUNT : outPacket.write( OdbcUtil.echoBackReplyString( portal.lcQuery, rOut.getUpdateCount())); outPacket.xmit('C', dataOutput); // end of rows (B or D packets) // This keeps session.autoUpdate in sync with client's // notion of transaction state. if (portal.lcQuery.equals("commit") || portal.lcQuery.startsWith("commit ") || portal.lcQuery.equals("rollback") || portal.lcQuery.startsWith( "rollback ")) { try { session.setAutoCommit(true); } catch (HsqlException he) { throw new RecoverableOdbcFailure( "Failed to change transaction state: " + he.getMessage(), he.getSQLState()); } } break MAIN_ODBC_COMM_SWITCH; case ResultConstants.DATA : break; case ResultConstants.ERROR : throw new RecoverableOdbcFailure(rOut); default : throw new RecoverableOdbcFailure( "Output Result from Portal execution is of " + "unexpected type: " + rOut.getType()); } // See Result.newDataHeadResult() for what we have here // .metaData, .navigator RowSetNavigator navigator = rOut.getNavigator(); int rowNum = 0; int colCount = portal.ackResult.metaData.getColumnCount(); while (navigator.next()) { rowNum++; Object[] rowData = navigator.getCurrent(); if (rowData == null) { throw new RecoverableOdbcFailure("Null row?"); } if (rowData.length < colCount) { throw new RecoverableOdbcFailure( "Data element mismatch. " + colCount + " metadata cols, yet " + rowData.length + " data elements for row " + rowNum); } //server.printWithThread("Row " + rowNum + " has " //+ rowData.length + " elements"); outPacket.writeShort(colCount); // This field is just swallowed by PG ODBC // client, but validated by psql. colTypes = portal.ackResult.metaData.columnTypes; pgTypes = new PgType[colCount]; for (int i = 0; i < pgTypes.length; i++) { pgTypes[i] = PgType.getPgType(colTypes[i]); } for (int i = 0; i < colCount; i++) { if (rowData[i] == null) { /* server.printWithThread("R" + rowNum + "C" + (i+1) + " => [null]"); */ outPacket.writeInt(-1); } else { dataString = pgTypes[i].valueString(rowData[i]); outPacket.writeSized(dataString); if (server.isTrace()) { server.printWithThread( "R" + rowNum + "C" + (i + 1) + " => (" + rowData[i].getClass().getName() + ") [" + dataString + ']'); } } } outPacket.xmit('D', dataOutput); } if (navigator.getSize() == 0 || navigator.afterLast()) { outPacket.write("SELECT"); outPacket.xmit('C', dataOutput); // end of rows (B or D packets) } else { outPacket.xmit('s', dataOutput); } // N.b., we return. // You might think that this completion of an EXTENDED sequence // would end in ReadyForQuery/Z, but no. break; case 'C' : // Close packet c = inPacket.readByteChar(); handle = inPacket.readString(); odbcPs = null; portal = null; if (c == 'S') { odbcPs = (OdbcPreparedStatement) sessionOdbcPsMap.get( handle); if (odbcPs != null) { odbcPs.close(); } } else if (c == 'P') { portal = (StatementPortal) sessionOdbcPortalMap.get(handle); if (portal != null) { portal.close(); } } else { throw new RecoverableOdbcFailure( null, "Description packet request type invalid: " + c, "08P01"); } // TODO: Try sending a warning to client for both == null. // Broke things earlier, but that may have been due to // other problems. if (server.isTrace()) { server.printWithThread("Closed " + c + " '" + handle + "'? " + (odbcPs != null || portal != null)); } outPacket.xmit('3', dataOutput); break; default : throw new RecoverableOdbcFailure( null, "Unsupported operation type (" + inPacket.packetType + ')', "0A000"); } OdbcUtil.validateInputPacketSize(inPacket); if (interposedStatement != null) { server.printWithThread("Interposing AFTER primary statement: " + interposedStatement); odbcExecDirect(interposedStatement); } if (sendReadyForQuery) { outPacket.reset(); // The reset is unnecessary now. For safety in case somebody // codes something above which may abort processing of a // packet before xmit. outPacket.writeByte(session.isAutoCommit() ? 'I' : 'T'); outPacket.xmit('Z', dataOutput); } } catch (RecoverableOdbcFailure rf) { Result errorResult = rf.getErrorResult(); if (errorResult == null) { String stateCode = rf.getSqlStateCode(); String svrMsg = rf.toString(); String cliMsg = rf.getClientMessage(); if (server.isTrace()) { server.printWithThread(svrMsg); } if (cliMsg != null) { OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR, cliMsg, stateCode, dataOutput); } } else { if (server.isTrace()) { server.printWithThread("Result object error: " + errorResult.getMainString()); } // This class of error is not considered a Server problem, so // we don't log on the server side. OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_ERROR, errorResult.getMainString(), errorResult.getSubString(), dataOutput); } switch (odbcCommMode) { case OdbcUtil.ODBC_SIMPLE_MODE : outPacket.reset(); /// transaction status = Error outPacket.writeByte('E'); /// transaction status = Error // TODO: Consider keeping this state until the session // is either committed or rolled back. // (Right now we just return 'E' here, then revert to // I or T). outPacket.xmit('Z', dataOutput); break; case OdbcUtil.ODBC_EXTENDED_MODE : odbcCommMode = OdbcUtil.ODBC_EXT_RECOVER_MODE; server.printWithThread("Reverting to EXT_RECOVER mode"); break; } } } /** * Initializes this connection and runs the request handling * loop until closed. */ public void run() { int msgType; init(); if (session != null) { try { while (keepAlive) { msgType = dataInput.readByte(); processor.receiveResult((char) msgType); } } catch (CleanExit ce) { keepAlive = false; } catch (IOException e) { // fredt - is thrown when connection drops server.printWithThread(mThread + ":disconnected " + user); } catch (HsqlException e) { // fredt - is thrown in unforeseen circumstances if (keepAlive) { server.printStackTrace(e); } } catch (Throwable e) { // fredt - is thrown in unforeseen circumstances if (keepAlive) { server.printStackTrace(e); } } } close(); } private Result setDatabase(Result resultIn) { try { String databaseName = resultIn.getDatabaseName(); dbIndex = server.getDBIndex(databaseName); dbID = server.dbID[dbIndex]; user = resultIn.getMainString(); if (!server.isSilent()) { server.printWithThread(mThread + ":Trying to connect user '" + user + "' to DB (" + databaseName + ')'); } session = DatabaseManager.newSession(dbID, user, resultIn.getSubString(), resultIn.getZoneString(), resultIn.getUpdateCount()); if (!server.isSilent()) { server.printWithThread(mThread + ":Connected user '" + user + "'"); } return Result.newConnectionAcknowledgeResponse(session); } catch (HsqlException e) { session = null; return Result.newErrorResult(e); } catch (Throwable e) { session = null; return Result.newErrorResult(e); } } private Result cancelStatement(Result resultIn) { try { dbID = resultIn.getDatabaseId(); long sessionId = resultIn.getSessionId(); session = DatabaseManager.getSession(dbID, sessionId); if (!server.isSilent()) { server.printWithThread(mThread + ":Trying to cancel statement " + " to DB (" + dbID + ')'); } return session.cancel(resultIn); } catch (HsqlException e) { session = null; return Result.updateZeroResult; } catch (Throwable t) { session = null; return Result.updateZeroResult; } } /** * Retrieves the thread name to be used when * this object is the Runnable object of a Thread. * * @return the thread name to be used when this object is the Runnable * object of a Thread. */ String getConnectionThreadName() { return "HSQLDB Connection @" + Integer.toString(hashCode(), 16); } /** * Don't want this too high, or users may give up before seeing the * banner. Can't be too low or we could close a valid but slow * client connection. */ public static long MAX_WAIT_FOR_CLIENT_DATA = 1000; // ms. public static long CLIENT_DATA_POLLING_PERIOD = 100; // ms. /** * The only known case where a connection attempt will get stuck is * if client connects with hsqls to a https server; or * hsql to a http server. * All other client X server combinations are handled gracefully. *

* If returns (a.o.t. throws), then state variable streamProtocol will * be set. * * @return int read as first thing off of stream */ public int handshake() throws IOException { long clientDataDeadline = new java.util.Date().getTime() + MAX_WAIT_FOR_CLIENT_DATA; if (!(socket instanceof javax.net.ssl.SSLSocket)) { // available() does not work for SSL socket input stream do { try { Thread.sleep(CLIENT_DATA_POLLING_PERIOD); } catch (InterruptedException ie) {} } while (dataInput.available() < 5 && new java.util.Date().getTime() < clientDataDeadline); // Old HSQLDB clients will send resultType byte + 4 length bytes // New HSQLDB clients will send NCV int + above = 9 bytes // ODBC clients will send a much larger StartupPacket if (dataInput.available() < 1) { dataOutput.write( (TEXTBANNER_PART1 + ClientConnection.NETWORK_COMPATIBILITY_VERSION + TEXTBANNER_PART2 + '\n').getBytes()); dataOutput.flush(); throw Error.error(ErrorCode.SERVER_UNKNOWN_CLIENT); } } int firstInt = dataInput.readInt(); switch (firstInt >> 24) { case 80 : // Empirically server.print( "Rejected attempt from client using hsql HTTP protocol"); return 0; case 0 : // For ODBC protocol, this is the first byte of a 4-byte int // size. The size can never be large enough that the first // byte will be non-zero. streamProtocol = ODBC_STREAM_PROTOCOL; processor = new OdbcInResultProcessor(); break; default : streamProtocol = HSQL_STREAM_PROTOCOL; processor = new HsqlInResultProcessor(); if (firstInt != ClientConnection .NETWORK_COMPATIBILITY_VERSION_INT) { if (firstInt == -1900000) { firstInt = -2000000; } String verString = ClientConnection.toNetCompVersionString(firstInt); throw Error.error(null, ErrorCode.SERVER_VERSIONS_INCOMPATIBLE, 0, new String[] { HsqlDatabaseProperties.THIS_VERSION, verString }); } // HSQL protocol client } return firstInt; } private void odbcConnect(int firstInt) throws IOException { /* Until client receives teh ReadyForQuery packet at the end of this * method, we (the server) initiate all packet exchanges. */ int major = dataInput.readUnsignedShort(); int minor = dataInput.readUnsignedShort(); // Can just return to fail, until the value of "session" is set below. if (major == 1 && minor == 7) { // This is what old HyperSQL versions always send // TODO: Consider sending client a 1.8-compatible SQLException server.print("A pre-version 2.0 client attempted to connect. " + "We rejected them."); return; } if (major == 1234 && minor == 5679) { // No reason to pay any attention to the size header in this case. dataOutput.writeByte('N'); // SSL not supported yet // TODO: Implement SSL here (and reply with 'S') odbcConnect(dataInput.readInt()); return; } if (major == 1234 && minor == 5678) { // No reason to pay any attention to the size header in this case. if (firstInt != 16) { server.print( "ODBC cancellation request sent wrong packet length: " + firstInt); } server.print( "Got an ODBC cancellation request for thread ID " + dataInput.readInt() + ", but we don't support " + "OOB cancellation yet. " + "Ignoring this request and closing the connection."); // N.b., Spec says to NOT reply to client in this case. return; } server.printWithThread("ODBC client connected. " + "ODBC Protocol Compatibility Version " + major + '.' + minor); OdbcPacketInputStream inPacket = OdbcPacketInputStream.newOdbcPacketInputStream('\0', dataInput, firstInt - 8); // - 4 for size of firstInt - 2 for major - 2 for minor java.util.Map stringPairs = inPacket.readStringPairs(); if (server.isTrace()) { server.print("String Pairs from ODBC client: " + stringPairs); } try { try { OdbcUtil.validateInputPacketSize(inPacket); } catch (RecoverableOdbcFailure rf) { // In this case, we do not treat it as recoverable throw new ClientFailure(rf.toString(), rf.getClientMessage()); } inPacket.close(); if (!stringPairs.containsKey("database")) { throw new ClientFailure("Client did not identify database", "Target database not identified"); } if (!stringPairs.containsKey("user")) { throw new ClientFailure("Client did not identify user", "Target account not identified"); } String databaseName = (String) stringPairs.get("database"); user = (String) stringPairs.get("user"); if (databaseName.equals("/")) { // Work-around because ODBC doesn't allow "" for Database name databaseName = ""; } /* Unencoded/unsalted authentication */ dataOutput.writeByte('R'); dataOutput.writeInt(8); //size dataOutput.writeInt(OdbcUtil.ODBC_AUTH_REQ_PASSWORD); dataOutput.flush(); // areq of auth. mode. char c = '\0'; try { c = (char) dataInput.readByte(); } catch (EOFException eofe) { server.printWithThread( "Looks like we got a goofy psql no-auth attempt. " + "Will probably retry properly very shortly"); return; } if (c != 'p') { throw new ClientFailure( "Expected password prefix 'p', " + "but got '" + c + "'", "Password value not prefixed with 'p'"); } int len = dataInput.readInt() - 5; // Is password len after -4 for count int -1 for null term if (len < 0) { throw new ClientFailure( "Client submitted invalid password length " + len, "Invalid password length " + len); } String password = ServerConnection.readNullTermdUTF(len, dataInput); dbIndex = server.getDBIndex(databaseName); dbID = server.dbID[dbIndex]; if (!server.isSilent()) { server.printWithThread(mThread + ":Trying to connect user '" + user + "' to DB (" + databaseName + ')'); } try { session = DatabaseManager.newSession(dbID, user, password, null, 0); // TODO: Find out what updateCount, the last para, is for: // resultIn.getUpdateCount()); } catch (Exception e) { throw new ClientFailure("User name or password denied: " + e, "Login attempt rejected"); } } catch (ClientFailure cf) { server.print(cf.toString()); // Code below means CONNECTION FAILURE OdbcUtil.alertClient(OdbcUtil.ODBC_SEVERITY_FATAL, cf.getClientMessage(), "08006", dataOutput); return; } outPacket = OdbcPacketOutputStream.newOdbcPacketOutputStream(); outPacket.writeInt(OdbcUtil.ODBC_AUTH_REQ_OK); //success outPacket.xmit('R', dataOutput); // Notify client of success for (int i = 0; i < OdbcUtil.hardcodedParams.length; i++) { OdbcUtil.writeParam(OdbcUtil.hardcodedParams[i][0], OdbcUtil.hardcodedParams[i][1], dataOutput); } // If/when we implement OOB cancellation, we would send the // Session identifier and key here, with a 'K' packet. outPacket.writeByte('I'); // Trans. status = Not in transaction outPacket.xmit('Z', dataOutput); // Notify client of success // This ReadyForQuery turns over responsibility to initiate packet // exchanges to the client. OdbcUtil.alertClient( OdbcUtil.ODBC_SEVERITY_INFO, "MHello\nYou have connected to HyperSQL ODBC Server", dataOutput); dataOutput.flush(); } private java.util.Map sessionOdbcPsMap = new java.util.HashMap(); private java.util.Map sessionOdbcPortalMap = new java.util.HashMap(); /** * Read String directly from dataInput. * * @param reqLength Required length */ private static String readNullTermdUTF(int reqLength, java.io.InputStream istream) throws IOException { /* Would be MUCH easier to do this with Java6's String * encoding/decoding operations */ int bytesRead = 0; byte[] ba = new byte[reqLength + 3]; ba[0] = (byte) (reqLength >>> 8); ba[1] = (byte) reqLength; while (bytesRead < reqLength + 1) { bytesRead += istream.read(ba, 2 + bytesRead, reqLength + 1 - bytesRead); } if (ba[ba.length - 1] != 0) { throw new IOException("String not null-terminated"); } for (int i = 2; i < ba.length - 1; i++) { if (ba[i] == 0) { throw new RuntimeException("Null internal to String at offset " + (i - 2)); } } java.io.DataInputStream dis = new java.io.DataInputStream(new ByteArrayInputStream(ba)); String s = dis.readUTF(); //String s = java.io.DataInputStream.readUTF(dis); // TODO: Test the previous two to see if one works better for // high-order characters. dis.close(); return s; } // Tentative state variable private int streamProtocol = UNDEFINED_STREAM_PROTOCOL; static final int UNDEFINED_STREAM_PROTOCOL = 0; static final int HSQL_STREAM_PROTOCOL = 1; static final int ODBC_STREAM_PROTOCOL = 2; int odbcCommMode = OdbcUtil.ODBC_SIMPLE_MODE; private void odbcExecDirect(String inStatement) throws RecoverableOdbcFailure, IOException { String statement = inStatement; String norm = statement.trim().toLowerCase(); if (norm.startsWith("release ") && !norm.startsWith("release savepoint")) { server.printWithThread( "Transmogrifying 'RELEASE ...' to 'RELEASE SAVEPOINT..."); statement = statement.trim().substring(0, "release ".length()) + "SAVEPOINT " + statement.trim().substring("release ".length()); } Result r = Result.newExecuteDirectRequest(); r.setPrepareOrExecuteProperties( statement, 0, 0, StatementTypes.RETURN_COUNT, 0, ResultProperties.defaultPropsValue, ResultConstants.RETURN_NO_GENERATED_KEYS, null, null); server.printWithThread(statement); Result rOut = session.execute(r); switch (rOut.getType()) { case ResultConstants.UPDATECOUNT : break; case ResultConstants.ERROR : throw new RecoverableOdbcFailure(rOut); default : throw new RecoverableOdbcFailure( "Output Result from execution is of " + "unexpected type: " + rOut.getType()); } outPacket.reset(); outPacket.write(OdbcUtil.echoBackReplyString(norm, rOut.getUpdateCount())); // This keeps session.autoUpdate in sync with client's notion // of transaction state. outPacket.xmit('C', dataOutput); if (norm.equals("commit") || norm.startsWith("commit ") || norm.equals("rollback") || norm.startsWith("rollback ")) { try { session.setAutoCommit(true); } catch (HsqlException he) { throw new RecoverableOdbcFailure( "Failed to change transaction state: " + he.getMessage(), he.getSQLState()); } } } class OdbcInResultProcessor implements InResultProcessor { public void receiveConnection(int type) throws CleanExit, IOException { ServerConnection.this.odbcConnect(type); } public void receiveResult(int type) throws CleanExit, IOException { ServerConnection.this.receiveOdbcPacket((char) type); } } class HsqlInResultProcessor implements InResultProcessor { public void receiveConnection(int type) throws CleanExit, IOException { receiveResult(type); } public void receiveResult(int type) throws CleanExit, IOException { ServerConnection.this.receiveResult(type); } } } interface InResultProcessor { void receiveConnection(int type) throws CleanExit, IOException; void receiveResult(int type) throws CleanExit, IOException; } class CleanExit extends Exception {}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy