();
-
- try
- {
-
- //create a socket, inetSocketAddress and a corresponding socketConnector per inetAddress
- noOfSpawnedThreads = inetAddrs.size();
- for(InetAddress inetAddress: inetAddrs)
- {
- Socket s = new Socket();
- sockets.add(s);
-
- InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, portNumber);
-
- SocketConnector socketConnector = new SocketConnector(s, inetSocketAddress, timeoutInMilliSeconds, this);
- socketConnectors.add(socketConnector);
- }
-
- //acquire parent lock and spawn all threads
- synchronized(parentThreadLock)
- {
- for(SocketConnector sc: socketConnectors)
- {
- threadPoolExecutor.execute(sc);
- }
-
- long timerNow = System.currentTimeMillis();
- long timerExpire = timerNow + timeoutInMilliSeconds;
-
- //The below loop is to guard against the spurious wake up problem
- while(true)
- {
- long timeRemaining = timerExpire - timerNow;
-
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer(this.toString() + " TimeRemaining:" + timeRemaining + "; Result:" + result + "; Max. open thread count: " + threadPoolExecutor.getLargestPoolSize() + "; Current open thread count:" + threadPoolExecutor.getActiveCount());
- }
-
- //if there is no time left or if the result is determined, break.
- //Note that a dirty read of result is totally fine here.
- //Since this thread holds the parentThreadLock, even if we do a dirty
- //read here, the child thread, after updating the result, would not be
- //able to call notify on the parentThreadLock
- //(and thus finish execution) as it would be waiting on parentThreadLock
- //held by this thread(the parent thread).
- //So, this thread will wait again and then be notified by the childThread.
- //On the other hand, if we try to take socketFinderLock here to avoid
- //dirty read, we would introduce a dead lock due to the
- //reverse order of locking in updateResult method.
- if(timeRemaining <=0 || (!result.equals(Result.UNKNOWN)))
- break;
-
- parentThreadLock.wait(timeRemaining);
-
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer(this.toString() + " The parent thread wokeup.");
- }
-
-
- timerNow = System.currentTimeMillis();
- }
-
- }
-
- }
- finally
- {
- //Close all sockets except the selected one.
- //As we close sockets pro-actively in the child threads,
- //its possible that we close a socket twice.
- //Closing a socket second time is a no-op.
- //If a child thread is waiting on the connect call on a socket s,
- //closing the socket s here ensures that an exception is thrown
- //in the child thread immediately. This mitigates the problem
- //of thread explosion by ensuring that unnecessary threads die
- //quickly without waiting for "min(timeOut, 21)" seconds
- for(Socket s : sockets)
- {
- if(s!=selectedSocket)
- {
- close(s);
- }
- }
- }
- }
-
-
- /**
- * search result
- */
- Result getResult()
- {
- return result;
- }
-
- void close(Selector selector)
- {
- if (null != selector)
- {
- if (logger.isLoggable(Level.FINER))
- logger.finer(this.toString() + ": Closing Selector");
-
- try
- {
- selector.close();
- }
- catch(IOException e)
- {
- if (logger.isLoggable(Level.FINE))
- logger.log(Level.FINE, this.toString() + ": Ignored the following error while closing Selector", e);
- }
- }
- }
-
- void close(Socket socket)
- {
- if (null != socket)
- {
- if (logger.isLoggable(Level.FINER))
- logger.finer(this.toString() + ": Closing TCP socket:" + socket);
-
- try
- {
- socket.close();
- }
- catch(IOException e)
- {
- if (logger.isLoggable(Level.FINE))
- logger.log(Level.FINE, this.toString() + ": Ignored the following error while closing socket", e);
- }
- }
- }
-
- void close(SocketChannel socketChannel)
- {
- if (null != socketChannel)
- {
- if (logger.isLoggable(Level.FINER))
- logger.finer(this.toString() + ": Closing TCP socket channel:" + socketChannel);
-
- try
- {
- socketChannel.close();
- }
- catch(IOException e)
- {
- if (logger.isLoggable(Level.FINE))
- logger.log(Level.FINE, this.toString() +"Ignored the following error while closing socketChannel", e);
- }
- }
- }
-
- /**
- * Used by socketConnector threads to notify the
- * socketFinder of their connection attempt result(a connected socket or
- * exception).
- * It updates the result, socket and exception variables
- * of socketFinder object.
- * This method notifies the parent thread if a socket is found
- * or if all the spawned threads have notified.
- * It also closes a socket if it is not selected for use
- * by socketFinder.
- * @param socket the SocketConnector's socket
- * @param exception Exception that occurred in socket connector thread
- * @param threadId Id of the calling Thread for diagnosis
- */
- void updateResult(Socket socket, IOException exception, String threadId)
- {
- if(result.equals(Result.UNKNOWN))
- {
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The following child thread is waiting for socketFinderLock:" +threadId);
- }
-
- synchronized(socketFinderlock)
- {
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The following child thread acquired socketFinderLock:" +threadId);
- }
-
- if(result.equals(Result.UNKNOWN))
- {
- //if the connection was successful and no socket has been
- //selected yet
- if(exception==null && selectedSocket==null)
- {
- selectedSocket = socket;
- result = Result.SUCCESS;
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The socket of the following thread has been chosen:" +threadId);
- }
- }
-
- //if an exception occurred
- if(exception!=null)
- {
- updateSelectedException(exception, threadId);
- }
- }
-
- noOfThreadsThatNotified++;
-
- //if all threads notified, but the result is still unknown,
- //update the result to failure
- if((noOfThreadsThatNotified >= noOfSpawnedThreads)
- && result.equals(Result.UNKNOWN))
- {
- result = Result.FAILURE;
- }
-
- if(!result.equals(Result.UNKNOWN))
- {
- // 1) Note that at any point of time, there is only one
- // thread(parent/child thread) competing for parentThreadLock.
- // 2) The only time where a child thread could be waiting on
- // parentThreadLock is before the wait call in the parentThread
- // 3) After the above happens, the parent thread waits to be
- // notified on parentThreadLock. After being notified,
- // it would be the ONLY thread competing for the lock.
- // for the following reasons
- // a) The parentThreadLock is taken while holding the socketFinderLock.
- // So, all child threads, except one, block on socketFinderLock
- // (not parentThreadLock)
- // b) After parentThreadLock is notified by a child thread, the result
- // would be known(Refer the double-checked locking done at the
- // start of this method). So, all child threads would exit
- // as no-ops and would never compete with parent thread
- // for acquiring parentThreadLock
- // 4) As the parent thread is the only thread that competes for the
- // parentThreadLock, it need not wait to acquire the lock once it wakes
- // up and gets scheduled.
- // This results in better performance as it would close unnecessary
- // sockets and thus help child threads die quickly.
-
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The following child thread is waiting for parentThreadLock:" +threadId);
- }
-
- synchronized(parentThreadLock)
- {
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The following child thread acquired parentThreadLock:" +threadId);
- }
-
- parentThreadLock.notify();
- }
-
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The following child thread released parentThreadLock and notified the parent thread:" +threadId);
- }
- }
- }
-
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The following child thread released socketFinderLock:" +threadId);
- }
- }
-
- }
-
- /**
- * Updates the selectedException if
- *
- * a) selectedException is null
- *
- * b) ex is a non-socketTimeoutException and selectedException is a socketTimeoutException
- *
- * If there are multiple exceptions, that are not related to socketTimeout
- * the first non-socketTimeout exception is picked.
- * If all exceptions are related to socketTimeout, the first exception is picked.
- * Note: This method is not thread safe. The caller should ensure thread safety.
- * @param ex the IOException
- * @param traceId the traceId of the thread
- */
- public void updateSelectedException(IOException ex, String traceId)
- {
- boolean updatedException = false;
- if(selectedException==null)
- {
- selectedException = ex;
- updatedException =true;
- }
- else if((!(ex instanceof SocketTimeoutException)) && (selectedException instanceof SocketTimeoutException))
- {
- selectedException = ex;
- updatedException =true;
- }
-
- if(updatedException)
- {
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer("The selected exception is updated to the following: ExceptionType:"
- + ex.getClass()+"; ExceptionMessage:" +ex.getMessage()+
- "; by the following thread:" +traceId);
- }
- }
- }
-
- /**
- * Used fof tracing
- * @return traceID string
- */
- public String toString()
- {
- return traceID;
- }
-}
+final class SocketFinder {
+ /**
+ * Indicates the result of a search
+ */
+ enum Result {
+ UNKNOWN,// search is still in progress
+ SUCCESS,// found a socket
+ FAILURE// failed in finding a socket
+ }
-/**
- * This is used to connect a socket in a separate thread
- */
-final class SocketConnector implements Runnable
-{
- //socket on which connection attempt would be made
- private final Socket socket;
-
- //the socketFinder associated with this connector
- private final SocketFinder socketFinder;
-
- //inetSocketAddress to connect to
- private final InetSocketAddress inetSocketAddress;
-
- //timeout in milliseconds
- private final int timeoutInMilliseconds;
-
- //Logging variables
- private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.SocketConnector");
- private final String traceID;
-
- //Id of the thread. used for diagnosis
- private final String threadID;
-
- //a counter used to give unique IDs to each connector thread.
- //this will have the id of the thread that was last created.
- private static long lastThreadID = 0;
-
- /**
- * Constructs a new SocketConnector object with the associated socket and
- * socketFinder
- */
- SocketConnector(Socket socket, InetSocketAddress inetSocketAddress, int timeOutInMilliSeconds, SocketFinder socketFinder)
- {
- this.socket = socket;
- this.inetSocketAddress = inetSocketAddress;
- this.timeoutInMilliseconds = timeOutInMilliSeconds;
- this.socketFinder = socketFinder;
- this.threadID = Long.toString(nextThreadID());
- this.traceID = "SocketConnector:" + this.threadID + "(" + socketFinder.toString() + ")";
- }
-
-
- /**
- * If search for socket has not finished, this function
- * tries to connect a socket(with a timeout) synchronously.
- * It further notifies the socketFinder the result of
- * the connection attempt
- */
- public void run()
- {
- IOException exception = null;
- //Note that we do not need socketFinder lock here
- //as we update nothing in socketFinder based on the condition.
- //So, its perfectly fine to make a dirty read.
- SocketFinder.Result result = socketFinder.getResult();
- if(result.equals(SocketFinder.Result.UNKNOWN))
- {
- try
- {
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer(this.toString() + " connecting to InetSocketAddress:"
- + inetSocketAddress + " with timeout:" + timeoutInMilliseconds);
- }
-
- socket.connect(inetSocketAddress, timeoutInMilliseconds);
- }
- catch(IOException ex)
- {
- if(logger.isLoggable(Level.FINER))
- {
- logger.finer(this.toString() + " exception:" + ex.getClass()
- +" with message:" + ex.getMessage()
- + " occured while connecting to InetSocketAddress:"
- + inetSocketAddress);
- }
- exception = ex;
- }
-
- socketFinder.updateResult(socket, exception, this.toString());
- }
-
-
- }
-
- /**
- * Used for tracing
- * @return traceID string
- */
- public String toString()
- {
- return traceID;
- }
-
- /**
- * Generates the next unique thread id.
- */
- private static synchronized long nextThreadID()
- {
- if(lastThreadID == Long.MAX_VALUE)
- {
- if (logger.isLoggable(Level.FINER))
- logger.finer("Resetting the Id count" );
- lastThreadID = 1;
- }
- else
- {
- lastThreadID++;
- }
- return lastThreadID;
- }
-}
+ // Thread pool - the values in the constructor are chosen based on the
+ // explanation given in design_connection_director_multisubnet.doc
+ private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 5, TimeUnit.SECONDS,
+ new SynchronousQueue());
-/**
- * TDSWriter implements the client to server TDS data pipe.
- */
-final class TDSWriter
-{
- private static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Writer");
- private final String traceID;
- final public String toString() { return traceID; }
-
- private final TDSChannel tdsChannel;
- private final SQLServerConnection con;
-
- // Flag to indicate whether data written via writeXXX() calls
- // is loggable. Data is normally loggable. But sensitive
- // data, such as user credentials, should never be logged for
- // security reasons.
- private boolean dataIsLoggable = true;
- void setDataLoggable(boolean value) { dataIsLoggable = value; }
-
- private TDSCommand command = null;
-
- // TDS message type (Query, RPC, DTC, etc.) sent at the beginning
- // of every TDS message header. Value is set when starting a new
- // TDS message of the specified type.
- private byte tdsMessageType;
-
- private volatile int sendResetConnection = 0;
-
- // Size (in bytes) of the TDS packets to/from the server.
- // This size is normally fixed for the life of the connection,
- // but it can change once after the logon packet because packet
- // size negotiation happens at logon time.
- private int currentPacketSize = 0;
-
- // Size of the TDS packet header, which is:
- // byte type
- // byte status
- // short length
- // short SPID
- // byte packet
- // byte window
- private final static int TDS_PACKET_HEADER_SIZE = 8;
- private final static byte[] placeholderHeader = new byte[TDS_PACKET_HEADER_SIZE];
-
- // Intermediate array used to convert typically "small" values such as fixed-length types
- // (byte, int, long, etc.) and Strings from their native form to bytes for sending to
- // the channel buffers.
- private byte valueBytes[] = new byte[256];
-
- // Monotonically increasing packet number associated with the current message
- private volatile int packetNum = 0;
-
- // Bytes for sending decimal/numeric data
- private final static int BYTES4 = 4;
- private final static int BYTES8 = 8;
- private final static int BYTES12 = 12;
- private final static int BYTES16 = 16;
- public final static int BIGDECIMAL_MAX_LENGTH = 0x11;
-
- //is set to true when EOM is sent for the current message.
- //Note that this variable will never be accessed from multiple threads
- //simultaneously and so it need not be volatile
- private boolean isEOMSent = false;
- boolean isEOMSent()
- {
- return isEOMSent;
- }
-
- // Packet data buffers
- private ByteBuffer stagingBuffer;
- private ByteBuffer socketBuffer;
- private ByteBuffer logBuffer;
-
- private CryptoMetadata cryptoMeta = null;
-
- TDSWriter(TDSChannel tdsChannel, SQLServerConnection con)
- {
- this.tdsChannel = tdsChannel;
- this.con = con;
- traceID = "TDSWriter@" + Integer.toHexString(hashCode()) + " (" + con.toString() + ")";
- }
-
- // TDS message start/end operations
-
- void preparePacket() throws SQLServerException
- {
- if (tdsChannel.isLoggingPackets())
- {
- Arrays.fill(logBuffer.array(), (byte)0xFE);
- logBuffer.clear();
- }
-
- // Write a placeholder packet header. This will be replaced
- // with the real packet header when the packet is flushed.
- writeBytes(placeholderHeader);
- }
-
- /**
- * Start a new TDS message.
- */
- void writeMessageHeader() throws SQLServerException
- {
- // TDS 7.2 & later:
- // Include ALL_Headers/MARS header in message's first packet
- // Note: The PKT_BULK message does not nees this ALL_HEADERS
- if ((TDS.PKT_QUERY == tdsMessageType ||
- TDS.PKT_DTC == tdsMessageType ||
- TDS.PKT_RPC == tdsMessageType))
- {
- boolean includeTraceHeader = false;
- int totalHeaderLength = TDS.MESSAGE_HEADER_LENGTH;
- if(TDS.PKT_QUERY == tdsMessageType || TDS.PKT_RPC == tdsMessageType)
- {
- if(con.isDenaliOrLater() && !ActivityCorrelator.getCurrent().IsSentToServer() && Util.IsActivityTraceOn())
- {
- includeTraceHeader = true;
- totalHeaderLength += TDS.TRACE_HEADER_LENGTH;
- }
- }
- writeInt(totalHeaderLength); // allHeaders.TotalLength (DWORD)
- writeInt(TDS.MARS_HEADER_LENGTH); // MARS header length (DWORD)
- writeShort((short) 2); // allHeaders.HeaderType(MARS header) (USHORT)
- writeBytes(con.getTransactionDescriptor());
- writeInt(1); // marsHeader.OutstandingRequestCount
- if(includeTraceHeader)
- {
- writeInt(TDS.TRACE_HEADER_LENGTH); // trace header length (DWORD)
- writeTraceHeaderData();
- ActivityCorrelator.setCurrentActivityIdSentFlag(); // set the flag to indicate this ActivityId is sent
- }
- }
- }
-
-
- void writeTraceHeaderData() throws SQLServerException
- {
- ActivityId activityId = ActivityCorrelator.getCurrent();
- final byte[] actIdByteArray = Util.asGuidByteArray(activityId.getId());
- long seqNum = activityId.getSequence();
- writeShort(TDS.HEADERTYPE_TRACE); // trace header type
- writeBytes(actIdByteArray, 0, actIdByteArray.length); // guid part of ActivityId
- writeInt((int)seqNum); // sequence number of ActivityId
-
- if (logger.isLoggable(Level.FINER))
- logger.finer("Send Trace Header - ActivityID: " + activityId.toString());
- }
-
- /**
- * Convenience method to prepare the TDS channel for writing and start
- * a new TDS message.
- *
- * @param command The TDS command
- * @param tdsMessageType The TDS message type (PKT_QUERY, PKT_RPC, etc.)
- */
- void startMessage(TDSCommand command, byte tdsMessageType) throws SQLServerException
- {
- this.command = command;
- this.tdsMessageType = tdsMessageType;
- this.packetNum = 0;
- this.isEOMSent = false;
- this.dataIsLoggable = true;
-
- // If the TDS packet size has changed since the last request
- // (which should really only happen after the login packet)
- // then allocate new buffers that are the correct size.
- int negotiatedPacketSize = con.getTDSPacketSize();
- if (currentPacketSize != negotiatedPacketSize)
- {
- socketBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN);
- stagingBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN);
- logBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN);
- currentPacketSize = negotiatedPacketSize;
- }
-
- socketBuffer.position(socketBuffer.limit());
- stagingBuffer.clear();
-
- preparePacket();
- writeMessageHeader();
- }
-
- final void endMessage() throws SQLServerException
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(toString() + " Finishing TDS message");
- writePacket(TDS.STATUS_BIT_EOM);
- }
-
- //If a complete request has not been sent to the server,
- //the client MUST send the next packet with both ignore bit (0x02) and EOM bit (0x01)
- //set in the status to cancel the request.
- final boolean ignoreMessage() throws SQLServerException
- {
- if (packetNum > 0)
- {
- assert !isEOMSent;
-
- if (logger.isLoggable(Level.FINER))
- logger.finest(toString() + " Finishing TDS message by sending ignore bit and end of message");
- writePacket(TDS.STATUS_BIT_EOM|TDS.STATUS_BIT_ATTENTION);
- return true;
- }
- return false;
- }
-
- final void resetPooledConnection()
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(toString() + " resetPooledConnection");
- sendResetConnection = TDS.STATUS_BIT_RESET_CONN;
- }
-
- // Primitive write operations
-
- void writeByte(byte value) throws SQLServerException
- {
- if (stagingBuffer.remaining() >= 1)
- {
- stagingBuffer.put(value);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.put(value);
- else
- logBuffer.position(logBuffer.position() + 1);
- }
- }
- else
- {
- valueBytes[0] = value;
- writeWrappedBytes(valueBytes, 1);
- }
- }
-
- void writeChar(char value) throws SQLServerException
- {
- if (stagingBuffer.remaining() >= 2)
- {
- stagingBuffer.putChar(value);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.putChar(value);
- else
- logBuffer.position(logBuffer.position() + 2);
- }
- }
- else
- {
- Util.writeShort((short) value, valueBytes, 0);
- writeWrappedBytes(valueBytes, 2);
- }
- }
-
- void writeShort(short value) throws SQLServerException
- {
- if (stagingBuffer.remaining() >= 2)
- {
- stagingBuffer.putShort(value);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.putShort(value);
- else
- logBuffer.position(logBuffer.position() + 2);
- }
- }
- else
- {
- Util.writeShort(value, valueBytes, 0);
- writeWrappedBytes(valueBytes, 2);
- }
- }
-
- void writeInt(int value) throws SQLServerException
- {
- if (stagingBuffer.remaining() >= 4)
- {
- stagingBuffer.putInt(value);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.putInt(value);
- else
- logBuffer.position(logBuffer.position() + 4);
- }
- }
- else
- {
- Util.writeInt(value, valueBytes, 0);
- writeWrappedBytes(valueBytes, 4);
- }
- }
-
- /**
- * Append a real value in the TDS stream.
- * @param value the data value
- */
- void writeReal(Float value) throws SQLServerException
- {
- if (false) //stagingBuffer.remaining() >= 4)
- {
- stagingBuffer.putFloat(value);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.putFloat(value);
- else
- logBuffer.position(logBuffer.position() + 4);
- }
- }
- else
- {
- writeInt(Float.floatToRawIntBits(value.floatValue()));
- }
- }
-
- /**
- * Append a double value in the TDS stream.
- * @param value the data value
- */
- void writeDouble(double value) throws SQLServerException
- {
- if (stagingBuffer.remaining() >= 8)
- {
- stagingBuffer.putDouble(value);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.putDouble(value);
- else
- logBuffer.position(logBuffer.position() + 8);
- }
- }
- else
- {
- long bits = Double.doubleToLongBits(value);
- long mask = 0xFF;
- int nShift =0;
- for (int i=0; i<8; i++)
- {
- writeByte((byte) ((bits & mask) >> nShift));
- nShift += 8;
- mask = mask << 8;
- }
- }
- }
-
- /**
- * Append a big decimal in the TDS stream.
- * @param bigDecimalVal the big decimal data value
- * @param srcJdbcType the source JDBCType
- * @param precision the precision of the data value
- */
- void writeBigDecimal(BigDecimal bigDecimalVal, int srcJdbcType, int precision) throws SQLServerException
- {
- /*
- * Length including sign byte
- * One 1-byte unsigned integer that represents the sign of the decimal value (0 => Negative, 1 => positive)
- * One 4-, 8-, 12-, or 16-byte signed integer that represents the decimal value multiplied by 10^scale.
- * The maximum size of this integer is determined based on p as follows:
- * 4 bytes if 1 <= p <= 9.
- * 8 bytes if 10 <= p <= 19.
- * 12 bytes if 20 <= p <= 28.
- * 16 bytes if 29 <= p <= 38.
- */
-
- boolean isNegative = (bigDecimalVal.signum() < 0);
- BigInteger bi = bigDecimalVal.unscaledValue();
- if (isNegative)
- bi=bi.negate();
- if (9 >= precision)
- {
- writeByte((byte) (BYTES4 + 1));
- writeByte((byte) (isNegative ? 0 : 1));
- writeInt(bi.intValue());
- }
- else if (19 >= precision)
- {
- writeByte((byte) (BYTES8 + 1));
- writeByte((byte) (isNegative ? 0 : 1));
- writeLong(bi.longValue());
- }
- else
- {
- int bLength;
- if (28 >= precision)
- bLength = BYTES12;
- else
- bLength = BYTES16;
- writeByte((byte) (bLength + 1));
- writeByte((byte) (isNegative ? 0 : 1));
-
- // Get the bytes of the BigInteger value. It is in reverse order, with
- // most significant byte in 0-th element. We need to reverse it first before sending over TDS.
- byte[] unscaledBytes = bi.toByteArray();
-
- if(unscaledBytes.length > bLength)
- {
- // If precession of input is greater than maximum allowed (p><= 38) throw Exception
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
- Object[] msgArgs = {JDBCType.of(srcJdbcType)};
- throw new SQLServerException(
- form.format(msgArgs),
- SQLState.DATA_EXCEPTION_LENGTH_MISMATCH,
- DriverError.NOT_SET,
- null);
- }
-
- // Byte array to hold all the reversed and padding bytes.
- byte[] bytes = new byte[bLength];
-
- // We need to fill up the rest of the array with zeros, as unscaledBytes may have less bytes
- // than the required size for TDS.
- int remaining = bLength - unscaledBytes.length;
-
- // Reverse the bytes.
- int i, j;
- for (i = 0, j = unscaledBytes.length - 1; i < unscaledBytes.length;)
- bytes[i++] = unscaledBytes[j--];
-
- // Fill the rest of the array with zeros.
- for(; i < remaining; i++)
- bytes[i] = (byte) 0x00;
- writeBytes(bytes);
- }
- }
-
- void writeSmalldatetime(String value) throws SQLServerException
- {
- GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
- long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
- java.sql.Timestamp timestampValue = java.sql.Timestamp.valueOf(value);
- utcMillis = timestampValue.getTime();
-
- // Load the calendar with the desired value
- calendar.setTimeInMillis(utcMillis);
-
- // Number of days since the SQL Server Base Date (January 1, 1900)
- int daysSinceSQLBaseDate =
- DDC.daysSinceBaseDate(
- calendar.get(Calendar.YEAR),
- calendar.get(Calendar.DAY_OF_YEAR),
- TDS.BASE_YEAR_1900);
-
- // Next, figure out the number of milliseconds since midnight of the current day.
- int millisSinceMidnight = 1000 * calendar.get(Calendar.SECOND) + // Seconds into the current minute
- 60 * 1000 * calendar.get(Calendar.MINUTE) + // Minutes into the current hour
- 60 * 60 * 1000 * calendar.get(Calendar.HOUR_OF_DAY); // Hours into the current day
-
- // The last millisecond of the current day is always rounded to the first millisecond
- // of the next day because DATETIME is only accurate to 1/300th of a second.
- if (1000 * 60 * 60 * 24 - 1 <= millisSinceMidnight)
- {
- ++daysSinceSQLBaseDate;
- millisSinceMidnight = 0;
- }
-
- // Number of days since the SQL Server Base Date (January 1, 1900)
- writeShort((short)daysSinceSQLBaseDate);
-
- int secondsSinceMidnight = (millisSinceMidnight/1000);
- int minutesSinceMidnight = (secondsSinceMidnight/60);
-
- // Values that are 29.998 seconds or less are rounded down to the nearest minute
- minutesSinceMidnight = ((secondsSinceMidnight % 60) > 29.998) ? minutesSinceMidnight+1 : minutesSinceMidnight;
-
- // Minutes since midnight
- writeShort((short)minutesSinceMidnight);
- }
-
- void writeDatetime(String value) throws SQLServerException
- {
- GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
- long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
- int subSecondNanos = 0;
- java.sql.Timestamp timestampValue = java.sql.Timestamp.valueOf(value);
- utcMillis = timestampValue.getTime();
- subSecondNanos = timestampValue.getNanos();
-
- // Load the calendar with the desired value
- calendar.setTimeInMillis(utcMillis);
-
- // Number of days there have been since the SQL Base Date.
- //These are based on SQL Server algorithms
- int daysSinceSQLBaseDate =
- DDC.daysSinceBaseDate(
- calendar.get(Calendar.YEAR),
- calendar.get(Calendar.DAY_OF_YEAR),
- TDS.BASE_YEAR_1900);
-
- // Number of milliseconds since midnight of the current day.
- int millisSinceMidnight =
- (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into the current second
- 1000 * calendar.get(Calendar.SECOND) + // Seconds into the current minute
- 60 * 1000 * calendar.get(Calendar.MINUTE) + // Minutes into the current hour
- 60 * 60 * 1000 * calendar.get(Calendar.HOUR_OF_DAY); // Hours into the current day
-
- // The last millisecond of the current day is always rounded to the first millisecond
- // of the next day because DATETIME is only accurate to 1/300th of a second.
- if (1000 * 60 * 60 * 24 - 1 <= millisSinceMidnight)
- {
- ++daysSinceSQLBaseDate;
- millisSinceMidnight = 0;
- }
-
- // Last-ditch verification that the value is in the valid range for the
- // DATETIMEN TDS data type (1/1/1753 to 12/31/9999). If it's not, then
- // throw an exception now so that statement execution is safely canceled.
- // Attempting to put an invalid value on the wire would result in a TDS
- // exception, which would close the connection.
- //These are based on SQL Server algorithms
- if (daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1753, 1, TDS.BASE_YEAR_1900) ||
- daysSinceSQLBaseDate >= DDC.daysSinceBaseDate(10000, 1, TDS.BASE_YEAR_1900))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
- Object[] msgArgs = {SSType.DATETIME};
- throw new SQLServerException(
- form.format(msgArgs),
- SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW,
- DriverError.NOT_SET,
- null);
- }
-
- // Number of days since the SQL Server Base Date (January 1, 1900)
- writeInt(daysSinceSQLBaseDate);
-
- // Milliseconds since midnight (at a resolution of three hundredths of a second)
- writeInt((3 * millisSinceMidnight + 5) / 10);
- }
-
- void writeDate(String value) throws SQLServerException
- {
- GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
- long utcMillis = 0;
- java.sql.Date dateValue = java.sql.Date.valueOf(value);
- utcMillis = dateValue.getTime();
-
- // Load the calendar with the desired value
- calendar.setTimeInMillis(utcMillis);
-
- writeScaledTemporal(
- calendar,
- 0, // subsecond nanos (none for a date value)
- 0, // scale (dates are not scaled)
- SSType.DATE);
- }
-
- void writeTime(java.sql.Timestamp value, int scale) throws SQLServerException
- {
- GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
- long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
- int subSecondNanos = 0;
- utcMillis = value.getTime();
- subSecondNanos = value.getNanos();
-
- // Load the calendar with the desired value
- calendar.setTimeInMillis(utcMillis);
-
- writeScaledTemporal(
- calendar,
- subSecondNanos,
- scale,
- SSType.TIME);
- }
-
- void writeDateTimeOffset(Object value,int scale,SSType destSSType) throws SQLServerException
- {
- GregorianCalendar calendar = null;
- TimeZone timeZone = TimeZone.getDefault(); // Time zone to associate with the value in the Gregorian calendar
- long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
- int subSecondNanos = 0;
- int minutesOffset=0;
-
- microsoft.sql.DateTimeOffset dtoValue = (microsoft.sql.DateTimeOffset) value;
- utcMillis = dtoValue.getTimestamp().getTime();
- subSecondNanos = dtoValue.getTimestamp().getNanos();
- minutesOffset = dtoValue.getMinutesOffset();
-
- // If the target data type is DATETIMEOFFSET, then use UTC for the calendar that
- // will hold the value, since writeRPCDateTimeOffset expects a UTC calendar.
- // Otherwise, when converting from DATETIMEOFFSET to other temporal data types,
- // use a local time zone determined by the minutes offset of the value, since
- // the writers for those types expect local calendars.
- timeZone = (SSType.DATETIMEOFFSET == destSSType) ?
- UTC.timeZone : new SimpleTimeZone(minutesOffset * 60 * 1000, "");
-
- calendar = new GregorianCalendar(timeZone, Locale.US);
- calendar.setLenient(true);
- calendar.clear();
- calendar.setTimeInMillis(utcMillis);
-
- writeScaledTemporal(
- calendar,
- subSecondNanos,
- scale,
- SSType.DATETIMEOFFSET);
-
- writeShort((short) minutesOffset);
- }
-
- void writeOffsetDateTimeWithTimezone(OffsetDateTime offsetDateTimeValue, int scale) throws SQLServerException
- {
- GregorianCalendar calendar = null;
- TimeZone timeZone;
- long utcMillis = 0;
- int subSecondNanos = 0;
- int minutesOffset=0;
-
- try
- {
- // offsetTimeValue.getOffset() returns a ZoneOffset object which has only hours and minutes
- // components. So the result of the division will be an integer always. SQL Server also supports
- // offsets in minutes precision.
- minutesOffset = offsetDateTimeValue.getOffset().getTotalSeconds()/60;
- }
- catch(Exception e)
- {
- throw new SQLServerException(
- SQLServerException.getErrString("R_zoneOffsetError"),
- null, // SQLState is null as this error is generated in the driver
- 0, // Use 0 instead of DriverError.NOT_SET to use the correct constructor
- null);
- }
- subSecondNanos = offsetDateTimeValue.getNano();
-
- // writeScaledTemporal() expects subSecondNanos in 9 digits precssion
- // but getNano() used in OffsetDateTime returns precession based on nanoseconds read from csv
- // padding zeros to match the expectation of writeScaledTemporal()
- int padding = 9 - String.valueOf(subSecondNanos).length();
- while (padding > 0)
- {
- subSecondNanos= subSecondNanos*10;
- padding--;
- }
-
- // For TIME_WITH_TIMEZONE, use UTC for the calendar that will hold the value
- timeZone=UTC.timeZone;
-
- // The behavior is similar to microsoft.sql.DateTimeOffset
- // In Timestamp format, only YEAR needs to have 4 digits. The leading zeros for the rest of the fields can be omitted.
- String offDateTimeStr = String.format("%04d",offsetDateTimeValue.getYear())
- + '-' + offsetDateTimeValue.getMonthValue()
- + '-' + offsetDateTimeValue.getDayOfMonth()
- + ' ' + offsetDateTimeValue.getHour()
- + ':' + offsetDateTimeValue.getMinute()
- + ':' + offsetDateTimeValue.getSecond();
- utcMillis = Timestamp.valueOf(offDateTimeStr).getTime();
- calendar = initializeCalender(timeZone);
- calendar.setTimeInMillis(utcMillis);
-
- // Local timezone value in minutes
- int minuteAdjustment = ((TimeZone.getDefault().getRawOffset())/(60 * 1000));
- // check if date is in day light savings and add daylight saving minutes
- if(TimeZone.getDefault().inDaylightTime(calendar.getTime()))
- minuteAdjustment += (TimeZone.getDefault().getDSTSavings())/(60*1000);
- // If the local time is negative then positive minutesOffset must be subtracted from calender
- minuteAdjustment += (minuteAdjustment < 0) ? (minutesOffset*(-1)) : minutesOffset;
- calendar.add(Calendar.MINUTE, minuteAdjustment);
-
- writeScaledTemporal(
- calendar,
- subSecondNanos,
- scale,
- SSType.DATETIMEOFFSET);
- writeShort((short) minutesOffset);
- }
-
- void writeOffsetTimeWithTimezone(OffsetTime offsetTimeValue, int scale) throws SQLServerException
- {
- GregorianCalendar calendar = null;
- TimeZone timeZone;
- long utcMillis = 0;
- int subSecondNanos = 0;
- int minutesOffset=0;
-
- try
- {
- // offsetTimeValue.getOffset() returns a ZoneOffset object which has only hours and minutes
- // components. So the result of the division will be an integer always. SQL Server also supports
- // offsets in minutes precision.
- minutesOffset = offsetTimeValue.getOffset().getTotalSeconds()/60;
- }
- catch(Exception e)
- {
- throw new SQLServerException(
- SQLServerException.getErrString("R_zoneOffsetError"),
- null, // SQLState is null as this error is generated in the driver
- 0, // Use 0 instead of DriverError.NOT_SET to use the correct constructor
- null);
- }
- subSecondNanos = offsetTimeValue.getNano();
-
- // writeScaledTemporal() expects subSecondNanos in 9 digits precssion
- // but getNano() used in OffsetDateTime returns precession based on nanoseconds read from csv
- // padding zeros to match the expectation of writeScaledTemporal()
- int padding = 9 - String.valueOf(subSecondNanos).length();
- while (padding > 0)
- {
- subSecondNanos= subSecondNanos*10;
- padding--;
- }
-
- // For TIME_WITH_TIMEZONE, use UTC for the calendar that will hold the value
- timeZone = UTC.timeZone;
-
- // Using TDS.BASE_YEAR_1900, based on SQL server behavious
- // If date only contains a time part, the return value is 1900, the base year.
- // https://msdn.microsoft.com/en-us/library/ms186313.aspx
- // In Timestamp format, leading zeros for the fields can be omitted.
- String offsetTimeStr = TDS.BASE_YEAR_1900 + "-01-01"
- + ' ' + offsetTimeValue.getHour()
- + ':' + offsetTimeValue.getMinute()
- + ':' + offsetTimeValue.getSecond();
- utcMillis = Timestamp.valueOf(offsetTimeStr).getTime();
-
- calendar = initializeCalender(timeZone);
- calendar.setTimeInMillis(utcMillis);
-
- int minuteAdjustment=(TimeZone.getDefault().getRawOffset())/(60 * 1000);
- // check if date is in day light savings and add daylight saving minutes to Local timezone(in minutes)
- if(TimeZone.getDefault().inDaylightTime(calendar.getTime()))
- minuteAdjustment += ((TimeZone.getDefault().getDSTSavings())/(60*1000));
- // If the local time is negative then positive minutesOffset must be subtracted from calender
- minuteAdjustment += (minuteAdjustment < 0) ? (minutesOffset*(-1)) : minutesOffset;
- calendar.add(Calendar.MINUTE, minuteAdjustment);
-
- writeScaledTemporal(
- calendar,
- subSecondNanos,
- scale,
- SSType.DATETIMEOFFSET);
- writeShort((short) minutesOffset);
- }
-
- void writeLong(long value) throws SQLServerException
- {
- if (stagingBuffer.remaining() >= 8)
- {
- stagingBuffer.putLong(value);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.putLong(value);
- else
- logBuffer.position(logBuffer.position() + 8);
- }
- }
- else
- {
- valueBytes[0] = (byte)((value >> 0) & 0xFF);
- valueBytes[1] = (byte)((value >> 8) & 0xFF);
- valueBytes[2] = (byte)((value >> 16) & 0xFF);
- valueBytes[3] = (byte)((value >> 24) & 0xFF);
- valueBytes[4] = (byte)((value >> 32) & 0xFF);
- valueBytes[5] = (byte)((value >> 40) & 0xFF);
- valueBytes[6] = (byte)((value >> 48) & 0xFF);
- valueBytes[7] = (byte)((value >> 56) & 0xFF);
- writeWrappedBytes(valueBytes, 8);
- }
- }
-
- void writeBytes(byte[] value) throws SQLServerException
- {
- writeBytes(value, 0, value.length);
- }
-
- void writeBytes(byte[] value, int offset, int length) throws SQLServerException
- {
- assert length <= value.length;
-
- int bytesWritten = 0;
- int bytesToWrite;
-
- if (logger.isLoggable(Level.FINEST))
- logger.finest(toString() + " Writing " + length + " bytes");
-
- while ((bytesToWrite = length - bytesWritten) > 0)
- {
- if (0 == stagingBuffer.remaining())
- writePacket(TDS.STATUS_NORMAL);
-
- if (bytesToWrite > stagingBuffer.remaining())
- bytesToWrite = stagingBuffer.remaining();
-
- stagingBuffer.put(value, offset + bytesWritten, bytesToWrite);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.put(value, offset + bytesWritten, bytesToWrite);
- else
- logBuffer.position(logBuffer.position() + bytesToWrite);
- }
-
- bytesWritten += bytesToWrite;
- }
- }
-
- void writeWrappedBytes(byte value[], int valueLength) throws SQLServerException
- {
- // This function should only be used to write a value that is longer than
- // what remains in the current staging buffer. However, the value must
- // be short enough to fit in an empty buffer.
- assert valueLength <= value.length;
- assert stagingBuffer.remaining() < valueLength;
- assert valueLength <= stagingBuffer.capacity();
-
- // Fill any remaining space in the staging buffer
- int remaining = stagingBuffer.remaining();
- if (remaining > 0)
- {
- stagingBuffer.put(value, 0, remaining);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.put(value, 0, remaining);
- else
- logBuffer.position(logBuffer.position() + remaining);
- }
- }
-
- writePacket(TDS.STATUS_NORMAL);
-
- // After swapping, the staging buffer should once again be empty, so the
- // remainder of the value can be written to it.
- stagingBuffer.put(value, remaining, valueLength - remaining);
- if (tdsChannel.isLoggingPackets())
- {
- if (dataIsLoggable)
- logBuffer.put(value, remaining, valueLength - remaining);
- else
- logBuffer.position(logBuffer.position() + remaining);
- }
- }
-
- void writeString(String value) throws SQLServerException
- {
- int charsCopied = 0;
- int length = value.length();
- while (charsCopied < length)
- {
- int bytesToCopy = 2 * (length - charsCopied);
-
- if (bytesToCopy > valueBytes.length)
- bytesToCopy = valueBytes.length;
-
- int bytesCopied = 0;
- while (bytesCopied < bytesToCopy)
- {
- char ch = value.charAt(charsCopied++);
- valueBytes[bytesCopied++] = (byte)((ch >> 0) & 0xFF);
- valueBytes[bytesCopied++] = (byte)((ch >> 8) & 0xFF);
- }
-
- writeBytes(valueBytes, 0, bytesCopied);
- }
- }
-
- void writeStream(
- InputStream inputStream,
- long advertisedLength,
- boolean writeChunkSizes) throws SQLServerException
- {
- assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0;
-
- long actualLength = 0;
- final byte[] streamByteBuffer = new byte[4 * currentPacketSize];
- int bytesRead = 0;
- int bytesToWrite;
- do
- {
- // Read in next chunk
- for (bytesToWrite = 0; -1 != bytesRead && bytesToWrite < streamByteBuffer.length; bytesToWrite += bytesRead)
- {
- try
- {
- bytesRead = inputStream.read(streamByteBuffer, bytesToWrite, streamByteBuffer.length - bytesToWrite);
- }
- catch (IOException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {e.toString()};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
- }
-
- if (-1 == bytesRead)
- break;
-
- // Check for invalid bytesRead returned from InputStream.read
- if (bytesRead < 0 || bytesRead > streamByteBuffer.length - bytesToWrite)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {SQLServerException.getErrString("R_streamReadReturnedInvalidValue")};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
- }
- }
-
- // Write it out
- if (writeChunkSizes)
- writeInt(bytesToWrite);
-
- writeBytes(streamByteBuffer, 0, bytesToWrite);
- actualLength += bytesToWrite;
- }
- while (-1 != bytesRead || bytesToWrite > 0);
-
- // If we were given an input stream length that we had to match and
- // the actual stream length did not match then cancel the request.
- if (DataTypes.UNKNOWN_STREAM_LENGTH != advertisedLength && actualLength != advertisedLength)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
- Object[] msgArgs = {Long.valueOf(advertisedLength), Long.valueOf(actualLength)};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET);
- }
- }
-
-
- /*
- * Adding another function for writing non-unicode reader instead of re-factoring the writeReader() for performance efficiency.
- * As this method will only be used in bulk copy, it needs to be efficient.
- * Note: Any changes in algorithm/logic should propagate to both writeReader() and writeNonUnicodeReader().
- */
-
- void writeNonUnicodeReader(
- Reader reader,
- long advertisedLength,
- boolean isDestBinary,
- Charset charSet) throws SQLServerException
- {
- assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0;
-
- long actualLength = 0;
- char[] streamCharBuffer = new char[currentPacketSize];
- // The unicode version, writeReader() allocates a byte buffer that is 4 times the currentPacketSize, not sure why.
- byte[] streamByteBuffer = new byte[currentPacketSize];
- int charsRead = 0;
- int charsToWrite;
- int bytesToWrite;
- String streamString;
-
- do
- {
- // Read in next chunk
- for (charsToWrite = 0; -1 != charsRead && charsToWrite < streamCharBuffer.length; charsToWrite += charsRead)
- {
- try
- {
- charsRead = reader.read(streamCharBuffer, charsToWrite, streamCharBuffer.length - charsToWrite);
- }
- catch (IOException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {e.toString()};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
- }
-
- if (-1 == charsRead)
- break;
-
- // Check for invalid bytesRead returned from Reader.read
- if (charsRead < 0 || charsRead > streamCharBuffer.length - charsToWrite)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {SQLServerException.getErrString("R_streamReadReturnedInvalidValue")};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
- }
- }
-
- if(!isDestBinary)
- {
- // Write it out
- // This also writes the PLP_TERMINATOR token after all the data in the the stream are sent.
- // The Do-While loop goes on one more time as charsToWrite is greater than 0 for the last chunk, and
- // in this last round the only thing that is written is an int value of 0, which is the PLP Terminator token(0x00000000).
- writeInt(charsToWrite);
-
- for (int charsCopied = 0; charsCopied < charsToWrite; ++charsCopied)
- {
- if(null == charSet)
- {
- streamByteBuffer[charsCopied] = (byte)(streamCharBuffer[charsCopied] & 0xFF);
- }
- else
- {
- // encoding as per collation
- streamByteBuffer[charsCopied] = new String(streamCharBuffer[charsCopied] +
- "")
- .getBytes(charSet)[0];
- }
- }
- writeBytes(streamByteBuffer, 0, charsToWrite);
- }
- else
- {
- bytesToWrite = charsToWrite;
- if(0 != charsToWrite)
- bytesToWrite = charsToWrite /2;
-
- streamString = new String(streamCharBuffer);
- byte[] bytes = ParameterUtils.HexToBin(streamString.trim());
- writeInt(bytesToWrite);
- writeBytes(bytes , 0 , bytesToWrite);
- }
- actualLength += charsToWrite;
- }
- while (-1 != charsRead || charsToWrite > 0);
-
- // If we were given an input stream length that we had to match and
- // the actual stream length did not match then cancel the request.
- if (DataTypes.UNKNOWN_STREAM_LENGTH != advertisedLength && actualLength != advertisedLength)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
- Object[] msgArgs = {Long.valueOf(advertisedLength), Long.valueOf(actualLength)};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET);
- }
- }
-
-
- /*
- * Note: There is another method with same code logic for non unicode reader, writeNonUnicodeReader(), implemented for performance efficiency.
- * Any changes in algorithm/logic should propagate to both writeReader() and writeNonUnicodeReader().
- */
- void writeReader(
- Reader reader,
- long advertisedLength,
- boolean writeChunkSizes) throws SQLServerException
- {
- assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0;
-
- long actualLength = 0;
- char[] streamCharBuffer = new char[2 * currentPacketSize];
- byte[] streamByteBuffer = new byte[4 * currentPacketSize];
- int charsRead = 0;
- int charsToWrite;
- do
- {
- // Read in next chunk
- for (charsToWrite = 0; -1 != charsRead && charsToWrite < streamCharBuffer.length; charsToWrite += charsRead)
- {
- try
- {
- charsRead = reader.read(streamCharBuffer, charsToWrite, streamCharBuffer.length - charsToWrite);
- }
- catch (IOException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {e.toString()};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
- }
-
- if (-1 == charsRead)
- break;
-
- // Check for invalid bytesRead returned from Reader.read
- if (charsRead < 0 || charsRead > streamCharBuffer.length - charsToWrite)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {SQLServerException.getErrString("R_streamReadReturnedInvalidValue")};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
- }
- }
-
- // Write it out
- if (writeChunkSizes)
- writeInt(2 * charsToWrite);
-
- // Convert from Unicode characters to bytes
- //
- // Note: The following inlined code is much faster than the equivalent
- // call to (new String(streamCharBuffer)).getBytes("UTF-16LE") because it
- // saves a conversion to String and use of Charset in that conversion.
- for (int charsCopied = 0; charsCopied < charsToWrite; ++charsCopied)
- {
- streamByteBuffer[2 * charsCopied] = (byte)((streamCharBuffer[charsCopied] >> 0) & 0xFF);
- streamByteBuffer[2 * charsCopied + 1] = (byte)((streamCharBuffer[charsCopied] >> 8) & 0xFF);
- }
-
- writeBytes(streamByteBuffer, 0, 2 * charsToWrite);
- actualLength += charsToWrite;
- }
- while (-1 != charsRead || charsToWrite > 0);
-
- // If we were given an input stream length that we had to match and
- // the actual stream length did not match then cancel the request.
- if (DataTypes.UNKNOWN_STREAM_LENGTH != advertisedLength && actualLength != advertisedLength)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
- Object[] msgArgs = {Long.valueOf(advertisedLength), Long.valueOf(actualLength)};
- error(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET);
- }
- }
-
- GregorianCalendar initializeCalender(TimeZone timeZone)
- {
- GregorianCalendar calendar = null;
-
- // Create the calendar that will hold the value. For DateTimeOffset values, the calendar's
- // time zone is UTC. For other values, the calendar's time zone is a local time zone.
- calendar = new GregorianCalendar(timeZone, Locale.US);
-
- // Set the calendar lenient to allow setting the DAY_OF_YEAR and MILLISECOND fields
- // to roll other fields to their correct values.
- calendar.setLenient(true);
-
- // Clear the calendar of any existing state. The state of a new Calendar object always
- // reflects the current date, time, DST offset, etc.
- calendar.clear();
-
- return calendar;
- }
-
- final void error(String reason, SQLState sqlState, DriverError driverError) throws SQLServerException
- {
- assert null != command;
- command.interrupt(reason);
- throw new SQLServerException(reason, sqlState, driverError, null);
- }
-
- /**
- * Sends an attention signal to the server, if necessary, to tell it to stop processing
- * the current command on this connection.
- *
- * If no packets of the command's request have yet been sent to the server, then no
- * attention signal needs to be sent. The interrupt will be handled entirely by the
- * driver.
- *
- * This method does not need synchronization as it does not manipulate interrupt
- * state and writing is guaranteed to occur only from one thread at a time.
- */
- final boolean sendAttention() throws SQLServerException
- {
- // If any request packets were already written to the server then send an
- // attention signal to the server to tell it to ignore the request or
- // cancel its execution.
- if (packetNum > 0)
- {
- //Ideally, we would want to add the following assert here.
- //But to add that the variable isEOMSent would have to be made
- //volatile as this piece of code would be reached from multiple
- //threads. So, not doing it to avoid perf hit. Note that
- //isEOMSent would be updated in writePacket everytime an EOM is sent
- //assert isEOMSent;
-
- if (logger.isLoggable(Level.FINE))
- logger.fine(this + ": sending attention...");
-
- ++tdsChannel.numMsgsSent;
-
- startMessage(command, TDS.PKT_CANCEL_REQ);
- endMessage();
-
- return true;
- }
-
- return false;
- }
-
- private void writePacket(int tdsMessageStatus) throws SQLServerException
- {
- final boolean atEOM = (TDS.STATUS_BIT_EOM == (TDS.STATUS_BIT_EOM & tdsMessageStatus));
- final boolean isCancelled = ( (TDS.PKT_CANCEL_REQ == tdsMessageType)
- ||
- ((tdsMessageStatus & TDS.STATUS_BIT_ATTENTION)==TDS.STATUS_BIT_ATTENTION)
- );
- // Before writing each packet to the channel, check if an interrupt has occurred.
- if (null != command && (!isCancelled))
- command.checkForInterrupt();
-
- writePacketHeader(tdsMessageStatus|sendResetConnection);
- sendResetConnection = 0;
-
- flush(atEOM);
-
- // If this is the last packet then flush the remainder of the request
- // through the socket. The first flush() call ensured that data currently
- // waiting in the socket buffer was sent, flipped the buffers, and started
- // sending data from the staging buffer (flipped to be the new socket buffer).
- // This flush() call ensures that all remaining data in the socket buffer is sent.
- if (atEOM)
- {
- flush(atEOM);
- isEOMSent = true;
- ++tdsChannel.numMsgsSent;
- }
-
- // If we just sent the first login request packet and SSL encryption was enabled
- // for login only, then disable SSL now.
- if (TDS.PKT_LOGON70 == tdsMessageType &&
- 1 == packetNum &&
- TDS.ENCRYPT_OFF == con.getNegotiatedEncryptionLevel())
- {
- tdsChannel.disableSSL();
- }
-
- // Notify the currently associated command (if any) that we have written the last
- // of the response packets to the channel.
- if (null != command && (!isCancelled) && atEOM)
- command.onRequestComplete();
- }
-
- private void writePacketHeader(int tdsMessageStatus)
- {
- int tdsMessageLength = stagingBuffer.position();
- ++packetNum;
-
- // Write the TDS packet header back at the start of the staging buffer
- stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_TYPE, tdsMessageType);
- stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_STATUS, (byte) tdsMessageStatus);
- stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH, (byte)((tdsMessageLength >> 8) & 0xFF)); // Note: message length is 16 bits,
- stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH + 1, (byte)((tdsMessageLength >> 0) & 0xFF)); // written BIG ENDIAN
- stagingBuffer.put(TDS.PACKET_HEADER_SPID, (byte)((tdsChannel.getSPID() >> 8) & 0xFF)); // Note: SPID is 16 bits,
- stagingBuffer.put(TDS.PACKET_HEADER_SPID + 1, (byte)((tdsChannel.getSPID() >> 0) & 0xFF)); // written BIG ENDIAN
- stagingBuffer.put(TDS.PACKET_HEADER_SEQUENCE_NUM, (byte)(packetNum % 256));
- stagingBuffer.put(TDS.PACKET_HEADER_WINDOW, (byte)0); // Window (Reserved/Not used)
-
- // Write the header to the log buffer too if logging.
- if (tdsChannel.isLoggingPackets())
- {
- logBuffer.put(TDS.PACKET_HEADER_MESSAGE_TYPE, tdsMessageType);
- logBuffer.put(TDS.PACKET_HEADER_MESSAGE_STATUS, (byte) tdsMessageStatus);
- logBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH, (byte)((tdsMessageLength >> 8) & 0xFF)); // Note: message length is 16 bits,
- logBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH + 1, (byte)((tdsMessageLength >> 0) & 0xFF)); // written BIG ENDIAN
- logBuffer.put(TDS.PACKET_HEADER_SPID, (byte)((tdsChannel.getSPID() >> 8) & 0xFF)); // Note: SPID is 16 bits,
- logBuffer.put(TDS.PACKET_HEADER_SPID + 1, (byte)((tdsChannel.getSPID() >> 0) & 0xFF)); // written BIG ENDIAN
- logBuffer.put(TDS.PACKET_HEADER_SEQUENCE_NUM, (byte)(packetNum % 256));
- logBuffer.put(TDS.PACKET_HEADER_WINDOW, (byte)0); // Window (Reserved/Not used);
- }
- }
-
- void flush(boolean atEOM) throws SQLServerException
- {
- // First, flush any data left in the socket buffer.
- tdsChannel.write(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining());
- socketBuffer.position(socketBuffer.limit());
-
- // If there is data in the staging buffer that needs to be written
- // to the socket, the socket buffer is now empty, so swap buffers
- // and start writing data from the staging buffer.
- if (stagingBuffer.position() >= TDS_PACKET_HEADER_SIZE)
- {
- // Swap the packet buffers ...
- ByteBuffer swapBuffer = stagingBuffer;
- stagingBuffer = socketBuffer;
- socketBuffer = swapBuffer;
-
- // ... and prepare to send data from the from the new socket
- // buffer (the old staging buffer).
- //
- // We need to use flip() rather than rewind() here so that
- // the socket buffer's limit is properly set for the last
- // packet, which may be shorter than the other packets.
- socketBuffer.flip();
- stagingBuffer.clear();
-
- // If we are logging TDS packets then log the packet we're about
- // to send over the wire now.
- if (tdsChannel.isLoggingPackets())
- {
- tdsChannel.logPacket(
- logBuffer.array(),
- 0,
- socketBuffer.limit(),
- this.toString() + " sending packet (" + socketBuffer.limit() + " bytes)");
- }
-
- // Prepare for the next packet
- if (!atEOM)
- preparePacket();
-
- // Finally, start sending data from the new socket buffer.
- tdsChannel.write(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining());
- socketBuffer.position(socketBuffer.limit());
- }
- }
-
-
- // Composite write operations
-
- /**
- * Write out elements common to all RPC values.
- * @param sName the optional parameter name
- * @param bOut boolean true if the value that follows is being registered as an ouput parameter
- * @param tdsType TDS type of the value that follows
- */
- void writeRPCNameValType(String sName, boolean bOut, TDSType tdsType) throws SQLServerException
- {
- int nNameLen=0;
-
- if (null != sName)
- nNameLen = sName.length()+1; //The @ prefix is required for the param
-
- writeByte((byte) nNameLen); // param name len
- if (nNameLen > 0)
- {
- writeChar('@');
- writeString(sName);
- }
-
- if(null!=cryptoMeta)
- writeByte((byte) (bOut ? 1|TDS.AE_METADATA : 0|TDS.AE_METADATA)); // status
- else
- writeByte((byte) (bOut ? 1 : 0)); // status
- writeByte(tdsType.byteValue()); // type
- }
-
- /**
- * Append a boolean value in RPC transmission format.
- * @param sName the optional parameter name
- * @param booleanValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCBit(String sName, Boolean booleanValue, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.BITN);
- writeByte((byte) 1); // max length of datatype
- if (null == booleanValue)
- {
- writeByte((byte) 0); // len of data bytes
- }
- else
- {
- writeByte((byte) 1); //length of datatype
- writeByte((byte)(booleanValue.booleanValue() ? 1 : 0));
- }
- }
-
- /**
- * Append a short value in RPC transmission format.
- * @param sName the optional parameter name
- * @param shortValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCByte(String sName, Byte byteValue, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.INTN);
- writeByte((byte) 1); // max length of datatype
- if (null == byteValue)
- {
- writeByte((byte) 0); // len of data bytes
- }
- else
- {
- writeByte((byte) 1); //length of datatype
- writeByte(byteValue.byteValue());
- }
- }
-
- /**
- * Append a short value in RPC transmission format.
- * @param sName the optional parameter name
- * @param shortValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCShort(String sName, Short shortValue, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.INTN);
- writeByte((byte) 2); // max length of datatype
- if (null == shortValue)
- {
- writeByte((byte) 0); // len of data bytes
- }
- else
- {
- writeByte((byte) 2); //length of datatype
- writeShort(shortValue.shortValue());
- }
- }
-
- /**
- * Append an int value in RPC transmission format.
- * @param sName the optional parameter name
- * @param intValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCInt(String sName, Integer intValue, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.INTN);
- writeByte((byte) 4); // max length of datatype
- if (null == intValue)
- {
- writeByte((byte) 0); // len of data bytes
- }
- else
- {
- writeByte((byte) 4); // length of datatype
- writeInt(intValue.intValue());
- }
- }
-
- /**
- * Append a long value in RPC transmission format.
- * @param sName the optional parameter name
- * @param longValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCLong(String sName, Long longValue, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.INTN);
- writeByte((byte) 8); // max length of datatype
- if (null == longValue)
- {
- writeByte((byte) 0); // len of data bytes
- }
- else
- {
- writeByte((byte) 8); // length of datatype
- writeLong(longValue.longValue());
- }
- }
-
- /**
- * Append a real value in RPC transmission format.
- * @param sName the optional parameter name
- * @param floatValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCReal(String sName, Float floatValue, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.FLOATN);
-
- // Data and length
- if (null == floatValue)
- {
- writeByte((byte) 4); // max length
- writeByte((byte) 0); // actual length (0 == null)
- }
- else
- {
- writeByte((byte) 4); // max length
- writeByte((byte) 4); // actual length
- writeInt(Float.floatToRawIntBits(floatValue.floatValue()));
- }
- }
-
- /**
- * Append a double value in RPC transmission format.
- * @param sName the optional parameter name
- * @param doubleValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCDouble(String sName, Double doubleValue, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.FLOATN);
-
- int l = 8;
- writeByte((byte) l); // max length of datatype
-
- // Data and length
- if (null == doubleValue)
- {
- writeByte((byte) 0); // len of data bytes
- }
- else
- {
- writeByte((byte) l); // len of data bytes
- long bits = Double.doubleToLongBits(doubleValue.doubleValue());
- long mask = 0xFF;
- int nShift =0;
- for (int i=0; i<8; i++)
- {
- writeByte((byte) ((bits & mask) >> nShift));
- nShift += 8;
- mask = mask << 8;
- }
- }
- }
-
- /**
- * Append a big decimal in RPC transmission format.
- * @param sName the optional parameter name
- * @param bdValue the data value
- * @param nScale the desired scale
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- */
- void writeRPCBigDecimal(String sName, BigDecimal bdValue, int nScale, boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.DECIMALN);
- writeByte((byte) 0x11); // maximum length
- writeByte((byte) SQLServerConnection.maxDecimalPrecision); // precision
-
- byte[] valueBytes = DDC.convertBigDecimalToBytes(bdValue, nScale);
- writeBytes(valueBytes, 0, valueBytes.length);
- }
-
- /**
- * Appends a standard v*max header for RPC parameter transmission.
- * @param headerLength the total length of the PLP data block.
- * @param isNull true if the value is NULL.
- * @param collation The SQL collation associated with the value that follows the v*max header. Null for non-textual types.
- */
- void writeVMaxHeader(long headerLength, boolean isNull, SQLCollation collation) throws SQLServerException
- {
- // Send v*max length indicator 0xFFFF.
- writeShort((short) 0xFFFF);
-
- // Send collation if requested.
- if (null != collation)
- collation.writeCollation(this);
-
- // Handle null here and return, we're done here if it's null.
- if (isNull)
- {
- // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
- writeLong(0xFFFFFFFFFFFFFFFFL);
- }
- else if (DataTypes.UNKNOWN_STREAM_LENGTH == headerLength)
- {
- // Append v*max length.
- // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
- writeLong(0xFFFFFFFFFFFFFFFEL);
-
- // NOTE: Don't send the first chunk length, this will be calculated by caller.
- }
- else
- {
- // For v*max types with known length, length is
- // We're sending same total length as chunk length (as we're sending 1 chunk).
- writeLong(headerLength);
- }
- }
-
- /**
- * Utility for internal writeRPCString calls
- */
- void writeRPCStringUnicode(String sValue) throws SQLServerException
- {
- writeRPCStringUnicode(null, sValue, false, null);
- }
-
- /**
- * Writes a string value as Unicode for RPC
- * @param sName the optional parameter name
- * @param sValue the data value
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- * @param collation the collation of the data value
- */
- void writeRPCStringUnicode(
- String sName,
- String sValue,
- boolean bOut,
- SQLCollation collation) throws SQLServerException
- {
- boolean bValueNull = (sValue==null);
- int nValueLen = bValueNull ? 0 : (2 * sValue.length());
- boolean isShortValue = nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
-
- // Textual RPC requires a collation. If none is provided, as is the case when
- // the SSType is non-textual, then use the database collation by default.
- if (null == collation)
- collation = con.getDatabaseCollation();
-
- // Use PLP encoding on Yukon and later with long values and OUT parameters
- boolean usePLP = (!isShortValue || bOut);
- if (usePLP)
- {
- writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);
-
- // Handle Yukon v*max type header here.
- writeVMaxHeader(
- nValueLen, // Length
- bValueNull, // Is null?
- collation);
-
- // Send the data.
- if (!bValueNull)
- {
- if (nValueLen > 0)
- {
- writeInt(nValueLen);
- writeString(sValue);
- }
-
- // Send the terminator PLP chunk.
- writeInt(0);
- }
- }
- else // non-PLP type
- {
- // Write maximum length of data
- if (isShortValue)
- {
- writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);
- writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
- }
- else
- {
- writeRPCNameValType(sName, bOut, TDSType.NTEXT);
- writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
- }
-
- collation.writeCollation(this);
-
- // Data and length
- if (bValueNull)
- {
- writeShort((short) -1); // actual len
- }
- else
- {
- // Write actual length of data
- if (isShortValue)
- writeShort((short) nValueLen);
- else
- writeInt(nValueLen);
-
- // If length is zero, we're done.
- if (0 != nValueLen)
- writeString(sValue); //data
- }
- }
- }
-
- void writeTVP(TVP value) throws SQLServerException
- {
- if (!value.isNull())
- {
- writeByte((byte) 0); // status
- }
- else
- {
- // Default TVP
- writeByte((byte) TDS.TVP_STATUS_DEFAULT); // default TVP
- }
-
- writeByte((byte)TDS.TDS_TVP);
-
- /*
- TVP_TYPENAME = DbName
- OwningSchema
- TypeName
- */
- // Database where TVP type resides
- if(null != value.getDbNameTVP())
- {
- writeByte((byte) value.getDbNameTVP().length());
- writeString(value.getDbNameTVP());
- }
- else
- writeByte((byte) 0x00); // empty DB name
-
- // Schema where TVP type resides
- if(null != value.getOwningSchemaNameTVP())
- {
- writeByte((byte) value.getOwningSchemaNameTVP().length());
- writeString(value.getOwningSchemaNameTVP());
- }
- else
- writeByte((byte) 0x00); // empty Schema name
-
- // TVP type name
- if(null != value.getTVPName())
- {
- writeByte((byte) value.getTVPName().length());
- writeString(value.getTVPName());
- }
- else
- writeByte((byte) 0x00); // empty TVP name
-
-
- if (!value.isNull())
- {
- writeTVPColumnMetaData(value);
-
- // optional OrderUnique metadata
- writeTvpOrderUnique(value);
- }
- else
- {
- writeShort((short) TDS.TVP_NULL_TOKEN);
- }
-
- // TVP_END_TOKEN
- writeByte((byte) 0x00);
-
- try
- {
- writeTVPRows(value);
- }
- catch(NumberFormatException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
- }
- catch(ClassCastException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
- }
- }
-
- void writeTVPRows(TVP value) throws SQLServerException
- {
- boolean isShortValue, isNull;
- int dataLength;
-
- if (!value.isNull())
- {
- Map columnMetadata = value.getColumnMetadata();
- Iterator> columnsIterator;
-
- while(value.next())
- {
- Object[] rowData = value.getRowData();
-
- // ROW
- writeByte((byte) TDS.TVP_ROW);
- columnsIterator = columnMetadata.entrySet().iterator();
- int currentColumn = 0;
- while(columnsIterator.hasNext())
- {
- Map.Entry columnPair = columnsIterator.next();
-
- // If useServerDefault is set, client MUST NOT emit TvpColumnData for the associated column
- if (columnPair.getValue().useServerDefault)
- {
- currentColumn++;
- continue;
- }
-
- JDBCType jdbcType = JDBCType.of(columnPair.getValue().javaSqlType);
- String currentColumnStringValue = null;
-
- Object currentObject = null;
- if (null != rowData)
- {
- // if rowData has value for the current column, retrieve it. If not, current column will stay null.
- if (rowData.length > currentColumn)
- {
- currentObject = rowData[currentColumn];
- if (null != currentObject)
- {
- currentColumnStringValue = String.valueOf(currentObject);
- }
- }
- }
- switch(jdbcType)
- {
- case BIGINT:
- if(null == currentColumnStringValue)
- writeByte((byte) 0);
- else
- {
- writeByte((byte) 8);
- writeLong(Long.valueOf(currentColumnStringValue).longValue());
- }
- break;
-
- case BIT:
- if(null == currentColumnStringValue)
- writeByte((byte) 0);
- else
- {
- writeByte((byte) 1);
- writeByte((byte) (Boolean.valueOf(currentColumnStringValue).booleanValue() ? 1 : 0));
- }
- break;
-
- case INTEGER:
- if(null == currentColumnStringValue)
- writeByte((byte) 0);
- else
- {
- writeByte((byte)4);
- writeInt(Integer.valueOf(currentColumnStringValue).intValue());
- }
- break;
-
- case SMALLINT:
- case TINYINT:
- if(null == currentColumnStringValue)
- writeByte((byte) 0);
- else
- {
- writeByte((byte) 2); //length of datatype
- writeShort(Short.valueOf(currentColumnStringValue).shortValue());
- }
- break;
-
- case DECIMAL:
- case NUMERIC:
- if(null == currentColumnStringValue)
- writeByte((byte) 0);
- else
- {
- writeByte((byte) TDSWriter.BIGDECIMAL_MAX_LENGTH); // maximum length
- BigDecimal bdValue = new BigDecimal(currentColumnStringValue);
-
- // setScale of all BigDecimal value based on metadata sent
- bdValue = bdValue.setScale(columnPair.getValue().scale, RoundingMode.HALF_UP);
- byte[] valueBytes = DDC.convertBigDecimalToBytes(bdValue, bdValue.scale());
-
- // 1-byte for sign and 16-byte for integer
- byte[] byteValue = new byte[17];
-
- // removing the precision and scale information from the valueBytes array
- System.arraycopy(valueBytes, 2, byteValue, 0, valueBytes.length - 2);
- writeBytes(byteValue);
- }
- break;
-
- case DOUBLE:
- if (null == currentColumnStringValue)
- writeByte((byte) 0); // len of data bytes
- else
- {
- writeByte((byte) 8); // len of data bytes
- long bits = Double.doubleToLongBits(Double.valueOf(currentColumnStringValue).doubleValue());
- long mask = 0xFF;
- int nShift =0;
- for (int i=0; i<8; i++)
- {
- writeByte((byte) ((bits & mask) >> nShift));
- nShift += 8;
- mask = mask << 8;
- }
- }
- break;
-
- case FLOAT:
- case REAL:
- if (null == currentColumnStringValue)
- writeByte((byte) 0); // actual length (0 == null)
- else
- {
- writeByte((byte) 4); // actual length
- writeInt(Float.floatToRawIntBits(Float.valueOf(currentColumnStringValue).floatValue()));
- }
- break;
-
- case DATE:
- case TIME:
- case TIMESTAMP:
- case DATETIMEOFFSET:
- case TIMESTAMP_WITH_TIMEZONE:
- case TIME_WITH_TIMEZONE:
- case CHAR:
- case VARCHAR:
- case NCHAR:
- case NVARCHAR:
- isShortValue = (2 * columnPair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
- isNull = (null == currentColumnStringValue);
- dataLength = isNull ? 0: currentColumnStringValue.length() * 2 ;
- if(!isShortValue)
- {
- // check null
- if (isNull)
- // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
- writeLong(0xFFFFFFFFFFFFFFFFL);
- else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength)
- // Append v*max length.
- // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
- writeLong(0xFFFFFFFFFFFFFFFEL);
- else
- // For v*max types with known length, length is
- writeLong(dataLength);
- if(!isNull)
- {
- if(dataLength > 0)
- {
- writeInt(dataLength);
- writeString(currentColumnStringValue);
- }
- // Send the terminator PLP chunk.
- writeInt(0);
- }
- }
- else
- {
- if(isNull)
- writeShort((short) -1); // actual len
- else
- {
- writeShort((short) dataLength);
- writeString(currentColumnStringValue);
- }
- }
- break;
-
- case BINARY:
- case VARBINARY:
- // Handle conversions as done in other types.
- isShortValue = columnPair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
- isNull = (null == currentObject);
- if(currentObject instanceof String)
- dataLength = isNull ? 0: (toByteArray(currentObject.toString())).length ;
- else
- dataLength = isNull ? 0: ((byte[])currentObject).length ;
- if(!isShortValue)
- {
- // check null
- if (isNull)
- // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
- writeLong(0xFFFFFFFFFFFFFFFFL);
- else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength)
- // Append v*max length.
- // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
- writeLong(0xFFFFFFFFFFFFFFFEL);
- else
- // For v*max types with known length, length is
- writeLong(dataLength);
- if(!isNull)
- {
- if(dataLength > 0)
- {
- writeInt(dataLength);
- if(currentObject instanceof String)
- writeBytes(toByteArray(currentObject.toString()));
- else
- writeBytes((byte[])currentObject);
- }
- // Send the terminator PLP chunk.
- writeInt(0);
- }
- }
- else
- {
- if(isNull)
- writeShort((short) -1); // actual len
- else
- {
- writeShort((short) dataLength);
- if(currentObject instanceof String)
- writeBytes( toByteArray(currentObject.toString())) ;
- else
- writeBytes((byte[])currentObject);
- }
- }
- break;
-
- default:
- assert false : "Unexpected JDBC type " + jdbcType.toString();
- }
- currentColumn ++;
- }
- }
- }
- // TVP_END_TOKEN
- writeByte((byte) 0x00);
- }
-
- private static byte[] toByteArray(String s)
- {
- return DatatypeConverter.parseHexBinary(s);
- }
-
- void writeTVPColumnMetaData(TVP value) throws SQLServerException
- {
- boolean isShortValue;
-
- //TVP_COLMETADATA
- writeShort((short) value.getTVPColumnCount());
-
- Map columnMetadata = value.getColumnMetadata();
- Iterator> columnsIterator = columnMetadata.entrySet().iterator();
- /*
- TypeColumnMetaData = UserType
- Flags
- TYPE_INFO
- ColName ;
- */
-
- while(columnsIterator.hasNext())
- {
- Map.Entry pair = columnsIterator.next();
- JDBCType jdbcType = JDBCType.of(pair.getValue().javaSqlType);
- boolean useServerDefault = pair.getValue().useServerDefault;
- // ULONG ; UserType of column
- // The value will be 0x0000 with the exceptions of TIMESTAMP (0x0050) and alias types (greater than 0x00FF).
- writeInt(0);
- /*
- Flags = fNullable ; Column is nullable - %x01
- fCaseSen -- Ignored ;
- usUpdateable -- Ignored ;
- fIdentity ; Column is identity column - %x10
- fComputed ; Column is computed - %x20
- usReservedODBC -- Ignored ;
- fFixedLenCLRType-- Ignored ;
- fDefault ; Column is default value - %x200
- usReserved -- Ignored ;
- */
-
- short flags = TDS.FLAG_NULLABLE;
- if (useServerDefault)
- {
- flags |= TDS.FLAG_TVP_DEFAULT_COLUMN;
- }
- writeShort(flags);
-
- // Type info
- switch(jdbcType)
- {
- case BIGINT:
- writeByte(TDSType.INTN.byteValue());
- writeByte((byte) 8); // max length of datatype
- break;
- case BIT:
- writeByte(TDSType.BITN.byteValue());
- writeByte((byte) 1); // max length of datatype
- break;
- case INTEGER:
- writeByte(TDSType.INTN.byteValue());
- writeByte((byte) 4); // max length of datatype
- break;
- case SMALLINT:
- case TINYINT:
- writeByte(TDSType.INTN.byteValue());
- writeByte((byte) 2); // max length of datatype
- break;
-
- case DECIMAL:
- case NUMERIC:
- writeByte(TDSType.NUMERICN.byteValue());
- writeByte((byte) 0x11); // maximum length
- writeByte((byte) pair.getValue().precision);
- writeByte((byte) pair.getValue().scale);
- break;
-
- case DOUBLE:
- writeByte(TDSType.FLOATN.byteValue());
- writeByte((byte) 8); // max length of datatype
- break;
-
- case FLOAT:
- case REAL:
- writeByte(TDSType.FLOATN.byteValue());
- writeByte((byte) 4); // max length of datatype
- break;
-
- case DATE:
- case TIME:
- case TIMESTAMP:
- case DATETIMEOFFSET:
- case TIMESTAMP_WITH_TIMEZONE:
- case TIME_WITH_TIMEZONE:
- case CHAR:
- case VARCHAR:
- case NCHAR:
- case NVARCHAR:
- writeByte(TDSType.NVARCHAR.byteValue());
- isShortValue = (2 * pair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
- // Use PLP encoding on Yukon and later with long values
- if (!isShortValue) // PLP
- {
- // Handle Yukon v*max type header here.
- writeShort((short) 0xFFFF);
- con.getDatabaseCollation().writeCollation(this);
- }
- else // non PLP
- {
- writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
- con.getDatabaseCollation().writeCollation(this);
- }
-
- break;
-
- case BINARY:
- case VARBINARY:
- writeByte(TDSType.BIGVARBINARY.byteValue());
- isShortValue = pair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
- // Use PLP encoding on Yukon and later with long values
- if (!isShortValue) // PLP
- // Handle Yukon v*max type header here.
- writeShort((short) 0xFFFF);
- else // non PLP
- writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
- break;
-
- default:
- assert false : "Unexpected JDBC type " + jdbcType.toString();
- }
- // Column name - must be null (from TDS - TVP_COLMETADATA)
- writeByte((byte) 0x00);
-
- //[TVP_ORDER_UNIQUE]
- //[TVP_COLUMN_ORDERING]
- }
- }
-
- void writeTvpOrderUnique(TVP value) throws SQLServerException
- {
- /*
- TVP_ORDER_UNIQUE = TVP_ORDER_UNIQUE_TOKEN (Count (ColNum OrderUniqueFlags))
- */
-
- Map columnMetadata = value.getColumnMetadata();
- Iterator> columnsIterator = columnMetadata.entrySet().iterator();
- LinkedList columnList = new LinkedList();
-
- while(columnsIterator.hasNext())
- {
- byte flags = 0;
- Map.Entry pair = columnsIterator.next();
- SQLServerMetaData metaData = pair.getValue();
-
- if( SQLServerSortOrder.Ascending == metaData.sortOrder )
- flags = TDS.TVP_ORDERASC_FLAG;
- else if( SQLServerSortOrder.Descending == metaData.sortOrder )
- flags = TDS.TVP_ORDERDESC_FLAG;
- if(metaData.isUniqueKey)
- flags |= TDS.TVP_UNIQUE_FLAG;
-
- // Remember this column if any flags were set
- if (0 != flags)
- columnList.add(new TdsOrderUnique(pair.getKey(), flags));
- }
-
- // Write flagged columns
- if(!columnList.isEmpty())
- {
- writeByte((byte) TDS.TVP_ORDER_UNIQUE_TOKEN);
- writeShort((short)columnList.size());
- for(TdsOrderUnique column: columnList)
- {
- writeShort((short) (column.columnOrdinal+1));
- writeByte(column.flags);
- }
- }
- }
-
- private class TdsOrderUnique
- {
- int columnOrdinal;
- byte flags;
-
- TdsOrderUnique(int ordinal, byte flags)
- {
- this.columnOrdinal = ordinal;
- this.flags = flags;
- }
- }
-
- void setCryptoMetaData(CryptoMetadata cryptoMetaForBulk)
- {
- this.cryptoMeta = cryptoMetaForBulk;
- }
-
- CryptoMetadata getCryptoMetaData()
- {
- return cryptoMeta;
- }
-
- void writeEncryptedRPCByteArray(byte bValue[]) throws SQLServerException
- {
- boolean bValueNull = (bValue==null);
- long nValueLen = bValueNull ? 0 : bValue.length;
- boolean isShortValue = (nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES);
-
- boolean isPLP = (!isShortValue) && (nValueLen <= DataTypes.MAX_VARTYPE_MAX_BYTES);
-
- // Handle Shiloh types here.
- if (isShortValue)
- {
- writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
- }
- else if (isPLP){
- writeShort((short) DataTypes.SQL_USHORTVARMAXLEN);
- }
- else
- {
- writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
- }
-
- // Data and length
- if (bValueNull)
- {
- writeShort((short) -1); // actual len
- }
- else
- {
- if (isShortValue)
- {
- writeShort((short) nValueLen); // actual len
- }
- else if (isPLP)
- {
- writeLong(nValueLen); //actual length
- }
- else
- {
- writeInt((int)nValueLen); // actual len
- }
-
- // If length is zero, we're done.
- if (0 != nValueLen){
- if(isPLP){
- writeInt((int)nValueLen);
- }
- writeBytes(bValue);
- }
-
- if(isPLP){
- writeInt(0); //PLP_TERMINATOR, 0x00000000
- }
- }
- }
-
- void writeEncryptedRPCPLP() throws SQLServerException
- {
- writeShort((short) DataTypes.SQL_USHORTVARMAXLEN);
- writeLong((long) 0); //actual length
- writeInt(0); //PLP_TERMINATOR, 0x00000000
- }
-
- void writeCryptoMetaData() throws SQLServerException
- {
- writeByte(cryptoMeta.cipherAlgorithmId);
- writeByte(cryptoMeta.encryptionType.getValue());
- writeInt(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).databaseId);
- writeInt(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).cekId);
- writeInt(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).cekVersion);
- writeBytes(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).cekMdVersion);
- writeByte(cryptoMeta.normalizationRuleVersion);
- }
-
- void writeRPCByteArray(
- String sName,
- byte bValue[],
- boolean bOut,
- JDBCType jdbcType,
- SQLCollation collation) throws SQLServerException
- {
- boolean bValueNull = (bValue==null);
- int nValueLen = bValueNull ? 0 : bValue.length;
- boolean isShortValue = (nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES);
-
- // Use PLP encoding on Yukon and later with long values and OUT parameters
- boolean usePLP = (!isShortValue || bOut);
-
- TDSType tdsType;
-
- if(null != cryptoMeta)
- {
- // send encrypted data as BIGVARBINARY
- tdsType = (isShortValue || usePLP) ? TDSType.BIGVARBINARY : TDSType.IMAGE;
- collation = null;
- }
- else
- switch (jdbcType)
- {
- case BINARY:
- case VARBINARY:
- case LONGVARBINARY:
- case BLOB:
- default:
- tdsType = (isShortValue || usePLP) ? TDSType.BIGVARBINARY : TDSType.IMAGE;
- collation = null;
- break;
-
- case CHAR:
- case VARCHAR:
- case LONGVARCHAR:
- case CLOB:
- tdsType = (isShortValue || usePLP) ? TDSType.BIGVARCHAR : TDSType.TEXT;
- if (null == collation)
- collation = con.getDatabaseCollation();
- break;
-
- case NCHAR:
- case NVARCHAR:
- case LONGNVARCHAR:
- case NCLOB:
- tdsType = (isShortValue || usePLP) ? TDSType.NVARCHAR : TDSType.NTEXT;
- if (null == collation)
- collation = con.getDatabaseCollation();
- break;
- }
-
- writeRPCNameValType(sName, bOut, tdsType);
-
- if (usePLP)
- {
- // Handle Yukon v*max type header here.
- writeVMaxHeader( nValueLen, bValueNull, collation);
-
- // Send the data.
- if (!bValueNull)
- {
- if (nValueLen > 0)
- {
- writeInt(nValueLen);
- writeBytes(bValue);
- }
-
- // Send the terminator PLP chunk.
- writeInt(0);
- }
- }
- else // non-PLP type
- {
- // Handle Shiloh types here.
- if (isShortValue)
- {
- writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
- }
- else
- {
- writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
- }
-
- if (null != collation)
- collation.writeCollation(this);
-
- // Data and length
- if (bValueNull)
- {
- writeShort((short) -1); // actual len
- }
- else
- {
- if (isShortValue)
- writeShort((short) nValueLen); // actual len
- else
- writeInt(nValueLen); // actual len
-
- // If length is zero, we're done.
- if (0 != nValueLen)
- writeBytes(bValue);
- }
- }
- }
-
- /**
- * Append a timestamp in RPC transmission format as a SQL Server DATETIME data type
- * @param sName the optional parameter name
- * @param cal Pure Gregorian calendar containing the timestamp, including its associated time zone
- * @param subSecondNanos the sub-second nanoseconds (0 - 999,999,999)
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- *
- */
- void writeRPCDateTime(
- String sName,
- GregorianCalendar cal,
- int subSecondNanos,
- boolean bOut) throws SQLServerException
- {
- assert (subSecondNanos >= 0) && (subSecondNanos < Nanos.PER_SECOND):"Invalid subNanoSeconds value: " + subSecondNanos;
- assert (cal != null) || (cal == null && subSecondNanos == 0):"Invalid subNanoSeconds value when calendar is null: " + subSecondNanos;
-
- writeRPCNameValType(sName, bOut, TDSType.DATETIMEN);
- writeByte((byte) 8); // max length of datatype
-
- if (null == cal)
- {
- writeByte((byte) 0); // len of data bytes
- return;
- }
-
- writeByte((byte) 8); // len of data bytes
-
- // We need to extract the Calendar's current date & time in terms
- // of the number of days since the SQL Base Date (1/1/1900) plus
- // the number of milliseconds since midnight in the current day.
- //
- // We cannot rely on any pre-calculated value for the number of
- // milliseconds in a day or the number of milliseconds since the
- // base date to do this because days with DST changes are shorter
- // or longer than "normal" days.
- //
- // ASSUMPTION: We assume we are dealing with a GregorianCalendar here.
- // If not, we have no basis in which to compare dates. E.g. if we
- // are dealing with a Chinese Calendar implementation which does not
- // use the same value for Calendar.YEAR as the GregorianCalendar,
- // we cannot meaningfully compute a value relative to 1/1/1900.
-
- // First, figure out how many days there have been since the SQL Base Date.
- //These are based on SQL Server algorithms
- int daysSinceSQLBaseDate =
- DDC.daysSinceBaseDate(
- cal.get(Calendar.YEAR),
- cal.get(Calendar.DAY_OF_YEAR),
- TDS.BASE_YEAR_1900);
-
- // Next, figure out the number of milliseconds since midnight of the current day.
- int millisSinceMidnight =
- (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into the current second
- 1000 * cal.get(Calendar.SECOND) + // Seconds into the current minute
- 60 * 1000 * cal.get(Calendar.MINUTE) + // Minutes into the current hour
- 60 * 60 * 1000 * cal.get(Calendar.HOUR_OF_DAY); // Hours into the current day
-
- // The last millisecond of the current day is always rounded to the first millisecond
- // of the next day because DATETIME is only accurate to 1/300th of a second.
- if (millisSinceMidnight >= 1000 * 60 * 60 * 24 - 1)
- {
- ++daysSinceSQLBaseDate;
- millisSinceMidnight = 0;
- }
-
- // Last-ditch verification that the value is in the valid range for the
- // DATETIMEN TDS data type (1/1/1753 to 12/31/9999). If it's not, then
- // throw an exception now so that statement execution is safely canceled.
- // Attempting to put an invalid value on the wire would result in a TDS
- // exception, which would close the connection.
- //These are based on SQL Server algorithms
- if (daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1753, 1, TDS.BASE_YEAR_1900) ||
- daysSinceSQLBaseDate >= DDC.daysSinceBaseDate(10000, 1, TDS.BASE_YEAR_1900))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
- Object[] msgArgs = {SSType.DATETIME};
- throw new SQLServerException(
- form.format(msgArgs),
- SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW,
- DriverError.NOT_SET,
- null);
- }
-
- // And put it all on the wire...
-
- // Number of days since the SQL Server Base Date (January 1, 1900)
- writeInt(daysSinceSQLBaseDate);
-
- // Milliseconds since midnight (at a resolution of three hundredths of a second)
- writeInt((3 * millisSinceMidnight + 5) / 10);
- }
-
- void writeRPCTime(
- String sName,
- GregorianCalendar localCalendar,
- int subSecondNanos,
- int scale,
- boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.TIMEN);
- writeByte((byte) scale);
-
- if (null == localCalendar)
- {
- writeByte((byte) 0);
- return;
- }
-
- writeByte((byte) TDS.timeValueLength(scale));
- writeScaledTemporal(
- localCalendar,
- subSecondNanos,
- scale,
- SSType.TIME);
- }
-
- void writeRPCDate(
- String sName,
- GregorianCalendar localCalendar,
- boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.DATEN);
- if (null == localCalendar)
- {
- writeByte((byte) 0);
- return;
- }
-
- writeByte((byte) TDS.DAYS_INTO_CE_LENGTH);
- writeScaledTemporal(
- localCalendar,
- 0, // subsecond nanos (none for a date value)
- 0, // scale (dates are not scaled)
- SSType.DATE);
- }
-
- void writeEncryptedRPCTime(
- String sName,
- GregorianCalendar localCalendar,
- int subSecondNanos,
- int scale,
- boolean bOut) throws SQLServerException
- {
- if (con.getSendTimeAsDatetime())
- {
- throw new SQLServerException(SQLServerException.getErrString("R_sendTimeAsDateTimeForAE"), null);
- }
- writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
-
- if (null == localCalendar)
- writeEncryptedRPCByteArray(null);
- else
- writeEncryptedRPCByteArray(
- writeEncryptedScaledTemporal(
- localCalendar,
- subSecondNanos,
- scale,
- SSType.TIME,
- (short) 0));
-
- writeByte(TDSType.TIMEN.byteValue());
- writeByte((byte) scale);
- writeCryptoMetaData();
- }
-
- void writeEncryptedRPCDate(
- String sName,
- GregorianCalendar localCalendar,
- boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
-
- if (null == localCalendar)
- writeEncryptedRPCByteArray(null);
- else
- writeEncryptedRPCByteArray
- (writeEncryptedScaledTemporal(
- localCalendar,
- 0, // subsecond nanos (none for a date value)
- 0, // scale (dates are not scaled)
- SSType.DATE,
- (short) 0));
-
- writeByte(TDSType.DATEN.byteValue());
- writeCryptoMetaData();
- }
-
- void writeEncryptedRPCDateTime(
- String sName,
- GregorianCalendar cal,
- int subSecondNanos,
- boolean bOut,
- JDBCType jdbcType) throws SQLServerException
- {
- assert (subSecondNanos >= 0) && (subSecondNanos < Nanos.PER_SECOND):"Invalid subNanoSeconds value: " + subSecondNanos;
- assert (cal != null) || (cal == null && subSecondNanos == 0):"Invalid subNanoSeconds value when calendar is null: " + subSecondNanos;
-
- writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
-
- if (null == cal)
- writeEncryptedRPCByteArray(null);
- else
- writeEncryptedRPCByteArray(
- getEncryptedDateTimeAsBytes(cal, subSecondNanos, jdbcType));
-
- if(JDBCType.SMALLDATETIME == jdbcType)
- {
- writeByte(TDSType.DATETIMEN.byteValue());
- writeByte((byte) 4);
- }
- else
- {
- writeByte(TDSType.DATETIMEN.byteValue());
- writeByte((byte) 8);
- }
- writeCryptoMetaData();
- }
-
- // getEncryptedDateTimeAsBytes is called if jdbcType/ssType is SMALLDATETIME or DATETIME
- byte[] getEncryptedDateTimeAsBytes(GregorianCalendar cal, int subSecondNanos, JDBCType jdbcType) throws SQLServerException
- {
- int daysSinceSQLBaseDate =
- DDC.daysSinceBaseDate(
- cal.get(Calendar.YEAR),
- cal.get(Calendar.DAY_OF_YEAR),
- TDS.BASE_YEAR_1900);
-
- // Next, figure out the number of milliseconds since midnight of the current day.
- int millisSinceMidnight =
- (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into the current second
- 1000 * cal.get(Calendar.SECOND) + // Seconds into the current minute
- 60 * 1000 * cal.get(Calendar.MINUTE) + // Minutes into the current hour
- 60 * 60 * 1000 * cal.get(Calendar.HOUR_OF_DAY); // Hours into the current day
-
- // The last millisecond of the current day is always rounded to the first millisecond
- // of the next day because DATETIME is only accurate to 1/300th of a second.
- if (millisSinceMidnight >= 1000 * 60 * 60 * 24 - 1)
- {
- ++daysSinceSQLBaseDate;
- millisSinceMidnight = 0;
- }
-
- if(JDBCType.SMALLDATETIME == jdbcType)
- {
-
- int secondsSinceMidnight = (millisSinceMidnight/1000);
- int minutesSinceMidnight = (secondsSinceMidnight/60);
-
- // Values that are 29.998 seconds or less are rounded down to the nearest minute
- minutesSinceMidnight = ((secondsSinceMidnight % 60) > 29.998) ? minutesSinceMidnight+1 : minutesSinceMidnight;
-
- // minutesSinceMidnight for (23:59:30)
- int maxMinutesSinceMidnight_SmallDateTime = 1440;
- //Verification for smalldatetime to be within valid range of (1900.01.01) to (2079.06.06)
- // smalldatetime for unencrypted does not allow insertion of 2079.06.06 23:59:59 and it is rounded up
- // to 2079.06.07 00:00:00, therefore, we are checking minutesSinceMidnight for that condition. If it's not within valid range, then
- // throw an exception now so that statement execution is safely canceled.
- // 157 is the calculated day of year from 06-06 , 1440 is minutesince midnight for (23:59:30)
- if ( (daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1900, 1, TDS.BASE_YEAR_1900) || daysSinceSQLBaseDate > DDC.daysSinceBaseDate(2079, 157, TDS.BASE_YEAR_1900) )
- || (daysSinceSQLBaseDate == DDC.daysSinceBaseDate(2079, 157, TDS.BASE_YEAR_1900) && minutesSinceMidnight >= maxMinutesSinceMidnight_SmallDateTime ) )
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
- Object[] msgArgs = {SSType.SMALLDATETIME};
- throw new SQLServerException(
- form.format(msgArgs),
- SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW,
- DriverError.NOT_SET,
- null);
- }
-
-
- ByteBuffer days = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
- days.putShort((short)daysSinceSQLBaseDate);
- ByteBuffer seconds = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
- seconds.putShort((short)minutesSinceMidnight);
-
- byte[] value = new byte[4];
- System.arraycopy(days.array(), 0, value, 0, 2);
- System.arraycopy(seconds.array(), 0, value, 2, 2);
- return SQLServerSecurityUtility.encryptWithKey(value, cryptoMeta, con);
- }
- else if(JDBCType.DATETIME == jdbcType)
- {
- // Last-ditch verification that the value is in the valid range for the
- // DATETIMEN TDS data type (1/1/1753 to 12/31/9999). If it's not, then
- // throw an exception now so that statement execution is safely canceled.
- // Attempting to put an invalid value on the wire would result in a TDS
- // exception, which would close the connection.
- //These are based on SQL Server algorithms
- // And put it all on the wire...
- if (daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1753, 1, TDS.BASE_YEAR_1900) ||
- daysSinceSQLBaseDate >= DDC.daysSinceBaseDate(10000, 1, TDS.BASE_YEAR_1900))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
- Object[] msgArgs = {SSType.DATETIME};
- throw new SQLServerException(
- form.format(msgArgs),
- SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW,
- DriverError.NOT_SET,
- null);
- }
-
- // Number of days since the SQL Server Base Date (January 1, 1900)
- ByteBuffer days = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
- days.putInt(daysSinceSQLBaseDate);
- ByteBuffer seconds = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
- seconds.putInt((3 * millisSinceMidnight + 5) / 10);
-
- byte[] value = new byte[8];
- System.arraycopy(days.array(), 0, value, 0, 4);
- System.arraycopy(seconds.array(), 0, value, 4, 4);
- return SQLServerSecurityUtility.encryptWithKey(value, cryptoMeta, con);
- }
-
- assert false : "Unexpected JDBCType type " + jdbcType;
- return null;
- }
-
- void writeEncryptedRPCDateTime2(
- String sName,
- GregorianCalendar localCalendar,
- int subSecondNanos,
- int scale,
- boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
-
- if (null == localCalendar)
- writeEncryptedRPCByteArray(null);
- else
- writeEncryptedRPCByteArray(
- writeEncryptedScaledTemporal(
- localCalendar,
- subSecondNanos,
- scale,
- SSType.DATETIME2,
- (short) 0));
-
- writeByte(TDSType.DATETIME2N.byteValue());
- writeByte((byte) (scale));
- writeCryptoMetaData();
- }
-
- void writeEncryptedRPCDateTimeOffset(
- String sName,
- GregorianCalendar utcCalendar,
- int minutesOffset,
- int subSecondNanos,
- int scale,
- boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
-
- if (null == utcCalendar)
- writeEncryptedRPCByteArray(null);
- else
- {
- assert 0 == utcCalendar.get(Calendar.ZONE_OFFSET);
- writeEncryptedRPCByteArray(
- writeEncryptedScaledTemporal(
- utcCalendar,
- subSecondNanos,
- scale,
- SSType.DATETIMEOFFSET,
- (short) minutesOffset));
- }
-
- writeByte(TDSType.DATETIMEOFFSETN.byteValue());
- writeByte((byte) (scale));
- writeCryptoMetaData();
-
- }
-
- void writeRPCDateTime2(
- String sName,
- GregorianCalendar localCalendar,
- int subSecondNanos,
- int scale,
- boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.DATETIME2N);
- writeByte((byte) scale);
-
- if (null == localCalendar)
- {
- writeByte((byte) 0);
- return;
- }
-
- writeByte((byte) TDS.datetime2ValueLength(scale));
- writeScaledTemporal(
- localCalendar,
- subSecondNanos,
- scale,
- SSType.DATETIME2);
- }
-
- void writeRPCDateTimeOffset(
- String sName,
- GregorianCalendar utcCalendar,
- int minutesOffset,
- int subSecondNanos,
- int scale,
- boolean bOut) throws SQLServerException
- {
- writeRPCNameValType(sName, bOut, TDSType.DATETIMEOFFSETN);
- writeByte((byte) scale);
-
- if (null == utcCalendar)
- {
- writeByte((byte) 0);
- return;
- }
-
- assert 0 == utcCalendar.get(Calendar.ZONE_OFFSET);
-
- writeByte((byte) TDS.datetimeoffsetValueLength(scale));
- writeScaledTemporal(
- utcCalendar,
- subSecondNanos,
- scale,
- SSType.DATETIMEOFFSET);
-
- writeShort((short) minutesOffset);
- }
-
-
- /**
- * Returns subSecondNanos rounded to the maximum precision supported.
- * The maximum fractional scale is MAX_FRACTIONAL_SECONDS_SCALE(7).
- * Eg1: if you pass 456,790,123 the function would return 456,790,100
- * Eg2: if you pass 456,790,150 the function would return 456,790,200
- * Eg3: if you pass 999,999,951 the function would return 1,000,000,000
- * This is done to ensure that we have consistent rounding behaviour in setters and getters. Bug #507919
- */
- private int getRoundedSubSecondNanos(int subSecondNanos)
- {
- int roundedNanos = ((subSecondNanos + (Nanos.PER_MAX_SCALE_INTERVAL/2))/Nanos.PER_MAX_SCALE_INTERVAL)*Nanos.PER_MAX_SCALE_INTERVAL;
- return roundedNanos;
- }
-
- /**
- * Writes to the TDS channel a temporal value as an instance instance of one of
- * the scaled temporal SQL types: DATE, TIME, DATETIME2, or DATETIMEOFFSET.
- *
- * @param cal Calendar representing the value to write, except for any sub-second nanoseconds
- * @param subSecondNanos the sub-second nanoseconds (0 - 999,999,999)
- * @param scale the scale (in digits: 0 - 7) to use for the sub-second nanos component
- * @param ssType the SQL Server data type (DATE, TIME, DATETIME2, or DATETIMEOFFSET)
- *
- * @throws SQLServerException if an I/O error occurs or if the value is not in the valid range
- */
- private void writeScaledTemporal(
- GregorianCalendar cal,
- int subSecondNanos,
- int scale,
- SSType ssType) throws SQLServerException
- {
-
- assert con.isKatmaiOrLater();
-
- assert
- SSType.DATE == ssType ||
- SSType.TIME == ssType ||
- SSType.DATETIME2 == ssType ||
- SSType.DATETIMEOFFSET == ssType :
- "Unexpected SSType: " + ssType;
-
- // First, for types with a time component, write the scaled nanos since midnight
- if (SSType.TIME == ssType ||
- SSType.DATETIME2 == ssType ||
- SSType.DATETIMEOFFSET == ssType)
- {
- assert subSecondNanos >= 0;
- assert subSecondNanos < Nanos.PER_SECOND;
- assert scale >= 0;
- assert scale <= TDS.MAX_FRACTIONAL_SECONDS_SCALE;
-
- int secondsSinceMidnight =
- cal.get(Calendar.SECOND) +
- 60 * cal.get(Calendar.MINUTE) +
- 60 * 60 * cal.get(Calendar.HOUR_OF_DAY);
-
- // Scale nanos since midnight to the desired scale, rounding the value as necessary
- long divisor = Nanos.PER_MAX_SCALE_INTERVAL * (long) Math.pow(10, TDS.MAX_FRACTIONAL_SECONDS_SCALE - scale);
-
- //The scaledNanos variable represents the fractional seconds of the value at the scale
- //indicated by the scale variable. So, for example, scaledNanos = 3 means 300 nanoseconds
- //at scale TDS.MAX_FRACTIONAL_SECONDS_SCALE, but 3000 nanoseconds at
- //TDS.MAX_FRACTIONAL_SECONDS_SCALE - 1
- long scaledNanos =
- ((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos) + divisor/2) / divisor;
-
- //SQL Server rounding behavior indicates that it always rounds up unless
- //we are at the max value of the type(NOT every day), in which case it truncates.
- // Side effect on Calendar date:
- // If rounding nanos to the specified scale rolls the value to the next day ...
- if (Nanos.PER_DAY / divisor == scaledNanos)
- {
-
- //If the type is time, always truncate
- if(SSType.TIME == ssType)
- {
- --scaledNanos;
- }
- //If the type is datetime2 or datetimeoffset, truncate only if its the max value supported
- else
- {
- assert SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType:
- "Unexpected SSType: " + ssType;
-
- // ... then bump the date, provided that the resulting date is still within
- // the valid date range.
- //
- // Extreme edge case (literally, the VERY edge...):
- // If nanos overflow rolls the date value out of range (that is, we have a value
- // a few nanoseconds later than 9999-12-31 23:59:59) then truncate the nanos
- // instead of rolling.
- //
- // This case is very likely never hit by "real world" applications, but exists
- // here as a security measure to ensure that such values don't result in a
- // connection-closing TDS exception.
- cal.add(Calendar.SECOND, 1);
-
- if (cal.get(Calendar.YEAR) <= 9999)
- {
- scaledNanos = 0;
- }
- else
- {
- cal.add(Calendar.SECOND, -1);
- --scaledNanos;
- }
- }
- }
-
- // Encode the scaled nanos to TDS
- int encodedLength = TDS.nanosSinceMidnightLength(scale);
- byte[] encodedBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
-
- writeBytes(encodedBytes);
- }
-
- // Second, for types with a date component, write the days into the Common Era
- if (SSType.DATE == ssType ||
- SSType.DATETIME2 == ssType ||
- SSType.DATETIMEOFFSET == ssType)
- {
- // Computation of the number of days into the Common Era assumes that
- // the DAY_OF_YEAR field reflects a pure Gregorian calendar - one that
- // uses Gregorian leap year rules across the entire range of dates.
- //
- // For the DAY_OF_YEAR field to accurately reflect pure Gregorian behavior,
- // we need to use a pure Gregorian calendar for dates that are Julian dates
- // under a standard Gregorian calendar and for (Gregorian) dates later than
- // the cutover date in the cutover year.
- if (cal.getTimeInMillis() < GregorianChange.STANDARD_CHANGE_DATE.getTime() ||
- cal.getActualMaximum(Calendar.DAY_OF_YEAR) < TDS.DAYS_PER_YEAR)
- {
- int year = cal.get(Calendar.YEAR);
- int month = cal.get(Calendar.MONTH);
- int date = cal.get(Calendar.DATE);
-
- // Set the cutover as early as possible (pure Gregorian behavior)
- cal.setGregorianChange(GregorianChange.PURE_CHANGE_DATE);
-
- // Initialize the date field by field (preserving the "wall calendar" value)
- cal.set(year, month, date);
- }
-
- int daysIntoCE =
- DDC.daysSinceBaseDate(
- cal.get(Calendar.YEAR),
- cal.get(Calendar.DAY_OF_YEAR),
- 1);
-
- // Last-ditch verification that the value is in the valid range for the
- // DATE/DATETIME2/DATETIMEOFFSET TDS data type (1/1/0001 to 12/31/9999).
- // If it's not, then throw an exception now so that statement execution
- // is safely canceled. Attempting to put an invalid value on the wire
- // would result in a TDS exception, which would close the connection.
- if (daysIntoCE < 0 || daysIntoCE >= DDC.daysSinceBaseDate(10000, 1, 1))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
- Object[] msgArgs = {ssType};
- throw new SQLServerException(
- form.format(msgArgs),
- SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW,
- DriverError.NOT_SET,
- null);
- }
-
- byte encodedBytes[] = new byte[3];
- encodedBytes[0] = (byte)((daysIntoCE >> 0) & 0xFF);
- encodedBytes[1] = (byte)((daysIntoCE >> 8) & 0xFF);
- encodedBytes[2] = (byte)((daysIntoCE >> 16) & 0xFF);
- writeBytes(encodedBytes);
- }
- }
-
- /**
- * Writes to the TDS channel a temporal value as an instance instance of one of
- * the scaled temporal SQL types: DATE, TIME, DATETIME2, or DATETIMEOFFSET.
- *
- * @param cal Calendar representing the value to write, except for any sub-second nanoseconds
- * @param subSecondNanos the sub-second nanoseconds (0 - 999,999,999)
- * @param scale the scale (in digits: 0 - 7) to use for the sub-second nanos component
- * @param ssType the SQL Server data type (DATE, TIME, DATETIME2, or DATETIMEOFFSET)
- * @param minutesOffset the offset value for DATETIMEOFFSET
- * @throws SQLServerException if an I/O error occurs or if the value is not in the valid range
- */
- byte[] writeEncryptedScaledTemporal(
- GregorianCalendar cal,
- int subSecondNanos,
- int scale,
- SSType ssType,
- short minutesOffset) throws SQLServerException
- {
- assert con.isKatmaiOrLater();
-
- assert
- SSType.DATE == ssType ||
- SSType.TIME == ssType ||
- SSType.DATETIME2 == ssType ||
- SSType.DATETIMEOFFSET == ssType :
- "Unexpected SSType: " + ssType;
-
- // store the time and minutesOffset portion of DATETIME2 and DATETIMEOFFSET to be used with date portion
- byte encodedBytesForEncryption[] = null;
-
- int secondsSinceMidnight = 0;
- long divisor = 0;
- long scaledNanos = 0;
-
- // First, for types with a time component, write the scaled nanos since midnight
- if (SSType.TIME == ssType ||
- SSType.DATETIME2 == ssType ||
- SSType.DATETIMEOFFSET == ssType)
- {
- assert subSecondNanos >= 0;
- assert subSecondNanos < Nanos.PER_SECOND;
- assert scale >= 0;
- assert scale <= TDS.MAX_FRACTIONAL_SECONDS_SCALE;
-
- secondsSinceMidnight =
- cal.get(Calendar.SECOND) +
- 60 * cal.get(Calendar.MINUTE) +
- 60 * 60 * cal.get(Calendar.HOUR_OF_DAY);
-
- // Scale nanos since midnight to the desired scale, rounding the value as necessary
- divisor = Nanos.PER_MAX_SCALE_INTERVAL * (long) Math.pow(10, TDS.MAX_FRACTIONAL_SECONDS_SCALE - scale);
-
- //The scaledNanos variable represents the fractional seconds of the value at the scale
- //indicated by the scale variable. So, for example, scaledNanos = 3 means 300 nanoseconds
- //at scale TDS.MAX_FRACTIONAL_SECONDS_SCALE, but 3000 nanoseconds at
- //TDS.MAX_FRACTIONAL_SECONDS_SCALE - 1
- scaledNanos =
- (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos) + divisor/2) / divisor) * divisor/100;
-
- //for encrypted time value, SQL server cannot do rounding or casting,
- //So, driver needs to cast it before encryption.
- if(SSType.TIME == ssType && 864000000000L <= scaledNanos){
- scaledNanos =
- (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos)) / divisor) * divisor/100;
- }
-
- //SQL Server rounding behavior indicates that it always rounds up unless
- //we are at the max value of the type(NOT every day), in which case it truncates.
- // Side effect on Calendar date:
- // If rounding nanos to the specified scale rolls the value to the next day ...
- if (Nanos.PER_DAY / divisor == scaledNanos)
- {
-
- //If the type is time, always truncate
- if(SSType.TIME == ssType)
- {
- --scaledNanos;
- }
- //If the type is datetime2 or datetimeoffset, truncate only if its the max value supported
- else
- {
- assert SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType:
- "Unexpected SSType: " + ssType;
-
- // ... then bump the date, provided that the resulting date is still within
- // the valid date range.
- //
- // Extreme edge case (literally, the VERY edge...):
- // If nanos overflow rolls the date value out of range (that is, we have a value
- // a few nanoseconds later than 9999-12-31 23:59:59) then truncate the nanos
- // instead of rolling.
- //
- // This case is very likely never hit by "real world" applications, but exists
- // here as a security measure to ensure that such values don't result in a
- // connection-closing TDS exception.
- cal.add(Calendar.SECOND, 1);
-
- if (cal.get(Calendar.YEAR) <= 9999)
- {
- scaledNanos = 0;
- }
- else
- {
- cal.add(Calendar.SECOND, -1);
- --scaledNanos;
- }
- }
- }
-
- // Encode the scaled nanos to TDS
- int encodedLength = TDS.nanosSinceMidnightLength(TDS.MAX_FRACTIONAL_SECONDS_SCALE);
- byte[] encodedBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
-
-
- if(SSType.TIME == ssType)
- {
- byte [] cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytes, cryptoMeta, con);
- return cipherText;
- }
- else if(SSType.DATETIME2 == ssType)
- {
- // for DATETIME2 sends both date and time part together for encryption
- encodedBytesForEncryption = new byte[encodedLength + 3];
- System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, 0, encodedBytes.length);
- }
- else if(SSType.DATETIMEOFFSET == ssType)
- {
- // for DATETIMEOFFSET sends date, time and offset part together for encryption
- encodedBytesForEncryption = new byte[encodedLength + 5];
- System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, 0, encodedBytes.length);
- }
- }
-
- // Second, for types with a date component, write the days into the Common Era
- if (SSType.DATE == ssType ||
- SSType.DATETIME2 == ssType ||
- SSType.DATETIMEOFFSET == ssType)
- {
- // Computation of the number of days into the Common Era assumes that
- // the DAY_OF_YEAR field reflects a pure Gregorian calendar - one that
- // uses Gregorian leap year rules across the entire range of dates.
- //
- // For the DAY_OF_YEAR field to accurately reflect pure Gregorian behavior,
- // we need to use a pure Gregorian calendar for dates that are Julian dates
- // under a standard Gregorian calendar and for (Gregorian) dates later than
- // the cutover date in the cutover year.
- if (cal.getTimeInMillis() < GregorianChange.STANDARD_CHANGE_DATE.getTime() ||
- cal.getActualMaximum(Calendar.DAY_OF_YEAR) < TDS.DAYS_PER_YEAR)
- {
- int year = cal.get(Calendar.YEAR);
- int month = cal.get(Calendar.MONTH);
- int date = cal.get(Calendar.DATE);
-
- // Set the cutover as early as possible (pure Gregorian behavior)
- cal.setGregorianChange(GregorianChange.PURE_CHANGE_DATE);
-
- // Initialize the date field by field (preserving the "wall calendar" value)
- cal.set(year, month, date);
- }
-
- int daysIntoCE =
- DDC.daysSinceBaseDate(
- cal.get(Calendar.YEAR),
- cal.get(Calendar.DAY_OF_YEAR),
- 1);
-
- // Last-ditch verification that the value is in the valid range for the
- // DATE/DATETIME2/DATETIMEOFFSET TDS data type (1/1/0001 to 12/31/9999).
- // If it's not, then throw an exception now so that statement execution
- // is safely canceled. Attempting to put an invalid value on the wire
- // would result in a TDS exception, which would close the connection.
- if (daysIntoCE < 0 || daysIntoCE >= DDC.daysSinceBaseDate(10000, 1, 1))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
- Object[] msgArgs = {ssType};
- throw new SQLServerException(
- form.format(msgArgs),
- SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW,
- DriverError.NOT_SET,
- null);
- }
-
- byte encodedBytes[] = new byte[3];
- encodedBytes[0] = (byte)((daysIntoCE >> 0) & 0xFF);
- encodedBytes[1] = (byte)((daysIntoCE >> 8) & 0xFF);
- encodedBytes[2] = (byte)((daysIntoCE >> 16) & 0xFF);
-
- byte [] cipherText;
- if(SSType.DATE == ssType)
- {
- cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytes, cryptoMeta, con);
- }
- else if(SSType.DATETIME2 == ssType)
- {
- //for Max value, does not round up, do casting instead.
- if(3652058 == daysIntoCE){ //9999-12-31
- if(864000000000L == scaledNanos){ //24:00:00 in nanoseconds
- //does not round up
- scaledNanos =
- (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos)) / divisor) * divisor/100;
-
- int encodedLength = TDS.nanosSinceMidnightLength(TDS.MAX_FRACTIONAL_SECONDS_SCALE);
- byte[] encodedNanoBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
-
- // for DATETIME2 sends both date and time part together for encryption
- encodedBytesForEncryption = new byte[encodedLength + 3];
- System.arraycopy(encodedNanoBytes, 0, encodedBytesForEncryption, 0, encodedNanoBytes.length);
- }
- }
- // Copy the 3 byte date value
- System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, (encodedBytesForEncryption.length - 3), 3);
-
- cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytesForEncryption, cryptoMeta, con);
- }
- else
- {
- //for Max value, does not round up, do casting instead.
- if(3652058 == daysIntoCE){ //9999-12-31
- if(864000000000L == scaledNanos){ //24:00:00 in nanoseconds
- //does not round up
- scaledNanos =
- (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos)) / divisor) * divisor/100;
-
- int encodedLength = TDS.nanosSinceMidnightLength(TDS.MAX_FRACTIONAL_SECONDS_SCALE);
- byte[] encodedNanoBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
-
- // for DATETIMEOFFSET sends date, time and offset part together for encryption
- encodedBytesForEncryption = new byte[encodedLength + 5];
- System.arraycopy(encodedNanoBytes, 0, encodedBytesForEncryption, 0, encodedNanoBytes.length);
- }
- }
-
- // Copy the 3 byte date value
- System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, (encodedBytesForEncryption.length - 5), 3);
- // Copy the 2 byte minutesOffset value
- System.arraycopy(
- ByteBuffer.allocate(Short.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putShort(minutesOffset).array(),
- 0,
- encodedBytesForEncryption,
- (encodedBytesForEncryption.length - 2),
- 2);
-
- cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytesForEncryption, cryptoMeta, con);
- }
- return cipherText;
- }
-
- // Invalid type ssType. This condition should never happen.
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unknownSSType"));
- Object[] msgArgs = {ssType};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
-
- return null;
- }
-
- private byte[] scaledNanosToEncodedBytes(long scaledNanos, int encodedLength) {
- byte encodedBytes[] = new byte[encodedLength];
- for (int i = 0; i < encodedLength; i++)
- encodedBytes[i] = (byte)((scaledNanos >> (8 * i)) & 0xFF);
- return encodedBytes;
- }
-
- /**
- * Append the data in a stream in RPC transmission format.
- * @param sName the optional parameter name
- * @param stream is the stream
- * @param streamLength length of the stream (may be unknown)
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- * @param jdbcType The JDBC type used to determine whether the value is textual or non-textual.
- * @param collation The SQL collation associated with the value. Null for non-textual SQL Server types.
- * @throws SQLServerException
- */
- void writeRPCInputStream(
- String sName,
- InputStream stream,
- long streamLength,
- boolean bOut,
- JDBCType jdbcType,
- SQLCollation collation) throws SQLServerException
- {
- assert null != stream;
- assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength >= 0;
-
- // Send long values and values with unknown length
- // using PLP chunking on Yukon and later.
- boolean usePLP = (DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength > DataTypes.SHORT_VARTYPE_MAX_BYTES);
- if (usePLP)
- {
- assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength <= DataTypes.MAX_VARTYPE_MAX_BYTES;
-
- writeRPCNameValType(
- sName,
- bOut,
- jdbcType.isTextual() ?
- TDSType.BIGVARCHAR :
- TDSType.BIGVARBINARY);
-
- // Handle Yukon v*max type header here.
- writeVMaxHeader(
- streamLength,
- false,
- jdbcType.isTextual() ? collation : null);
- }
-
- // Send non-PLP in all other cases
- else
- {
- // If the length of the InputStream is unknown then we need to buffer the entire stream
- // in memory so that we can determine its length and send that length to the server
- // before the stream data itself.
- if (DataTypes.UNKNOWN_STREAM_LENGTH == streamLength)
- {
- // Create ByteArrayOutputStream with initial buffer size of 8K to handle typical
- // binary field sizes more efficiently. Note we can grow beyond 8000 bytes.
- ByteArrayOutputStream baos = new ByteArrayOutputStream(8000);
- streamLength = 0L;
-
- // Since Shiloh is limited to 64K TDS packets, that's a good upper bound on the maximum
- // length of InputStream we should try to handle before throwing an exception.
- long maxStreamLength = 65535L * con.getTDSPacketSize();
-
- try
- {
- byte buff[] = new byte[8000];
- int bytesRead;
-
- while (streamLength < maxStreamLength && -1 != (bytesRead = stream.read(buff, 0, buff.length)))
- {
- baos.write(buff);
- streamLength += bytesRead;
- }
- }
- catch (IOException e)
- {
- throw new SQLServerException(
- e.getMessage(),
- SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
- DriverError.NOT_SET,
- e);
- }
-
- if (streamLength >= maxStreamLength)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
- Object[] msgArgs = {Long.valueOf(streamLength)};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
- }
-
- assert streamLength <= Integer.MAX_VALUE;
- stream = new ByteArrayInputStream(baos.toByteArray(), 0, (int) streamLength);
- }
-
- assert 0 <= streamLength && streamLength <= DataTypes.IMAGE_TEXT_MAX_BYTES;
-
- boolean useVarType = streamLength <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
-
- writeRPCNameValType(
- sName,
- bOut,
- jdbcType.isTextual() ?
- (useVarType ? TDSType.BIGVARCHAR : TDSType.TEXT) :
- (useVarType ? TDSType.BIGVARBINARY : TDSType.IMAGE));
-
- // Write maximum length, optional collation, and actual length
- if (useVarType)
- {
- writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
- if (jdbcType.isTextual())
- collation.writeCollation(this);
- writeShort((short) streamLength);
- }
- else
- {
- writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
- if (jdbcType.isTextual())
- collation.writeCollation(this);
- writeInt((int) streamLength);
- }
- }
-
- // Write the data
- writeStream(stream, streamLength, usePLP);
- }
-
- /**
- * Append the XML data in a stream in RPC transmission format.
- * @param sName the optional parameter name
- * @param stream is the stream
- * @param streamLength length of the stream (may be unknown)
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- * @throws SQLServerException
- */
- void writeRPCXML(
- String sName,
- InputStream stream,
- long streamLength,
- boolean bOut
- ) throws SQLServerException
- {
- assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength >= 0;
- assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength <= DataTypes.MAX_VARTYPE_MAX_BYTES;
-
- writeRPCNameValType(
- sName,
- bOut,
- TDSType.XML);
- writeByte((byte) 0); // No schema
- // Handle null here and return, we're done here if it's null.
- if (null ==stream)
- {
- // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
- writeLong(0xFFFFFFFFFFFFFFFFL);
- }
- else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamLength)
- {
- // Append v*max length.
- // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
- writeLong(0xFFFFFFFFFFFFFFFEL);
-
- // NOTE: Don't send the first chunk length, this will be calculated by caller.
- }
- else
- {
- // For v*max types with known length, length is
- // We're sending same total length as chunk length (as we're sending 1 chunk).
- writeLong(streamLength);
- }
- if(null != stream)
- // Write the data
- writeStream(stream, streamLength, true);
- }
-
-
- /**
- * Append the data in a character reader in RPC transmission format.
- * @param sName the optional parameter name
- * @param re the reader
- * @param reLength the reader data length (in characters)
- * @param bOut boolean true if the data value is being registered as an ouput parameter
- * @param collation The SQL collation associated with the value. Null for non-textual SQL Server types.
- * @throws SQLServerException
- */
- void writeRPCReaderUnicode(
- String sName,
- Reader re,
- long reLength,
- boolean bOut,
- SQLCollation collation) throws SQLServerException
- {
- assert null != re;
- assert DataTypes.UNKNOWN_STREAM_LENGTH == reLength || reLength >= 0;
-
- // Textual RPC requires a collation. If none is provided, as is the case when
- // the SSType is non-textual, then use the database collation by default.
- if (null == collation)
- collation = con.getDatabaseCollation();
-
- // Send long values and values with unknown length
- // using PLP chunking on Yukon and later.
- boolean usePLP = (DataTypes.UNKNOWN_STREAM_LENGTH == reLength || reLength > DataTypes.SHORT_VARTYPE_MAX_CHARS);
- if (usePLP)
- {
- assert DataTypes.UNKNOWN_STREAM_LENGTH == reLength || reLength <= DataTypes.MAX_VARTYPE_MAX_CHARS;
-
- writeRPCNameValType(
- sName,
- bOut,
- TDSType.NVARCHAR);
-
- // Handle Yukon v*max type header here.
- writeVMaxHeader(
- (DataTypes.UNKNOWN_STREAM_LENGTH == reLength) ? DataTypes.UNKNOWN_STREAM_LENGTH : 2 * reLength, // Length (in bytes)
- false,
- collation);
- }
-
- // Send non-PLP in all other cases
- else
- {
- // Length must be known if we're not sending PLP-chunked data. Yukon is handled above.
- // For Shiloh, this is enforced in DTV by converting the Reader to some other length-
- // prefixed value in the setter.
- assert 0 <= reLength && reLength <= DataTypes.NTEXT_MAX_CHARS;
-
- // For non-PLP types, use the long TEXT type rather than the short VARCHAR
- // type if the stream is too long to fit in the latter or if we don't know the length up
- // front so we have to assume that it might be too long.
- boolean useVarType = reLength <= DataTypes.SHORT_VARTYPE_MAX_CHARS;
-
- writeRPCNameValType(
- sName,
- bOut,
- useVarType ? TDSType.NVARCHAR : TDSType.NTEXT);
-
- // Write maximum length, collation, and actual length of the data
- if (useVarType)
- {
- writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
- collation.writeCollation(this);
- writeShort((short) (2 * reLength));
- }
- else
- {
- writeInt(DataTypes.NTEXT_MAX_CHARS);
- collation.writeCollation(this);
- writeInt((int) (2 * reLength));
- }
- }
-
- // Write the data
- writeReader(re, reLength, usePLP);
- }
-}
+ // When parallel connections are to be used, use minimum timeout slice of 1500 milliseconds.
+ private static final int minTimeoutForParallelConnections = 1500;
-/**
- * TDSPacket provides a mechanism for chaining TDS response packets
- * together in a singly-linked list.
- *
- * Having both the link and the data in the same class allows TDSReader
- * marks (see below) to automatically hold onto exactly as much response
- * data as they need, and no more. Java reference semantics ensure that
- * a mark holds onto its referenced packet and subsequent packets (through
- * next references). When all marked references to a packet go away,
- * the packet, and any linked unmarked packets, can be reclaimed by GC.
- */
-final class TDSPacket
-{
- final byte[] header = new byte[TDS.PACKET_HEADER_SIZE];
- final byte[] payload;
- int payloadLength;
- volatile TDSPacket next;
- final public String toString()
- {
- return "TDSPacket(SPID:" + Util.readUnsignedShortBigEndian(header, TDS.PACKET_HEADER_SPID) +
- " Seq:" + header[TDS.PACKET_HEADER_SEQUENCE_NUM] + ")";
- }
-
- TDSPacket(int size)
- {
- payload = new byte[size];
- payloadLength = 0;
- next = null;
- }
-
- final boolean isEOM() { return TDS.STATUS_BIT_EOM == (header[TDS.PACKET_HEADER_MESSAGE_STATUS] & TDS.STATUS_BIT_EOM); }
-};
+ // lock used for synchronization while updating
+ // data within a socketFinder object
+ private final Object socketFinderlock = new Object();
-/**
- * TDSReaderMark encapsulates a fixed position in the response data stream.
- *
- * Response data is quantized into a linked chain of packets. A mark refers
- * to a specific location in a specific packet and relies on Java's reference
- * semantics to automatically keep all subsequent packets accessible until
- * the mark is destroyed.
- */
-final class TDSReaderMark
-{
- final TDSPacket packet;
- final int payloadOffset;
-
- TDSReaderMark(TDSPacket packet, int payloadOffset)
- {
- this.packet = packet;
- this.payloadOffset = payloadOffset;
- }
-}
+ // lock on which the parent thread would wait
+ // after spawning threads.
+ private final Object parentThreadLock = new Object();
-/**
- * TDSReader encapsulates the TDS response data stream.
- *
- * Bytes are read from SQL Server into a FIFO of packets.
- * Reader methods traverse the packets to access the data.
- */
-final class TDSReader
-{
- private final static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Reader");
- final private String traceID;
- final public String toString() { return traceID; }
-
- private final TDSChannel tdsChannel;
- private final SQLServerConnection con;
-
- private final TDSCommand command;
- final TDSCommand getCommand() { assert null != command; return command; }
-
- final SQLServerConnection getConnection() { return con; }
-
- private TDSPacket currentPacket = new TDSPacket(0);
- private TDSPacket lastPacket = currentPacket;
- private int payloadOffset = 0;
- private int packetNum = 0;
-
- private boolean isStreaming = true;
- private boolean useColumnEncryption = false;
- private boolean serverSupportsColumnEncryption = false;
-
- private final byte valueBytes[] = new byte[256];
- private static final AtomicInteger lastReaderID = new AtomicInteger(0);
- private static int nextReaderID() { return lastReaderID.incrementAndGet(); }
-
-
- TDSReader(TDSChannel tdsChannel, SQLServerConnection con, TDSCommand command)
- {
- this.tdsChannel = tdsChannel;
- this.con = con;
- this.command = command; // may be null
- // if the logging level is not detailed than fine or more we will not have proper readerids.
- if (logger.isLoggable(Level.FINE))
- traceID = "TDSReader@" + nextReaderID()+ " (" + con.toString() + ")";
- else
- traceID = con.toString();
- if (con.isColumnEncryptionSettingEnabled())
- {
- useColumnEncryption = true;
- }
- serverSupportsColumnEncryption = con.getServerSupportsColumnEncryption();
- }
-
- final boolean isColumnEncryptionSettingEnabled()
- {
- return useColumnEncryption;
- }
-
- final boolean getServerSupportsColumnEncryption()
- {
- return serverSupportsColumnEncryption;
- }
-
- final void throwInvalidTDS() throws SQLServerException
- {
- if(logger.isLoggable(Level.SEVERE))
- logger.severe(toString() + " got unexpected value in TDS response at offset:" + payloadOffset);
- con.throwInvalidTDS();
- }
-
- final void throwInvalidTDSToken(String tokenName) throws SQLServerException
- {
- if(logger.isLoggable(Level.SEVERE))
- logger.severe(toString() + " got unexpected value in TDS response at offset:" + payloadOffset);
- con.throwInvalidTDSToken(tokenName);
- }
-
- /**
- * Ensures that payload data is available to be read, automatically advancing to
- * (and possibly reading) the next packet.
- *
- * @return true if additional data is available to be read
- * false if no more data is available
- */
- private boolean ensurePayload() throws SQLServerException
- {
- if (payloadOffset == currentPacket.payloadLength)
- if (!nextPacket()) return false;
- assert payloadOffset < currentPacket.payloadLength;
- return true;
- }
-
- /**
- * Advance (and possibly read) the next packet.
- *
- * @return true if additional data is available to be read
- * false if no more data is available
- */
- private boolean nextPacket() throws SQLServerException
- {
- assert null != currentPacket;
-
- // Shouldn't call this function unless we're at the end of the current packet...
- TDSPacket consumedPacket = currentPacket;
- assert payloadOffset == consumedPacket.payloadLength;
-
- // If no buffered packets are left then maybe we can read one...
- // This action must be synchronized against against another thread calling
- // readAllPackets() to read in ALL of the remaining packets of the current response.
- if (null == consumedPacket.next)
- {
- readPacket();
-
- if (null == consumedPacket.next)
- return false;
- }
-
- // Advance to that packet. If we are streaming through the
- // response, then unlink the current packet from the next
- // before moving to allow the packet to be reclaimed.
- TDSPacket nextPacket = consumedPacket.next;
- if (isStreaming)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(toString() + " Moving to next packet -- unlinking consumed packet");
-
- consumedPacket.next = null;
- }
- currentPacket = nextPacket;
- payloadOffset = 0;
- return true;
- }
-
- /**
- * Reads the next packet of the TDS channel.
- *
- * This method is synchronized to guard against simultaneously reading packets
- * from one thread that is processing the response and another thread that is
- * trying to buffer it with TDSCommand.detach().
- */
- synchronized final boolean readPacket() throws SQLServerException
- {
- if (null != command && !command.readingResponse())
- return false;
-
- // Number of packets in should always be less than number of packets out.
- // If the server has been notified for an interrupt, it may be less by
- // more than one packet.
- assert
- tdsChannel.numMsgsRcvd < tdsChannel.numMsgsSent :
- "numMsgsRcvd:" + tdsChannel.numMsgsRcvd +
- " should be less than numMsgsSent:" + tdsChannel.numMsgsSent;
-
- TDSPacket newPacket = new TDSPacket(con.getTDSPacketSize());
-
- // First, read the packet header.
- for (int headerBytesRead = 0; headerBytesRead < TDS.PACKET_HEADER_SIZE;)
- {
- int bytesRead = tdsChannel.read(newPacket.header, headerBytesRead, TDS.PACKET_HEADER_SIZE - headerBytesRead);
- if (bytesRead < 0)
- {
- if (logger.isLoggable(Level.FINER))
- logger.finer(toString() + " Premature EOS in response. packetNum:" + packetNum + " headerBytesRead:" + headerBytesRead);
-
- con.terminate(
- SQLServerException.DRIVER_ERROR_IO_FAILED,
- ((0 == packetNum && 0 == headerBytesRead) ?
- SQLServerException.getErrString("R_noServerResponse") :
- SQLServerException.getErrString("R_truncatedServerResponse")));
- }
-
- headerBytesRead += bytesRead;
- }
-
- // Header size is a 2 byte unsigned short integer in big-endian order.
- int packetLength = Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_MESSAGE_LENGTH);
-
- // Make header size is properly bounded and compute length of the packet payload.
- if (packetLength < TDS.PACKET_HEADER_SIZE || packetLength > con.getTDSPacketSize())
- {
- logger.warning(toString() + " TDS header contained invalid packet length:" + packetLength + "; packet size:" + con.getTDSPacketSize());
- throwInvalidTDS();
- }
-
- newPacket.payloadLength = packetLength - TDS.PACKET_HEADER_SIZE;
-
- // Just grab the SPID for logging (another big-endian unsigned short).
- tdsChannel.setSPID(Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_SPID));
-
- // Packet header looks good enough.
- // When logging, copy the packet header to the log buffer.
- byte[] logBuffer = null;
- if (tdsChannel.isLoggingPackets())
- {
- logBuffer = new byte[packetLength];
- System.arraycopy(newPacket.header, 0, logBuffer, 0, TDS.PACKET_HEADER_SIZE);
- }
-
- // Now for the payload...
- for (int payloadBytesRead = 0; payloadBytesRead < newPacket.payloadLength;)
- {
- int bytesRead = tdsChannel.read(newPacket.payload, payloadBytesRead, newPacket.payloadLength - payloadBytesRead);
- if (bytesRead < 0)
- con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, SQLServerException.getErrString("R_truncatedServerResponse"));
-
- payloadBytesRead += bytesRead;
- }
-
- ++packetNum;
-
- lastPacket.next = newPacket;
- lastPacket = newPacket;
-
- // When logging, append the payload to the log buffer and write out the whole thing.
- if (tdsChannel.isLoggingPackets())
- {
- System.arraycopy(newPacket.payload, 0, logBuffer, TDS.PACKET_HEADER_SIZE, newPacket.payloadLength);
- tdsChannel.logPacket(logBuffer, 0, packetLength,
- this.toString() + " received Packet:" + packetNum + " (" + newPacket.payloadLength + " bytes)");
- }
-
- // If end of message, then bump the count of messages received and disable
- // interrupts. If an interrupt happened prior to disabling, then expect
- // to read the attention ack packet as well.
- if (newPacket.isEOM())
- {
- ++tdsChannel.numMsgsRcvd;
-
- // Notify the command (if any) that we've reached the end of the response.
- if (null != command)
- command.onResponseEOM();
- }
-
- return true;
- }
-
- final TDSReaderMark mark()
- {
- TDSReaderMark mark = new TDSReaderMark(currentPacket, payloadOffset);
- isStreaming = false;
-
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Buffering from: " + mark.toString());
-
- return mark;
- }
-
- final void reset(TDSReaderMark mark)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Resetting to: " + mark.toString());
-
- currentPacket = mark.packet;
- payloadOffset = mark.payloadOffset;
- }
-
- final void stream()
- {
- isStreaming = true;
- }
-
- /**
- * Returns the number of bytes that can be read (or skipped over) from this
- * TDSReader without blocking by the next caller of a method for this TDSReader.
- *
- * @return the actual number of bytes available.
- */
- final int available()
- {
- // The number of bytes that can be read without blocking is just the number
- // of bytes that are currently buffered. That is the number of bytes left
- // in the current packet plus the number of bytes in the remaining packets.
- int available = currentPacket.payloadLength - payloadOffset;
- for (TDSPacket packet = currentPacket.next; null != packet; packet = packet.next)
- available += packet.payloadLength;
- return available;
- }
+ // indicates whether the socketFinder has succeeded or failed
+ // in finding a socket or is still trying to find a socket
+ private volatile Result result = Result.UNKNOWN;
+
+ // total no of socket connector threads
+ // spawned by a socketFinder object
+ private int noOfSpawnedThreads = 0;
+
+ // no of threads that finished their socket connection
+ // attempts and notified socketFinder about their result
+ private volatile int noOfThreadsThatNotified = 0;
+
+ // If a valid connected socket is found, this value would be non-null,
+ // else this would be null
+ private volatile Socket selectedSocket = null;
+
+ // This would be one of the exceptions returned by the
+ // socketConnector threads
+ private volatile IOException selectedException = null;
+
+ // Logging variables
+ private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.SocketFinder");
+ private final String traceID;
+
+ // maximum number of IP Addresses supported
+ private static final int ipAddressLimit = 64;
+
+ // necessary for raising exceptions so that the connection pool can be notified
+ private final SQLServerConnection conn;
/**
+ * Constructs a new SocketFinder object with appropriate traceId
*
- * @return number of bytes available in the current packet
+ * @param callerTraceID
+ * traceID of the caller
+ * @param sqlServerConnection
+ * the SQLServer connection
*/
- final int availableCurrentPacket() {
- /*
- * The number of bytes that can be read from the current chunk, without including the next chunk that is buffered. This is so the driver can
- * confirm if the next chunk sent is new packet or just continuation
- */
- int available = currentPacket.payloadLength - payloadOffset;
- return available;
+ SocketFinder(String callerTraceID,
+ SQLServerConnection sqlServerConnection) {
+ traceID = "SocketFinder(" + callerTraceID + ")";
+ conn = sqlServerConnection;
}
-
- final int peekTokenType() throws SQLServerException
- {
- // Check whether we're at EOF
- if (!ensurePayload()) return -1;
-
- // Peek at the current byte (don't increment payloadOffset!)
- return currentPacket.payload[payloadOffset] & 0xFF;
- }
-
-
- final short peekStatusFlag() throws SQLServerException {
- // skip the current packet(i.e, TDS packet type) and peek into the status flag (USHORT)
- if (payloadOffset + 3 <= currentPacket.payloadLength) {
- short value = Util.readShort(currentPacket.payload, payloadOffset + 1);
- return value;
- }
-
- // as per TDS protocol, TDS_DONE packet should always be followed by status flag
- // throw exception if status packet is not available
- throwInvalidTDS();
- return 0;
- }
-
- final int readUnsignedByte() throws SQLServerException
- {
- // Ensure that we have a packet to read from.
- if (!ensurePayload())
- throwInvalidTDS();
-
- return currentPacket.payload[payloadOffset++] & 0xFF;
- }
-
- final short readShort() throws SQLServerException
- {
- if (payloadOffset + 2 <= currentPacket.payloadLength)
- {
- short value = Util.readShort(currentPacket.payload, payloadOffset);
- payloadOffset += 2;
- return value;
- }
-
- return Util.readShort(readWrappedBytes(2), 0);
- }
-
- final int readUnsignedShort() throws SQLServerException
- {
- if (payloadOffset + 2 <= currentPacket.payloadLength)
- {
- int value = Util.readUnsignedShort(currentPacket.payload, payloadOffset);
- payloadOffset += 2;
- return value;
- }
-
- return Util.readUnsignedShort(readWrappedBytes(2), 0);
- }
-
- final String readUnicodeString(int length) throws SQLServerException
- {
- int byteLength = 2 * length;
- byte bytes[] = new byte[byteLength];
- readBytes(bytes, 0, byteLength);
- return Util.readUnicodeString(bytes, 0, byteLength, con);
-
- }
-
- final char readChar() throws SQLServerException
- {
- return (char) readShort();
- }
-
- final int readInt() throws SQLServerException
- {
- if (payloadOffset + 4 <= currentPacket.payloadLength)
- {
- int value = Util.readInt(currentPacket.payload, payloadOffset);
- payloadOffset += 4;
- return value;
- }
-
- return Util.readInt(readWrappedBytes(4), 0);
- }
-
- final int readIntBigEndian() throws SQLServerException
- {
- if (payloadOffset + 4 <= currentPacket.payloadLength)
- {
- int value = Util.readIntBigEndian(currentPacket.payload, payloadOffset);
- payloadOffset += 4;
- return value;
- }
-
- return Util.readIntBigEndian(readWrappedBytes(4), 0);
- }
-
- final long readUnsignedInt() throws SQLServerException
- {
- return readInt() & 0xFFFFFFFFL;
- }
-
- final long readLong() throws SQLServerException
- {
- if (payloadOffset + 8 <= currentPacket.payloadLength)
- {
- long value = Util.readLong(currentPacket.payload, payloadOffset);
- payloadOffset += 8;
- return value;
- }
-
- return Util.readLong(readWrappedBytes(8), 0);
- }
-
- final void readBytes(byte[] value, int valueOffset, int valueLength) throws SQLServerException
- {
- for (int bytesRead = 0; bytesRead < valueLength;)
- {
- // Ensure that we have a packet to read from.
- if (!ensurePayload())
- throwInvalidTDS();
-
- // Figure out how many bytes to copy from the current packet
- // (the lesser of the remaining value bytes and the bytes left in the packet).
- int bytesToCopy = valueLength - bytesRead;
- if (bytesToCopy > currentPacket.payloadLength - payloadOffset)
- bytesToCopy = currentPacket.payloadLength - payloadOffset;
-
- // Copy some bytes from the current packet to the destination value.
- if(logger.isLoggable(Level.FINEST))
- logger.finest(toString() +" Reading " + bytesToCopy + " bytes from offset " + payloadOffset);
-
- System.arraycopy(currentPacket.payload, payloadOffset, value, valueOffset + bytesRead, bytesToCopy);
- bytesRead += bytesToCopy;
- payloadOffset += bytesToCopy;
- }
- }
-
- final byte[] readWrappedBytes(int valueLength) throws SQLServerException
- {
- assert valueLength <= valueBytes.length;
- readBytes(valueBytes, 0, valueLength);
- return valueBytes;
- }
-
- final Object readDecimal(
- int valueLength,
- TypeInfo typeInfo,
- JDBCType jdbcType,
- StreamType streamType) throws SQLServerException
- {
- if (valueLength > valueBytes.length)
- {
- logger.warning(toString() + " Invalid value length:" + valueLength);
- throwInvalidTDS();
- }
-
- readBytes(valueBytes, 0, valueLength);
- return DDC.convertBigDecimalToObject(
- Util.readBigDecimal(valueBytes, valueLength, typeInfo.getScale()),
- jdbcType,
- streamType);
- }
-
- final Object readMoney(int valueLength, JDBCType jdbcType, StreamType streamType) throws SQLServerException
- {
- BigInteger bi;
- switch (valueLength)
- {
- case 8: // money
- {
- int intBitsHi = readInt();
- int intBitsLo = readInt();
-
- if (JDBCType.BINARY == jdbcType)
- {
- byte value[] = new byte[8];
- Util.writeIntBigEndian(intBitsHi, value, 0);
- Util.writeIntBigEndian(intBitsLo, value, 4);
- return value;
- }
-
- bi = BigInteger.valueOf(((long) intBitsHi << 32) | (intBitsLo & 0xFFFFFFFFL));
- break;
- }
-
- case 4: // smallmoney
- if (JDBCType.BINARY == jdbcType)
- {
- byte value[] = new byte[4];
- Util.writeIntBigEndian(readInt(), value, 0);
- return value;
- }
-
- bi = BigInteger.valueOf(readInt());
- break;
-
- default:
- throwInvalidTDS();
- return null;
- }
-
- return DDC.convertBigDecimalToObject(new BigDecimal(bi, 4), jdbcType, streamType);
- }
-
- final Object readReal(
- int valueLength,
- JDBCType jdbcType,
- StreamType streamType) throws SQLServerException
- {
- if (4 != valueLength)
- throwInvalidTDS();
-
- return DDC.convertFloatToObject(Float.intBitsToFloat(readInt()), jdbcType, streamType);
- }
-
- final Object readFloat(
- int valueLength,
- JDBCType jdbcType,
- StreamType streamType) throws SQLServerException
- {
- if (8 != valueLength)
- throwInvalidTDS();
-
- return DDC.convertDoubleToObject(Double.longBitsToDouble(readLong()), jdbcType, streamType);
- }
-
- final Object readDateTime(
- int valueLength,
- Calendar appTimeZoneCalendar,
- JDBCType jdbcType,
- StreamType streamType) throws SQLServerException
- {
- // Build and return the right kind of temporal object.
- int daysSinceSQLBaseDate;
- int ticksSinceMidnight;
- int msecSinceMidnight;
-
- switch (valueLength)
- {
- case 8:
- // SQL datetime is 4 bytes for days since SQL Base Date
- // (January 1, 1900 00:00:00 GMT) and 4 bytes for
- // the number of three hundredths (1/300) of a second
- // since midnight.
- daysSinceSQLBaseDate = readInt();
- ticksSinceMidnight = readInt();
-
- if (JDBCType.BINARY == jdbcType)
- {
- byte value[] = new byte[8];
- Util.writeIntBigEndian(daysSinceSQLBaseDate, value, 0);
- Util.writeIntBigEndian(ticksSinceMidnight, value, 4);
- return value;
- }
-
- msecSinceMidnight = (ticksSinceMidnight * 10 + 1) / 3; // Convert to msec (1 tick = 1 300th of a sec = 3 msec)
- break;
-
- case 4:
- // SQL smalldatetime has less precision. It stores 2 bytes
- // for the days since SQL Base Date and 2 bytes for minutes
- // after midnight.
- daysSinceSQLBaseDate = readUnsignedShort();
- ticksSinceMidnight = readUnsignedShort();
-
- if (JDBCType.BINARY == jdbcType)
- {
- byte value[] = new byte[4];
- Util.writeShortBigEndian((short) daysSinceSQLBaseDate, value, 0);
- Util.writeShortBigEndian((short) ticksSinceMidnight, value, 2);
- return value;
- }
-
- msecSinceMidnight = ticksSinceMidnight * 60 * 1000; // Convert to msec (1 tick = 1 min = 60,000 msec)
- break;
-
- default:
- throwInvalidTDS();
- return null;
- }
-
- // Convert the DATETIME/SMALLDATETIME value to the desired Java type.
- return DDC.convertTemporalToObject(
- jdbcType,
- SSType.DATETIME,
- appTimeZoneCalendar,
- daysSinceSQLBaseDate,
- msecSinceMidnight,
- 0); // scale (ignored for fixed-scale DATETIME/SMALLDATETIME types)
- }
-
- final Object readDate(
- int valueLength,
- Calendar appTimeZoneCalendar,
- JDBCType jdbcType) throws SQLServerException
- {
- if (TDS.DAYS_INTO_CE_LENGTH != valueLength)
- throwInvalidTDS();
-
- // Initialize the date fields to their appropriate values.
- int localDaysIntoCE = readDaysIntoCE();
-
- // Convert the DATE value to the desired Java type.
- return DDC.convertTemporalToObject(
- jdbcType,
- SSType.DATE,
- appTimeZoneCalendar,
- localDaysIntoCE,
- 0, // midnight local to app time zone
- 0); // scale (ignored for DATE)
- }
-
- final Object readTime(
- int valueLength,
- TypeInfo typeInfo,
- Calendar appTimeZoneCalendar,
- JDBCType jdbcType) throws SQLServerException
- {
- if (TDS.timeValueLength(typeInfo.getScale()) != valueLength)
- throwInvalidTDS();
-
- // Read the value from the server
- long localNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
-
- // Convert the TIME value to the desired Java type.
- return DDC.convertTemporalToObject(
- jdbcType,
- SSType.TIME,
- appTimeZoneCalendar,
- 0,
- localNanosSinceMidnight,
- typeInfo.getScale());
- }
-
- final Object readDateTime2(
- int valueLength,
- TypeInfo typeInfo,
- Calendar appTimeZoneCalendar,
- JDBCType jdbcType) throws SQLServerException
- {
- if (TDS.datetime2ValueLength(typeInfo.getScale()) != valueLength)
- throwInvalidTDS();
-
- // Read the value's constituent components
- long localNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
- int localDaysIntoCE = readDaysIntoCE();
-
- // Convert the DATETIME2 value to the desired Java type.
- return DDC.convertTemporalToObject(
- jdbcType,
- SSType.DATETIME2,
- appTimeZoneCalendar,
- localDaysIntoCE,
- localNanosSinceMidnight,
- typeInfo.getScale());
- }
-
- final Object readDateTimeOffset(
- int valueLength,
- TypeInfo typeInfo,
- JDBCType jdbcType) throws SQLServerException
- {
- if (TDS.datetimeoffsetValueLength(typeInfo.getScale()) != valueLength)
- throwInvalidTDS();
-
- // The nanos since midnight and days into Common Era parts of DATETIMEOFFSET values
- // are in UTC. Use the minutes offset part to convert to local.
- long utcNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
- int utcDaysIntoCE = readDaysIntoCE();
- int localMinutesOffset = readShort();
-
- // Convert the DATETIMEOFFSET value to the desired Java type.
- return DDC.convertTemporalToObject(
- jdbcType,
- SSType.DATETIMEOFFSET,
- new GregorianCalendar(new SimpleTimeZone(localMinutesOffset * 60 * 1000, ""), Locale.US),
- utcDaysIntoCE,
- utcNanosSinceMidnight,
- typeInfo.getScale());
- }
-
- private int readDaysIntoCE() throws SQLServerException
- {
- byte value[] = new byte[TDS.DAYS_INTO_CE_LENGTH];
- readBytes(value, 0, value.length);
-
- int daysIntoCE = 0;
- for (int i = 0; i < value.length; i++)
- daysIntoCE |= ((value[i] & 0xFF) << (8 * i));
-
- // Theoretically should never encounter a value that is outside of the valid date range
- if (daysIntoCE < 0)
- throwInvalidTDS();
-
- return daysIntoCE;
- }
-
- // Scale multipliers used to convert variable-scaled temporal values to a fixed 100ns scale.
- //
- // Using this array is measurably faster than using Math.pow(10, ...)
- private final static int[] SCALED_MULTIPLIERS =
- {
- 10000000,
- 1000000,
- 100000,
- 10000,
- 1000,
- 100,
- 10,
- 1
- };
-
- private long readNanosSinceMidnight(int scale) throws SQLServerException
- {
- assert 0 <= scale && scale <= TDS.MAX_FRACTIONAL_SECONDS_SCALE;
-
- byte value[] = new byte[TDS.nanosSinceMidnightLength(scale)];
- readBytes(value, 0, value.length);
-
- long hundredNanosSinceMidnight = 0;
- for (int i = 0; i < value.length; i++)
- hundredNanosSinceMidnight |= (value[i] & 0xFFL) << (8 * i);
-
- hundredNanosSinceMidnight *= SCALED_MULTIPLIERS[scale];
-
- if (!(0 <= hundredNanosSinceMidnight && hundredNanosSinceMidnight < Nanos.PER_DAY / 100))
- throwInvalidTDS();
-
- return 100 * hundredNanosSinceMidnight;
- }
-
- final static String guidTemplate = "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN";
- final Object readGUID(int valueLength, JDBCType jdbcType, StreamType streamType) throws SQLServerException
- {
- // GUIDs must be exactly 16 bytes
- if (16 != valueLength)
- throwInvalidTDS();
-
- // Read in the GUID's binary value
- byte guid[] = new byte[16];
- readBytes(guid, 0, 16);
-
- switch (jdbcType)
- {
- case CHAR:
- case VARCHAR:
- case LONGVARCHAR:
- case GUID:
- {
- StringBuilder sb = new StringBuilder(guidTemplate.length());
- for (int i=0; i<4; i++)
- {
- sb.append(Util.hexChars[(guid[3-i] & 0xF0) >> 4]);
- sb.append(Util.hexChars[guid[3-i] & 0x0F]);
- }
- sb.append('-');
- for (int i=0; i<2; i++)
- {
- sb.append(Util.hexChars[(guid[5-i] & 0xF0) >> 4]);
- sb.append(Util.hexChars[guid[5-i] & 0x0F]);
- }
- sb.append('-');
- for (int i=0; i<2; i++)
- {
- sb.append(Util.hexChars[(guid[7-i] & 0xF0) >> 4]);
- sb.append(Util.hexChars[guid[7-i] & 0x0F]);
- }
- sb.append('-');
- for (int i=0; i<2; i++)
- {
- sb.append(Util.hexChars[(guid[8+i] & 0xF0) >> 4]);
- sb.append(Util.hexChars[guid[8+i] & 0x0F]);
- }
- sb.append('-');
- for (int i=0; i<6; i++)
- {
- sb.append(Util.hexChars[(guid[10+i] & 0xF0) >> 4]);
- sb.append(Util.hexChars[guid[10+i] & 0x0F]);
- }
-
- try
- {
- return DDC.convertStringToObject(sb.toString(), Encoding.UNICODE.charset(), jdbcType, streamType);
- }
- catch (UnsupportedEncodingException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
- throw new SQLServerException(form.format(new Object[] {"UNIQUEIDENTIFIER", jdbcType}), null, 0, e);
- }
- }
-
- default:
- {
- if (StreamType.BINARY == streamType || StreamType.ASCII == streamType)
- return new ByteArrayInputStream(guid);
-
- return guid;
- }
- }
- }
-
- /**
- * Reads a multi-part table name from TDS and returns it as an array of Strings.
- */
- final SQLIdentifier readSQLIdentifier() throws SQLServerException
- {
- // Multi-part names should have between 1 and 4 parts
- int numParts = readUnsignedByte();
- if (!(1 <= numParts && numParts <= 4))
- throwInvalidTDS();
-
- // Each part is a length-prefixed Unicode string
- String[] nameParts = new String[numParts];
- for (int i = 0; i < numParts; i++)
- nameParts[i] = readUnicodeString(readUnsignedShort());
-
- // Build the identifier from the name parts
- SQLIdentifier identifier = new SQLIdentifier();
- identifier.setObjectName(nameParts[numParts-1]);
- if (numParts >= 2)
- identifier.setSchemaName(nameParts[numParts-2]);
- if (numParts >= 3)
- identifier.setDatabaseName(nameParts[numParts-3]);
- if (4 == numParts)
- identifier.setServerName(nameParts[numParts-4]);
-
- return identifier;
- }
-
- final SQLCollation readCollation() throws SQLServerException
- {
- SQLCollation collation = null;
-
- try
- {
- collation = new SQLCollation(this);
- }
- catch (UnsupportedEncodingException e)
- {
- con.terminate(SQLServerException.DRIVER_ERROR_INVALID_TDS, e.getMessage(), e);
- // not reached
- }
-
- return collation;
- }
-
- final void skip(int bytesToSkip) throws SQLServerException
- {
- assert bytesToSkip >= 0;
-
- while (bytesToSkip > 0)
- {
- // Ensure that we have a packet to read from.
- if (!ensurePayload())
- throwInvalidTDS();
-
- int bytesSkipped = bytesToSkip;
- if (bytesSkipped > currentPacket.payloadLength - payloadOffset)
- bytesSkipped = currentPacket.payloadLength - payloadOffset;
-
- bytesToSkip -= bytesSkipped;
- payloadOffset += bytesSkipped;
- }
- }
-
- final void TryProcessFeatureExtAck(boolean featureExtAckReceived) throws SQLServerException
- {
- //in case of redirection, do not check if TDS_FEATURE_EXTENSION_ACK is received or not.
- if (null != this.con.getRoutingInfo()) {
- return;
- }
-
- if( isColumnEncryptionSettingEnabled() && !featureExtAckReceived)
- throw new SQLServerException(this , SQLServerException.getErrString("R_AE_NotSupportedByServer"), null, 0 , false);
- }
-}
-/**
- * Timer for use with Commands that support a timeout.
- *
- * Once started, the timer runs for the prescribed number
- * of seconds unless stopped. If the timer runs out, it
- * interrupts its associated Command with a reason like
- * "timed out".
- */
-final class TimeoutTimer implements Runnable
-{
- private final int timeoutSeconds;
- private final TDSCommand command;
- private Thread timerThread;
- private volatile boolean canceled = false;
-
- TimeoutTimer(int timeoutSeconds, TDSCommand command)
- {
- assert timeoutSeconds > 0;
- assert null != command;
-
- this.timeoutSeconds = timeoutSeconds;
- this.command = command;
- }
-
- final void start()
- {
- timerThread = new Thread(this);
- timerThread.setDaemon(true);
- timerThread.start();
- }
-
- final void stop()
- {
- canceled = true;
- timerThread.interrupt();
- }
-
- public void run()
- {
- int secondsRemaining = timeoutSeconds;
- try
- {
- // Poll every second while time is left on the timer.
- // Return if/when the timer is canceled.
- do
- {
- if (canceled)
- return;
-
- Thread.sleep(1000);
- }
- while (--secondsRemaining > 0);
- }
- catch (InterruptedException e)
- {
- return;
- }
-
- // If the timer wasn't canceled before it ran out of
- // time then interrupt the registered command.
- try
- {
- command.interrupt(SQLServerException.getErrString("R_queryTimedOut"));
- }
- catch (SQLServerException e)
- {
- // Unfortunately, there's nothing we can do if we
- // fail to time out the request. There is no way
- // to report back what happened.
- command.log(Level.FINE, "Command could not be timed out. Reason: " + e.getMessage());
- }
- }
-}
+ /**
+ * Used to find a socket to which a connection can be made
+ *
+ * @param hostName
+ * @param portNumber
+ * @param timeoutInMilliSeconds
+ * @return connected socket
+ * @throws IOException
+ */
+ Socket findSocket(String hostName,
+ int portNumber,
+ int timeoutInMilliSeconds,
+ boolean useParallel,
+ boolean useTnir,
+ boolean isTnirFirstAttempt,
+ int timeoutInMilliSecondsForFullTimeout) throws SQLServerException {
+ assert timeoutInMilliSeconds != 0 : "The driver does not allow a time out of 0";
+
+ try {
+ InetAddress[] inetAddrs = null;
+
+ // inetAddrs is only used if useParallel is true or TNIR is true. Skip resolving address if that's not the case.
+ if (useParallel || useTnir) {
+ // Ignore TNIR if host resolves to more than 64 IPs. Make sure we are using original timeout for this.
+ inetAddrs = InetAddress.getAllByName(hostName);
+
+ if ((useTnir) && (inetAddrs.length > ipAddressLimit)) {
+ useTnir = false;
+ timeoutInMilliSeconds = timeoutInMilliSecondsForFullTimeout;
+ }
+ }
+
+ if (!useParallel) {
+ // MSF is false. TNIR could be true or false. DBMirroring could be true or false.
+ // For TNIR first attempt, we should do existing behavior including how host name is resolved.
+ if (useTnir && isTnirFirstAttempt) {
+ return getDefaultSocket(hostName, portNumber, SQLServerConnection.TnirFirstAttemptTimeoutMs);
+ }
+ else if (!useTnir) {
+ return getDefaultSocket(hostName, portNumber, timeoutInMilliSeconds);
+ }
+ }
+
+ // Code reaches here only if MSF = true or (TNIR = true and not TNIR first attempt)
+
+ if (logger.isLoggable(Level.FINER)) {
+ String loggingString = this.toString() + " Total no of InetAddresses: " + inetAddrs.length + ". They are: ";
+ for (InetAddress inetAddr : inetAddrs) {
+ loggingString = loggingString + inetAddr.toString() + ";";
+ }
+
+ logger.finer(loggingString);
+ }
+
+ if (inetAddrs.length > ipAddressLimit) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ipAddressLimitWithMultiSubnetFailover"));
+ Object[] msgArgs = {Integer.toString(ipAddressLimit)};
+ String errorStr = form.format(msgArgs);
+ // we do not want any retry to happen here. So, terminate the connection
+ // as the config is unsupported.
+ conn.terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, errorStr);
+ }
+
+ if (Util.isIBM()) {
+ timeoutInMilliSeconds = Math.max(timeoutInMilliSeconds, minTimeoutForParallelConnections);
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString() + "Using Java NIO with timeout:" + timeoutInMilliSeconds);
+ }
+ findSocketUsingJavaNIO(inetAddrs, portNumber, timeoutInMilliSeconds);
+ }
+ else {
+ LinkedList inet4Addrs = new LinkedList();
+ LinkedList inet6Addrs = new LinkedList();
+
+ for (InetAddress inetAddr : inetAddrs) {
+ if (inetAddr instanceof Inet4Address) {
+ inet4Addrs.add((Inet4Address) inetAddr);
+ }
+ else {
+ assert inetAddr instanceof Inet6Address : "Unexpected IP address " + inetAddr.toString();
+ inet6Addrs.add((Inet6Address) inetAddr);
+ }
+ }
+
+ // use half timeout only if both IPv4 and IPv6 addresses are present
+ int timeoutForEachIPAddressType;
+ if ((!inet4Addrs.isEmpty()) && (!inet6Addrs.isEmpty())) {
+ timeoutForEachIPAddressType = Math.max(timeoutInMilliSeconds / 2, minTimeoutForParallelConnections);
+ }
+ else
+ timeoutForEachIPAddressType = Math.max(timeoutInMilliSeconds, minTimeoutForParallelConnections);
+
+ if (!inet4Addrs.isEmpty()) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString() + "Using Java NIO with timeout:" + timeoutForEachIPAddressType);
+ }
+
+ // inet4Addrs.toArray(new InetAddress[0]) is java style of converting a linked list to an array of reqd size
+ findSocketUsingJavaNIO(inet4Addrs.toArray(new InetAddress[0]), portNumber, timeoutForEachIPAddressType);
+ }
+
+ if (!result.equals(Result.SUCCESS)) {
+ // try threading logic
+ if (!inet6Addrs.isEmpty()) {
+ // do not start any threads if there is only one ipv6 address
+ if (inet6Addrs.size() == 1) {
+ return getConnectedSocket(inet6Addrs.get(0), portNumber, timeoutForEachIPAddressType);
+ }
+
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString() + "Using Threading with timeout:" + timeoutForEachIPAddressType);
+ }
+
+ findSocketUsingThreading(inet6Addrs, portNumber, timeoutForEachIPAddressType);
+ }
+ }
+ }
+
+ // If the thread continued execution due to timeout, the result may not be known.
+ // In that case, update the result to failure. Note that this case is possible
+ // for both IPv4 and IPv6.
+ // Using double-checked locking for performance reasons.
+ if (result.equals(Result.UNKNOWN)) {
+ synchronized (socketFinderlock) {
+ if (result.equals(Result.UNKNOWN)) {
+ result = Result.FAILURE;
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString() + " The parent thread updated the result to failure");
+ }
+ }
+ }
+ }
+
+ // After we reach this point, there is no need for synchronization any more.
+ // Because, the result would be known(success/failure).
+ // And no threads would update SocketFinder
+ // as their function calls would now be no-ops.
+ if (result.equals(Result.FAILURE)) {
+ if (selectedException == null) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString()
+ + " There is no selectedException. The wait calls timed out before any connect call returned or timed out.");
+ }
+ String message = SQLServerException.getErrString("R_connectionTimedOut");
+ selectedException = new IOException(message);
+ }
+ throw selectedException;
+ }
+
+ }
+ catch (InterruptedException ex) {
+ close(selectedSocket);
+ SQLServerException.ConvertConnectExceptionToSQLServerException(hostName, portNumber, conn, ex);
+ }
+ catch (IOException ex) {
+ close(selectedSocket);
+ // The code below has been moved from connectHelper.
+ // If we do not move it, the functions open(caller of findSocket)
+ // and findSocket will have to
+ // declare both IOException and SQLServerException in the throws clause
+ // as we throw custom SQLServerExceptions(eg:IPAddressLimit, wrapping other exceptions
+ // like interruptedException) in findSocket.
+ // That would be a bit awkward, because connecthelper(the caller of open)
+ // just wraps IOException into SQLServerException and throws SQLServerException.
+ // Instead, it would be good to wrap all exceptions at one place - Right here, their origin.
+ SQLServerException.ConvertConnectExceptionToSQLServerException(hostName, portNumber, conn, ex);
+
+ }
+
+ assert result.equals(Result.SUCCESS) == true;
+ assert selectedSocket != null : "Bug in code. Selected Socket cannot be null here.";
+
+ return selectedSocket;
+ }
-/**
- * TDSCommand encapsulates an interruptable TDS conversation.
- *
- * A conversation may consist of one or more TDS request and response messages.
- * A command may be interrupted at any point, from any thread, and for any reason.
- * Acknowledgement and handling of an interrupt is fully encapsulated by this class.
- *
- * Commands may be created with an optional timeout (in seconds). Timeouts are implemented as
- * a form of interrupt, where the interrupt event occurs when the timeout period expires.
- * Currently, only the time to receive the response from the channel counts against
- * the timeout period.
- */
-abstract class TDSCommand
-{
- abstract boolean doExecute() throws SQLServerException;
-
- final static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Command");
- private final String logContext;
- final String getLogContext() { return logContext; }
- private String traceID;
- final public String toString()
- {
- if(traceID== null)
- traceID="TDSCommand@" + Integer.toHexString(hashCode()) + " (" + logContext + ")";
- return traceID;
- }
- final void log(Level level, String message) { logger.log(level, toString() + ": " + message); }
-
- // Optional timer that is set if the command was created with a non-zero timeout period.
- // When the timer expires, the command is interrupted.
- private final TimeoutTimer timeoutTimer;
-
- // TDS channel accessors
- // These are set/reset at command execution time.
- // Volatile ensures visibility to execution thread and interrupt thread
- private volatile TDSWriter tdsWriter;
- private volatile TDSReader tdsReader;
-
- // Lock to ensure atomicity when manipulating more than one of the following
- // shared interrupt state variables below.
- private final Object interruptLock = new Object();
-
- // Flag set when this command starts execution, indicating that it is
- // ready to respond to interrupts; and cleared when its last response packet is
- // received, indicating that it is no longer able to respond to interrupts.
- // If the command is interrupted after interrupts have been disabled, then the
- // interrupt is ignored.
- private volatile boolean interruptsEnabled = false;
-
- // Flag set to indicate that an interrupt has happened.
- private volatile boolean wasInterrupted = false;
- private boolean wasInterrupted() { return wasInterrupted; }
-
- // The reason for the interrupt.
- private volatile String interruptReason = null;
-
- // Flag set when this command's request to the server is complete.
- // If a command is interrupted before its request is complete, it is the executing
- // thread's responsibility to send the attention signal to the server if necessary.
- // After the request is complete, the interrupting thread must send the attention signal.
- private volatile boolean requestComplete;
-
- // Flag set when an attention signal has been sent to the server, indicating that a
- // TDS packet containing the attention ack message is to be expected in the response.
- // This flag is cleared after the attention ack message has been received and processed.
- private volatile boolean attentionPending = false;
- boolean attentionPending() { return attentionPending; }
-
- // Flag set when this command's response has been processed. Until this flag is set,
- // there may be unprocessed information left in the response, such as transaction
- // ENVCHANGE notifications.
- private volatile boolean processedResponse;
-
- // Flag set when this command's response is ready to be read from the server and cleared
- // after its response has been received, but not necessarily processed, up to and including
- // any attention ack. The command's response is read either on demand as it is processed,
- // or by detaching.
- private volatile boolean readingResponse;
- final boolean readingResponse() { return readingResponse; }
-
-
- /**
- * Creates this command with an optional timeout.
- *
- * @param logContext the string describing the context for this command.
- * @param timeoutSeconds (optional) the time before which the command must complete before it
- * is interrupted. A value of 0 means no timeout.
- */
- TDSCommand(String logContext, int timeoutSeconds)
- {
- this.logContext = logContext;
- this.timeoutTimer = (timeoutSeconds > 0) ? (new TimeoutTimer(timeoutSeconds, this)) : null;
- }
-
- /**
- * Executes this command.
- *
- * @param tdsWriter
- * @param tdsReader
- * @throws SQLServerException on any error executing the command, including cancel or timeout.
- */
-
- boolean execute(
- TDSWriter tdsWriter,
- TDSReader tdsReader) throws SQLServerException
- {
- this.tdsWriter = tdsWriter;
- this.tdsReader = tdsReader;
- assert null != tdsReader;
- try
- {
- return doExecute(); // Derived classes implement the execution details
- }
- catch (SQLServerException e)
- {
- try
- {
- // If command execution threw an exception for any reason before the request
- // was complete then interrupt the command (it may already be interrupted)
- // and close it out to ensure that any response to the error/interrupt
- // is processed.
- // no point in trying to cancel on a closed connection.
- if (!requestComplete && !tdsReader.getConnection().isClosed())
- {
- interrupt(e.getMessage());
- onRequestComplete();
- close();
- }
- }
- catch (SQLServerException interruptException)
- {
- if (logger.isLoggable(Level.FINE))
- logger.fine(this.toString() + ": Ignoring error in sending attention: "
- + interruptException.getMessage());
- }
- // throw the original exception even if trying to interrupt fails even in the case
- // of trying to send a cancel to the server.
- throw e;
- }
- }
-
- /**
- * Provides sane default response handling.
- *
- * This default implementation just consumes everything in the response message.
- */
- void processResponse(TDSReader tdsReader) throws SQLServerException
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Processing response");
- try
- {
- TDSParser.parse(tdsReader, getLogContext());
- }
- catch (SQLServerException e)
- {
- if (SQLServerException.DRIVER_ERROR_FROM_DATABASE != e.getDriverErrorCode())
- throw e;
-
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Ignoring error from database: " + e.getMessage());
- }
- }
-
- /**
- * Clears this command from the TDS channel so that another command can execute.
- *
- * This method does not process the response. It just buffers it in memory,
- * including any attention ack that may be present.
- */
- final void detach() throws SQLServerException
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": detaching...");
-
- // Read any remaining response packets from the server.
- // This operation may be timed out or cancelled from another thread.
- while (tdsReader.readPacket())
- ;
-
- // Postcondition: the entire response has been read
- assert !readingResponse;
- }
-
- final void close()
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": closing...");
-
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": processing response...");
-
- while (!processedResponse)
- {
- try
- {
- processResponse(tdsReader);
- }
- catch (SQLServerException e)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": close ignoring error processing response: " + e.getMessage());
-
- if (tdsReader.getConnection().isSessionUnAvailable())
- {
- processedResponse = true;
- attentionPending = false;
- }
- }
- }
-
- if (attentionPending)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": processing attention ack...");
-
- try
- {
- TDSParser.parse(tdsReader, "attention ack");
- }
- catch (SQLServerException e)
- {
- if (tdsReader.getConnection().isSessionUnAvailable())
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": giving up on attention ack after connection closed by exception: " + e);
- attentionPending = false;
- }
- else
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": ignored exception: " + e);
- }
- }
-
- // If the parser returns to us without processing the expected attention ack,
- // then assume that no attention ack is forthcoming from the server and
- // terminate the connection to prevent any other command from executing.
- if (attentionPending)
- {
- logger.severe(this + ": expected attn ack missing or not processed; terminating connection...");
-
- try
- {
- tdsReader.throwInvalidTDS();
- }
- catch (SQLServerException e)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": ignored expected invalid TDS exception: " + e);
-
- assert tdsReader.getConnection().isSessionUnAvailable();
- attentionPending = false;
- }
- }
- }
-
- // Postcondition:
- // Response has been processed and there is no attention pending -- the command is closed.
- // Of course the connection may be closed too, but the command is done regardless...
- assert processedResponse && !attentionPending;
- }
-
- /**
- * Interrupts execution of this command, typically from another thread.
- *
- * Only the first interrupt has any effect. Subsequent interrupts are ignored.
- * Interrupts are also ignored until enabled. If interrupting the command
- * requires an attention signal to be sent to the server, then this method sends
- * that signal if the command's request is already complete.
- *
- * Signalling mechanism is "fire and forget". It is up to either the execution
- * thread or, possibly, a detaching thread, to ensure that any pending attention
- * ack later will be received and processed.
- *
- * @param reason the reason for the interrupt, typically cancel or timeout.
- * @throws SQLServerException if interrupting fails for some reason. This call does not
- * throw the reason for the interrupt.
- */
- void interrupt(String reason) throws SQLServerException
- {
- // Multiple, possibly simultaneous, interrupts may occur.
- // Only the first one should be recognized and acted upon.
- synchronized (interruptLock)
- {
- if (interruptsEnabled && !wasInterrupted())
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": Raising interrupt for reason:" + reason);
-
- wasInterrupted = true;
- interruptReason = reason;
- if (requestComplete)
- attentionPending = tdsWriter.sendAttention();
-
- }
- }
- }
-
- private boolean interruptChecked = false;
- /**
- * Checks once whether an interrupt has occurred, and, if it has, throws an
- * exception indicating that fact.
- *
- * Any calls after the first to check for interrupts are no-ops. This method
- * is called periodically from this command's execution thread to notify the
- * app when an interrupt has happened.
- *
- * It should only be called from places where consistent behavior can be ensured
- * after the exception is thrown. For example, it should not be called at arbitrary
- * times while processing the response, as doing so could leave the response token
- * stream in an inconsistent state. Currently, response processing only checks for
- * interrupts after every result or OUT parameter.
- *
- * Request processing checks for interrupts before writing each packet.
- *
- * @throws SQLServerException if this command was interrupted, throws the
- * reason for the interrupt.
- */
- final void checkForInterrupt() throws SQLServerException
- {
- // Throw an exception with the interrupt reason if this command was interrupted.
- // Note that the interrupt reason may be null. Checking whether the
- // command was interrupted does not require the interrupt lock since only one
- // of the shared state variables is being manipulated; interruptChecked is not
- // shared with the interrupt thread.
- if (wasInterrupted() && !interruptChecked)
- {
- interruptChecked = true;
-
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": throwing interrupt exception, reason: " + interruptReason);
-
- throw new SQLServerException(
- interruptReason,
- SQLState.STATEMENT_CANCELED,
- DriverError.NOT_SET,
- null);
- }
- }
-
- /**
- * Notifies this command when no more request packets are to be sent to the server.
- *
- * After the last packet has been sent, the only way to interrupt the request
- * is to send an attention signal from the interrupt() method.
- *
- * Note that this method is called when the request completes normally
- * (last packet sent with EOM bit) or when it completes after being interrupted
- * (0 or more packets sent with no EOM bit).
- */
- final void onRequestComplete() throws SQLServerException
- {
- assert !requestComplete;
-
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": request complete");
-
- synchronized (interruptLock)
- {
- requestComplete = true;
-
- // If this command was interrupted before its request was complete then
- // we need to send the attention signal if necessary. Note that if no
- // attention signal is sent (i.e. no packets were sent to the server before
- // the interrupt happened), then don't expect an attention ack or any
- // other response.
- if (!interruptsEnabled)
- {
- assert !attentionPending;
- assert !processedResponse;
- assert !readingResponse;
- processedResponse = true;
- }
- else if (wasInterrupted())
- {
-
- if(tdsWriter.isEOMSent())
- {
- attentionPending = tdsWriter.sendAttention();
- readingResponse = attentionPending;
- }
- else
- {
- assert !attentionPending;
- readingResponse = tdsWriter.ignoreMessage();
- }
-
- processedResponse = !readingResponse;
- }
- else
- {
- assert !attentionPending;
- assert !processedResponse;
- readingResponse = true;
- }
- }
- }
-
- /**
- * Notifies this command when the last packet of the response has been read.
- *
- * When the last packet is read, interrupts are disabled. If an interrupt
- * occurred prior to disabling that caused an attention signal to be sent
- * to the server, then an extra packet containing the attention ack is read.
- *
- * This ensures that on return from this method, the TDS channel is clear
- * of all response packets for this command.
- *
- * Note that this method is called for the attention ack message itself as well,
- * so we need to be sure not to expect more than one attention ack...
- */
- final void onResponseEOM() throws SQLServerException
- {
- boolean readAttentionAck = false;
-
- // Atomically disable interrupts and check for a previous interrupt requiring
- // an attention ack to be read.
- synchronized (interruptLock)
- {
- if (interruptsEnabled)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": disabling interrupts");
-
- // Determine whether we still need to read the attention ack packet.
- //
- // When a command is interrupted, Yukon (and later) always sends a response
- // containing at least a DONE(ERROR) token before it sends the attention ack,
- // even if the command's request was not complete.
- readAttentionAck = attentionPending;
-
- interruptsEnabled = false;
- }
- }
-
- // If an attention packet needs to be read then read it. This should
- // be done outside of the interrupt lock to avoid unnecessarily blocking
- // interrupting threads. Note that it is remotely possible that the call
- // to readPacket won't actually read anything if the attention ack was
- // already read by TDSCommand.detach(), in which case this method could
- // be called from multiple threads, leading to a benign race to clear the
- // readingResponse flag.
- if (readAttentionAck)
- tdsReader.readPacket();
-
- readingResponse = false;
- }
-
- /**
- * Notifies this command when the end of its response token stream has been reached.
- *
- * After this call, we are guaranteed that tokens in the response have been processed.
- */
- final void onTokenEOF()
- {
- processedResponse = true;
- }
-
- /**
- * Notifies this command when the attention ack (a DONE token with a special flag)
- * has been processed.
- *
- * After this call, the attention ack should no longer be expected.
- */
- final void onAttentionAck()
- {
- assert attentionPending;
- attentionPending = false;
- }
-
- /**
- * Starts sending this command's TDS request to the server.
- *
- * @param tdsMessageType the type of the TDS message (RPC, QUERY, etc.)
- * @return the TDS writer used to write the request.
- * @throws SQLServerException on any error, including acknowledgement of an interrupt.
- */
- final TDSWriter startRequest(byte tdsMessageType) throws SQLServerException
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": starting request...");
-
- // Start this command's request message
- try
- {
- tdsWriter.startMessage(this, tdsMessageType);
- }
- catch (SQLServerException e)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": starting request: exception: " + e.getMessage());
-
- throw e;
- }
-
- // (Re)initialize this command's interrupt state for its current execution.
- // To ensure atomically consistent behavior, do not leave the interrupt lock
- // until interrupts have been (re)enabled.
- synchronized (interruptLock)
- {
- requestComplete = false;
- readingResponse = false;
- processedResponse = false;
- attentionPending = false;
- wasInterrupted = false;
- interruptReason = null;
- interruptsEnabled = true;
- }
-
- return tdsWriter;
- }
-
- /**
- * Finishes the TDS request and then starts reading the TDS response from the server.
- *
- * @return the TDS reader used to read the response.
- * @throws SQLServerException if there is any kind of error.
- */
- final TDSReader startResponse() throws SQLServerException
- {
- return startResponse(false);
- }
-
- final TDSReader startResponse(boolean isAdaptive) throws SQLServerException
- {
- // Finish sending the request message. If this command was interrupted
- // at any point before endMessage() returns, then endMessage() throws an
- // exception with the reason for the interrupt. Request interrupts
- // are disabled by the time endMessage() returns.
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": finishing request");
-
- try
- {
- tdsWriter.endMessage();
- }
- catch (SQLServerException e)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this + ": finishing request: endMessage threw exception: " + e.getMessage());
-
- throw e;
- }
-
- // If command execution is subject to timeout then start timing until
- // the server returns the first response packet.
- if (null != timeoutTimer)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Starting timer...");
-
- timeoutTimer.start();
- }
-
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Reading response...");
-
- try
- {
- // Wait for the server to execute the request and read the first packet
- // (responseBuffering=adaptive) or all packets (responseBuffering=full)
- // of the response.
- if (isAdaptive)
- {
- tdsReader.readPacket();
- }
- else
- {
- while (tdsReader.readPacket())
- ;
- }
- }
- catch (SQLServerException e)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Exception reading response: " + e.getMessage());
-
- throw e;
- }
- finally
- {
- // If command execution was subject to timeout then stop timing as soon
- // as the server returns the first response packet or errors out.
- if (null != timeoutTimer)
- {
- if (logger.isLoggable(Level.FINEST))
- logger.finest(this.toString() + ": Stopping timer...");
-
- timeoutTimer.stop();
- }
- }
-
- return tdsReader;
- }
-}
+ /**
+ * This function uses java NIO to connect to all the addresses in inetAddrs with in a specified timeout. If it succeeds in connecting, it closes
+ * all the other open sockets and updates the result to success.
+ *
+ * @param inetAddrs
+ * the array of inetAddress to which connection should be made
+ * @param portNumber
+ * the port number at which connection should be made
+ * @param timeoutInMilliSeconds
+ * @throws IOException
+ */
+ private void findSocketUsingJavaNIO(InetAddress[] inetAddrs,
+ int portNumber,
+ int timeoutInMilliSeconds) throws IOException {
+ // The driver does not allow a time out of zero.
+ // Also, the unit of time the user can specify in the driver is seconds.
+ // So, even if the user specifies 1 second(least value), the least possible
+ // value that can come here as timeoutInMilliSeconds is 500 milliseconds.
+ assert timeoutInMilliSeconds != 0 : "The timeout cannot be zero";
+ assert inetAddrs.length != 0 : "Number of inetAddresses should not be zero in this function";
+
+ Selector selector = null;
+ LinkedList socketChannels = new LinkedList();
+ SocketChannel selectedChannel = null;
+
+ try {
+ selector = Selector.open();
+
+ for (int i = 0; i < inetAddrs.length; i++) {
+ SocketChannel sChannel = SocketChannel.open();
+ socketChannels.add(sChannel);
+
+ // make the channel non-blocking
+ sChannel.configureBlocking(false);
+
+ // register the channel for connect event
+ int ops = SelectionKey.OP_CONNECT;
+ SelectionKey key = sChannel.register(selector, ops);
+
+ sChannel.connect(new InetSocketAddress(inetAddrs[i], portNumber));
+
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + " initiated connection to address: " + inetAddrs[i] + ", portNumber: " + portNumber);
+ }
+
+ long timerNow = System.currentTimeMillis();
+ long timerExpire = timerNow + timeoutInMilliSeconds;
+
+ // Denotes the no of channels that still need to processed
+ int noOfOutstandingChannels = inetAddrs.length;
+
+ while (true) {
+ long timeRemaining = timerExpire - timerNow;
+ // if the timeout expired or a channel is selected or there are no more channels left to processes
+ if ((timeRemaining <= 0) || (selectedChannel != null) || (noOfOutstandingChannels <= 0))
+ break;
+
+ // denotes the no of channels that are ready to be processed. i.e. they are either connected
+ // or encountered an exception while trying to connect
+ int readyChannels = selector.select(timeRemaining);
+
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + " no of channels ready: " + readyChannels);
+
+ // There are no real time guarantees on the time out of the select API used above.
+ // This check is necessary
+ // a) to guard against cases where the select returns faster than expected.
+ // b) for cases where no channels could connect with in the time out
+ if (readyChannels != 0) {
+ Set selectedKeys = selector.selectedKeys();
+ Iterator keyIterator = selectedKeys.iterator();
+
+ while (keyIterator.hasNext()) {
+
+ SelectionKey key = keyIterator.next();
+ SocketChannel ch = (SocketChannel) key.channel();
+
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + " processing the channel :" + ch);// this traces the IP by default
+
+ boolean connected = false;
+ try {
+ connected = ch.finishConnect();
+
+ // ch.finishConnect should either return true or throw an exception
+ // as we have subscribed for OP_CONNECT.
+ assert connected == true : "finishConnect on channel:" + ch + " cannot be false";
+
+ selectedChannel = ch;
+
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + " selected the channel :" + selectedChannel);
+
+ break;
+ }
+ catch (IOException ex) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + " the exception: " + ex.getClass() + " with message: " + ex.getMessage()
+ + " occured while processing the channel: " + ch);
+ updateSelectedException(ex, this.toString());
+ // close the channel pro-actively so that we do not
+ // hang on to network resources
+ ch.close();
+ }
+
+ // unregister the key and remove from the selector's selectedKeys
+ key.cancel();
+ keyIterator.remove();
+ noOfOutstandingChannels--;
+ }
+ }
+
+ timerNow = System.currentTimeMillis();
+ }
+ }
+ catch (IOException ex) {
+ // in case of an exception, close the selected channel.
+ // All other channels will be closed in the finally block,
+ // as they need to be closed irrespective of a success/failure
+ close(selectedChannel);
+ throw ex;
+ }
+ finally {
+ // close the selector
+ // As per java docs, on selector.close(), any uncancelled keys still
+ // associated with this
+ // selector are invalidated, their channels are deregistered, and any other
+ // resources associated with this selector are released.
+ // So, its not necessary to cancel each key again
+ close(selector);
+
+ // Close all channels except the selected one.
+ // As we close channels pro-actively in the try block,
+ // its possible that we close a channel twice.
+ // Closing a channel second time is a no-op.
+ // This code is should be in the finally block to guard against cases where
+ // we pre-maturely exit try block due to an exception in selector or other places.
+ for (SocketChannel s : socketChannels) {
+ if (s != selectedChannel) {
+ close(s);
+ }
+ }
+ }
+
+ // if a channel was selected, make the necessary updates
+ if (selectedChannel != null) {
+ // Note that this must be done after selector is closed. Otherwise,
+ // we would get an illegalBlockingMode exception at run time.
+ selectedChannel.configureBlocking(true);
+ selectedSocket = selectedChannel.socket();
+ result = Result.SUCCESS;
+ }
+ }
-/**
- * UninterruptableTDSCommand encapsulates an uninterruptable TDS conversation.
- *
- * TDSCommands have interruptability built in. However, some TDSCommands such as
- * DTC commands, connection commands, cursor close and prepared statement handle close
- * shouldn't be interruptable. This class provides a base implementation for such
- * commands.
- */
-abstract class UninterruptableTDSCommand extends TDSCommand
-{
- UninterruptableTDSCommand(String logContext)
- {
- super(logContext, 0);
- }
-
- final void interrupt(String reason) throws SQLServerException
- {
- // Interrupting an uninterruptable command is a no-op. That is,
- // it can happen, but it should have no effect.
- logger.finest(toString() + " Ignoring interrupt of uninterruptable TDS command; Reason:" + reason);
- }
+ // This method contains the old logic of connecting to
+ // a socket of one of the IPs corresponding to a given host name.
+ // In the old code below, the logic around 0 timeout has been removed as
+ // 0 timeout is not allowed. The code has been re-factored so that the logic
+ // is common for hostName or InetAddress.
+ private Socket getDefaultSocket(String hostName,
+ int portNumber,
+ int timeoutInMilliSeconds) throws IOException {
+ // Open the socket, with or without a timeout, throwing an UnknownHostException
+ // if there is a failure to resolve the host name to an InetSocketAddress.
+ //
+ // Note that Socket(host, port) throws an UnknownHostException if the host name
+ // cannot be resolved, but that InetSocketAddress(host, port) does not - it sets
+ // the returned InetSocketAddress as unresolved.
+ InetSocketAddress addr = new InetSocketAddress(hostName, portNumber);
+ return getConnectedSocket(addr, timeoutInMilliSeconds);
+ }
+
+ private Socket getConnectedSocket(InetAddress inetAddr,
+ int portNumber,
+ int timeoutInMilliSeconds) throws IOException {
+ InetSocketAddress addr = new InetSocketAddress(inetAddr, portNumber);
+ return getConnectedSocket(addr, timeoutInMilliSeconds);
+ }
+
+ private Socket getConnectedSocket(InetSocketAddress addr,
+ int timeoutInMilliSeconds) throws IOException {
+ assert timeoutInMilliSeconds != 0 : "timeout cannot be zero";
+ if (addr.isUnresolved())
+ throw new java.net.UnknownHostException();
+ selectedSocket = new Socket();
+ selectedSocket.connect(addr, timeoutInMilliSeconds);
+ return selectedSocket;
+ }
+
+ private void findSocketUsingThreading(LinkedList inetAddrs,
+ int portNumber,
+ int timeoutInMilliSeconds) throws IOException, InterruptedException {
+ assert timeoutInMilliSeconds != 0 : "The timeout cannot be zero";
+ assert inetAddrs.isEmpty() == false : "Number of inetAddresses should not be zero in this function";
+
+ LinkedList sockets = new LinkedList();
+ LinkedList socketConnectors = new LinkedList();
+
+ try {
+
+ // create a socket, inetSocketAddress and a corresponding socketConnector per inetAddress
+ noOfSpawnedThreads = inetAddrs.size();
+ for (InetAddress inetAddress : inetAddrs) {
+ Socket s = new Socket();
+ sockets.add(s);
+
+ InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, portNumber);
+
+ SocketConnector socketConnector = new SocketConnector(s, inetSocketAddress, timeoutInMilliSeconds, this);
+ socketConnectors.add(socketConnector);
+ }
+
+ // acquire parent lock and spawn all threads
+ synchronized (parentThreadLock) {
+ for (SocketConnector sc : socketConnectors) {
+ threadPoolExecutor.execute(sc);
+ }
+
+ long timerNow = System.currentTimeMillis();
+ long timerExpire = timerNow + timeoutInMilliSeconds;
+
+ // The below loop is to guard against the spurious wake up problem
+ while (true) {
+ long timeRemaining = timerExpire - timerNow;
+
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString() + " TimeRemaining:" + timeRemaining + "; Result:" + result + "; Max. open thread count: "
+ + threadPoolExecutor.getLargestPoolSize() + "; Current open thread count:" + threadPoolExecutor.getActiveCount());
+ }
+
+ // if there is no time left or if the result is determined, break.
+ // Note that a dirty read of result is totally fine here.
+ // Since this thread holds the parentThreadLock, even if we do a dirty
+ // read here, the child thread, after updating the result, would not be
+ // able to call notify on the parentThreadLock
+ // (and thus finish execution) as it would be waiting on parentThreadLock
+ // held by this thread(the parent thread).
+ // So, this thread will wait again and then be notified by the childThread.
+ // On the other hand, if we try to take socketFinderLock here to avoid
+ // dirty read, we would introduce a dead lock due to the
+ // reverse order of locking in updateResult method.
+ if (timeRemaining <= 0 || (!result.equals(Result.UNKNOWN)))
+ break;
+
+ parentThreadLock.wait(timeRemaining);
+
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString() + " The parent thread wokeup.");
+ }
+
+ timerNow = System.currentTimeMillis();
+ }
+
+ }
+
+ }
+ finally {
+ // Close all sockets except the selected one.
+ // As we close sockets pro-actively in the child threads,
+ // its possible that we close a socket twice.
+ // Closing a socket second time is a no-op.
+ // If a child thread is waiting on the connect call on a socket s,
+ // closing the socket s here ensures that an exception is thrown
+ // in the child thread immediately. This mitigates the problem
+ // of thread explosion by ensuring that unnecessary threads die
+ // quickly without waiting for "min(timeOut, 21)" seconds
+ for (Socket s : sockets) {
+ if (s != selectedSocket) {
+ close(s);
+ }
+ }
+ }
+ }
+
+ /**
+ * search result
+ */
+ Result getResult() {
+ return result;
+ }
+
+ void close(Selector selector) {
+ if (null != selector) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + ": Closing Selector");
+
+ try {
+ selector.close();
+ }
+ catch (IOException e) {
+ if (logger.isLoggable(Level.FINE))
+ logger.log(Level.FINE, this.toString() + ": Ignored the following error while closing Selector", e);
+ }
+ }
+ }
+
+ void close(Socket socket) {
+ if (null != socket) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + ": Closing TCP socket:" + socket);
+
+ try {
+ socket.close();
+ }
+ catch (IOException e) {
+ if (logger.isLoggable(Level.FINE))
+ logger.log(Level.FINE, this.toString() + ": Ignored the following error while closing socket", e);
+ }
+ }
+ }
+
+ void close(SocketChannel socketChannel) {
+ if (null != socketChannel) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(this.toString() + ": Closing TCP socket channel:" + socketChannel);
+
+ try {
+ socketChannel.close();
+ }
+ catch (IOException e) {
+ if (logger.isLoggable(Level.FINE))
+ logger.log(Level.FINE, this.toString() + "Ignored the following error while closing socketChannel", e);
+ }
+ }
+ }
+
+ /**
+ * Used by socketConnector threads to notify the socketFinder of their connection attempt result(a connected socket or exception). It updates the
+ * result, socket and exception variables of socketFinder object. This method notifies the parent thread if a socket is found or if all the
+ * spawned threads have notified. It also closes a socket if it is not selected for use by socketFinder.
+ *
+ * @param socket
+ * the SocketConnector's socket
+ * @param exception
+ * Exception that occurred in socket connector thread
+ * @param threadId
+ * Id of the calling Thread for diagnosis
+ */
+ void updateResult(Socket socket,
+ IOException exception,
+ String threadId) {
+ if (result.equals(Result.UNKNOWN)) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The following child thread is waiting for socketFinderLock:" + threadId);
+ }
+
+ synchronized (socketFinderlock) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The following child thread acquired socketFinderLock:" + threadId);
+ }
+
+ if (result.equals(Result.UNKNOWN)) {
+ // if the connection was successful and no socket has been
+ // selected yet
+ if (exception == null && selectedSocket == null) {
+ selectedSocket = socket;
+ result = Result.SUCCESS;
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The socket of the following thread has been chosen:" + threadId);
+ }
+ }
+
+ // if an exception occurred
+ if (exception != null) {
+ updateSelectedException(exception, threadId);
+ }
+ }
+
+ noOfThreadsThatNotified++;
+
+ // if all threads notified, but the result is still unknown,
+ // update the result to failure
+ if ((noOfThreadsThatNotified >= noOfSpawnedThreads) && result.equals(Result.UNKNOWN)) {
+ result = Result.FAILURE;
+ }
+
+ if (!result.equals(Result.UNKNOWN)) {
+ // 1) Note that at any point of time, there is only one
+ // thread(parent/child thread) competing for parentThreadLock.
+ // 2) The only time where a child thread could be waiting on
+ // parentThreadLock is before the wait call in the parentThread
+ // 3) After the above happens, the parent thread waits to be
+ // notified on parentThreadLock. After being notified,
+ // it would be the ONLY thread competing for the lock.
+ // for the following reasons
+ // a) The parentThreadLock is taken while holding the socketFinderLock.
+ // So, all child threads, except one, block on socketFinderLock
+ // (not parentThreadLock)
+ // b) After parentThreadLock is notified by a child thread, the result
+ // would be known(Refer the double-checked locking done at the
+ // start of this method). So, all child threads would exit
+ // as no-ops and would never compete with parent thread
+ // for acquiring parentThreadLock
+ // 4) As the parent thread is the only thread that competes for the
+ // parentThreadLock, it need not wait to acquire the lock once it wakes
+ // up and gets scheduled.
+ // This results in better performance as it would close unnecessary
+ // sockets and thus help child threads die quickly.
+
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The following child thread is waiting for parentThreadLock:" + threadId);
+ }
+
+ synchronized (parentThreadLock) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The following child thread acquired parentThreadLock:" + threadId);
+ }
+
+ parentThreadLock.notify();
+ }
+
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The following child thread released parentThreadLock and notified the parent thread:" + threadId);
+ }
+ }
+ }
+
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The following child thread released socketFinderLock:" + threadId);
+ }
+ }
+
+ }
+
+ /**
+ * Updates the selectedException if
+ *
+ * a) selectedException is null
+ *
+ * b) ex is a non-socketTimeoutException and selectedException is a socketTimeoutException
+ *
+ * If there are multiple exceptions, that are not related to socketTimeout the first non-socketTimeout exception is picked. If all exceptions are
+ * related to socketTimeout, the first exception is picked. Note: This method is not thread safe. The caller should ensure thread safety.
+ *
+ * @param ex
+ * the IOException
+ * @param traceId
+ * the traceId of the thread
+ */
+ public void updateSelectedException(IOException ex,
+ String traceId) {
+ boolean updatedException = false;
+ if (selectedException == null) {
+ selectedException = ex;
+ updatedException = true;
+ }
+ else if ((!(ex instanceof SocketTimeoutException)) && (selectedException instanceof SocketTimeoutException)) {
+ selectedException = ex;
+ updatedException = true;
+ }
+
+ if (updatedException) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The selected exception is updated to the following: ExceptionType:" + ex.getClass() + "; ExceptionMessage:"
+ + ex.getMessage() + "; by the following thread:" + traceId);
+ }
+ }
+ }
+
+ /**
+ * Used fof tracing
+ *
+ * @return traceID string
+ */
+ public String toString() {
+ return traceID;
+ }
+}
+
+/**
+ * This is used to connect a socket in a separate thread
+ */
+final class SocketConnector implements Runnable {
+ // socket on which connection attempt would be made
+ private final Socket socket;
+
+ // the socketFinder associated with this connector
+ private final SocketFinder socketFinder;
+
+ // inetSocketAddress to connect to
+ private final InetSocketAddress inetSocketAddress;
+
+ // timeout in milliseconds
+ private final int timeoutInMilliseconds;
+
+ // Logging variables
+ private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.SocketConnector");
+ private final String traceID;
+
+ // Id of the thread. used for diagnosis
+ private final String threadID;
+
+ // a counter used to give unique IDs to each connector thread.
+ // this will have the id of the thread that was last created.
+ private static long lastThreadID = 0;
+
+ /**
+ * Constructs a new SocketConnector object with the associated socket and socketFinder
+ */
+ SocketConnector(Socket socket,
+ InetSocketAddress inetSocketAddress,
+ int timeOutInMilliSeconds,
+ SocketFinder socketFinder) {
+ this.socket = socket;
+ this.inetSocketAddress = inetSocketAddress;
+ this.timeoutInMilliseconds = timeOutInMilliSeconds;
+ this.socketFinder = socketFinder;
+ this.threadID = Long.toString(nextThreadID());
+ this.traceID = "SocketConnector:" + this.threadID + "(" + socketFinder.toString() + ")";
+ }
+
+ /**
+ * If search for socket has not finished, this function tries to connect a socket(with a timeout) synchronously. It further notifies the
+ * socketFinder the result of the connection attempt
+ */
+ public void run() {
+ IOException exception = null;
+ // Note that we do not need socketFinder lock here
+ // as we update nothing in socketFinder based on the condition.
+ // So, its perfectly fine to make a dirty read.
+ SocketFinder.Result result = socketFinder.getResult();
+ if (result.equals(SocketFinder.Result.UNKNOWN)) {
+ try {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(
+ this.toString() + " connecting to InetSocketAddress:" + inetSocketAddress + " with timeout:" + timeoutInMilliseconds);
+ }
+
+ socket.connect(inetSocketAddress, timeoutInMilliseconds);
+ }
+ catch (IOException ex) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer(this.toString() + " exception:" + ex.getClass() + " with message:" + ex.getMessage()
+ + " occured while connecting to InetSocketAddress:" + inetSocketAddress);
+ }
+ exception = ex;
+ }
+
+ socketFinder.updateResult(socket, exception, this.toString());
+ }
+
+ }
+
+ /**
+ * Used for tracing
+ *
+ * @return traceID string
+ */
+ public String toString() {
+ return traceID;
+ }
+
+ /**
+ * Generates the next unique thread id.
+ */
+ private static synchronized long nextThreadID() {
+ if (lastThreadID == Long.MAX_VALUE) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("Resetting the Id count");
+ lastThreadID = 1;
+ }
+ else {
+ lastThreadID++;
+ }
+ return lastThreadID;
+ }
+}
+
+/**
+ * TDSWriter implements the client to server TDS data pipe.
+ */
+final class TDSWriter {
+ private static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Writer");
+ private final String traceID;
+
+ final public String toString() {
+ return traceID;
+ }
+
+ private final TDSChannel tdsChannel;
+ private final SQLServerConnection con;
+
+ // Flag to indicate whether data written via writeXXX() calls
+ // is loggable. Data is normally loggable. But sensitive
+ // data, such as user credentials, should never be logged for
+ // security reasons.
+ private boolean dataIsLoggable = true;
+
+ void setDataLoggable(boolean value) {
+ dataIsLoggable = value;
+ }
+
+ private TDSCommand command = null;
+
+ // TDS message type (Query, RPC, DTC, etc.) sent at the beginning
+ // of every TDS message header. Value is set when starting a new
+ // TDS message of the specified type.
+ private byte tdsMessageType;
+
+ private volatile int sendResetConnection = 0;
+
+ // Size (in bytes) of the TDS packets to/from the server.
+ // This size is normally fixed for the life of the connection,
+ // but it can change once after the logon packet because packet
+ // size negotiation happens at logon time.
+ private int currentPacketSize = 0;
+
+ // Size of the TDS packet header, which is:
+ // byte type
+ // byte status
+ // short length
+ // short SPID
+ // byte packet
+ // byte window
+ private final static int TDS_PACKET_HEADER_SIZE = 8;
+ private final static byte[] placeholderHeader = new byte[TDS_PACKET_HEADER_SIZE];
+
+ // Intermediate array used to convert typically "small" values such as fixed-length types
+ // (byte, int, long, etc.) and Strings from their native form to bytes for sending to
+ // the channel buffers.
+ private byte valueBytes[] = new byte[256];
+
+ // Monotonically increasing packet number associated with the current message
+ private volatile int packetNum = 0;
+
+ // Bytes for sending decimal/numeric data
+ private final static int BYTES4 = 4;
+ private final static int BYTES8 = 8;
+ private final static int BYTES12 = 12;
+ private final static int BYTES16 = 16;
+ public final static int BIGDECIMAL_MAX_LENGTH = 0x11;
+
+ // is set to true when EOM is sent for the current message.
+ // Note that this variable will never be accessed from multiple threads
+ // simultaneously and so it need not be volatile
+ private boolean isEOMSent = false;
+
+ boolean isEOMSent() {
+ return isEOMSent;
+ }
+
+ // Packet data buffers
+ private ByteBuffer stagingBuffer;
+ private ByteBuffer socketBuffer;
+ private ByteBuffer logBuffer;
+
+ private CryptoMetadata cryptoMeta = null;
+
+ TDSWriter(TDSChannel tdsChannel,
+ SQLServerConnection con) {
+ this.tdsChannel = tdsChannel;
+ this.con = con;
+ traceID = "TDSWriter@" + Integer.toHexString(hashCode()) + " (" + con.toString() + ")";
+ }
+
+ // TDS message start/end operations
+
+ void preparePacket() throws SQLServerException {
+ if (tdsChannel.isLoggingPackets()) {
+ Arrays.fill(logBuffer.array(), (byte) 0xFE);
+ logBuffer.clear();
+ }
+
+ // Write a placeholder packet header. This will be replaced
+ // with the real packet header when the packet is flushed.
+ writeBytes(placeholderHeader);
+ }
+
+ /**
+ * Start a new TDS message.
+ */
+ void writeMessageHeader() throws SQLServerException {
+ // TDS 7.2 & later:
+ // Include ALL_Headers/MARS header in message's first packet
+ // Note: The PKT_BULK message does not nees this ALL_HEADERS
+ if ((TDS.PKT_QUERY == tdsMessageType || TDS.PKT_DTC == tdsMessageType || TDS.PKT_RPC == tdsMessageType)) {
+ boolean includeTraceHeader = false;
+ int totalHeaderLength = TDS.MESSAGE_HEADER_LENGTH;
+ if (TDS.PKT_QUERY == tdsMessageType || TDS.PKT_RPC == tdsMessageType) {
+ if (con.isDenaliOrLater() && !ActivityCorrelator.getCurrent().IsSentToServer() && Util.IsActivityTraceOn()) {
+ includeTraceHeader = true;
+ totalHeaderLength += TDS.TRACE_HEADER_LENGTH;
+ }
+ }
+ writeInt(totalHeaderLength); // allHeaders.TotalLength (DWORD)
+ writeInt(TDS.MARS_HEADER_LENGTH); // MARS header length (DWORD)
+ writeShort((short) 2); // allHeaders.HeaderType(MARS header) (USHORT)
+ writeBytes(con.getTransactionDescriptor());
+ writeInt(1); // marsHeader.OutstandingRequestCount
+ if (includeTraceHeader) {
+ writeInt(TDS.TRACE_HEADER_LENGTH); // trace header length (DWORD)
+ writeTraceHeaderData();
+ ActivityCorrelator.setCurrentActivityIdSentFlag(); // set the flag to indicate this ActivityId is sent
+ }
+ }
+ }
+
+ void writeTraceHeaderData() throws SQLServerException {
+ ActivityId activityId = ActivityCorrelator.getCurrent();
+ final byte[] actIdByteArray = Util.asGuidByteArray(activityId.getId());
+ long seqNum = activityId.getSequence();
+ writeShort(TDS.HEADERTYPE_TRACE); // trace header type
+ writeBytes(actIdByteArray, 0, actIdByteArray.length); // guid part of ActivityId
+ writeInt((int) seqNum); // sequence number of ActivityId
+
+ if (logger.isLoggable(Level.FINER))
+ logger.finer("Send Trace Header - ActivityID: " + activityId.toString());
+ }
+
+ /**
+ * Convenience method to prepare the TDS channel for writing and start a new TDS message.
+ *
+ * @param command
+ * The TDS command
+ * @param tdsMessageType
+ * The TDS message type (PKT_QUERY, PKT_RPC, etc.)
+ */
+ void startMessage(TDSCommand command,
+ byte tdsMessageType) throws SQLServerException {
+ this.command = command;
+ this.tdsMessageType = tdsMessageType;
+ this.packetNum = 0;
+ this.isEOMSent = false;
+ this.dataIsLoggable = true;
+
+ // If the TDS packet size has changed since the last request
+ // (which should really only happen after the login packet)
+ // then allocate new buffers that are the correct size.
+ int negotiatedPacketSize = con.getTDSPacketSize();
+ if (currentPacketSize != negotiatedPacketSize) {
+ socketBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN);
+ stagingBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN);
+ logBuffer = ByteBuffer.allocate(negotiatedPacketSize).order(ByteOrder.LITTLE_ENDIAN);
+ currentPacketSize = negotiatedPacketSize;
+ }
+
+ socketBuffer.position(socketBuffer.limit());
+ stagingBuffer.clear();
+
+ preparePacket();
+ writeMessageHeader();
+ }
+
+ final void endMessage() throws SQLServerException {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(toString() + " Finishing TDS message");
+ writePacket(TDS.STATUS_BIT_EOM);
+ }
+
+ // If a complete request has not been sent to the server,
+ // the client MUST send the next packet with both ignore bit (0x02) and EOM bit (0x01)
+ // set in the status to cancel the request.
+ final boolean ignoreMessage() throws SQLServerException {
+ if (packetNum > 0) {
+ assert !isEOMSent;
+
+ if (logger.isLoggable(Level.FINER))
+ logger.finest(toString() + " Finishing TDS message by sending ignore bit and end of message");
+ writePacket(TDS.STATUS_BIT_EOM | TDS.STATUS_BIT_ATTENTION);
+ return true;
+ }
+ return false;
+ }
+
+ final void resetPooledConnection() {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(toString() + " resetPooledConnection");
+ sendResetConnection = TDS.STATUS_BIT_RESET_CONN;
+ }
+
+ // Primitive write operations
+
+ void writeByte(byte value) throws SQLServerException {
+ if (stagingBuffer.remaining() >= 1) {
+ stagingBuffer.put(value);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.put(value);
+ else
+ logBuffer.position(logBuffer.position() + 1);
+ }
+ }
+ else {
+ valueBytes[0] = value;
+ writeWrappedBytes(valueBytes, 1);
+ }
+ }
+
+ void writeChar(char value) throws SQLServerException {
+ if (stagingBuffer.remaining() >= 2) {
+ stagingBuffer.putChar(value);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.putChar(value);
+ else
+ logBuffer.position(logBuffer.position() + 2);
+ }
+ }
+ else {
+ Util.writeShort((short) value, valueBytes, 0);
+ writeWrappedBytes(valueBytes, 2);
+ }
+ }
+
+ void writeShort(short value) throws SQLServerException {
+ if (stagingBuffer.remaining() >= 2) {
+ stagingBuffer.putShort(value);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.putShort(value);
+ else
+ logBuffer.position(logBuffer.position() + 2);
+ }
+ }
+ else {
+ Util.writeShort(value, valueBytes, 0);
+ writeWrappedBytes(valueBytes, 2);
+ }
+ }
+
+ void writeInt(int value) throws SQLServerException {
+ if (stagingBuffer.remaining() >= 4) {
+ stagingBuffer.putInt(value);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.putInt(value);
+ else
+ logBuffer.position(logBuffer.position() + 4);
+ }
+ }
+ else {
+ Util.writeInt(value, valueBytes, 0);
+ writeWrappedBytes(valueBytes, 4);
+ }
+ }
+
+ /**
+ * Append a real value in the TDS stream.
+ *
+ * @param value
+ * the data value
+ */
+ void writeReal(Float value) throws SQLServerException {
+ if (false) // stagingBuffer.remaining() >= 4)
+ {
+ stagingBuffer.putFloat(value);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.putFloat(value);
+ else
+ logBuffer.position(logBuffer.position() + 4);
+ }
+ }
+ else {
+ writeInt(Float.floatToRawIntBits(value.floatValue()));
+ }
+ }
+
+ /**
+ * Append a double value in the TDS stream.
+ *
+ * @param value
+ * the data value
+ */
+ void writeDouble(double value) throws SQLServerException {
+ if (stagingBuffer.remaining() >= 8) {
+ stagingBuffer.putDouble(value);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.putDouble(value);
+ else
+ logBuffer.position(logBuffer.position() + 8);
+ }
+ }
+ else {
+ long bits = Double.doubleToLongBits(value);
+ long mask = 0xFF;
+ int nShift = 0;
+ for (int i = 0; i < 8; i++) {
+ writeByte((byte) ((bits & mask) >> nShift));
+ nShift += 8;
+ mask = mask << 8;
+ }
+ }
+ }
+
+ /**
+ * Append a big decimal in the TDS stream.
+ *
+ * @param bigDecimalVal
+ * the big decimal data value
+ * @param srcJdbcType
+ * the source JDBCType
+ * @param precision
+ * the precision of the data value
+ */
+ void writeBigDecimal(BigDecimal bigDecimalVal,
+ int srcJdbcType,
+ int precision) throws SQLServerException {
+ /*
+ * Length including sign byte One 1-byte unsigned integer that represents the sign of the decimal value (0 => Negative, 1 => positive) One 4-,
+ * 8-, 12-, or 16-byte signed integer that represents the decimal value multiplied by 10^scale. The maximum size of this integer is determined
+ * based on p as follows: 4 bytes if 1 <= p <= 9. 8 bytes if 10 <= p <= 19. 12 bytes if 20 <= p <= 28. 16 bytes if 29 <= p <= 38.
+ */
+
+ boolean isNegative = (bigDecimalVal.signum() < 0);
+ BigInteger bi = bigDecimalVal.unscaledValue();
+ if (isNegative)
+ bi = bi.negate();
+ if (9 >= precision) {
+ writeByte((byte) (BYTES4 + 1));
+ writeByte((byte) (isNegative ? 0 : 1));
+ writeInt(bi.intValue());
+ }
+ else if (19 >= precision) {
+ writeByte((byte) (BYTES8 + 1));
+ writeByte((byte) (isNegative ? 0 : 1));
+ writeLong(bi.longValue());
+ }
+ else {
+ int bLength;
+ if (28 >= precision)
+ bLength = BYTES12;
+ else
+ bLength = BYTES16;
+ writeByte((byte) (bLength + 1));
+ writeByte((byte) (isNegative ? 0 : 1));
+
+ // Get the bytes of the BigInteger value. It is in reverse order, with
+ // most significant byte in 0-th element. We need to reverse it first before sending over TDS.
+ byte[] unscaledBytes = bi.toByteArray();
+
+ if (unscaledBytes.length > bLength) {
+ // If precession of input is greater than maximum allowed (p><= 38) throw Exception
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
+ Object[] msgArgs = {JDBCType.of(srcJdbcType)};
+ throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET, null);
+ }
+
+ // Byte array to hold all the reversed and padding bytes.
+ byte[] bytes = new byte[bLength];
+
+ // We need to fill up the rest of the array with zeros, as unscaledBytes may have less bytes
+ // than the required size for TDS.
+ int remaining = bLength - unscaledBytes.length;
+
+ // Reverse the bytes.
+ int i, j;
+ for (i = 0, j = unscaledBytes.length - 1; i < unscaledBytes.length;)
+ bytes[i++] = unscaledBytes[j--];
+
+ // Fill the rest of the array with zeros.
+ for (; i < remaining; i++)
+ bytes[i] = (byte) 0x00;
+ writeBytes(bytes);
+ }
+ }
+
+ void writeSmalldatetime(String value) throws SQLServerException {
+ GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
+ long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
+ java.sql.Timestamp timestampValue = java.sql.Timestamp.valueOf(value);
+ utcMillis = timestampValue.getTime();
+
+ // Load the calendar with the desired value
+ calendar.setTimeInMillis(utcMillis);
+
+ // Number of days since the SQL Server Base Date (January 1, 1900)
+ int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR), TDS.BASE_YEAR_1900);
+
+ // Next, figure out the number of milliseconds since midnight of the current day.
+ int millisSinceMidnight = 1000 * calendar.get(Calendar.SECOND) + // Seconds into the current minute
+ 60 * 1000 * calendar.get(Calendar.MINUTE) + // Minutes into the current hour
+ 60 * 60 * 1000 * calendar.get(Calendar.HOUR_OF_DAY); // Hours into the current day
+
+ // The last millisecond of the current day is always rounded to the first millisecond
+ // of the next day because DATETIME is only accurate to 1/300th of a second.
+ if (1000 * 60 * 60 * 24 - 1 <= millisSinceMidnight) {
+ ++daysSinceSQLBaseDate;
+ millisSinceMidnight = 0;
+ }
+
+ // Number of days since the SQL Server Base Date (January 1, 1900)
+ writeShort((short) daysSinceSQLBaseDate);
+
+ int secondsSinceMidnight = (millisSinceMidnight / 1000);
+ int minutesSinceMidnight = (secondsSinceMidnight / 60);
+
+ // Values that are 29.998 seconds or less are rounded down to the nearest minute
+ minutesSinceMidnight = ((secondsSinceMidnight % 60) > 29.998) ? minutesSinceMidnight + 1 : minutesSinceMidnight;
+
+ // Minutes since midnight
+ writeShort((short) minutesSinceMidnight);
+ }
+
+ void writeDatetime(String value) throws SQLServerException {
+ GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
+ long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
+ int subSecondNanos = 0;
+ java.sql.Timestamp timestampValue = java.sql.Timestamp.valueOf(value);
+ utcMillis = timestampValue.getTime();
+ subSecondNanos = timestampValue.getNanos();
+
+ // Load the calendar with the desired value
+ calendar.setTimeInMillis(utcMillis);
+
+ // Number of days there have been since the SQL Base Date.
+ // These are based on SQL Server algorithms
+ int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR), TDS.BASE_YEAR_1900);
+
+ // Number of milliseconds since midnight of the current day.
+ int millisSinceMidnight = (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into the current second
+ 1000 * calendar.get(Calendar.SECOND) + // Seconds into the current minute
+ 60 * 1000 * calendar.get(Calendar.MINUTE) + // Minutes into the current hour
+ 60 * 60 * 1000 * calendar.get(Calendar.HOUR_OF_DAY); // Hours into the current day
+
+ // The last millisecond of the current day is always rounded to the first millisecond
+ // of the next day because DATETIME is only accurate to 1/300th of a second.
+ if (1000 * 60 * 60 * 24 - 1 <= millisSinceMidnight) {
+ ++daysSinceSQLBaseDate;
+ millisSinceMidnight = 0;
+ }
+
+ // Last-ditch verification that the value is in the valid range for the
+ // DATETIMEN TDS data type (1/1/1753 to 12/31/9999). If it's not, then
+ // throw an exception now so that statement execution is safely canceled.
+ // Attempting to put an invalid value on the wire would result in a TDS
+ // exception, which would close the connection.
+ // These are based on SQL Server algorithms
+ if (daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1753, 1, TDS.BASE_YEAR_1900)
+ || daysSinceSQLBaseDate >= DDC.daysSinceBaseDate(10000, 1, TDS.BASE_YEAR_1900)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
+ Object[] msgArgs = {SSType.DATETIME};
+ throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW, DriverError.NOT_SET, null);
+ }
+
+ // Number of days since the SQL Server Base Date (January 1, 1900)
+ writeInt(daysSinceSQLBaseDate);
+
+ // Milliseconds since midnight (at a resolution of three hundredths of a second)
+ writeInt((3 * millisSinceMidnight + 5) / 10);
+ }
+
+ void writeDate(String value) throws SQLServerException {
+ GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
+ long utcMillis = 0;
+ java.sql.Date dateValue = java.sql.Date.valueOf(value);
+ utcMillis = dateValue.getTime();
+
+ // Load the calendar with the desired value
+ calendar.setTimeInMillis(utcMillis);
+
+ writeScaledTemporal(calendar, 0, // subsecond nanos (none for a date value)
+ 0, // scale (dates are not scaled)
+ SSType.DATE);
+ }
+
+ void writeTime(java.sql.Timestamp value,
+ int scale) throws SQLServerException {
+ GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
+ long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
+ int subSecondNanos = 0;
+ utcMillis = value.getTime();
+ subSecondNanos = value.getNanos();
+
+ // Load the calendar with the desired value
+ calendar.setTimeInMillis(utcMillis);
+
+ writeScaledTemporal(calendar, subSecondNanos, scale, SSType.TIME);
+ }
+
+ void writeDateTimeOffset(Object value,
+ int scale,
+ SSType destSSType) throws SQLServerException {
+ GregorianCalendar calendar = null;
+ TimeZone timeZone = TimeZone.getDefault(); // Time zone to associate with the value in the Gregorian calendar
+ long utcMillis = 0; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
+ int subSecondNanos = 0;
+ int minutesOffset = 0;
+
+ microsoft.sql.DateTimeOffset dtoValue = (microsoft.sql.DateTimeOffset) value;
+ utcMillis = dtoValue.getTimestamp().getTime();
+ subSecondNanos = dtoValue.getTimestamp().getNanos();
+ minutesOffset = dtoValue.getMinutesOffset();
+
+ // If the target data type is DATETIMEOFFSET, then use UTC for the calendar that
+ // will hold the value, since writeRPCDateTimeOffset expects a UTC calendar.
+ // Otherwise, when converting from DATETIMEOFFSET to other temporal data types,
+ // use a local time zone determined by the minutes offset of the value, since
+ // the writers for those types expect local calendars.
+ timeZone = (SSType.DATETIMEOFFSET == destSSType) ? UTC.timeZone : new SimpleTimeZone(minutesOffset * 60 * 1000, "");
+
+ calendar = new GregorianCalendar(timeZone, Locale.US);
+ calendar.setLenient(true);
+ calendar.clear();
+ calendar.setTimeInMillis(utcMillis);
+
+ writeScaledTemporal(calendar, subSecondNanos, scale, SSType.DATETIMEOFFSET);
+
+ writeShort((short) minutesOffset);
+ }
+
+ void writeOffsetDateTimeWithTimezone(OffsetDateTime offsetDateTimeValue,
+ int scale) throws SQLServerException {
+ GregorianCalendar calendar = null;
+ TimeZone timeZone;
+ long utcMillis = 0;
+ int subSecondNanos = 0;
+ int minutesOffset = 0;
+
+ try {
+ // offsetTimeValue.getOffset() returns a ZoneOffset object which has only hours and minutes
+ // components. So the result of the division will be an integer always. SQL Server also supports
+ // offsets in minutes precision.
+ minutesOffset = offsetDateTimeValue.getOffset().getTotalSeconds() / 60;
+ }
+ catch (Exception e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_zoneOffsetError"), null, // SQLState is null as this error is generated in
+ // the driver
+ 0, // Use 0 instead of DriverError.NOT_SET to use the correct constructor
+ null);
+ }
+ subSecondNanos = offsetDateTimeValue.getNano();
+
+ // writeScaledTemporal() expects subSecondNanos in 9 digits precssion
+ // but getNano() used in OffsetDateTime returns precession based on nanoseconds read from csv
+ // padding zeros to match the expectation of writeScaledTemporal()
+ int padding = 9 - String.valueOf(subSecondNanos).length();
+ while (padding > 0) {
+ subSecondNanos = subSecondNanos * 10;
+ padding--;
+ }
+
+ // For TIME_WITH_TIMEZONE, use UTC for the calendar that will hold the value
+ timeZone = UTC.timeZone;
+
+ // The behavior is similar to microsoft.sql.DateTimeOffset
+ // In Timestamp format, only YEAR needs to have 4 digits. The leading zeros for the rest of the fields can be omitted.
+ String offDateTimeStr = String.format("%04d", offsetDateTimeValue.getYear()) + '-' + offsetDateTimeValue.getMonthValue() + '-'
+ + offsetDateTimeValue.getDayOfMonth() + ' ' + offsetDateTimeValue.getHour() + ':' + offsetDateTimeValue.getMinute() + ':'
+ + offsetDateTimeValue.getSecond();
+ utcMillis = Timestamp.valueOf(offDateTimeStr).getTime();
+ calendar = initializeCalender(timeZone);
+ calendar.setTimeInMillis(utcMillis);
+
+ // Local timezone value in minutes
+ int minuteAdjustment = ((TimeZone.getDefault().getRawOffset()) / (60 * 1000));
+ // check if date is in day light savings and add daylight saving minutes
+ if (TimeZone.getDefault().inDaylightTime(calendar.getTime()))
+ minuteAdjustment += (TimeZone.getDefault().getDSTSavings()) / (60 * 1000);
+ // If the local time is negative then positive minutesOffset must be subtracted from calender
+ minuteAdjustment += (minuteAdjustment < 0) ? (minutesOffset * (-1)) : minutesOffset;
+ calendar.add(Calendar.MINUTE, minuteAdjustment);
+
+ writeScaledTemporal(calendar, subSecondNanos, scale, SSType.DATETIMEOFFSET);
+ writeShort((short) minutesOffset);
+ }
+
+ void writeOffsetTimeWithTimezone(OffsetTime offsetTimeValue,
+ int scale) throws SQLServerException {
+ GregorianCalendar calendar = null;
+ TimeZone timeZone;
+ long utcMillis = 0;
+ int subSecondNanos = 0;
+ int minutesOffset = 0;
+
+ try {
+ // offsetTimeValue.getOffset() returns a ZoneOffset object which has only hours and minutes
+ // components. So the result of the division will be an integer always. SQL Server also supports
+ // offsets in minutes precision.
+ minutesOffset = offsetTimeValue.getOffset().getTotalSeconds() / 60;
+ }
+ catch (Exception e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_zoneOffsetError"), null, // SQLState is null as this error is generated in
+ // the driver
+ 0, // Use 0 instead of DriverError.NOT_SET to use the correct constructor
+ null);
+ }
+ subSecondNanos = offsetTimeValue.getNano();
+
+ // writeScaledTemporal() expects subSecondNanos in 9 digits precssion
+ // but getNano() used in OffsetDateTime returns precession based on nanoseconds read from csv
+ // padding zeros to match the expectation of writeScaledTemporal()
+ int padding = 9 - String.valueOf(subSecondNanos).length();
+ while (padding > 0) {
+ subSecondNanos = subSecondNanos * 10;
+ padding--;
+ }
+
+ // For TIME_WITH_TIMEZONE, use UTC for the calendar that will hold the value
+ timeZone = UTC.timeZone;
+
+ // Using TDS.BASE_YEAR_1900, based on SQL server behavious
+ // If date only contains a time part, the return value is 1900, the base year.
+ // https://msdn.microsoft.com/en-us/library/ms186313.aspx
+ // In Timestamp format, leading zeros for the fields can be omitted.
+ String offsetTimeStr = TDS.BASE_YEAR_1900 + "-01-01" + ' ' + offsetTimeValue.getHour() + ':' + offsetTimeValue.getMinute() + ':'
+ + offsetTimeValue.getSecond();
+ utcMillis = Timestamp.valueOf(offsetTimeStr).getTime();
+
+ calendar = initializeCalender(timeZone);
+ calendar.setTimeInMillis(utcMillis);
+
+ int minuteAdjustment = (TimeZone.getDefault().getRawOffset()) / (60 * 1000);
+ // check if date is in day light savings and add daylight saving minutes to Local timezone(in minutes)
+ if (TimeZone.getDefault().inDaylightTime(calendar.getTime()))
+ minuteAdjustment += ((TimeZone.getDefault().getDSTSavings()) / (60 * 1000));
+ // If the local time is negative then positive minutesOffset must be subtracted from calender
+ minuteAdjustment += (minuteAdjustment < 0) ? (minutesOffset * (-1)) : minutesOffset;
+ calendar.add(Calendar.MINUTE, minuteAdjustment);
+
+ writeScaledTemporal(calendar, subSecondNanos, scale, SSType.DATETIMEOFFSET);
+ writeShort((short) minutesOffset);
+ }
+
+ void writeLong(long value) throws SQLServerException {
+ if (stagingBuffer.remaining() >= 8) {
+ stagingBuffer.putLong(value);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.putLong(value);
+ else
+ logBuffer.position(logBuffer.position() + 8);
+ }
+ }
+ else {
+ valueBytes[0] = (byte) ((value >> 0) & 0xFF);
+ valueBytes[1] = (byte) ((value >> 8) & 0xFF);
+ valueBytes[2] = (byte) ((value >> 16) & 0xFF);
+ valueBytes[3] = (byte) ((value >> 24) & 0xFF);
+ valueBytes[4] = (byte) ((value >> 32) & 0xFF);
+ valueBytes[5] = (byte) ((value >> 40) & 0xFF);
+ valueBytes[6] = (byte) ((value >> 48) & 0xFF);
+ valueBytes[7] = (byte) ((value >> 56) & 0xFF);
+ writeWrappedBytes(valueBytes, 8);
+ }
+ }
+
+ void writeBytes(byte[] value) throws SQLServerException {
+ writeBytes(value, 0, value.length);
+ }
+
+ void writeBytes(byte[] value,
+ int offset,
+ int length) throws SQLServerException {
+ assert length <= value.length;
+
+ int bytesWritten = 0;
+ int bytesToWrite;
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(toString() + " Writing " + length + " bytes");
+
+ while ((bytesToWrite = length - bytesWritten) > 0) {
+ if (0 == stagingBuffer.remaining())
+ writePacket(TDS.STATUS_NORMAL);
+
+ if (bytesToWrite > stagingBuffer.remaining())
+ bytesToWrite = stagingBuffer.remaining();
+
+ stagingBuffer.put(value, offset + bytesWritten, bytesToWrite);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.put(value, offset + bytesWritten, bytesToWrite);
+ else
+ logBuffer.position(logBuffer.position() + bytesToWrite);
+ }
+
+ bytesWritten += bytesToWrite;
+ }
+ }
+
+ void writeWrappedBytes(byte value[],
+ int valueLength) throws SQLServerException {
+ // This function should only be used to write a value that is longer than
+ // what remains in the current staging buffer. However, the value must
+ // be short enough to fit in an empty buffer.
+ assert valueLength <= value.length;
+ assert stagingBuffer.remaining() < valueLength;
+ assert valueLength <= stagingBuffer.capacity();
+
+ // Fill any remaining space in the staging buffer
+ int remaining = stagingBuffer.remaining();
+ if (remaining > 0) {
+ stagingBuffer.put(value, 0, remaining);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.put(value, 0, remaining);
+ else
+ logBuffer.position(logBuffer.position() + remaining);
+ }
+ }
+
+ writePacket(TDS.STATUS_NORMAL);
+
+ // After swapping, the staging buffer should once again be empty, so the
+ // remainder of the value can be written to it.
+ stagingBuffer.put(value, remaining, valueLength - remaining);
+ if (tdsChannel.isLoggingPackets()) {
+ if (dataIsLoggable)
+ logBuffer.put(value, remaining, valueLength - remaining);
+ else
+ logBuffer.position(logBuffer.position() + remaining);
+ }
+ }
+
+ void writeString(String value) throws SQLServerException {
+ int charsCopied = 0;
+ int length = value.length();
+ while (charsCopied < length) {
+ int bytesToCopy = 2 * (length - charsCopied);
+
+ if (bytesToCopy > valueBytes.length)
+ bytesToCopy = valueBytes.length;
+
+ int bytesCopied = 0;
+ while (bytesCopied < bytesToCopy) {
+ char ch = value.charAt(charsCopied++);
+ valueBytes[bytesCopied++] = (byte) ((ch >> 0) & 0xFF);
+ valueBytes[bytesCopied++] = (byte) ((ch >> 8) & 0xFF);
+ }
+
+ writeBytes(valueBytes, 0, bytesCopied);
+ }
+ }
+
+ void writeStream(InputStream inputStream,
+ long advertisedLength,
+ boolean writeChunkSizes) throws SQLServerException {
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0;
+
+ long actualLength = 0;
+ final byte[] streamByteBuffer = new byte[4 * currentPacketSize];
+ int bytesRead = 0;
+ int bytesToWrite;
+ do {
+ // Read in next chunk
+ for (bytesToWrite = 0; -1 != bytesRead && bytesToWrite < streamByteBuffer.length; bytesToWrite += bytesRead) {
+ try {
+ bytesRead = inputStream.read(streamByteBuffer, bytesToWrite, streamByteBuffer.length - bytesToWrite);
+ }
+ catch (IOException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {e.toString()};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
+ }
+
+ if (-1 == bytesRead)
+ break;
+
+ // Check for invalid bytesRead returned from InputStream.read
+ if (bytesRead < 0 || bytesRead > streamByteBuffer.length - bytesToWrite) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {SQLServerException.getErrString("R_streamReadReturnedInvalidValue")};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
+ }
+ }
+
+ // Write it out
+ if (writeChunkSizes)
+ writeInt(bytesToWrite);
+
+ writeBytes(streamByteBuffer, 0, bytesToWrite);
+ actualLength += bytesToWrite;
+ }
+ while (-1 != bytesRead || bytesToWrite > 0);
+
+ // If we were given an input stream length that we had to match and
+ // the actual stream length did not match then cancel the request.
+ if (DataTypes.UNKNOWN_STREAM_LENGTH != advertisedLength && actualLength != advertisedLength) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
+ Object[] msgArgs = {Long.valueOf(advertisedLength), Long.valueOf(actualLength)};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET);
+ }
+ }
+
+ /*
+ * Adding another function for writing non-unicode reader instead of re-factoring the writeReader() for performance efficiency. As this method
+ * will only be used in bulk copy, it needs to be efficient. Note: Any changes in algorithm/logic should propagate to both writeReader() and
+ * writeNonUnicodeReader().
+ */
+
+ void writeNonUnicodeReader(Reader reader,
+ long advertisedLength,
+ boolean isDestBinary,
+ Charset charSet) throws SQLServerException {
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0;
+
+ long actualLength = 0;
+ char[] streamCharBuffer = new char[currentPacketSize];
+ // The unicode version, writeReader() allocates a byte buffer that is 4 times the currentPacketSize, not sure why.
+ byte[] streamByteBuffer = new byte[currentPacketSize];
+ int charsRead = 0;
+ int charsToWrite;
+ int bytesToWrite;
+ String streamString;
+
+ do {
+ // Read in next chunk
+ for (charsToWrite = 0; -1 != charsRead && charsToWrite < streamCharBuffer.length; charsToWrite += charsRead) {
+ try {
+ charsRead = reader.read(streamCharBuffer, charsToWrite, streamCharBuffer.length - charsToWrite);
+ }
+ catch (IOException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {e.toString()};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
+ }
+
+ if (-1 == charsRead)
+ break;
+
+ // Check for invalid bytesRead returned from Reader.read
+ if (charsRead < 0 || charsRead > streamCharBuffer.length - charsToWrite) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {SQLServerException.getErrString("R_streamReadReturnedInvalidValue")};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
+ }
+ }
+
+ if (!isDestBinary) {
+ // Write it out
+ // This also writes the PLP_TERMINATOR token after all the data in the the stream are sent.
+ // The Do-While loop goes on one more time as charsToWrite is greater than 0 for the last chunk, and
+ // in this last round the only thing that is written is an int value of 0, which is the PLP Terminator token(0x00000000).
+ writeInt(charsToWrite);
+
+ for (int charsCopied = 0; charsCopied < charsToWrite; ++charsCopied) {
+ if (null == charSet) {
+ streamByteBuffer[charsCopied] = (byte) (streamCharBuffer[charsCopied] & 0xFF);
+ }
+ else {
+ // encoding as per collation
+ streamByteBuffer[charsCopied] = new String(streamCharBuffer[charsCopied] + "").getBytes(charSet)[0];
+ }
+ }
+ writeBytes(streamByteBuffer, 0, charsToWrite);
+ }
+ else {
+ bytesToWrite = charsToWrite;
+ if (0 != charsToWrite)
+ bytesToWrite = charsToWrite / 2;
+
+ streamString = new String(streamCharBuffer);
+ byte[] bytes = ParameterUtils.HexToBin(streamString.trim());
+ writeInt(bytesToWrite);
+ writeBytes(bytes, 0, bytesToWrite);
+ }
+ actualLength += charsToWrite;
+ }
+ while (-1 != charsRead || charsToWrite > 0);
+
+ // If we were given an input stream length that we had to match and
+ // the actual stream length did not match then cancel the request.
+ if (DataTypes.UNKNOWN_STREAM_LENGTH != advertisedLength && actualLength != advertisedLength) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
+ Object[] msgArgs = {Long.valueOf(advertisedLength), Long.valueOf(actualLength)};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET);
+ }
+ }
+
+ /*
+ * Note: There is another method with same code logic for non unicode reader, writeNonUnicodeReader(), implemented for performance efficiency. Any
+ * changes in algorithm/logic should propagate to both writeReader() and writeNonUnicodeReader().
+ */
+ void writeReader(Reader reader,
+ long advertisedLength,
+ boolean writeChunkSizes) throws SQLServerException {
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == advertisedLength || advertisedLength >= 0;
+
+ long actualLength = 0;
+ char[] streamCharBuffer = new char[2 * currentPacketSize];
+ byte[] streamByteBuffer = new byte[4 * currentPacketSize];
+ int charsRead = 0;
+ int charsToWrite;
+ do {
+ // Read in next chunk
+ for (charsToWrite = 0; -1 != charsRead && charsToWrite < streamCharBuffer.length; charsToWrite += charsRead) {
+ try {
+ charsRead = reader.read(streamCharBuffer, charsToWrite, streamCharBuffer.length - charsToWrite);
+ }
+ catch (IOException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {e.toString()};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
+ }
+
+ if (-1 == charsRead)
+ break;
+
+ // Check for invalid bytesRead returned from Reader.read
+ if (charsRead < 0 || charsRead > streamCharBuffer.length - charsToWrite) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {SQLServerException.getErrString("R_streamReadReturnedInvalidValue")};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET);
+ }
+ }
+
+ // Write it out
+ if (writeChunkSizes)
+ writeInt(2 * charsToWrite);
+
+ // Convert from Unicode characters to bytes
+ //
+ // Note: The following inlined code is much faster than the equivalent
+ // call to (new String(streamCharBuffer)).getBytes("UTF-16LE") because it
+ // saves a conversion to String and use of Charset in that conversion.
+ for (int charsCopied = 0; charsCopied < charsToWrite; ++charsCopied) {
+ streamByteBuffer[2 * charsCopied] = (byte) ((streamCharBuffer[charsCopied] >> 0) & 0xFF);
+ streamByteBuffer[2 * charsCopied + 1] = (byte) ((streamCharBuffer[charsCopied] >> 8) & 0xFF);
+ }
+
+ writeBytes(streamByteBuffer, 0, 2 * charsToWrite);
+ actualLength += charsToWrite;
+ }
+ while (-1 != charsRead || charsToWrite > 0);
+
+ // If we were given an input stream length that we had to match and
+ // the actual stream length did not match then cancel the request.
+ if (DataTypes.UNKNOWN_STREAM_LENGTH != advertisedLength && actualLength != advertisedLength) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
+ Object[] msgArgs = {Long.valueOf(advertisedLength), Long.valueOf(actualLength)};
+ error(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET);
+ }
+ }
+
+ GregorianCalendar initializeCalender(TimeZone timeZone) {
+ GregorianCalendar calendar = null;
+
+ // Create the calendar that will hold the value. For DateTimeOffset values, the calendar's
+ // time zone is UTC. For other values, the calendar's time zone is a local time zone.
+ calendar = new GregorianCalendar(timeZone, Locale.US);
+
+ // Set the calendar lenient to allow setting the DAY_OF_YEAR and MILLISECOND fields
+ // to roll other fields to their correct values.
+ calendar.setLenient(true);
+
+ // Clear the calendar of any existing state. The state of a new Calendar object always
+ // reflects the current date, time, DST offset, etc.
+ calendar.clear();
+
+ return calendar;
+ }
+
+ final void error(String reason,
+ SQLState sqlState,
+ DriverError driverError) throws SQLServerException {
+ assert null != command;
+ command.interrupt(reason);
+ throw new SQLServerException(reason, sqlState, driverError, null);
+ }
+
+ /**
+ * Sends an attention signal to the server, if necessary, to tell it to stop processing the current command on this connection.
+ *
+ * If no packets of the command's request have yet been sent to the server, then no attention signal needs to be sent. The interrupt will be
+ * handled entirely by the driver.
+ *
+ * This method does not need synchronization as it does not manipulate interrupt state and writing is guaranteed to occur only from one thread at
+ * a time.
+ */
+ final boolean sendAttention() throws SQLServerException {
+ // If any request packets were already written to the server then send an
+ // attention signal to the server to tell it to ignore the request or
+ // cancel its execution.
+ if (packetNum > 0) {
+ // Ideally, we would want to add the following assert here.
+ // But to add that the variable isEOMSent would have to be made
+ // volatile as this piece of code would be reached from multiple
+ // threads. So, not doing it to avoid perf hit. Note that
+ // isEOMSent would be updated in writePacket everytime an EOM is sent
+ // assert isEOMSent;
+
+ if (logger.isLoggable(Level.FINE))
+ logger.fine(this + ": sending attention...");
+
+ ++tdsChannel.numMsgsSent;
+
+ startMessage(command, TDS.PKT_CANCEL_REQ);
+ endMessage();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private void writePacket(int tdsMessageStatus) throws SQLServerException {
+ final boolean atEOM = (TDS.STATUS_BIT_EOM == (TDS.STATUS_BIT_EOM & tdsMessageStatus));
+ final boolean isCancelled = ((TDS.PKT_CANCEL_REQ == tdsMessageType)
+ || ((tdsMessageStatus & TDS.STATUS_BIT_ATTENTION) == TDS.STATUS_BIT_ATTENTION));
+ // Before writing each packet to the channel, check if an interrupt has occurred.
+ if (null != command && (!isCancelled))
+ command.checkForInterrupt();
+
+ writePacketHeader(tdsMessageStatus | sendResetConnection);
+ sendResetConnection = 0;
+
+ flush(atEOM);
+
+ // If this is the last packet then flush the remainder of the request
+ // through the socket. The first flush() call ensured that data currently
+ // waiting in the socket buffer was sent, flipped the buffers, and started
+ // sending data from the staging buffer (flipped to be the new socket buffer).
+ // This flush() call ensures that all remaining data in the socket buffer is sent.
+ if (atEOM) {
+ flush(atEOM);
+ isEOMSent = true;
+ ++tdsChannel.numMsgsSent;
+ }
+
+ // If we just sent the first login request packet and SSL encryption was enabled
+ // for login only, then disable SSL now.
+ if (TDS.PKT_LOGON70 == tdsMessageType && 1 == packetNum && TDS.ENCRYPT_OFF == con.getNegotiatedEncryptionLevel()) {
+ tdsChannel.disableSSL();
+ }
+
+ // Notify the currently associated command (if any) that we have written the last
+ // of the response packets to the channel.
+ if (null != command && (!isCancelled) && atEOM)
+ command.onRequestComplete();
+ }
+
+ private void writePacketHeader(int tdsMessageStatus) {
+ int tdsMessageLength = stagingBuffer.position();
+ ++packetNum;
+
+ // Write the TDS packet header back at the start of the staging buffer
+ stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_TYPE, tdsMessageType);
+ stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_STATUS, (byte) tdsMessageStatus);
+ stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH, (byte) ((tdsMessageLength >> 8) & 0xFF)); // Note: message length is 16 bits,
+ stagingBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH + 1, (byte) ((tdsMessageLength >> 0) & 0xFF)); // written BIG ENDIAN
+ stagingBuffer.put(TDS.PACKET_HEADER_SPID, (byte) ((tdsChannel.getSPID() >> 8) & 0xFF)); // Note: SPID is 16 bits,
+ stagingBuffer.put(TDS.PACKET_HEADER_SPID + 1, (byte) ((tdsChannel.getSPID() >> 0) & 0xFF)); // written BIG ENDIAN
+ stagingBuffer.put(TDS.PACKET_HEADER_SEQUENCE_NUM, (byte) (packetNum % 256));
+ stagingBuffer.put(TDS.PACKET_HEADER_WINDOW, (byte) 0); // Window (Reserved/Not used)
+
+ // Write the header to the log buffer too if logging.
+ if (tdsChannel.isLoggingPackets()) {
+ logBuffer.put(TDS.PACKET_HEADER_MESSAGE_TYPE, tdsMessageType);
+ logBuffer.put(TDS.PACKET_HEADER_MESSAGE_STATUS, (byte) tdsMessageStatus);
+ logBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH, (byte) ((tdsMessageLength >> 8) & 0xFF)); // Note: message length is 16 bits,
+ logBuffer.put(TDS.PACKET_HEADER_MESSAGE_LENGTH + 1, (byte) ((tdsMessageLength >> 0) & 0xFF)); // written BIG ENDIAN
+ logBuffer.put(TDS.PACKET_HEADER_SPID, (byte) ((tdsChannel.getSPID() >> 8) & 0xFF)); // Note: SPID is 16 bits,
+ logBuffer.put(TDS.PACKET_HEADER_SPID + 1, (byte) ((tdsChannel.getSPID() >> 0) & 0xFF)); // written BIG ENDIAN
+ logBuffer.put(TDS.PACKET_HEADER_SEQUENCE_NUM, (byte) (packetNum % 256));
+ logBuffer.put(TDS.PACKET_HEADER_WINDOW, (byte) 0); // Window (Reserved/Not used);
+ }
+ }
+
+ void flush(boolean atEOM) throws SQLServerException {
+ // First, flush any data left in the socket buffer.
+ tdsChannel.write(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining());
+ socketBuffer.position(socketBuffer.limit());
+
+ // If there is data in the staging buffer that needs to be written
+ // to the socket, the socket buffer is now empty, so swap buffers
+ // and start writing data from the staging buffer.
+ if (stagingBuffer.position() >= TDS_PACKET_HEADER_SIZE) {
+ // Swap the packet buffers ...
+ ByteBuffer swapBuffer = stagingBuffer;
+ stagingBuffer = socketBuffer;
+ socketBuffer = swapBuffer;
+
+ // ... and prepare to send data from the from the new socket
+ // buffer (the old staging buffer).
+ //
+ // We need to use flip() rather than rewind() here so that
+ // the socket buffer's limit is properly set for the last
+ // packet, which may be shorter than the other packets.
+ socketBuffer.flip();
+ stagingBuffer.clear();
+
+ // If we are logging TDS packets then log the packet we're about
+ // to send over the wire now.
+ if (tdsChannel.isLoggingPackets()) {
+ tdsChannel.logPacket(logBuffer.array(), 0, socketBuffer.limit(),
+ this.toString() + " sending packet (" + socketBuffer.limit() + " bytes)");
+ }
+
+ // Prepare for the next packet
+ if (!atEOM)
+ preparePacket();
+
+ // Finally, start sending data from the new socket buffer.
+ tdsChannel.write(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining());
+ socketBuffer.position(socketBuffer.limit());
+ }
+ }
+
+ // Composite write operations
+
+ /**
+ * Write out elements common to all RPC values.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param bOut
+ * boolean true if the value that follows is being registered as an ouput parameter
+ * @param tdsType
+ * TDS type of the value that follows
+ */
+ void writeRPCNameValType(String sName,
+ boolean bOut,
+ TDSType tdsType) throws SQLServerException {
+ int nNameLen = 0;
+
+ if (null != sName)
+ nNameLen = sName.length() + 1; // The @ prefix is required for the param
+
+ writeByte((byte) nNameLen); // param name len
+ if (nNameLen > 0) {
+ writeChar('@');
+ writeString(sName);
+ }
+
+ if (null != cryptoMeta)
+ writeByte((byte) (bOut ? 1 | TDS.AE_METADATA : 0 | TDS.AE_METADATA)); // status
+ else
+ writeByte((byte) (bOut ? 1 : 0)); // status
+ writeByte(tdsType.byteValue()); // type
+ }
+
+ /**
+ * Append a boolean value in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param booleanValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCBit(String sName,
+ Boolean booleanValue,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.BITN);
+ writeByte((byte) 1); // max length of datatype
+ if (null == booleanValue) {
+ writeByte((byte) 0); // len of data bytes
+ }
+ else {
+ writeByte((byte) 1); // length of datatype
+ writeByte((byte) (booleanValue.booleanValue() ? 1 : 0));
+ }
+ }
+
+ /**
+ * Append a short value in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param shortValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCByte(String sName,
+ Byte byteValue,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.INTN);
+ writeByte((byte) 1); // max length of datatype
+ if (null == byteValue) {
+ writeByte((byte) 0); // len of data bytes
+ }
+ else {
+ writeByte((byte) 1); // length of datatype
+ writeByte(byteValue.byteValue());
+ }
+ }
+
+ /**
+ * Append a short value in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param shortValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCShort(String sName,
+ Short shortValue,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.INTN);
+ writeByte((byte) 2); // max length of datatype
+ if (null == shortValue) {
+ writeByte((byte) 0); // len of data bytes
+ }
+ else {
+ writeByte((byte) 2); // length of datatype
+ writeShort(shortValue.shortValue());
+ }
+ }
+
+ /**
+ * Append an int value in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param intValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCInt(String sName,
+ Integer intValue,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.INTN);
+ writeByte((byte) 4); // max length of datatype
+ if (null == intValue) {
+ writeByte((byte) 0); // len of data bytes
+ }
+ else {
+ writeByte((byte) 4); // length of datatype
+ writeInt(intValue.intValue());
+ }
+ }
+
+ /**
+ * Append a long value in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param longValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCLong(String sName,
+ Long longValue,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.INTN);
+ writeByte((byte) 8); // max length of datatype
+ if (null == longValue) {
+ writeByte((byte) 0); // len of data bytes
+ }
+ else {
+ writeByte((byte) 8); // length of datatype
+ writeLong(longValue.longValue());
+ }
+ }
+
+ /**
+ * Append a real value in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param floatValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCReal(String sName,
+ Float floatValue,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.FLOATN);
+
+ // Data and length
+ if (null == floatValue) {
+ writeByte((byte) 4); // max length
+ writeByte((byte) 0); // actual length (0 == null)
+ }
+ else {
+ writeByte((byte) 4); // max length
+ writeByte((byte) 4); // actual length
+ writeInt(Float.floatToRawIntBits(floatValue.floatValue()));
+ }
+ }
+
+ /**
+ * Append a double value in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param doubleValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCDouble(String sName,
+ Double doubleValue,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.FLOATN);
+
+ int l = 8;
+ writeByte((byte) l); // max length of datatype
+
+ // Data and length
+ if (null == doubleValue) {
+ writeByte((byte) 0); // len of data bytes
+ }
+ else {
+ writeByte((byte) l); // len of data bytes
+ long bits = Double.doubleToLongBits(doubleValue.doubleValue());
+ long mask = 0xFF;
+ int nShift = 0;
+ for (int i = 0; i < 8; i++) {
+ writeByte((byte) ((bits & mask) >> nShift));
+ nShift += 8;
+ mask = mask << 8;
+ }
+ }
+ }
+
+ /**
+ * Append a big decimal in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param bdValue
+ * the data value
+ * @param nScale
+ * the desired scale
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ */
+ void writeRPCBigDecimal(String sName,
+ BigDecimal bdValue,
+ int nScale,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.DECIMALN);
+ writeByte((byte) 0x11); // maximum length
+ writeByte((byte) SQLServerConnection.maxDecimalPrecision); // precision
+
+ byte[] valueBytes = DDC.convertBigDecimalToBytes(bdValue, nScale);
+ writeBytes(valueBytes, 0, valueBytes.length);
+ }
+
+ /**
+ * Appends a standard v*max header for RPC parameter transmission.
+ *
+ * @param headerLength
+ * the total length of the PLP data block.
+ * @param isNull
+ * true if the value is NULL.
+ * @param collation
+ * The SQL collation associated with the value that follows the v*max header. Null for non-textual types.
+ */
+ void writeVMaxHeader(long headerLength,
+ boolean isNull,
+ SQLCollation collation) throws SQLServerException {
+ // Send v*max length indicator 0xFFFF.
+ writeShort((short) 0xFFFF);
+
+ // Send collation if requested.
+ if (null != collation)
+ collation.writeCollation(this);
+
+ // Handle null here and return, we're done here if it's null.
+ if (isNull) {
+ // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
+ writeLong(0xFFFFFFFFFFFFFFFFL);
+ }
+ else if (DataTypes.UNKNOWN_STREAM_LENGTH == headerLength) {
+ // Append v*max length.
+ // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
+ writeLong(0xFFFFFFFFFFFFFFFEL);
+
+ // NOTE: Don't send the first chunk length, this will be calculated by caller.
+ }
+ else {
+ // For v*max types with known length, length is
+ // We're sending same total length as chunk length (as we're sending 1 chunk).
+ writeLong(headerLength);
+ }
+ }
+
+ /**
+ * Utility for internal writeRPCString calls
+ */
+ void writeRPCStringUnicode(String sValue) throws SQLServerException {
+ writeRPCStringUnicode(null, sValue, false, null);
+ }
+
+ /**
+ * Writes a string value as Unicode for RPC
+ *
+ * @param sName
+ * the optional parameter name
+ * @param sValue
+ * the data value
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ * @param collation
+ * the collation of the data value
+ */
+ void writeRPCStringUnicode(String sName,
+ String sValue,
+ boolean bOut,
+ SQLCollation collation) throws SQLServerException {
+ boolean bValueNull = (sValue == null);
+ int nValueLen = bValueNull ? 0 : (2 * sValue.length());
+ boolean isShortValue = nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
+
+ // Textual RPC requires a collation. If none is provided, as is the case when
+ // the SSType is non-textual, then use the database collation by default.
+ if (null == collation)
+ collation = con.getDatabaseCollation();
+
+ // Use PLP encoding on Yukon and later with long values and OUT parameters
+ boolean usePLP = (!isShortValue || bOut);
+ if (usePLP) {
+ writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);
+
+ // Handle Yukon v*max type header here.
+ writeVMaxHeader(nValueLen, // Length
+ bValueNull, // Is null?
+ collation);
+
+ // Send the data.
+ if (!bValueNull) {
+ if (nValueLen > 0) {
+ writeInt(nValueLen);
+ writeString(sValue);
+ }
+
+ // Send the terminator PLP chunk.
+ writeInt(0);
+ }
+ }
+ else // non-PLP type
+ {
+ // Write maximum length of data
+ if (isShortValue) {
+ writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);
+ writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ }
+ else {
+ writeRPCNameValType(sName, bOut, TDSType.NTEXT);
+ writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
+ }
+
+ collation.writeCollation(this);
+
+ // Data and length
+ if (bValueNull) {
+ writeShort((short) -1); // actual len
+ }
+ else {
+ // Write actual length of data
+ if (isShortValue)
+ writeShort((short) nValueLen);
+ else
+ writeInt(nValueLen);
+
+ // If length is zero, we're done.
+ if (0 != nValueLen)
+ writeString(sValue); // data
+ }
+ }
+ }
+
+ void writeTVP(TVP value) throws SQLServerException {
+ if (!value.isNull()) {
+ writeByte((byte) 0); // status
+ }
+ else {
+ // Default TVP
+ writeByte((byte) TDS.TVP_STATUS_DEFAULT); // default TVP
+ }
+
+ writeByte((byte) TDS.TDS_TVP);
+
+ /*
+ * TVP_TYPENAME = DbName OwningSchema TypeName
+ */
+ // Database where TVP type resides
+ if (null != value.getDbNameTVP()) {
+ writeByte((byte) value.getDbNameTVP().length());
+ writeString(value.getDbNameTVP());
+ }
+ else
+ writeByte((byte) 0x00); // empty DB name
+
+ // Schema where TVP type resides
+ if (null != value.getOwningSchemaNameTVP()) {
+ writeByte((byte) value.getOwningSchemaNameTVP().length());
+ writeString(value.getOwningSchemaNameTVP());
+ }
+ else
+ writeByte((byte) 0x00); // empty Schema name
+
+ // TVP type name
+ if (null != value.getTVPName()) {
+ writeByte((byte) value.getTVPName().length());
+ writeString(value.getTVPName());
+ }
+ else
+ writeByte((byte) 0x00); // empty TVP name
+
+ if (!value.isNull()) {
+ writeTVPColumnMetaData(value);
+
+ // optional OrderUnique metadata
+ writeTvpOrderUnique(value);
+ }
+ else {
+ writeShort((short) TDS.TVP_NULL_TOKEN);
+ }
+
+ // TVP_END_TOKEN
+ writeByte((byte) 0x00);
+
+ try {
+ writeTVPRows(value);
+ }
+ catch (NumberFormatException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
+ }
+ catch (ClassCastException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e);
+ }
+ }
+
+ void writeTVPRows(TVP value) throws SQLServerException {
+ boolean isShortValue, isNull;
+ int dataLength;
+
+ if (!value.isNull()) {
+ Map columnMetadata = value.getColumnMetadata();
+ Iterator> columnsIterator;
+
+ while (value.next()) {
+ Object[] rowData = value.getRowData();
+
+ // ROW
+ writeByte((byte) TDS.TVP_ROW);
+ columnsIterator = columnMetadata.entrySet().iterator();
+ int currentColumn = 0;
+ while (columnsIterator.hasNext()) {
+ Map.Entry columnPair = columnsIterator.next();
+
+ // If useServerDefault is set, client MUST NOT emit TvpColumnData for the associated column
+ if (columnPair.getValue().useServerDefault) {
+ currentColumn++;
+ continue;
+ }
+
+ JDBCType jdbcType = JDBCType.of(columnPair.getValue().javaSqlType);
+ String currentColumnStringValue = null;
+
+ Object currentObject = null;
+ if (null != rowData) {
+ // if rowData has value for the current column, retrieve it. If not, current column will stay null.
+ if (rowData.length > currentColumn) {
+ currentObject = rowData[currentColumn];
+ if (null != currentObject) {
+ currentColumnStringValue = String.valueOf(currentObject);
+ }
+ }
+ }
+ switch (jdbcType) {
+ case BIGINT:
+ if (null == currentColumnStringValue)
+ writeByte((byte) 0);
+ else {
+ writeByte((byte) 8);
+ writeLong(Long.valueOf(currentColumnStringValue).longValue());
+ }
+ break;
+
+ case BIT:
+ if (null == currentColumnStringValue)
+ writeByte((byte) 0);
+ else {
+ writeByte((byte) 1);
+ writeByte((byte) (Boolean.valueOf(currentColumnStringValue).booleanValue() ? 1 : 0));
+ }
+ break;
+
+ case INTEGER:
+ if (null == currentColumnStringValue)
+ writeByte((byte) 0);
+ else {
+ writeByte((byte) 4);
+ writeInt(Integer.valueOf(currentColumnStringValue).intValue());
+ }
+ break;
+
+ case SMALLINT:
+ case TINYINT:
+ if (null == currentColumnStringValue)
+ writeByte((byte) 0);
+ else {
+ writeByte((byte) 2); // length of datatype
+ writeShort(Short.valueOf(currentColumnStringValue).shortValue());
+ }
+ break;
+
+ case DECIMAL:
+ case NUMERIC:
+ if (null == currentColumnStringValue)
+ writeByte((byte) 0);
+ else {
+ writeByte((byte) TDSWriter.BIGDECIMAL_MAX_LENGTH); // maximum length
+ BigDecimal bdValue = new BigDecimal(currentColumnStringValue);
+
+ // setScale of all BigDecimal value based on metadata sent
+ bdValue = bdValue.setScale(columnPair.getValue().scale);
+ byte[] valueBytes = DDC.convertBigDecimalToBytes(bdValue, bdValue.scale());
+
+ // 1-byte for sign and 16-byte for integer
+ byte[] byteValue = new byte[17];
+
+ // removing the precision and scale information from the valueBytes array
+ System.arraycopy(valueBytes, 2, byteValue, 0, valueBytes.length - 2);
+ writeBytes(byteValue);
+ }
+ break;
+
+ case DOUBLE:
+ if (null == currentColumnStringValue)
+ writeByte((byte) 0); // len of data bytes
+ else {
+ writeByte((byte) 8); // len of data bytes
+ long bits = Double.doubleToLongBits(Double.valueOf(currentColumnStringValue).doubleValue());
+ long mask = 0xFF;
+ int nShift = 0;
+ for (int i = 0; i < 8; i++) {
+ writeByte((byte) ((bits & mask) >> nShift));
+ nShift += 8;
+ mask = mask << 8;
+ }
+ }
+ break;
+
+ case FLOAT:
+ case REAL:
+ if (null == currentColumnStringValue)
+ writeByte((byte) 0); // actual length (0 == null)
+ else {
+ writeByte((byte) 4); // actual length
+ writeInt(Float.floatToRawIntBits(Float.valueOf(currentColumnStringValue).floatValue()));
+ }
+ break;
+
+ case DATE:
+ case TIME:
+ case TIMESTAMP:
+ case DATETIMEOFFSET:
+ case TIMESTAMP_WITH_TIMEZONE:
+ case TIME_WITH_TIMEZONE:
+ case CHAR:
+ case VARCHAR:
+ case NCHAR:
+ case NVARCHAR:
+ isShortValue = (2 * columnPair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
+ isNull = (null == currentColumnStringValue);
+ dataLength = isNull ? 0 : currentColumnStringValue.length() * 2;
+ if (!isShortValue) {
+ // check null
+ if (isNull)
+ // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
+ writeLong(0xFFFFFFFFFFFFFFFFL);
+ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength)
+ // Append v*max length.
+ // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
+ writeLong(0xFFFFFFFFFFFFFFFEL);
+ else
+ // For v*max types with known length, length is
+ writeLong(dataLength);
+ if (!isNull) {
+ if (dataLength > 0) {
+ writeInt(dataLength);
+ writeString(currentColumnStringValue);
+ }
+ // Send the terminator PLP chunk.
+ writeInt(0);
+ }
+ }
+ else {
+ if (isNull)
+ writeShort((short) -1); // actual len
+ else {
+ writeShort((short) dataLength);
+ writeString(currentColumnStringValue);
+ }
+ }
+ break;
+
+ case BINARY:
+ case VARBINARY:
+ // Handle conversions as done in other types.
+ isShortValue = columnPair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
+ isNull = (null == currentObject);
+ if (currentObject instanceof String)
+ dataLength = isNull ? 0 : (toByteArray(currentObject.toString())).length;
+ else
+ dataLength = isNull ? 0 : ((byte[]) currentObject).length;
+ if (!isShortValue) {
+ // check null
+ if (isNull)
+ // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
+ writeLong(0xFFFFFFFFFFFFFFFFL);
+ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength)
+ // Append v*max length.
+ // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
+ writeLong(0xFFFFFFFFFFFFFFFEL);
+ else
+ // For v*max types with known length, length is
+ writeLong(dataLength);
+ if (!isNull) {
+ if (dataLength > 0) {
+ writeInt(dataLength);
+ if (currentObject instanceof String)
+ writeBytes(toByteArray(currentObject.toString()));
+ else
+ writeBytes((byte[]) currentObject);
+ }
+ // Send the terminator PLP chunk.
+ writeInt(0);
+ }
+ }
+ else {
+ if (isNull)
+ writeShort((short) -1); // actual len
+ else {
+ writeShort((short) dataLength);
+ if (currentObject instanceof String)
+ writeBytes(toByteArray(currentObject.toString()));
+ else
+ writeBytes((byte[]) currentObject);
+ }
+ }
+ break;
+
+ default:
+ assert false : "Unexpected JDBC type " + jdbcType.toString();
+ }
+ currentColumn++;
+ }
+ }
+ }
+ // TVP_END_TOKEN
+ writeByte((byte) 0x00);
+ }
+
+ private static byte[] toByteArray(String s) {
+ return DatatypeConverter.parseHexBinary(s);
+ }
+
+ void writeTVPColumnMetaData(TVP value) throws SQLServerException {
+ boolean isShortValue;
+
+ // TVP_COLMETADATA
+ writeShort((short) value.getTVPColumnCount());
+
+ Map columnMetadata = value.getColumnMetadata();
+ Iterator> columnsIterator = columnMetadata.entrySet().iterator();
+ /*
+ * TypeColumnMetaData = UserType Flags TYPE_INFO ColName ;
+ */
+
+ while (columnsIterator.hasNext()) {
+ Map.Entry pair = columnsIterator.next();
+ JDBCType jdbcType = JDBCType.of(pair.getValue().javaSqlType);
+ boolean useServerDefault = pair.getValue().useServerDefault;
+ // ULONG ; UserType of column
+ // The value will be 0x0000 with the exceptions of TIMESTAMP (0x0050) and alias types (greater than 0x00FF).
+ writeInt(0);
+ /*
+ * Flags = fNullable ; Column is nullable - %x01 fCaseSen -- Ignored ; usUpdateable -- Ignored ; fIdentity ; Column is identity column -
+ * %x10 fComputed ; Column is computed - %x20 usReservedODBC -- Ignored ; fFixedLenCLRType-- Ignored ; fDefault ; Column is default value
+ * - %x200 usReserved -- Ignored ;
+ */
+
+ short flags = TDS.FLAG_NULLABLE;
+ if (useServerDefault) {
+ flags |= TDS.FLAG_TVP_DEFAULT_COLUMN;
+ }
+ writeShort(flags);
+
+ // Type info
+ switch (jdbcType) {
+ case BIGINT:
+ writeByte(TDSType.INTN.byteValue());
+ writeByte((byte) 8); // max length of datatype
+ break;
+ case BIT:
+ writeByte(TDSType.BITN.byteValue());
+ writeByte((byte) 1); // max length of datatype
+ break;
+ case INTEGER:
+ writeByte(TDSType.INTN.byteValue());
+ writeByte((byte) 4); // max length of datatype
+ break;
+ case SMALLINT:
+ case TINYINT:
+ writeByte(TDSType.INTN.byteValue());
+ writeByte((byte) 2); // max length of datatype
+ break;
+
+ case DECIMAL:
+ case NUMERIC:
+ writeByte(TDSType.NUMERICN.byteValue());
+ writeByte((byte) 0x11); // maximum length
+ writeByte((byte) pair.getValue().precision);
+ writeByte((byte) pair.getValue().scale);
+ break;
+
+ case DOUBLE:
+ writeByte(TDSType.FLOATN.byteValue());
+ writeByte((byte) 8); // max length of datatype
+ break;
+
+ case FLOAT:
+ case REAL:
+ writeByte(TDSType.FLOATN.byteValue());
+ writeByte((byte) 4); // max length of datatype
+ break;
+
+ case DATE:
+ case TIME:
+ case TIMESTAMP:
+ case DATETIMEOFFSET:
+ case TIMESTAMP_WITH_TIMEZONE:
+ case TIME_WITH_TIMEZONE:
+ case CHAR:
+ case VARCHAR:
+ case NCHAR:
+ case NVARCHAR:
+ writeByte(TDSType.NVARCHAR.byteValue());
+ isShortValue = (2 * pair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
+ // Use PLP encoding on Yukon and later with long values
+ if (!isShortValue) // PLP
+ {
+ // Handle Yukon v*max type header here.
+ writeShort((short) 0xFFFF);
+ con.getDatabaseCollation().writeCollation(this);
+ }
+ else // non PLP
+ {
+ writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ con.getDatabaseCollation().writeCollation(this);
+ }
+
+ break;
+
+ case BINARY:
+ case VARBINARY:
+ writeByte(TDSType.BIGVARBINARY.byteValue());
+ isShortValue = pair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
+ // Use PLP encoding on Yukon and later with long values
+ if (!isShortValue) // PLP
+ // Handle Yukon v*max type header here.
+ writeShort((short) 0xFFFF);
+ else // non PLP
+ writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ break;
+
+ default:
+ assert false : "Unexpected JDBC type " + jdbcType.toString();
+ }
+ // Column name - must be null (from TDS - TVP_COLMETADATA)
+ writeByte((byte) 0x00);
+
+ // [TVP_ORDER_UNIQUE]
+ // [TVP_COLUMN_ORDERING]
+ }
+ }
+
+ void writeTvpOrderUnique(TVP value) throws SQLServerException {
+ /*
+ * TVP_ORDER_UNIQUE = TVP_ORDER_UNIQUE_TOKEN (Count (ColNum OrderUniqueFlags))
+ */
+
+ Map columnMetadata = value.getColumnMetadata();
+ Iterator> columnsIterator = columnMetadata.entrySet().iterator();
+ LinkedList columnList = new LinkedList();
+
+ while (columnsIterator.hasNext()) {
+ byte flags = 0;
+ Map.Entry pair = columnsIterator.next();
+ SQLServerMetaData metaData = pair.getValue();
+
+ if (SQLServerSortOrder.Ascending == metaData.sortOrder)
+ flags = TDS.TVP_ORDERASC_FLAG;
+ else if (SQLServerSortOrder.Descending == metaData.sortOrder)
+ flags = TDS.TVP_ORDERDESC_FLAG;
+ if (metaData.isUniqueKey)
+ flags |= TDS.TVP_UNIQUE_FLAG;
+
+ // Remember this column if any flags were set
+ if (0 != flags)
+ columnList.add(new TdsOrderUnique(pair.getKey(), flags));
+ }
+
+ // Write flagged columns
+ if (!columnList.isEmpty()) {
+ writeByte((byte) TDS.TVP_ORDER_UNIQUE_TOKEN);
+ writeShort((short) columnList.size());
+ for (TdsOrderUnique column : columnList) {
+ writeShort((short) (column.columnOrdinal + 1));
+ writeByte(column.flags);
+ }
+ }
+ }
+
+ private class TdsOrderUnique {
+ int columnOrdinal;
+ byte flags;
+
+ TdsOrderUnique(int ordinal,
+ byte flags) {
+ this.columnOrdinal = ordinal;
+ this.flags = flags;
+ }
+ }
+
+ void setCryptoMetaData(CryptoMetadata cryptoMetaForBulk) {
+ this.cryptoMeta = cryptoMetaForBulk;
+ }
+
+ CryptoMetadata getCryptoMetaData() {
+ return cryptoMeta;
+ }
+
+ void writeEncryptedRPCByteArray(byte bValue[]) throws SQLServerException {
+ boolean bValueNull = (bValue == null);
+ long nValueLen = bValueNull ? 0 : bValue.length;
+ boolean isShortValue = (nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES);
+
+ boolean isPLP = (!isShortValue) && (nValueLen <= DataTypes.MAX_VARTYPE_MAX_BYTES);
+
+ // Handle Shiloh types here.
+ if (isShortValue) {
+ writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ }
+ else if (isPLP) {
+ writeShort((short) DataTypes.SQL_USHORTVARMAXLEN);
+ }
+ else {
+ writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
+ }
+
+ // Data and length
+ if (bValueNull) {
+ writeShort((short) -1); // actual len
+ }
+ else {
+ if (isShortValue) {
+ writeShort((short) nValueLen); // actual len
+ }
+ else if (isPLP) {
+ writeLong(nValueLen); // actual length
+ }
+ else {
+ writeInt((int) nValueLen); // actual len
+ }
+
+ // If length is zero, we're done.
+ if (0 != nValueLen) {
+ if (isPLP) {
+ writeInt((int) nValueLen);
+ }
+ writeBytes(bValue);
+ }
+
+ if (isPLP) {
+ writeInt(0); // PLP_TERMINATOR, 0x00000000
+ }
+ }
+ }
+
+ void writeEncryptedRPCPLP() throws SQLServerException {
+ writeShort((short) DataTypes.SQL_USHORTVARMAXLEN);
+ writeLong((long) 0); // actual length
+ writeInt(0); // PLP_TERMINATOR, 0x00000000
+ }
+
+ void writeCryptoMetaData() throws SQLServerException {
+ writeByte(cryptoMeta.cipherAlgorithmId);
+ writeByte(cryptoMeta.encryptionType.getValue());
+ writeInt(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).databaseId);
+ writeInt(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).cekId);
+ writeInt(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).cekVersion);
+ writeBytes(cryptoMeta.cekTableEntry.getColumnEncryptionKeyValues().get(0).cekMdVersion);
+ writeByte(cryptoMeta.normalizationRuleVersion);
+ }
+
+ void writeRPCByteArray(String sName,
+ byte bValue[],
+ boolean bOut,
+ JDBCType jdbcType,
+ SQLCollation collation) throws SQLServerException {
+ boolean bValueNull = (bValue == null);
+ int nValueLen = bValueNull ? 0 : bValue.length;
+ boolean isShortValue = (nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES);
+
+ // Use PLP encoding on Yukon and later with long values and OUT parameters
+ boolean usePLP = (!isShortValue || bOut);
+
+ TDSType tdsType;
+
+ if (null != cryptoMeta) {
+ // send encrypted data as BIGVARBINARY
+ tdsType = (isShortValue || usePLP) ? TDSType.BIGVARBINARY : TDSType.IMAGE;
+ collation = null;
+ }
+ else
+ switch (jdbcType) {
+ case BINARY:
+ case VARBINARY:
+ case LONGVARBINARY:
+ case BLOB:
+ default:
+ tdsType = (isShortValue || usePLP) ? TDSType.BIGVARBINARY : TDSType.IMAGE;
+ collation = null;
+ break;
+
+ case CHAR:
+ case VARCHAR:
+ case LONGVARCHAR:
+ case CLOB:
+ tdsType = (isShortValue || usePLP) ? TDSType.BIGVARCHAR : TDSType.TEXT;
+ if (null == collation)
+ collation = con.getDatabaseCollation();
+ break;
+
+ case NCHAR:
+ case NVARCHAR:
+ case LONGNVARCHAR:
+ case NCLOB:
+ tdsType = (isShortValue || usePLP) ? TDSType.NVARCHAR : TDSType.NTEXT;
+ if (null == collation)
+ collation = con.getDatabaseCollation();
+ break;
+ }
+
+ writeRPCNameValType(sName, bOut, tdsType);
+
+ if (usePLP) {
+ // Handle Yukon v*max type header here.
+ writeVMaxHeader(nValueLen, bValueNull, collation);
+
+ // Send the data.
+ if (!bValueNull) {
+ if (nValueLen > 0) {
+ writeInt(nValueLen);
+ writeBytes(bValue);
+ }
+
+ // Send the terminator PLP chunk.
+ writeInt(0);
+ }
+ }
+ else // non-PLP type
+ {
+ // Handle Shiloh types here.
+ if (isShortValue) {
+ writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ }
+ else {
+ writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
+ }
+
+ if (null != collation)
+ collation.writeCollation(this);
+
+ // Data and length
+ if (bValueNull) {
+ writeShort((short) -1); // actual len
+ }
+ else {
+ if (isShortValue)
+ writeShort((short) nValueLen); // actual len
+ else
+ writeInt(nValueLen); // actual len
+
+ // If length is zero, we're done.
+ if (0 != nValueLen)
+ writeBytes(bValue);
+ }
+ }
+ }
+
+ /**
+ * Append a timestamp in RPC transmission format as a SQL Server DATETIME data type
+ *
+ * @param sName
+ * the optional parameter name
+ * @param cal
+ * Pure Gregorian calendar containing the timestamp, including its associated time zone
+ * @param subSecondNanos
+ * the sub-second nanoseconds (0 - 999,999,999)
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ *
+ */
+ void writeRPCDateTime(String sName,
+ GregorianCalendar cal,
+ int subSecondNanos,
+ boolean bOut) throws SQLServerException {
+ assert (subSecondNanos >= 0) && (subSecondNanos < Nanos.PER_SECOND) : "Invalid subNanoSeconds value: " + subSecondNanos;
+ assert (cal != null) || (cal == null && subSecondNanos == 0) : "Invalid subNanoSeconds value when calendar is null: " + subSecondNanos;
+
+ writeRPCNameValType(sName, bOut, TDSType.DATETIMEN);
+ writeByte((byte) 8); // max length of datatype
+
+ if (null == cal) {
+ writeByte((byte) 0); // len of data bytes
+ return;
+ }
+
+ writeByte((byte) 8); // len of data bytes
+
+ // We need to extract the Calendar's current date & time in terms
+ // of the number of days since the SQL Base Date (1/1/1900) plus
+ // the number of milliseconds since midnight in the current day.
+ //
+ // We cannot rely on any pre-calculated value for the number of
+ // milliseconds in a day or the number of milliseconds since the
+ // base date to do this because days with DST changes are shorter
+ // or longer than "normal" days.
+ //
+ // ASSUMPTION: We assume we are dealing with a GregorianCalendar here.
+ // If not, we have no basis in which to compare dates. E.g. if we
+ // are dealing with a Chinese Calendar implementation which does not
+ // use the same value for Calendar.YEAR as the GregorianCalendar,
+ // we cannot meaningfully compute a value relative to 1/1/1900.
+
+ // First, figure out how many days there have been since the SQL Base Date.
+ // These are based on SQL Server algorithms
+ int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR), TDS.BASE_YEAR_1900);
+
+ // Next, figure out the number of milliseconds since midnight of the current day.
+ int millisSinceMidnight = (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into the current second
+ 1000 * cal.get(Calendar.SECOND) + // Seconds into the current minute
+ 60 * 1000 * cal.get(Calendar.MINUTE) + // Minutes into the current hour
+ 60 * 60 * 1000 * cal.get(Calendar.HOUR_OF_DAY); // Hours into the current day
+
+ // The last millisecond of the current day is always rounded to the first millisecond
+ // of the next day because DATETIME is only accurate to 1/300th of a second.
+ if (millisSinceMidnight >= 1000 * 60 * 60 * 24 - 1) {
+ ++daysSinceSQLBaseDate;
+ millisSinceMidnight = 0;
+ }
+
+ // Last-ditch verification that the value is in the valid range for the
+ // DATETIMEN TDS data type (1/1/1753 to 12/31/9999). If it's not, then
+ // throw an exception now so that statement execution is safely canceled.
+ // Attempting to put an invalid value on the wire would result in a TDS
+ // exception, which would close the connection.
+ // These are based on SQL Server algorithms
+ if (daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1753, 1, TDS.BASE_YEAR_1900)
+ || daysSinceSQLBaseDate >= DDC.daysSinceBaseDate(10000, 1, TDS.BASE_YEAR_1900)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
+ Object[] msgArgs = {SSType.DATETIME};
+ throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW, DriverError.NOT_SET, null);
+ }
+
+ // And put it all on the wire...
+
+ // Number of days since the SQL Server Base Date (January 1, 1900)
+ writeInt(daysSinceSQLBaseDate);
+
+ // Milliseconds since midnight (at a resolution of three hundredths of a second)
+ writeInt((3 * millisSinceMidnight + 5) / 10);
+ }
+
+ void writeRPCTime(String sName,
+ GregorianCalendar localCalendar,
+ int subSecondNanos,
+ int scale,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.TIMEN);
+ writeByte((byte) scale);
+
+ if (null == localCalendar) {
+ writeByte((byte) 0);
+ return;
+ }
+
+ writeByte((byte) TDS.timeValueLength(scale));
+ writeScaledTemporal(localCalendar, subSecondNanos, scale, SSType.TIME);
+ }
+
+ void writeRPCDate(String sName,
+ GregorianCalendar localCalendar,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.DATEN);
+ if (null == localCalendar) {
+ writeByte((byte) 0);
+ return;
+ }
+
+ writeByte((byte) TDS.DAYS_INTO_CE_LENGTH);
+ writeScaledTemporal(localCalendar, 0, // subsecond nanos (none for a date value)
+ 0, // scale (dates are not scaled)
+ SSType.DATE);
+ }
+
+ void writeEncryptedRPCTime(String sName,
+ GregorianCalendar localCalendar,
+ int subSecondNanos,
+ int scale,
+ boolean bOut) throws SQLServerException {
+ if (con.getSendTimeAsDatetime()) {
+ throw new SQLServerException(SQLServerException.getErrString("R_sendTimeAsDateTimeForAE"), null);
+ }
+ writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
+
+ if (null == localCalendar)
+ writeEncryptedRPCByteArray(null);
+ else
+ writeEncryptedRPCByteArray(writeEncryptedScaledTemporal(localCalendar, subSecondNanos, scale, SSType.TIME, (short) 0));
+
+ writeByte(TDSType.TIMEN.byteValue());
+ writeByte((byte) scale);
+ writeCryptoMetaData();
+ }
+
+ void writeEncryptedRPCDate(String sName,
+ GregorianCalendar localCalendar,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
+
+ if (null == localCalendar)
+ writeEncryptedRPCByteArray(null);
+ else
+ writeEncryptedRPCByteArray(writeEncryptedScaledTemporal(localCalendar, 0, // subsecond nanos (none for a date value)
+ 0, // scale (dates are not scaled)
+ SSType.DATE, (short) 0));
+
+ writeByte(TDSType.DATEN.byteValue());
+ writeCryptoMetaData();
+ }
+
+ void writeEncryptedRPCDateTime(String sName,
+ GregorianCalendar cal,
+ int subSecondNanos,
+ boolean bOut,
+ JDBCType jdbcType) throws SQLServerException {
+ assert (subSecondNanos >= 0) && (subSecondNanos < Nanos.PER_SECOND) : "Invalid subNanoSeconds value: " + subSecondNanos;
+ assert (cal != null) || (cal == null && subSecondNanos == 0) : "Invalid subNanoSeconds value when calendar is null: " + subSecondNanos;
+
+ writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
+
+ if (null == cal)
+ writeEncryptedRPCByteArray(null);
+ else
+ writeEncryptedRPCByteArray(getEncryptedDateTimeAsBytes(cal, subSecondNanos, jdbcType));
+
+ if (JDBCType.SMALLDATETIME == jdbcType) {
+ writeByte(TDSType.DATETIMEN.byteValue());
+ writeByte((byte) 4);
+ }
+ else {
+ writeByte(TDSType.DATETIMEN.byteValue());
+ writeByte((byte) 8);
+ }
+ writeCryptoMetaData();
+ }
+
+ // getEncryptedDateTimeAsBytes is called if jdbcType/ssType is SMALLDATETIME or DATETIME
+ byte[] getEncryptedDateTimeAsBytes(GregorianCalendar cal,
+ int subSecondNanos,
+ JDBCType jdbcType) throws SQLServerException {
+ int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR), TDS.BASE_YEAR_1900);
+
+ // Next, figure out the number of milliseconds since midnight of the current day.
+ int millisSinceMidnight = (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into the current second
+ 1000 * cal.get(Calendar.SECOND) + // Seconds into the current minute
+ 60 * 1000 * cal.get(Calendar.MINUTE) + // Minutes into the current hour
+ 60 * 60 * 1000 * cal.get(Calendar.HOUR_OF_DAY); // Hours into the current day
+
+ // The last millisecond of the current day is always rounded to the first millisecond
+ // of the next day because DATETIME is only accurate to 1/300th of a second.
+ if (millisSinceMidnight >= 1000 * 60 * 60 * 24 - 1) {
+ ++daysSinceSQLBaseDate;
+ millisSinceMidnight = 0;
+ }
+
+ if (JDBCType.SMALLDATETIME == jdbcType) {
+
+ int secondsSinceMidnight = (millisSinceMidnight / 1000);
+ int minutesSinceMidnight = (secondsSinceMidnight / 60);
+
+ // Values that are 29.998 seconds or less are rounded down to the nearest minute
+ minutesSinceMidnight = ((secondsSinceMidnight % 60) > 29.998) ? minutesSinceMidnight + 1 : minutesSinceMidnight;
+
+ // minutesSinceMidnight for (23:59:30)
+ int maxMinutesSinceMidnight_SmallDateTime = 1440;
+ // Verification for smalldatetime to be within valid range of (1900.01.01) to (2079.06.06)
+ // smalldatetime for unencrypted does not allow insertion of 2079.06.06 23:59:59 and it is rounded up
+ // to 2079.06.07 00:00:00, therefore, we are checking minutesSinceMidnight for that condition. If it's not within valid range, then
+ // throw an exception now so that statement execution is safely canceled.
+ // 157 is the calculated day of year from 06-06 , 1440 is minutesince midnight for (23:59:30)
+ if ((daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1900, 1, TDS.BASE_YEAR_1900)
+ || daysSinceSQLBaseDate > DDC.daysSinceBaseDate(2079, 157, TDS.BASE_YEAR_1900))
+ || (daysSinceSQLBaseDate == DDC.daysSinceBaseDate(2079, 157, TDS.BASE_YEAR_1900)
+ && minutesSinceMidnight >= maxMinutesSinceMidnight_SmallDateTime)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
+ Object[] msgArgs = {SSType.SMALLDATETIME};
+ throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW, DriverError.NOT_SET, null);
+ }
+
+ ByteBuffer days = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
+ days.putShort((short) daysSinceSQLBaseDate);
+ ByteBuffer seconds = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN);
+ seconds.putShort((short) minutesSinceMidnight);
+
+ byte[] value = new byte[4];
+ System.arraycopy(days.array(), 0, value, 0, 2);
+ System.arraycopy(seconds.array(), 0, value, 2, 2);
+ return SQLServerSecurityUtility.encryptWithKey(value, cryptoMeta, con);
+ }
+ else if (JDBCType.DATETIME == jdbcType) {
+ // Last-ditch verification that the value is in the valid range for the
+ // DATETIMEN TDS data type (1/1/1753 to 12/31/9999). If it's not, then
+ // throw an exception now so that statement execution is safely canceled.
+ // Attempting to put an invalid value on the wire would result in a TDS
+ // exception, which would close the connection.
+ // These are based on SQL Server algorithms
+ // And put it all on the wire...
+ if (daysSinceSQLBaseDate < DDC.daysSinceBaseDate(1753, 1, TDS.BASE_YEAR_1900)
+ || daysSinceSQLBaseDate >= DDC.daysSinceBaseDate(10000, 1, TDS.BASE_YEAR_1900)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
+ Object[] msgArgs = {SSType.DATETIME};
+ throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW, DriverError.NOT_SET, null);
+ }
+
+ // Number of days since the SQL Server Base Date (January 1, 1900)
+ ByteBuffer days = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
+ days.putInt(daysSinceSQLBaseDate);
+ ByteBuffer seconds = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
+ seconds.putInt((3 * millisSinceMidnight + 5) / 10);
+
+ byte[] value = new byte[8];
+ System.arraycopy(days.array(), 0, value, 0, 4);
+ System.arraycopy(seconds.array(), 0, value, 4, 4);
+ return SQLServerSecurityUtility.encryptWithKey(value, cryptoMeta, con);
+ }
+
+ assert false : "Unexpected JDBCType type " + jdbcType;
+ return null;
+ }
+
+ void writeEncryptedRPCDateTime2(String sName,
+ GregorianCalendar localCalendar,
+ int subSecondNanos,
+ int scale,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
+
+ if (null == localCalendar)
+ writeEncryptedRPCByteArray(null);
+ else
+ writeEncryptedRPCByteArray(writeEncryptedScaledTemporal(localCalendar, subSecondNanos, scale, SSType.DATETIME2, (short) 0));
+
+ writeByte(TDSType.DATETIME2N.byteValue());
+ writeByte((byte) (scale));
+ writeCryptoMetaData();
+ }
+
+ void writeEncryptedRPCDateTimeOffset(String sName,
+ GregorianCalendar utcCalendar,
+ int minutesOffset,
+ int subSecondNanos,
+ int scale,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.BIGVARBINARY);
+
+ if (null == utcCalendar)
+ writeEncryptedRPCByteArray(null);
+ else {
+ assert 0 == utcCalendar.get(Calendar.ZONE_OFFSET);
+ writeEncryptedRPCByteArray(
+ writeEncryptedScaledTemporal(utcCalendar, subSecondNanos, scale, SSType.DATETIMEOFFSET, (short) minutesOffset));
+ }
+
+ writeByte(TDSType.DATETIMEOFFSETN.byteValue());
+ writeByte((byte) (scale));
+ writeCryptoMetaData();
+
+ }
+
+ void writeRPCDateTime2(String sName,
+ GregorianCalendar localCalendar,
+ int subSecondNanos,
+ int scale,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.DATETIME2N);
+ writeByte((byte) scale);
+
+ if (null == localCalendar) {
+ writeByte((byte) 0);
+ return;
+ }
+
+ writeByte((byte) TDS.datetime2ValueLength(scale));
+ writeScaledTemporal(localCalendar, subSecondNanos, scale, SSType.DATETIME2);
+ }
+
+ void writeRPCDateTimeOffset(String sName,
+ GregorianCalendar utcCalendar,
+ int minutesOffset,
+ int subSecondNanos,
+ int scale,
+ boolean bOut) throws SQLServerException {
+ writeRPCNameValType(sName, bOut, TDSType.DATETIMEOFFSETN);
+ writeByte((byte) scale);
+
+ if (null == utcCalendar) {
+ writeByte((byte) 0);
+ return;
+ }
+
+ assert 0 == utcCalendar.get(Calendar.ZONE_OFFSET);
+
+ writeByte((byte) TDS.datetimeoffsetValueLength(scale));
+ writeScaledTemporal(utcCalendar, subSecondNanos, scale, SSType.DATETIMEOFFSET);
+
+ writeShort((short) minutesOffset);
+ }
+
+ /**
+ * Returns subSecondNanos rounded to the maximum precision supported. The maximum fractional scale is MAX_FRACTIONAL_SECONDS_SCALE(7). Eg1: if you
+ * pass 456,790,123 the function would return 456,790,100 Eg2: if you pass 456,790,150 the function would return 456,790,200 Eg3: if you pass
+ * 999,999,951 the function would return 1,000,000,000 This is done to ensure that we have consistent rounding behaviour in setters and getters.
+ * Bug #507919
+ */
+ private int getRoundedSubSecondNanos(int subSecondNanos) {
+ int roundedNanos = ((subSecondNanos + (Nanos.PER_MAX_SCALE_INTERVAL / 2)) / Nanos.PER_MAX_SCALE_INTERVAL) * Nanos.PER_MAX_SCALE_INTERVAL;
+ return roundedNanos;
+ }
+
+ /**
+ * Writes to the TDS channel a temporal value as an instance instance of one of the scaled temporal SQL types: DATE, TIME, DATETIME2, or
+ * DATETIMEOFFSET.
+ *
+ * @param cal
+ * Calendar representing the value to write, except for any sub-second nanoseconds
+ * @param subSecondNanos
+ * the sub-second nanoseconds (0 - 999,999,999)
+ * @param scale
+ * the scale (in digits: 0 - 7) to use for the sub-second nanos component
+ * @param ssType
+ * the SQL Server data type (DATE, TIME, DATETIME2, or DATETIMEOFFSET)
+ *
+ * @throws SQLServerException
+ * if an I/O error occurs or if the value is not in the valid range
+ */
+ private void writeScaledTemporal(GregorianCalendar cal,
+ int subSecondNanos,
+ int scale,
+ SSType ssType) throws SQLServerException {
+
+ assert con.isKatmaiOrLater();
+
+ assert SSType.DATE == ssType || SSType.TIME == ssType || SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType : "Unexpected SSType: "
+ + ssType;
+
+ // First, for types with a time component, write the scaled nanos since midnight
+ if (SSType.TIME == ssType || SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType) {
+ assert subSecondNanos >= 0;
+ assert subSecondNanos < Nanos.PER_SECOND;
+ assert scale >= 0;
+ assert scale <= TDS.MAX_FRACTIONAL_SECONDS_SCALE;
+
+ int secondsSinceMidnight = cal.get(Calendar.SECOND) + 60 * cal.get(Calendar.MINUTE) + 60 * 60 * cal.get(Calendar.HOUR_OF_DAY);
+
+ // Scale nanos since midnight to the desired scale, rounding the value as necessary
+ long divisor = Nanos.PER_MAX_SCALE_INTERVAL * (long) Math.pow(10, TDS.MAX_FRACTIONAL_SECONDS_SCALE - scale);
+
+ // The scaledNanos variable represents the fractional seconds of the value at the scale
+ // indicated by the scale variable. So, for example, scaledNanos = 3 means 300 nanoseconds
+ // at scale TDS.MAX_FRACTIONAL_SECONDS_SCALE, but 3000 nanoseconds at
+ // TDS.MAX_FRACTIONAL_SECONDS_SCALE - 1
+ long scaledNanos = ((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos) + divisor / 2) / divisor;
+
+ // SQL Server rounding behavior indicates that it always rounds up unless
+ // we are at the max value of the type(NOT every day), in which case it truncates.
+ // Side effect on Calendar date:
+ // If rounding nanos to the specified scale rolls the value to the next day ...
+ if (Nanos.PER_DAY / divisor == scaledNanos) {
+
+ // If the type is time, always truncate
+ if (SSType.TIME == ssType) {
+ --scaledNanos;
+ }
+ // If the type is datetime2 or datetimeoffset, truncate only if its the max value supported
+ else {
+ assert SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType : "Unexpected SSType: " + ssType;
+
+ // ... then bump the date, provided that the resulting date is still within
+ // the valid date range.
+ //
+ // Extreme edge case (literally, the VERY edge...):
+ // If nanos overflow rolls the date value out of range (that is, we have a value
+ // a few nanoseconds later than 9999-12-31 23:59:59) then truncate the nanos
+ // instead of rolling.
+ //
+ // This case is very likely never hit by "real world" applications, but exists
+ // here as a security measure to ensure that such values don't result in a
+ // connection-closing TDS exception.
+ cal.add(Calendar.SECOND, 1);
+
+ if (cal.get(Calendar.YEAR) <= 9999) {
+ scaledNanos = 0;
+ }
+ else {
+ cal.add(Calendar.SECOND, -1);
+ --scaledNanos;
+ }
+ }
+ }
+
+ // Encode the scaled nanos to TDS
+ int encodedLength = TDS.nanosSinceMidnightLength(scale);
+ byte[] encodedBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
+
+ writeBytes(encodedBytes);
+ }
+
+ // Second, for types with a date component, write the days into the Common Era
+ if (SSType.DATE == ssType || SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType) {
+ // Computation of the number of days into the Common Era assumes that
+ // the DAY_OF_YEAR field reflects a pure Gregorian calendar - one that
+ // uses Gregorian leap year rules across the entire range of dates.
+ //
+ // For the DAY_OF_YEAR field to accurately reflect pure Gregorian behavior,
+ // we need to use a pure Gregorian calendar for dates that are Julian dates
+ // under a standard Gregorian calendar and for (Gregorian) dates later than
+ // the cutover date in the cutover year.
+ if (cal.getTimeInMillis() < GregorianChange.STANDARD_CHANGE_DATE.getTime()
+ || cal.getActualMaximum(Calendar.DAY_OF_YEAR) < TDS.DAYS_PER_YEAR) {
+ int year = cal.get(Calendar.YEAR);
+ int month = cal.get(Calendar.MONTH);
+ int date = cal.get(Calendar.DATE);
+
+ // Set the cutover as early as possible (pure Gregorian behavior)
+ cal.setGregorianChange(GregorianChange.PURE_CHANGE_DATE);
+
+ // Initialize the date field by field (preserving the "wall calendar" value)
+ cal.set(year, month, date);
+ }
+
+ int daysIntoCE = DDC.daysSinceBaseDate(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR), 1);
+
+ // Last-ditch verification that the value is in the valid range for the
+ // DATE/DATETIME2/DATETIMEOFFSET TDS data type (1/1/0001 to 12/31/9999).
+ // If it's not, then throw an exception now so that statement execution
+ // is safely canceled. Attempting to put an invalid value on the wire
+ // would result in a TDS exception, which would close the connection.
+ if (daysIntoCE < 0 || daysIntoCE >= DDC.daysSinceBaseDate(10000, 1, 1)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
+ Object[] msgArgs = {ssType};
+ throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW, DriverError.NOT_SET, null);
+ }
+
+ byte encodedBytes[] = new byte[3];
+ encodedBytes[0] = (byte) ((daysIntoCE >> 0) & 0xFF);
+ encodedBytes[1] = (byte) ((daysIntoCE >> 8) & 0xFF);
+ encodedBytes[2] = (byte) ((daysIntoCE >> 16) & 0xFF);
+ writeBytes(encodedBytes);
+ }
+ }
+
+ /**
+ * Writes to the TDS channel a temporal value as an instance instance of one of the scaled temporal SQL types: DATE, TIME, DATETIME2, or
+ * DATETIMEOFFSET.
+ *
+ * @param cal
+ * Calendar representing the value to write, except for any sub-second nanoseconds
+ * @param subSecondNanos
+ * the sub-second nanoseconds (0 - 999,999,999)
+ * @param scale
+ * the scale (in digits: 0 - 7) to use for the sub-second nanos component
+ * @param ssType
+ * the SQL Server data type (DATE, TIME, DATETIME2, or DATETIMEOFFSET)
+ * @param minutesOffset
+ * the offset value for DATETIMEOFFSET
+ * @throws SQLServerException
+ * if an I/O error occurs or if the value is not in the valid range
+ */
+ byte[] writeEncryptedScaledTemporal(GregorianCalendar cal,
+ int subSecondNanos,
+ int scale,
+ SSType ssType,
+ short minutesOffset) throws SQLServerException {
+ assert con.isKatmaiOrLater();
+
+ assert SSType.DATE == ssType || SSType.TIME == ssType || SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType : "Unexpected SSType: "
+ + ssType;
+
+ // store the time and minutesOffset portion of DATETIME2 and DATETIMEOFFSET to be used with date portion
+ byte encodedBytesForEncryption[] = null;
+
+ int secondsSinceMidnight = 0;
+ long divisor = 0;
+ long scaledNanos = 0;
+
+ // First, for types with a time component, write the scaled nanos since midnight
+ if (SSType.TIME == ssType || SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType) {
+ assert subSecondNanos >= 0;
+ assert subSecondNanos < Nanos.PER_SECOND;
+ assert scale >= 0;
+ assert scale <= TDS.MAX_FRACTIONAL_SECONDS_SCALE;
+
+ secondsSinceMidnight = cal.get(Calendar.SECOND) + 60 * cal.get(Calendar.MINUTE) + 60 * 60 * cal.get(Calendar.HOUR_OF_DAY);
+
+ // Scale nanos since midnight to the desired scale, rounding the value as necessary
+ divisor = Nanos.PER_MAX_SCALE_INTERVAL * (long) Math.pow(10, TDS.MAX_FRACTIONAL_SECONDS_SCALE - scale);
+
+ // The scaledNanos variable represents the fractional seconds of the value at the scale
+ // indicated by the scale variable. So, for example, scaledNanos = 3 means 300 nanoseconds
+ // at scale TDS.MAX_FRACTIONAL_SECONDS_SCALE, but 3000 nanoseconds at
+ // TDS.MAX_FRACTIONAL_SECONDS_SCALE - 1
+ scaledNanos = (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos) + divisor / 2) / divisor)
+ * divisor / 100;
+
+ // for encrypted time value, SQL server cannot do rounding or casting,
+ // So, driver needs to cast it before encryption.
+ if (SSType.TIME == ssType && 864000000000L <= scaledNanos) {
+ scaledNanos = (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos)) / divisor) * divisor / 100;
+ }
+
+ // SQL Server rounding behavior indicates that it always rounds up unless
+ // we are at the max value of the type(NOT every day), in which case it truncates.
+ // Side effect on Calendar date:
+ // If rounding nanos to the specified scale rolls the value to the next day ...
+ if (Nanos.PER_DAY / divisor == scaledNanos) {
+
+ // If the type is time, always truncate
+ if (SSType.TIME == ssType) {
+ --scaledNanos;
+ }
+ // If the type is datetime2 or datetimeoffset, truncate only if its the max value supported
+ else {
+ assert SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType : "Unexpected SSType: " + ssType;
+
+ // ... then bump the date, provided that the resulting date is still within
+ // the valid date range.
+ //
+ // Extreme edge case (literally, the VERY edge...):
+ // If nanos overflow rolls the date value out of range (that is, we have a value
+ // a few nanoseconds later than 9999-12-31 23:59:59) then truncate the nanos
+ // instead of rolling.
+ //
+ // This case is very likely never hit by "real world" applications, but exists
+ // here as a security measure to ensure that such values don't result in a
+ // connection-closing TDS exception.
+ cal.add(Calendar.SECOND, 1);
+
+ if (cal.get(Calendar.YEAR) <= 9999) {
+ scaledNanos = 0;
+ }
+ else {
+ cal.add(Calendar.SECOND, -1);
+ --scaledNanos;
+ }
+ }
+ }
+
+ // Encode the scaled nanos to TDS
+ int encodedLength = TDS.nanosSinceMidnightLength(TDS.MAX_FRACTIONAL_SECONDS_SCALE);
+ byte[] encodedBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
+
+ if (SSType.TIME == ssType) {
+ byte[] cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytes, cryptoMeta, con);
+ return cipherText;
+ }
+ else if (SSType.DATETIME2 == ssType) {
+ // for DATETIME2 sends both date and time part together for encryption
+ encodedBytesForEncryption = new byte[encodedLength + 3];
+ System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, 0, encodedBytes.length);
+ }
+ else if (SSType.DATETIMEOFFSET == ssType) {
+ // for DATETIMEOFFSET sends date, time and offset part together for encryption
+ encodedBytesForEncryption = new byte[encodedLength + 5];
+ System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, 0, encodedBytes.length);
+ }
+ }
+
+ // Second, for types with a date component, write the days into the Common Era
+ if (SSType.DATE == ssType || SSType.DATETIME2 == ssType || SSType.DATETIMEOFFSET == ssType) {
+ // Computation of the number of days into the Common Era assumes that
+ // the DAY_OF_YEAR field reflects a pure Gregorian calendar - one that
+ // uses Gregorian leap year rules across the entire range of dates.
+ //
+ // For the DAY_OF_YEAR field to accurately reflect pure Gregorian behavior,
+ // we need to use a pure Gregorian calendar for dates that are Julian dates
+ // under a standard Gregorian calendar and for (Gregorian) dates later than
+ // the cutover date in the cutover year.
+ if (cal.getTimeInMillis() < GregorianChange.STANDARD_CHANGE_DATE.getTime()
+ || cal.getActualMaximum(Calendar.DAY_OF_YEAR) < TDS.DAYS_PER_YEAR) {
+ int year = cal.get(Calendar.YEAR);
+ int month = cal.get(Calendar.MONTH);
+ int date = cal.get(Calendar.DATE);
+
+ // Set the cutover as early as possible (pure Gregorian behavior)
+ cal.setGregorianChange(GregorianChange.PURE_CHANGE_DATE);
+
+ // Initialize the date field by field (preserving the "wall calendar" value)
+ cal.set(year, month, date);
+ }
+
+ int daysIntoCE = DDC.daysSinceBaseDate(cal.get(Calendar.YEAR), cal.get(Calendar.DAY_OF_YEAR), 1);
+
+ // Last-ditch verification that the value is in the valid range for the
+ // DATE/DATETIME2/DATETIMEOFFSET TDS data type (1/1/0001 to 12/31/9999).
+ // If it's not, then throw an exception now so that statement execution
+ // is safely canceled. Attempting to put an invalid value on the wire
+ // would result in a TDS exception, which would close the connection.
+ if (daysIntoCE < 0 || daysIntoCE >= DDC.daysSinceBaseDate(10000, 1, 1)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
+ Object[] msgArgs = {ssType};
+ throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW, DriverError.NOT_SET, null);
+ }
+
+ byte encodedBytes[] = new byte[3];
+ encodedBytes[0] = (byte) ((daysIntoCE >> 0) & 0xFF);
+ encodedBytes[1] = (byte) ((daysIntoCE >> 8) & 0xFF);
+ encodedBytes[2] = (byte) ((daysIntoCE >> 16) & 0xFF);
+
+ byte[] cipherText;
+ if (SSType.DATE == ssType) {
+ cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytes, cryptoMeta, con);
+ }
+ else if (SSType.DATETIME2 == ssType) {
+ // for Max value, does not round up, do casting instead.
+ if (3652058 == daysIntoCE) { // 9999-12-31
+ if (864000000000L == scaledNanos) { // 24:00:00 in nanoseconds
+ // does not round up
+ scaledNanos = (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos)) / divisor)
+ * divisor / 100;
+
+ int encodedLength = TDS.nanosSinceMidnightLength(TDS.MAX_FRACTIONAL_SECONDS_SCALE);
+ byte[] encodedNanoBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
+
+ // for DATETIME2 sends both date and time part together for encryption
+ encodedBytesForEncryption = new byte[encodedLength + 3];
+ System.arraycopy(encodedNanoBytes, 0, encodedBytesForEncryption, 0, encodedNanoBytes.length);
+ }
+ }
+ // Copy the 3 byte date value
+ System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, (encodedBytesForEncryption.length - 3), 3);
+
+ cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytesForEncryption, cryptoMeta, con);
+ }
+ else {
+ // for Max value, does not round up, do casting instead.
+ if (3652058 == daysIntoCE) { // 9999-12-31
+ if (864000000000L == scaledNanos) { // 24:00:00 in nanoseconds
+ // does not round up
+ scaledNanos = (((long) Nanos.PER_SECOND * secondsSinceMidnight + getRoundedSubSecondNanos(subSecondNanos)) / divisor)
+ * divisor / 100;
+
+ int encodedLength = TDS.nanosSinceMidnightLength(TDS.MAX_FRACTIONAL_SECONDS_SCALE);
+ byte[] encodedNanoBytes = scaledNanosToEncodedBytes(scaledNanos, encodedLength);
+
+ // for DATETIMEOFFSET sends date, time and offset part together for encryption
+ encodedBytesForEncryption = new byte[encodedLength + 5];
+ System.arraycopy(encodedNanoBytes, 0, encodedBytesForEncryption, 0, encodedNanoBytes.length);
+ }
+ }
+
+ // Copy the 3 byte date value
+ System.arraycopy(encodedBytes, 0, encodedBytesForEncryption, (encodedBytesForEncryption.length - 5), 3);
+ // Copy the 2 byte minutesOffset value
+ System.arraycopy(ByteBuffer.allocate(Short.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putShort(minutesOffset).array(), 0,
+ encodedBytesForEncryption, (encodedBytesForEncryption.length - 2), 2);
+
+ cipherText = SQLServerSecurityUtility.encryptWithKey(encodedBytesForEncryption, cryptoMeta, con);
+ }
+ return cipherText;
+ }
+
+ // Invalid type ssType. This condition should never happen.
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unknownSSType"));
+ Object[] msgArgs = {ssType};
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
+
+ return null;
+ }
+
+ private byte[] scaledNanosToEncodedBytes(long scaledNanos,
+ int encodedLength) {
+ byte encodedBytes[] = new byte[encodedLength];
+ for (int i = 0; i < encodedLength; i++)
+ encodedBytes[i] = (byte) ((scaledNanos >> (8 * i)) & 0xFF);
+ return encodedBytes;
+ }
+
+ /**
+ * Append the data in a stream in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param stream
+ * is the stream
+ * @param streamLength
+ * length of the stream (may be unknown)
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ * @param jdbcType
+ * The JDBC type used to determine whether the value is textual or non-textual.
+ * @param collation
+ * The SQL collation associated with the value. Null for non-textual SQL Server types.
+ * @throws SQLServerException
+ */
+ void writeRPCInputStream(String sName,
+ InputStream stream,
+ long streamLength,
+ boolean bOut,
+ JDBCType jdbcType,
+ SQLCollation collation) throws SQLServerException {
+ assert null != stream;
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength >= 0;
+
+ // Send long values and values with unknown length
+ // using PLP chunking on Yukon and later.
+ boolean usePLP = (DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength > DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ if (usePLP) {
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength <= DataTypes.MAX_VARTYPE_MAX_BYTES;
+
+ writeRPCNameValType(sName, bOut, jdbcType.isTextual() ? TDSType.BIGVARCHAR : TDSType.BIGVARBINARY);
+
+ // Handle Yukon v*max type header here.
+ writeVMaxHeader(streamLength, false, jdbcType.isTextual() ? collation : null);
+ }
+
+ // Send non-PLP in all other cases
+ else {
+ // If the length of the InputStream is unknown then we need to buffer the entire stream
+ // in memory so that we can determine its length and send that length to the server
+ // before the stream data itself.
+ if (DataTypes.UNKNOWN_STREAM_LENGTH == streamLength) {
+ // Create ByteArrayOutputStream with initial buffer size of 8K to handle typical
+ // binary field sizes more efficiently. Note we can grow beyond 8000 bytes.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(8000);
+ streamLength = 0L;
+
+ // Since Shiloh is limited to 64K TDS packets, that's a good upper bound on the maximum
+ // length of InputStream we should try to handle before throwing an exception.
+ long maxStreamLength = 65535L * con.getTDSPacketSize();
+
+ try {
+ byte buff[] = new byte[8000];
+ int bytesRead;
+
+ while (streamLength < maxStreamLength && -1 != (bytesRead = stream.read(buff, 0, buff.length))) {
+ baos.write(buff);
+ streamLength += bytesRead;
+ }
+ }
+ catch (IOException e) {
+ throw new SQLServerException(e.getMessage(), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, e);
+ }
+
+ if (streamLength >= maxStreamLength) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
+ Object[] msgArgs = {Long.valueOf(streamLength)};
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
+ }
+
+ assert streamLength <= Integer.MAX_VALUE;
+ stream = new ByteArrayInputStream(baos.toByteArray(), 0, (int) streamLength);
+ }
+
+ assert 0 <= streamLength && streamLength <= DataTypes.IMAGE_TEXT_MAX_BYTES;
+
+ boolean useVarType = streamLength <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
+
+ writeRPCNameValType(sName, bOut,
+ jdbcType.isTextual() ? (useVarType ? TDSType.BIGVARCHAR : TDSType.TEXT) : (useVarType ? TDSType.BIGVARBINARY : TDSType.IMAGE));
+
+ // Write maximum length, optional collation, and actual length
+ if (useVarType) {
+ writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ if (jdbcType.isTextual())
+ collation.writeCollation(this);
+ writeShort((short) streamLength);
+ }
+ else {
+ writeInt(DataTypes.IMAGE_TEXT_MAX_BYTES);
+ if (jdbcType.isTextual())
+ collation.writeCollation(this);
+ writeInt((int) streamLength);
+ }
+ }
+
+ // Write the data
+ writeStream(stream, streamLength, usePLP);
+ }
+
+ /**
+ * Append the XML data in a stream in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param stream
+ * is the stream
+ * @param streamLength
+ * length of the stream (may be unknown)
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ * @throws SQLServerException
+ */
+ void writeRPCXML(String sName,
+ InputStream stream,
+ long streamLength,
+ boolean bOut) throws SQLServerException {
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength >= 0;
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == streamLength || streamLength <= DataTypes.MAX_VARTYPE_MAX_BYTES;
+
+ writeRPCNameValType(sName, bOut, TDSType.XML);
+ writeByte((byte) 0); // No schema
+ // Handle null here and return, we're done here if it's null.
+ if (null == stream) {
+ // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.
+ writeLong(0xFFFFFFFFFFFFFFFFL);
+ }
+ else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamLength) {
+ // Append v*max length.
+ // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE
+ writeLong(0xFFFFFFFFFFFFFFFEL);
+
+ // NOTE: Don't send the first chunk length, this will be calculated by caller.
+ }
+ else {
+ // For v*max types with known length, length is
+ // We're sending same total length as chunk length (as we're sending 1 chunk).
+ writeLong(streamLength);
+ }
+ if (null != stream)
+ // Write the data
+ writeStream(stream, streamLength, true);
+ }
+
+ /**
+ * Append the data in a character reader in RPC transmission format.
+ *
+ * @param sName
+ * the optional parameter name
+ * @param re
+ * the reader
+ * @param reLength
+ * the reader data length (in characters)
+ * @param bOut
+ * boolean true if the data value is being registered as an ouput parameter
+ * @param collation
+ * The SQL collation associated with the value. Null for non-textual SQL Server types.
+ * @throws SQLServerException
+ */
+ void writeRPCReaderUnicode(String sName,
+ Reader re,
+ long reLength,
+ boolean bOut,
+ SQLCollation collation) throws SQLServerException {
+ assert null != re;
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == reLength || reLength >= 0;
+
+ // Textual RPC requires a collation. If none is provided, as is the case when
+ // the SSType is non-textual, then use the database collation by default.
+ if (null == collation)
+ collation = con.getDatabaseCollation();
+
+ // Send long values and values with unknown length
+ // using PLP chunking on Yukon and later.
+ boolean usePLP = (DataTypes.UNKNOWN_STREAM_LENGTH == reLength || reLength > DataTypes.SHORT_VARTYPE_MAX_CHARS);
+ if (usePLP) {
+ assert DataTypes.UNKNOWN_STREAM_LENGTH == reLength || reLength <= DataTypes.MAX_VARTYPE_MAX_CHARS;
+
+ writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);
+
+ // Handle Yukon v*max type header here.
+ writeVMaxHeader((DataTypes.UNKNOWN_STREAM_LENGTH == reLength) ? DataTypes.UNKNOWN_STREAM_LENGTH : 2 * reLength, // Length (in bytes)
+ false, collation);
+ }
+
+ // Send non-PLP in all other cases
+ else {
+ // Length must be known if we're not sending PLP-chunked data. Yukon is handled above.
+ // For Shiloh, this is enforced in DTV by converting the Reader to some other length-
+ // prefixed value in the setter.
+ assert 0 <= reLength && reLength <= DataTypes.NTEXT_MAX_CHARS;
+
+ // For non-PLP types, use the long TEXT type rather than the short VARCHAR
+ // type if the stream is too long to fit in the latter or if we don't know the length up
+ // front so we have to assume that it might be too long.
+ boolean useVarType = reLength <= DataTypes.SHORT_VARTYPE_MAX_CHARS;
+
+ writeRPCNameValType(sName, bOut, useVarType ? TDSType.NVARCHAR : TDSType.NTEXT);
+
+ // Write maximum length, collation, and actual length of the data
+ if (useVarType) {
+ writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
+ collation.writeCollation(this);
+ writeShort((short) (2 * reLength));
+ }
+ else {
+ writeInt(DataTypes.NTEXT_MAX_CHARS);
+ collation.writeCollation(this);
+ writeInt((int) (2 * reLength));
+ }
+ }
+
+ // Write the data
+ writeReader(re, reLength, usePLP);
+ }
+}
+
+/**
+ * TDSPacket provides a mechanism for chaining TDS response packets together in a singly-linked list.
+ *
+ * Having both the link and the data in the same class allows TDSReader marks (see below) to automatically hold onto exactly as much response data as
+ * they need, and no more. Java reference semantics ensure that a mark holds onto its referenced packet and subsequent packets (through next
+ * references). When all marked references to a packet go away, the packet, and any linked unmarked packets, can be reclaimed by GC.
+ */
+final class TDSPacket {
+ final byte[] header = new byte[TDS.PACKET_HEADER_SIZE];
+ final byte[] payload;
+ int payloadLength;
+ volatile TDSPacket next;
+
+ final public String toString() {
+ return "TDSPacket(SPID:" + Util.readUnsignedShortBigEndian(header, TDS.PACKET_HEADER_SPID) + " Seq:" + header[TDS.PACKET_HEADER_SEQUENCE_NUM]
+ + ")";
+ }
+
+ TDSPacket(int size) {
+ payload = new byte[size];
+ payloadLength = 0;
+ next = null;
+ }
+
+ final boolean isEOM() {
+ return TDS.STATUS_BIT_EOM == (header[TDS.PACKET_HEADER_MESSAGE_STATUS] & TDS.STATUS_BIT_EOM);
+ }
+};
+
+/**
+ * TDSReaderMark encapsulates a fixed position in the response data stream.
+ *
+ * Response data is quantized into a linked chain of packets. A mark refers to a specific location in a specific packet and relies on Java's reference
+ * semantics to automatically keep all subsequent packets accessible until the mark is destroyed.
+ */
+final class TDSReaderMark {
+ final TDSPacket packet;
+ final int payloadOffset;
+
+ TDSReaderMark(TDSPacket packet,
+ int payloadOffset) {
+ this.packet = packet;
+ this.payloadOffset = payloadOffset;
+ }
+}
+
+/**
+ * TDSReader encapsulates the TDS response data stream.
+ *
+ * Bytes are read from SQL Server into a FIFO of packets. Reader methods traverse the packets to access the data.
+ */
+final class TDSReader {
+ private final static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Reader");
+ final private String traceID;
+
+ final public String toString() {
+ return traceID;
+ }
+
+ private final TDSChannel tdsChannel;
+ private final SQLServerConnection con;
+
+ private final TDSCommand command;
+
+ final TDSCommand getCommand() {
+ assert null != command;
+ return command;
+ }
+
+ final SQLServerConnection getConnection() {
+ return con;
+ }
+
+ private TDSPacket currentPacket = new TDSPacket(0);
+ private TDSPacket lastPacket = currentPacket;
+ private int payloadOffset = 0;
+ private int packetNum = 0;
+
+ private boolean isStreaming = true;
+ private boolean useColumnEncryption = false;
+ private boolean serverSupportsColumnEncryption = false;
+
+ private final byte valueBytes[] = new byte[256];
+ private static final AtomicInteger lastReaderID = new AtomicInteger(0);
+
+ private static int nextReaderID() {
+ return lastReaderID.incrementAndGet();
+ }
+
+ TDSReader(TDSChannel tdsChannel,
+ SQLServerConnection con,
+ TDSCommand command) {
+ this.tdsChannel = tdsChannel;
+ this.con = con;
+ this.command = command; // may be null
+ // if the logging level is not detailed than fine or more we will not have proper readerids.
+ if (logger.isLoggable(Level.FINE))
+ traceID = "TDSReader@" + nextReaderID() + " (" + con.toString() + ")";
+ else
+ traceID = con.toString();
+ if (con.isColumnEncryptionSettingEnabled()) {
+ useColumnEncryption = true;
+ }
+ serverSupportsColumnEncryption = con.getServerSupportsColumnEncryption();
+ }
+
+ final boolean isColumnEncryptionSettingEnabled() {
+ return useColumnEncryption;
+ }
+
+ final boolean getServerSupportsColumnEncryption() {
+ return serverSupportsColumnEncryption;
+ }
+
+ final void throwInvalidTDS() throws SQLServerException {
+ if (logger.isLoggable(Level.SEVERE))
+ logger.severe(toString() + " got unexpected value in TDS response at offset:" + payloadOffset);
+ con.throwInvalidTDS();
+ }
+
+ final void throwInvalidTDSToken(String tokenName) throws SQLServerException {
+ if (logger.isLoggable(Level.SEVERE))
+ logger.severe(toString() + " got unexpected value in TDS response at offset:" + payloadOffset);
+ con.throwInvalidTDSToken(tokenName);
+ }
+
+ /**
+ * Ensures that payload data is available to be read, automatically advancing to (and possibly reading) the next packet.
+ *
+ * @return true if additional data is available to be read false if no more data is available
+ */
+ private boolean ensurePayload() throws SQLServerException {
+ if (payloadOffset == currentPacket.payloadLength)
+ if (!nextPacket())
+ return false;
+ assert payloadOffset < currentPacket.payloadLength;
+ return true;
+ }
+
+ /**
+ * Advance (and possibly read) the next packet.
+ *
+ * @return true if additional data is available to be read false if no more data is available
+ */
+ private boolean nextPacket() throws SQLServerException {
+ assert null != currentPacket;
+
+ // Shouldn't call this function unless we're at the end of the current packet...
+ TDSPacket consumedPacket = currentPacket;
+ assert payloadOffset == consumedPacket.payloadLength;
+
+ // If no buffered packets are left then maybe we can read one...
+ // This action must be synchronized against against another thread calling
+ // readAllPackets() to read in ALL of the remaining packets of the current response.
+ if (null == consumedPacket.next) {
+ readPacket();
+
+ if (null == consumedPacket.next)
+ return false;
+ }
+
+ // Advance to that packet. If we are streaming through the
+ // response, then unlink the current packet from the next
+ // before moving to allow the packet to be reclaimed.
+ TDSPacket nextPacket = consumedPacket.next;
+ if (isStreaming) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(toString() + " Moving to next packet -- unlinking consumed packet");
+
+ consumedPacket.next = null;
+ }
+ currentPacket = nextPacket;
+ payloadOffset = 0;
+ return true;
+ }
+
+ /**
+ * Reads the next packet of the TDS channel.
+ *
+ * This method is synchronized to guard against simultaneously reading packets from one thread that is processing the response and another thread
+ * that is trying to buffer it with TDSCommand.detach().
+ */
+ synchronized final boolean readPacket() throws SQLServerException {
+ if (null != command && !command.readingResponse())
+ return false;
+
+ // Number of packets in should always be less than number of packets out.
+ // If the server has been notified for an interrupt, it may be less by
+ // more than one packet.
+ assert tdsChannel.numMsgsRcvd < tdsChannel.numMsgsSent : "numMsgsRcvd:" + tdsChannel.numMsgsRcvd + " should be less than numMsgsSent:"
+ + tdsChannel.numMsgsSent;
+
+ TDSPacket newPacket = new TDSPacket(con.getTDSPacketSize());
+
+ // First, read the packet header.
+ for (int headerBytesRead = 0; headerBytesRead < TDS.PACKET_HEADER_SIZE;) {
+ int bytesRead = tdsChannel.read(newPacket.header, headerBytesRead, TDS.PACKET_HEADER_SIZE - headerBytesRead);
+ if (bytesRead < 0) {
+ if (logger.isLoggable(Level.FINER))
+ logger.finer(toString() + " Premature EOS in response. packetNum:" + packetNum + " headerBytesRead:" + headerBytesRead);
+
+ con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, ((0 == packetNum && 0 == headerBytesRead)
+ ? SQLServerException.getErrString("R_noServerResponse") : SQLServerException.getErrString("R_truncatedServerResponse")));
+ }
+
+ headerBytesRead += bytesRead;
+ }
+
+ // Header size is a 2 byte unsigned short integer in big-endian order.
+ int packetLength = Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_MESSAGE_LENGTH);
+
+ // Make header size is properly bounded and compute length of the packet payload.
+ if (packetLength < TDS.PACKET_HEADER_SIZE || packetLength > con.getTDSPacketSize()) {
+ logger.warning(toString() + " TDS header contained invalid packet length:" + packetLength + "; packet size:" + con.getTDSPacketSize());
+ throwInvalidTDS();
+ }
+
+ newPacket.payloadLength = packetLength - TDS.PACKET_HEADER_SIZE;
+
+ // Just grab the SPID for logging (another big-endian unsigned short).
+ tdsChannel.setSPID(Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_SPID));
+
+ // Packet header looks good enough.
+ // When logging, copy the packet header to the log buffer.
+ byte[] logBuffer = null;
+ if (tdsChannel.isLoggingPackets()) {
+ logBuffer = new byte[packetLength];
+ System.arraycopy(newPacket.header, 0, logBuffer, 0, TDS.PACKET_HEADER_SIZE);
+ }
+
+ // Now for the payload...
+ for (int payloadBytesRead = 0; payloadBytesRead < newPacket.payloadLength;) {
+ int bytesRead = tdsChannel.read(newPacket.payload, payloadBytesRead, newPacket.payloadLength - payloadBytesRead);
+ if (bytesRead < 0)
+ con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, SQLServerException.getErrString("R_truncatedServerResponse"));
+
+ payloadBytesRead += bytesRead;
+ }
+
+ ++packetNum;
+
+ lastPacket.next = newPacket;
+ lastPacket = newPacket;
+
+ // When logging, append the payload to the log buffer and write out the whole thing.
+ if (tdsChannel.isLoggingPackets()) {
+ System.arraycopy(newPacket.payload, 0, logBuffer, TDS.PACKET_HEADER_SIZE, newPacket.payloadLength);
+ tdsChannel.logPacket(logBuffer, 0, packetLength,
+ this.toString() + " received Packet:" + packetNum + " (" + newPacket.payloadLength + " bytes)");
+ }
+
+ // If end of message, then bump the count of messages received and disable
+ // interrupts. If an interrupt happened prior to disabling, then expect
+ // to read the attention ack packet as well.
+ if (newPacket.isEOM()) {
+ ++tdsChannel.numMsgsRcvd;
+
+ // Notify the command (if any) that we've reached the end of the response.
+ if (null != command)
+ command.onResponseEOM();
+ }
+
+ return true;
+ }
+
+ final TDSReaderMark mark() {
+ TDSReaderMark mark = new TDSReaderMark(currentPacket, payloadOffset);
+ isStreaming = false;
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Buffering from: " + mark.toString());
+
+ return mark;
+ }
+
+ final void reset(TDSReaderMark mark) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Resetting to: " + mark.toString());
+
+ currentPacket = mark.packet;
+ payloadOffset = mark.payloadOffset;
+ }
+
+ final void stream() {
+ isStreaming = true;
+ }
+
+ /**
+ * Returns the number of bytes that can be read (or skipped over) from this TDSReader without blocking by the next caller of a method for this
+ * TDSReader.
+ *
+ * @return the actual number of bytes available.
+ */
+ final int available() {
+ // The number of bytes that can be read without blocking is just the number
+ // of bytes that are currently buffered. That is the number of bytes left
+ // in the current packet plus the number of bytes in the remaining packets.
+ int available = currentPacket.payloadLength - payloadOffset;
+ for (TDSPacket packet = currentPacket.next; null != packet; packet = packet.next)
+ available += packet.payloadLength;
+ return available;
+ }
+
+ /**
+ *
+ * @return number of bytes available in the current packet
+ */
+ final int availableCurrentPacket() {
+ /*
+ * The number of bytes that can be read from the current chunk, without including the next chunk that is buffered. This is so the driver can
+ * confirm if the next chunk sent is new packet or just continuation
+ */
+ int available = currentPacket.payloadLength - payloadOffset;
+ return available;
+ }
+
+ final int peekTokenType() throws SQLServerException {
+ // Check whether we're at EOF
+ if (!ensurePayload())
+ return -1;
+
+ // Peek at the current byte (don't increment payloadOffset!)
+ return currentPacket.payload[payloadOffset] & 0xFF;
+ }
+
+ final short peekStatusFlag() throws SQLServerException {
+ // skip the current packet(i.e, TDS packet type) and peek into the status flag (USHORT)
+ if (payloadOffset + 3 <= currentPacket.payloadLength) {
+ short value = Util.readShort(currentPacket.payload, payloadOffset + 1);
+ return value;
+ }
+
+ // as per TDS protocol, TDS_DONE packet should always be followed by status flag
+ // throw exception if status packet is not available
+ throwInvalidTDS();
+ return 0;
+ }
+
+ final int readUnsignedByte() throws SQLServerException {
+ // Ensure that we have a packet to read from.
+ if (!ensurePayload())
+ throwInvalidTDS();
+
+ return currentPacket.payload[payloadOffset++] & 0xFF;
+ }
+
+ final short readShort() throws SQLServerException {
+ if (payloadOffset + 2 <= currentPacket.payloadLength) {
+ short value = Util.readShort(currentPacket.payload, payloadOffset);
+ payloadOffset += 2;
+ return value;
+ }
+
+ return Util.readShort(readWrappedBytes(2), 0);
+ }
+
+ final int readUnsignedShort() throws SQLServerException {
+ if (payloadOffset + 2 <= currentPacket.payloadLength) {
+ int value = Util.readUnsignedShort(currentPacket.payload, payloadOffset);
+ payloadOffset += 2;
+ return value;
+ }
+
+ return Util.readUnsignedShort(readWrappedBytes(2), 0);
+ }
+
+ final String readUnicodeString(int length) throws SQLServerException {
+ int byteLength = 2 * length;
+ byte bytes[] = new byte[byteLength];
+ readBytes(bytes, 0, byteLength);
+ return Util.readUnicodeString(bytes, 0, byteLength, con);
+
+ }
+
+ final char readChar() throws SQLServerException {
+ return (char) readShort();
+ }
+
+ final int readInt() throws SQLServerException {
+ if (payloadOffset + 4 <= currentPacket.payloadLength) {
+ int value = Util.readInt(currentPacket.payload, payloadOffset);
+ payloadOffset += 4;
+ return value;
+ }
+
+ return Util.readInt(readWrappedBytes(4), 0);
+ }
+
+ final int readIntBigEndian() throws SQLServerException {
+ if (payloadOffset + 4 <= currentPacket.payloadLength) {
+ int value = Util.readIntBigEndian(currentPacket.payload, payloadOffset);
+ payloadOffset += 4;
+ return value;
+ }
+
+ return Util.readIntBigEndian(readWrappedBytes(4), 0);
+ }
+
+ final long readUnsignedInt() throws SQLServerException {
+ return readInt() & 0xFFFFFFFFL;
+ }
+
+ final long readLong() throws SQLServerException {
+ if (payloadOffset + 8 <= currentPacket.payloadLength) {
+ long value = Util.readLong(currentPacket.payload, payloadOffset);
+ payloadOffset += 8;
+ return value;
+ }
+
+ return Util.readLong(readWrappedBytes(8), 0);
+ }
+
+ final void readBytes(byte[] value,
+ int valueOffset,
+ int valueLength) throws SQLServerException {
+ for (int bytesRead = 0; bytesRead < valueLength;) {
+ // Ensure that we have a packet to read from.
+ if (!ensurePayload())
+ throwInvalidTDS();
+
+ // Figure out how many bytes to copy from the current packet
+ // (the lesser of the remaining value bytes and the bytes left in the packet).
+ int bytesToCopy = valueLength - bytesRead;
+ if (bytesToCopy > currentPacket.payloadLength - payloadOffset)
+ bytesToCopy = currentPacket.payloadLength - payloadOffset;
+
+ // Copy some bytes from the current packet to the destination value.
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(toString() + " Reading " + bytesToCopy + " bytes from offset " + payloadOffset);
+
+ System.arraycopy(currentPacket.payload, payloadOffset, value, valueOffset + bytesRead, bytesToCopy);
+ bytesRead += bytesToCopy;
+ payloadOffset += bytesToCopy;
+ }
+ }
+
+ final byte[] readWrappedBytes(int valueLength) throws SQLServerException {
+ assert valueLength <= valueBytes.length;
+ readBytes(valueBytes, 0, valueLength);
+ return valueBytes;
+ }
+
+ final Object readDecimal(int valueLength,
+ TypeInfo typeInfo,
+ JDBCType jdbcType,
+ StreamType streamType) throws SQLServerException {
+ if (valueLength > valueBytes.length) {
+ logger.warning(toString() + " Invalid value length:" + valueLength);
+ throwInvalidTDS();
+ }
+
+ readBytes(valueBytes, 0, valueLength);
+ return DDC.convertBigDecimalToObject(Util.readBigDecimal(valueBytes, valueLength, typeInfo.getScale()), jdbcType, streamType);
+ }
+
+ final Object readMoney(int valueLength,
+ JDBCType jdbcType,
+ StreamType streamType) throws SQLServerException {
+ BigInteger bi;
+ switch (valueLength) {
+ case 8: // money
+ {
+ int intBitsHi = readInt();
+ int intBitsLo = readInt();
+
+ if (JDBCType.BINARY == jdbcType) {
+ byte value[] = new byte[8];
+ Util.writeIntBigEndian(intBitsHi, value, 0);
+ Util.writeIntBigEndian(intBitsLo, value, 4);
+ return value;
+ }
+
+ bi = BigInteger.valueOf(((long) intBitsHi << 32) | (intBitsLo & 0xFFFFFFFFL));
+ break;
+ }
+
+ case 4: // smallmoney
+ if (JDBCType.BINARY == jdbcType) {
+ byte value[] = new byte[4];
+ Util.writeIntBigEndian(readInt(), value, 0);
+ return value;
+ }
+
+ bi = BigInteger.valueOf(readInt());
+ break;
+
+ default:
+ throwInvalidTDS();
+ return null;
+ }
+
+ return DDC.convertBigDecimalToObject(new BigDecimal(bi, 4), jdbcType, streamType);
+ }
+
+ final Object readReal(int valueLength,
+ JDBCType jdbcType,
+ StreamType streamType) throws SQLServerException {
+ if (4 != valueLength)
+ throwInvalidTDS();
+
+ return DDC.convertFloatToObject(Float.intBitsToFloat(readInt()), jdbcType, streamType);
+ }
+
+ final Object readFloat(int valueLength,
+ JDBCType jdbcType,
+ StreamType streamType) throws SQLServerException {
+ if (8 != valueLength)
+ throwInvalidTDS();
+
+ return DDC.convertDoubleToObject(Double.longBitsToDouble(readLong()), jdbcType, streamType);
+ }
+
+ final Object readDateTime(int valueLength,
+ Calendar appTimeZoneCalendar,
+ JDBCType jdbcType,
+ StreamType streamType) throws SQLServerException {
+ // Build and return the right kind of temporal object.
+ int daysSinceSQLBaseDate;
+ int ticksSinceMidnight;
+ int msecSinceMidnight;
+
+ switch (valueLength) {
+ case 8:
+ // SQL datetime is 4 bytes for days since SQL Base Date
+ // (January 1, 1900 00:00:00 GMT) and 4 bytes for
+ // the number of three hundredths (1/300) of a second
+ // since midnight.
+ daysSinceSQLBaseDate = readInt();
+ ticksSinceMidnight = readInt();
+
+ if (JDBCType.BINARY == jdbcType) {
+ byte value[] = new byte[8];
+ Util.writeIntBigEndian(daysSinceSQLBaseDate, value, 0);
+ Util.writeIntBigEndian(ticksSinceMidnight, value, 4);
+ return value;
+ }
+
+ msecSinceMidnight = (ticksSinceMidnight * 10 + 1) / 3; // Convert to msec (1 tick = 1 300th of a sec = 3 msec)
+ break;
+
+ case 4:
+ // SQL smalldatetime has less precision. It stores 2 bytes
+ // for the days since SQL Base Date and 2 bytes for minutes
+ // after midnight.
+ daysSinceSQLBaseDate = readUnsignedShort();
+ ticksSinceMidnight = readUnsignedShort();
+
+ if (JDBCType.BINARY == jdbcType) {
+ byte value[] = new byte[4];
+ Util.writeShortBigEndian((short) daysSinceSQLBaseDate, value, 0);
+ Util.writeShortBigEndian((short) ticksSinceMidnight, value, 2);
+ return value;
+ }
+
+ msecSinceMidnight = ticksSinceMidnight * 60 * 1000; // Convert to msec (1 tick = 1 min = 60,000 msec)
+ break;
+
+ default:
+ throwInvalidTDS();
+ return null;
+ }
+
+ // Convert the DATETIME/SMALLDATETIME value to the desired Java type.
+ return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME, appTimeZoneCalendar, daysSinceSQLBaseDate, msecSinceMidnight, 0); // scale
+ // (ignored
+ // for
+ // fixed-scale
+ // DATETIME/SMALLDATETIME
+ // types)
+ }
+
+ final Object readDate(int valueLength,
+ Calendar appTimeZoneCalendar,
+ JDBCType jdbcType) throws SQLServerException {
+ if (TDS.DAYS_INTO_CE_LENGTH != valueLength)
+ throwInvalidTDS();
+
+ // Initialize the date fields to their appropriate values.
+ int localDaysIntoCE = readDaysIntoCE();
+
+ // Convert the DATE value to the desired Java type.
+ return DDC.convertTemporalToObject(jdbcType, SSType.DATE, appTimeZoneCalendar, localDaysIntoCE, 0, // midnight local to app time zone
+ 0); // scale (ignored for DATE)
+ }
+
+ final Object readTime(int valueLength,
+ TypeInfo typeInfo,
+ Calendar appTimeZoneCalendar,
+ JDBCType jdbcType) throws SQLServerException {
+ if (TDS.timeValueLength(typeInfo.getScale()) != valueLength)
+ throwInvalidTDS();
+
+ // Read the value from the server
+ long localNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
+
+ // Convert the TIME value to the desired Java type.
+ return DDC.convertTemporalToObject(jdbcType, SSType.TIME, appTimeZoneCalendar, 0, localNanosSinceMidnight, typeInfo.getScale());
+ }
+
+ final Object readDateTime2(int valueLength,
+ TypeInfo typeInfo,
+ Calendar appTimeZoneCalendar,
+ JDBCType jdbcType) throws SQLServerException {
+ if (TDS.datetime2ValueLength(typeInfo.getScale()) != valueLength)
+ throwInvalidTDS();
+
+ // Read the value's constituent components
+ long localNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
+ int localDaysIntoCE = readDaysIntoCE();
+
+ // Convert the DATETIME2 value to the desired Java type.
+ return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME2, appTimeZoneCalendar, localDaysIntoCE, localNanosSinceMidnight,
+ typeInfo.getScale());
+ }
+
+ final Object readDateTimeOffset(int valueLength,
+ TypeInfo typeInfo,
+ JDBCType jdbcType) throws SQLServerException {
+ if (TDS.datetimeoffsetValueLength(typeInfo.getScale()) != valueLength)
+ throwInvalidTDS();
+
+ // The nanos since midnight and days into Common Era parts of DATETIMEOFFSET values
+ // are in UTC. Use the minutes offset part to convert to local.
+ long utcNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
+ int utcDaysIntoCE = readDaysIntoCE();
+ int localMinutesOffset = readShort();
+
+ // Convert the DATETIMEOFFSET value to the desired Java type.
+ return DDC.convertTemporalToObject(jdbcType, SSType.DATETIMEOFFSET,
+ new GregorianCalendar(new SimpleTimeZone(localMinutesOffset * 60 * 1000, ""), Locale.US), utcDaysIntoCE, utcNanosSinceMidnight,
+ typeInfo.getScale());
+ }
+
+ private int readDaysIntoCE() throws SQLServerException {
+ byte value[] = new byte[TDS.DAYS_INTO_CE_LENGTH];
+ readBytes(value, 0, value.length);
+
+ int daysIntoCE = 0;
+ for (int i = 0; i < value.length; i++)
+ daysIntoCE |= ((value[i] & 0xFF) << (8 * i));
+
+ // Theoretically should never encounter a value that is outside of the valid date range
+ if (daysIntoCE < 0)
+ throwInvalidTDS();
+
+ return daysIntoCE;
+ }
+
+ // Scale multipliers used to convert variable-scaled temporal values to a fixed 100ns scale.
+ //
+ // Using this array is measurably faster than using Math.pow(10, ...)
+ private final static int[] SCALED_MULTIPLIERS = {10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};
+
+ private long readNanosSinceMidnight(int scale) throws SQLServerException {
+ assert 0 <= scale && scale <= TDS.MAX_FRACTIONAL_SECONDS_SCALE;
+
+ byte value[] = new byte[TDS.nanosSinceMidnightLength(scale)];
+ readBytes(value, 0, value.length);
+
+ long hundredNanosSinceMidnight = 0;
+ for (int i = 0; i < value.length; i++)
+ hundredNanosSinceMidnight |= (value[i] & 0xFFL) << (8 * i);
+
+ hundredNanosSinceMidnight *= SCALED_MULTIPLIERS[scale];
+
+ if (!(0 <= hundredNanosSinceMidnight && hundredNanosSinceMidnight < Nanos.PER_DAY / 100))
+ throwInvalidTDS();
+
+ return 100 * hundredNanosSinceMidnight;
+ }
+
+ final static String guidTemplate = "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN";
+
+ final Object readGUID(int valueLength,
+ JDBCType jdbcType,
+ StreamType streamType) throws SQLServerException {
+ // GUIDs must be exactly 16 bytes
+ if (16 != valueLength)
+ throwInvalidTDS();
+
+ // Read in the GUID's binary value
+ byte guid[] = new byte[16];
+ readBytes(guid, 0, 16);
+
+ switch (jdbcType) {
+ case CHAR:
+ case VARCHAR:
+ case LONGVARCHAR:
+ case GUID: {
+ StringBuilder sb = new StringBuilder(guidTemplate.length());
+ for (int i = 0; i < 4; i++) {
+ sb.append(Util.hexChars[(guid[3 - i] & 0xF0) >> 4]);
+ sb.append(Util.hexChars[guid[3 - i] & 0x0F]);
+ }
+ sb.append('-');
+ for (int i = 0; i < 2; i++) {
+ sb.append(Util.hexChars[(guid[5 - i] & 0xF0) >> 4]);
+ sb.append(Util.hexChars[guid[5 - i] & 0x0F]);
+ }
+ sb.append('-');
+ for (int i = 0; i < 2; i++) {
+ sb.append(Util.hexChars[(guid[7 - i] & 0xF0) >> 4]);
+ sb.append(Util.hexChars[guid[7 - i] & 0x0F]);
+ }
+ sb.append('-');
+ for (int i = 0; i < 2; i++) {
+ sb.append(Util.hexChars[(guid[8 + i] & 0xF0) >> 4]);
+ sb.append(Util.hexChars[guid[8 + i] & 0x0F]);
+ }
+ sb.append('-');
+ for (int i = 0; i < 6; i++) {
+ sb.append(Util.hexChars[(guid[10 + i] & 0xF0) >> 4]);
+ sb.append(Util.hexChars[guid[10 + i] & 0x0F]);
+ }
+
+ try {
+ return DDC.convertStringToObject(sb.toString(), Encoding.UNICODE.charset(), jdbcType, streamType);
+ }
+ catch (UnsupportedEncodingException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
+ throw new SQLServerException(form.format(new Object[] {"UNIQUEIDENTIFIER", jdbcType}), null, 0, e);
+ }
+ }
+
+ default: {
+ if (StreamType.BINARY == streamType || StreamType.ASCII == streamType)
+ return new ByteArrayInputStream(guid);
+
+ return guid;
+ }
+ }
+ }
+
+ /**
+ * Reads a multi-part table name from TDS and returns it as an array of Strings.
+ */
+ final SQLIdentifier readSQLIdentifier() throws SQLServerException {
+ // Multi-part names should have between 1 and 4 parts
+ int numParts = readUnsignedByte();
+ if (!(1 <= numParts && numParts <= 4))
+ throwInvalidTDS();
+
+ // Each part is a length-prefixed Unicode string
+ String[] nameParts = new String[numParts];
+ for (int i = 0; i < numParts; i++)
+ nameParts[i] = readUnicodeString(readUnsignedShort());
+
+ // Build the identifier from the name parts
+ SQLIdentifier identifier = new SQLIdentifier();
+ identifier.setObjectName(nameParts[numParts - 1]);
+ if (numParts >= 2)
+ identifier.setSchemaName(nameParts[numParts - 2]);
+ if (numParts >= 3)
+ identifier.setDatabaseName(nameParts[numParts - 3]);
+ if (4 == numParts)
+ identifier.setServerName(nameParts[numParts - 4]);
+
+ return identifier;
+ }
+
+ final SQLCollation readCollation() throws SQLServerException {
+ SQLCollation collation = null;
+
+ try {
+ collation = new SQLCollation(this);
+ }
+ catch (UnsupportedEncodingException e) {
+ con.terminate(SQLServerException.DRIVER_ERROR_INVALID_TDS, e.getMessage(), e);
+ // not reached
+ }
+
+ return collation;
+ }
+
+ final void skip(int bytesToSkip) throws SQLServerException {
+ assert bytesToSkip >= 0;
+
+ while (bytesToSkip > 0) {
+ // Ensure that we have a packet to read from.
+ if (!ensurePayload())
+ throwInvalidTDS();
+
+ int bytesSkipped = bytesToSkip;
+ if (bytesSkipped > currentPacket.payloadLength - payloadOffset)
+ bytesSkipped = currentPacket.payloadLength - payloadOffset;
+
+ bytesToSkip -= bytesSkipped;
+ payloadOffset += bytesSkipped;
+ }
+ }
+
+ final void TryProcessFeatureExtAck(boolean featureExtAckReceived) throws SQLServerException {
+ // in case of redirection, do not check if TDS_FEATURE_EXTENSION_ACK is received or not.
+ if (null != this.con.getRoutingInfo()) {
+ return;
+ }
+
+ if (isColumnEncryptionSettingEnabled() && !featureExtAckReceived)
+ throw new SQLServerException(this, SQLServerException.getErrString("R_AE_NotSupportedByServer"), null, 0, false);
+ }
+}
+
+/**
+ * Timer for use with Commands that support a timeout.
+ *
+ * Once started, the timer runs for the prescribed number of seconds unless stopped. If the timer runs out, it interrupts its associated Command with
+ * a reason like "timed out".
+ */
+final class TimeoutTimer implements Runnable {
+ private final int timeoutSeconds;
+ private final TDSCommand command;
+ private Thread timerThread;
+ private volatile boolean canceled = false;
+
+ TimeoutTimer(int timeoutSeconds,
+ TDSCommand command) {
+ assert timeoutSeconds > 0;
+ assert null != command;
+
+ this.timeoutSeconds = timeoutSeconds;
+ this.command = command;
+ }
+
+ final void start() {
+ timerThread = new Thread(this);
+ timerThread.setDaemon(true);
+ timerThread.start();
+ }
+
+ final void stop() {
+ canceled = true;
+ timerThread.interrupt();
+ }
+
+ public void run() {
+ int secondsRemaining = timeoutSeconds;
+ try {
+ // Poll every second while time is left on the timer.
+ // Return if/when the timer is canceled.
+ do {
+ if (canceled)
+ return;
+
+ Thread.sleep(1000);
+ }
+ while (--secondsRemaining > 0);
+ }
+ catch (InterruptedException e) {
+ return;
+ }
+
+ // If the timer wasn't canceled before it ran out of
+ // time then interrupt the registered command.
+ try {
+ command.interrupt(SQLServerException.getErrString("R_queryTimedOut"));
+ }
+ catch (SQLServerException e) {
+ // Unfortunately, there's nothing we can do if we
+ // fail to time out the request. There is no way
+ // to report back what happened.
+ command.log(Level.FINE, "Command could not be timed out. Reason: " + e.getMessage());
+ }
+ }
+}
+
+/**
+ * TDSCommand encapsulates an interruptable TDS conversation.
+ *
+ * A conversation may consist of one or more TDS request and response messages. A command may be interrupted at any point, from any thread, and for
+ * any reason. Acknowledgement and handling of an interrupt is fully encapsulated by this class.
+ *
+ * Commands may be created with an optional timeout (in seconds). Timeouts are implemented as a form of interrupt, where the interrupt event occurs
+ * when the timeout period expires. Currently, only the time to receive the response from the channel counts against the timeout period.
+ */
+abstract class TDSCommand {
+ abstract boolean doExecute() throws SQLServerException;
+
+ final static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Command");
+ private final String logContext;
+
+ final String getLogContext() {
+ return logContext;
+ }
+
+ private String traceID;
+
+ final public String toString() {
+ if (traceID == null)
+ traceID = "TDSCommand@" + Integer.toHexString(hashCode()) + " (" + logContext + ")";
+ return traceID;
+ }
+
+ final void log(Level level,
+ String message) {
+ logger.log(level, toString() + ": " + message);
+ }
+
+ // Optional timer that is set if the command was created with a non-zero timeout period.
+ // When the timer expires, the command is interrupted.
+ private final TimeoutTimer timeoutTimer;
+
+ // TDS channel accessors
+ // These are set/reset at command execution time.
+ // Volatile ensures visibility to execution thread and interrupt thread
+ private volatile TDSWriter tdsWriter;
+ private volatile TDSReader tdsReader;
+
+ // Lock to ensure atomicity when manipulating more than one of the following
+ // shared interrupt state variables below.
+ private final Object interruptLock = new Object();
+
+ // Flag set when this command starts execution, indicating that it is
+ // ready to respond to interrupts; and cleared when its last response packet is
+ // received, indicating that it is no longer able to respond to interrupts.
+ // If the command is interrupted after interrupts have been disabled, then the
+ // interrupt is ignored.
+ private volatile boolean interruptsEnabled = false;
+
+ // Flag set to indicate that an interrupt has happened.
+ private volatile boolean wasInterrupted = false;
+
+ private boolean wasInterrupted() {
+ return wasInterrupted;
+ }
+
+ // The reason for the interrupt.
+ private volatile String interruptReason = null;
+
+ // Flag set when this command's request to the server is complete.
+ // If a command is interrupted before its request is complete, it is the executing
+ // thread's responsibility to send the attention signal to the server if necessary.
+ // After the request is complete, the interrupting thread must send the attention signal.
+ private volatile boolean requestComplete;
+
+ // Flag set when an attention signal has been sent to the server, indicating that a
+ // TDS packet containing the attention ack message is to be expected in the response.
+ // This flag is cleared after the attention ack message has been received and processed.
+ private volatile boolean attentionPending = false;
+
+ boolean attentionPending() {
+ return attentionPending;
+ }
+
+ // Flag set when this command's response has been processed. Until this flag is set,
+ // there may be unprocessed information left in the response, such as transaction
+ // ENVCHANGE notifications.
+ private volatile boolean processedResponse;
+
+ // Flag set when this command's response is ready to be read from the server and cleared
+ // after its response has been received, but not necessarily processed, up to and including
+ // any attention ack. The command's response is read either on demand as it is processed,
+ // or by detaching.
+ private volatile boolean readingResponse;
+
+ final boolean readingResponse() {
+ return readingResponse;
+ }
+
+ /**
+ * Creates this command with an optional timeout.
+ *
+ * @param logContext
+ * the string describing the context for this command.
+ * @param timeoutSeconds
+ * (optional) the time before which the command must complete before it is interrupted. A value of 0 means no timeout.
+ */
+ TDSCommand(String logContext,
+ int timeoutSeconds) {
+ this.logContext = logContext;
+ this.timeoutTimer = (timeoutSeconds > 0) ? (new TimeoutTimer(timeoutSeconds, this)) : null;
+ }
+
+ /**
+ * Executes this command.
+ *
+ * @param tdsWriter
+ * @param tdsReader
+ * @throws SQLServerException
+ * on any error executing the command, including cancel or timeout.
+ */
+
+ boolean execute(TDSWriter tdsWriter,
+ TDSReader tdsReader) throws SQLServerException {
+ this.tdsWriter = tdsWriter;
+ this.tdsReader = tdsReader;
+ assert null != tdsReader;
+ try {
+ return doExecute(); // Derived classes implement the execution details
+ }
+ catch (SQLServerException e) {
+ try {
+ // If command execution threw an exception for any reason before the request
+ // was complete then interrupt the command (it may already be interrupted)
+ // and close it out to ensure that any response to the error/interrupt
+ // is processed.
+ // no point in trying to cancel on a closed connection.
+ if (!requestComplete && !tdsReader.getConnection().isClosed()) {
+ interrupt(e.getMessage());
+ onRequestComplete();
+ close();
+ }
+ }
+ catch (SQLServerException interruptException) {
+ if (logger.isLoggable(Level.FINE))
+ logger.fine(this.toString() + ": Ignoring error in sending attention: " + interruptException.getMessage());
+ }
+ // throw the original exception even if trying to interrupt fails even in the case
+ // of trying to send a cancel to the server.
+ throw e;
+ }
+ }
+
+ /**
+ * Provides sane default response handling.
+ *
+ * This default implementation just consumes everything in the response message.
+ */
+ void processResponse(TDSReader tdsReader) throws SQLServerException {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Processing response");
+ try {
+ TDSParser.parse(tdsReader, getLogContext());
+ }
+ catch (SQLServerException e) {
+ if (SQLServerException.DRIVER_ERROR_FROM_DATABASE != e.getDriverErrorCode())
+ throw e;
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Ignoring error from database: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Clears this command from the TDS channel so that another command can execute.
+ *
+ * This method does not process the response. It just buffers it in memory, including any attention ack that may be present.
+ */
+ final void detach() throws SQLServerException {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": detaching...");
+
+ // Read any remaining response packets from the server.
+ // This operation may be timed out or cancelled from another thread.
+ while (tdsReader.readPacket())
+ ;
+
+ // Postcondition: the entire response has been read
+ assert !readingResponse;
+ }
+
+ final void close() {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": closing...");
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": processing response...");
+
+ while (!processedResponse) {
+ try {
+ processResponse(tdsReader);
+ }
+ catch (SQLServerException e) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": close ignoring error processing response: " + e.getMessage());
+
+ if (tdsReader.getConnection().isSessionUnAvailable()) {
+ processedResponse = true;
+ attentionPending = false;
+ }
+ }
+ }
+
+ if (attentionPending) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": processing attention ack...");
+
+ try {
+ TDSParser.parse(tdsReader, "attention ack");
+ }
+ catch (SQLServerException e) {
+ if (tdsReader.getConnection().isSessionUnAvailable()) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": giving up on attention ack after connection closed by exception: " + e);
+ attentionPending = false;
+ }
+ else {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": ignored exception: " + e);
+ }
+ }
+
+ // If the parser returns to us without processing the expected attention ack,
+ // then assume that no attention ack is forthcoming from the server and
+ // terminate the connection to prevent any other command from executing.
+ if (attentionPending) {
+ logger.severe(this + ": expected attn ack missing or not processed; terminating connection...");
+
+ try {
+ tdsReader.throwInvalidTDS();
+ }
+ catch (SQLServerException e) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": ignored expected invalid TDS exception: " + e);
+
+ assert tdsReader.getConnection().isSessionUnAvailable();
+ attentionPending = false;
+ }
+ }
+ }
+
+ // Postcondition:
+ // Response has been processed and there is no attention pending -- the command is closed.
+ // Of course the connection may be closed too, but the command is done regardless...
+ assert processedResponse && !attentionPending;
+ }
+
+ /**
+ * Interrupts execution of this command, typically from another thread.
+ *
+ * Only the first interrupt has any effect. Subsequent interrupts are ignored. Interrupts are also ignored until enabled. If interrupting the
+ * command requires an attention signal to be sent to the server, then this method sends that signal if the command's request is already complete.
+ *
+ * Signalling mechanism is "fire and forget". It is up to either the execution thread or, possibly, a detaching thread, to ensure that any pending
+ * attention ack later will be received and processed.
+ *
+ * @param reason
+ * the reason for the interrupt, typically cancel or timeout.
+ * @throws SQLServerException
+ * if interrupting fails for some reason. This call does not throw the reason for the interrupt.
+ */
+ void interrupt(String reason) throws SQLServerException {
+ // Multiple, possibly simultaneous, interrupts may occur.
+ // Only the first one should be recognized and acted upon.
+ synchronized (interruptLock) {
+ if (interruptsEnabled && !wasInterrupted()) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": Raising interrupt for reason:" + reason);
+
+ wasInterrupted = true;
+ interruptReason = reason;
+ if (requestComplete)
+ attentionPending = tdsWriter.sendAttention();
+
+ }
+ }
+ }
+
+ private boolean interruptChecked = false;
+
+ /**
+ * Checks once whether an interrupt has occurred, and, if it has, throws an exception indicating that fact.
+ *
+ * Any calls after the first to check for interrupts are no-ops. This method is called periodically from this command's execution thread to notify
+ * the app when an interrupt has happened.
+ *
+ * It should only be called from places where consistent behavior can be ensured after the exception is thrown. For example, it should not be
+ * called at arbitrary times while processing the response, as doing so could leave the response token stream in an inconsistent state. Currently,
+ * response processing only checks for interrupts after every result or OUT parameter.
+ *
+ * Request processing checks for interrupts before writing each packet.
+ *
+ * @throws SQLServerException
+ * if this command was interrupted, throws the reason for the interrupt.
+ */
+ final void checkForInterrupt() throws SQLServerException {
+ // Throw an exception with the interrupt reason if this command was interrupted.
+ // Note that the interrupt reason may be null. Checking whether the
+ // command was interrupted does not require the interrupt lock since only one
+ // of the shared state variables is being manipulated; interruptChecked is not
+ // shared with the interrupt thread.
+ if (wasInterrupted() && !interruptChecked) {
+ interruptChecked = true;
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": throwing interrupt exception, reason: " + interruptReason);
+
+ throw new SQLServerException(interruptReason, SQLState.STATEMENT_CANCELED, DriverError.NOT_SET, null);
+ }
+ }
+
+ /**
+ * Notifies this command when no more request packets are to be sent to the server.
+ *
+ * After the last packet has been sent, the only way to interrupt the request is to send an attention signal from the interrupt() method.
+ *
+ * Note that this method is called when the request completes normally (last packet sent with EOM bit) or when it completes after being
+ * interrupted (0 or more packets sent with no EOM bit).
+ */
+ final void onRequestComplete() throws SQLServerException {
+ assert !requestComplete;
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": request complete");
+
+ synchronized (interruptLock) {
+ requestComplete = true;
+
+ // If this command was interrupted before its request was complete then
+ // we need to send the attention signal if necessary. Note that if no
+ // attention signal is sent (i.e. no packets were sent to the server before
+ // the interrupt happened), then don't expect an attention ack or any
+ // other response.
+ if (!interruptsEnabled) {
+ assert !attentionPending;
+ assert !processedResponse;
+ assert !readingResponse;
+ processedResponse = true;
+ }
+ else if (wasInterrupted()) {
+
+ if (tdsWriter.isEOMSent()) {
+ attentionPending = tdsWriter.sendAttention();
+ readingResponse = attentionPending;
+ }
+ else {
+ assert !attentionPending;
+ readingResponse = tdsWriter.ignoreMessage();
+ }
+
+ processedResponse = !readingResponse;
+ }
+ else {
+ assert !attentionPending;
+ assert !processedResponse;
+ readingResponse = true;
+ }
+ }
+ }
+
+ /**
+ * Notifies this command when the last packet of the response has been read.
+ *
+ * When the last packet is read, interrupts are disabled. If an interrupt occurred prior to disabling that caused an attention signal to be sent
+ * to the server, then an extra packet containing the attention ack is read.
+ *
+ * This ensures that on return from this method, the TDS channel is clear of all response packets for this command.
+ *
+ * Note that this method is called for the attention ack message itself as well, so we need to be sure not to expect more than one attention
+ * ack...
+ */
+ final void onResponseEOM() throws SQLServerException {
+ boolean readAttentionAck = false;
+
+ // Atomically disable interrupts and check for a previous interrupt requiring
+ // an attention ack to be read.
+ synchronized (interruptLock) {
+ if (interruptsEnabled) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": disabling interrupts");
+
+ // Determine whether we still need to read the attention ack packet.
+ //
+ // When a command is interrupted, Yukon (and later) always sends a response
+ // containing at least a DONE(ERROR) token before it sends the attention ack,
+ // even if the command's request was not complete.
+ readAttentionAck = attentionPending;
+
+ interruptsEnabled = false;
+ }
+ }
+
+ // If an attention packet needs to be read then read it. This should
+ // be done outside of the interrupt lock to avoid unnecessarily blocking
+ // interrupting threads. Note that it is remotely possible that the call
+ // to readPacket won't actually read anything if the attention ack was
+ // already read by TDSCommand.detach(), in which case this method could
+ // be called from multiple threads, leading to a benign race to clear the
+ // readingResponse flag.
+ if (readAttentionAck)
+ tdsReader.readPacket();
+
+ readingResponse = false;
+ }
+
+ /**
+ * Notifies this command when the end of its response token stream has been reached.
+ *
+ * After this call, we are guaranteed that tokens in the response have been processed.
+ */
+ final void onTokenEOF() {
+ processedResponse = true;
+ }
+
+ /**
+ * Notifies this command when the attention ack (a DONE token with a special flag) has been processed.
+ *
+ * After this call, the attention ack should no longer be expected.
+ */
+ final void onAttentionAck() {
+ assert attentionPending;
+ attentionPending = false;
+ }
+
+ /**
+ * Starts sending this command's TDS request to the server.
+ *
+ * @param tdsMessageType
+ * the type of the TDS message (RPC, QUERY, etc.)
+ * @return the TDS writer used to write the request.
+ * @throws SQLServerException
+ * on any error, including acknowledgement of an interrupt.
+ */
+ final TDSWriter startRequest(byte tdsMessageType) throws SQLServerException {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": starting request...");
+
+ // Start this command's request message
+ try {
+ tdsWriter.startMessage(this, tdsMessageType);
+ }
+ catch (SQLServerException e) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": starting request: exception: " + e.getMessage());
+
+ throw e;
+ }
+
+ // (Re)initialize this command's interrupt state for its current execution.
+ // To ensure atomically consistent behavior, do not leave the interrupt lock
+ // until interrupts have been (re)enabled.
+ synchronized (interruptLock) {
+ requestComplete = false;
+ readingResponse = false;
+ processedResponse = false;
+ attentionPending = false;
+ wasInterrupted = false;
+ interruptReason = null;
+ interruptsEnabled = true;
+ }
+
+ return tdsWriter;
+ }
+
+ /**
+ * Finishes the TDS request and then starts reading the TDS response from the server.
+ *
+ * @return the TDS reader used to read the response.
+ * @throws SQLServerException
+ * if there is any kind of error.
+ */
+ final TDSReader startResponse() throws SQLServerException {
+ return startResponse(false);
+ }
+
+ final TDSReader startResponse(boolean isAdaptive) throws SQLServerException {
+ // Finish sending the request message. If this command was interrupted
+ // at any point before endMessage() returns, then endMessage() throws an
+ // exception with the reason for the interrupt. Request interrupts
+ // are disabled by the time endMessage() returns.
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": finishing request");
+
+ try {
+ tdsWriter.endMessage();
+ }
+ catch (SQLServerException e) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this + ": finishing request: endMessage threw exception: " + e.getMessage());
+
+ throw e;
+ }
+
+ // If command execution is subject to timeout then start timing until
+ // the server returns the first response packet.
+ if (null != timeoutTimer) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Starting timer...");
+
+ timeoutTimer.start();
+ }
+
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Reading response...");
+
+ try {
+ // Wait for the server to execute the request and read the first packet
+ // (responseBuffering=adaptive) or all packets (responseBuffering=full)
+ // of the response.
+ if (isAdaptive) {
+ tdsReader.readPacket();
+ }
+ else {
+ while (tdsReader.readPacket())
+ ;
+ }
+ }
+ catch (SQLServerException e) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Exception reading response: " + e.getMessage());
+
+ throw e;
+ }
+ finally {
+ // If command execution was subject to timeout then stop timing as soon
+ // as the server returns the first response packet or errors out.
+ if (null != timeoutTimer) {
+ if (logger.isLoggable(Level.FINEST))
+ logger.finest(this.toString() + ": Stopping timer...");
+
+ timeoutTimer.stop();
+ }
+ }
+
+ return tdsReader;
+ }
+}
+
+/**
+ * UninterruptableTDSCommand encapsulates an uninterruptable TDS conversation.
+ *
+ * TDSCommands have interruptability built in. However, some TDSCommands such as DTC commands, connection commands, cursor close and prepared
+ * statement handle close shouldn't be interruptable. This class provides a base implementation for such commands.
+ */
+abstract class UninterruptableTDSCommand extends TDSCommand {
+ UninterruptableTDSCommand(String logContext) {
+ super(logContext, 0);
+ }
+
+ final void interrupt(String reason) throws SQLServerException {
+ // Interrupting an uninterruptable command is a no-op. That is,
+ // it can happen, but it should have no effect.
+ logger.finest(toString() + " Ignoring interrupt of uninterruptable TDS command; Reason:" + reason);
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkRecord.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkRecord.java
index f94f2d16d..9589cc239 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkRecord.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerBulkRecord.java
@@ -1,96 +1,90 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerBulkRecord.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.util.Set;
/**
- * The ISQLServerBulkRecord interface can be used to create classes that read in data from any source (such as a file)
- * and allow a SQLServerBulkCopy class to write the data to SQL Server tables.
+ * The ISQLServerBulkRecord interface can be used to create classes that read in data from any source (such as a file) and allow a SQLServerBulkCopy
+ * class to write the data to SQL Server tables.
*/
-public interface ISQLServerBulkRecord
-{
+public interface ISQLServerBulkRecord {
/**
* Get the ordinals for each of the columns represented in this data record.
*
* @return Set of ordinals for the columns.
*/
public Set getColumnOrdinals();
-
+
/**
* Get the name of the given column.
*
- * @param column Column ordinal
+ * @param column
+ * Column ordinal
* @return Name of the column
*/
public String getColumnName(int column);
-
+
/**
* Get the JDBC data type of the given column.
*
- * @param column Column ordinal
+ * @param column
+ * Column ordinal
* @return JDBC data type of the column
*/
public int getColumnType(int column);
-
+
/**
* Get the precision for the given column.
*
- * @param column Column ordinal
+ * @param column
+ * Column ordinal
* @return Precision of the column
*/
public int getPrecision(int column);
-
+
/**
* Get the scale for the given column.
*
- * @param column Column ordinal
+ * @param column
+ * Column ordinal
* @return Scale of the column
*/
public int getScale(int column);
-
+
/**
* Indicates whether the column represents an identity column.
*
- * @param column Column ordinal
+ * @param column
+ * Column ordinal
* @return True if the column is an identity column; false otherwise.
*/
public boolean isAutoIncrement(int column);
-
+
/**
* Gets the data for the current row as an array of Objects.
- *
- * Each Object must match the Java language Type that is used to represent the indicated
- * JDBC data type for the given column. For more information, see
- * 'Understanding the JDBC Driver Data Types' for the appropriate mappings.
+ *
+ * Each Object must match the Java language Type that is used to represent the indicated JDBC data type for the given column. For more
+ * information, see 'Understanding the JDBC Driver Data Types' for the appropriate mappings.
*
* @return The data for the row.
- * @throws SQLServerException If there are any errors in obtaining the data.
+ * @throws SQLServerException
+ * If there are any errors in obtaining the data.
*/
public Object[] getRowData() throws SQLServerException;
-
+
/**
* Advances to the next data row.
*
* @return True if rows are available; false if there are no more rows
- * @throws SQLServerException If there are any errors in advancing to the next row.
+ * @throws SQLServerException
+ * If there are any errors in advancing to the next row.
*/
public boolean next() throws SQLServerException;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement.java
index 071cfeaa8..280f66f1a 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerCallableStatement.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.sql.SQLException;
@@ -26,37 +15,42 @@
* This interface is implemented by SQLServerCallableStatement Class.
*
*/
-public interface ISQLServerCallableStatement extends java.sql.CallableStatement, ISQLServerPreparedStatement
-{
- /**
- * Sets parameter parameterName to DateTimeOffset x
- * @param parameterName the name of the parameter
- * @param x DateTimeOffset value
- * @throws SQLException if parameterName does not correspond to a named parameter;
- * if the driver can detect that a data conversion error could occur;
- * if a database access error occurs or
- * this method is called on a closed CallableStatement
- */
- public void setDateTimeOffset(String parameterName, microsoft.sql.DateTimeOffset x) throws SQLException;
-
- /**
+public interface ISQLServerCallableStatement extends java.sql.CallableStatement, ISQLServerPreparedStatement {
+ /**
+ * Sets parameter parameterName to DateTimeOffset x
+ *
+ * @param parameterName
+ * the name of the parameter
+ * @param x
+ * DateTimeOffset value
+ * @throws SQLException
+ * if parameterName does not correspond to a named parameter; if the driver can detect that a data conversion error could occur; if a
+ * database access error occurs or this method is called on a closed CallableStatement
+ */
+ public void setDateTimeOffset(String parameterName,
+ microsoft.sql.DateTimeOffset x) throws SQLException;
+
+ /**
* Gets the DateTimeOffset value of parameter with index parameterIndex
- * @param parameterIndex the first parameter is 1, the second is 2, and so on
+ *
+ * @param parameterIndex
+ * the first parameter is 1, the second is 2, and so on
* @return DateTimeOffset value
- * @throws SQLException if parameterIndex is out of range;
- * if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @throws SQLException
+ * if parameterIndex is out of range; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
public microsoft.sql.DateTimeOffset getDateTimeOffset(int parameterIndex) throws SQLException;
-
- /**
+
+ /**
* Gets the DateTimeOffset value of parameter with name parameterName
- * @param parameterName the name of the parameter
+ *
+ * @param parameterName
+ * the name of the parameter
* @return DateTimeOffset value
- * @throws SQLException if parameterName does not correspond to a named parameter;
- * if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @throws SQLException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
public microsoft.sql.DateTimeOffset getDateTimeOffset(String parameterName) throws SQLException;
}
-
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement42.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement42.java
index ef7bd215f..4684ec4ac 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement42.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerCallableStatement42.java
@@ -1,21 +1,10 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerCallableStatement42.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
@@ -25,59 +14,104 @@
* This interface requires all the CallableStatement methods including those are specific to JDBC 4.2
*
*/
-public interface ISQLServerCallableStatement42 extends ISQLServerCallableStatement, ISQLServerPreparedStatement42{
+public interface ISQLServerCallableStatement42 extends ISQLServerCallableStatement, ISQLServerPreparedStatement42 {
- public void registerOutParameter(int index, SQLType sqlType) throws SQLServerException;
+ public void registerOutParameter(int index,
+ SQLType sqlType) throws SQLServerException;
- public void registerOutParameter (int index, SQLType sqlType, String typeName) throws SQLServerException;
+ public void registerOutParameter(int index,
+ SQLType sqlType,
+ String typeName) throws SQLServerException;
- public void registerOutParameter(int index, SQLType sqlType, int scale) throws SQLServerException;
+ public void registerOutParameter(int index,
+ SQLType sqlType,
+ int scale) throws SQLServerException;
- /**
- * Registers the parameter in ordinal position index to be of JDBC type sqlType. All OUT parameters must be registered before a stored procedure is executed.
- *
- * The JDBC type specified by sqlType for an OUT parameter determines the Java type that must be used in the get method to read the value of that parameter.
- *
- * @param index the first parameter is 1, the second is 2,...
- * @param sqlType the JDBC type code defined by SQLType to use to register the OUT Parameter.
- * @param precision the sum of the desired number of digits to the left and right of the decimal point. It must be greater than or equal to zero.
- * @param scale the desired number of digits to the right of the decimal point. It must be greater than or equal to zero.
- * @throws SQLServerException If any errors occur.
- */
- public void registerOutParameter(int index, SQLType sqlType, int precision, int scale) throws SQLServerException;
+ /**
+ * Registers the parameter in ordinal position index to be of JDBC type sqlType. All OUT parameters must be registered before a stored procedure
+ * is executed.
+ *
+ * The JDBC type specified by sqlType for an OUT parameter determines the Java type that must be used in the get method to read the value of that
+ * parameter.
+ *
+ * @param index
+ * the first parameter is 1, the second is 2,...
+ * @param sqlType
+ * the JDBC type code defined by SQLType to use to register the OUT Parameter.
+ * @param precision
+ * the sum of the desired number of digits to the left and right of the decimal point. It must be greater than or equal to zero.
+ * @param scale
+ * the desired number of digits to the right of the decimal point. It must be greater than or equal to zero.
+ * @throws SQLServerException
+ * If any errors occur.
+ */
+ public void registerOutParameter(int index,
+ SQLType sqlType,
+ int precision,
+ int scale) throws SQLServerException;
- public void setObject(String sCol, Object obj, SQLType jdbcType) throws SQLServerException;
+ public void setObject(String sCol,
+ Object obj,
+ SQLType jdbcType) throws SQLServerException;
- public void setObject(String sCol, Object obj, SQLType jdbcType, int scale) throws SQLServerException;
+ public void setObject(String sCol,
+ Object obj,
+ SQLType jdbcType,
+ int scale) throws SQLServerException;
- /**
- * Sets the value of the designated parameter with the given object.
- *
- * @param sCol the name of the parameter
- * @param obj the object containing the input parameter value
- * @param jdbcType the SQL type to be sent to the database
- * @param scale scale the desired number of digits to the right of the decimal point. It must be greater than or equal to zero.
- * @param forceEncrypt true if force encryption is on, false if force encryption is off
- * @throws SQLServerException If any errors occur.
- */
- public void setObject(String sCol, Object obj, SQLType jdbcType, int scale, boolean forceEncrypt) throws SQLServerException;
+ /**
+ * Sets the value of the designated parameter with the given object.
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param obj
+ * the object containing the input parameter value
+ * @param jdbcType
+ * the SQL type to be sent to the database
+ * @param scale
+ * scale the desired number of digits to the right of the decimal point. It must be greater than or equal to zero.
+ * @param forceEncrypt
+ * true if force encryption is on, false if force encryption is off
+ * @throws SQLServerException
+ * If any errors occur.
+ */
+ public void setObject(String sCol,
+ Object obj,
+ SQLType jdbcType,
+ int scale,
+ boolean forceEncrypt) throws SQLServerException;
- public void registerOutParameter(String parameterName, SQLType sqlType, String typeName) throws SQLServerException;
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType,
+ String typeName) throws SQLServerException;
- public void registerOutParameter(String parameterName, SQLType sqlType, int scale) throws SQLServerException;
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType,
+ int scale) throws SQLServerException;
- /**
- * Registers the parameter in ordinal position index to be of JDBC type sqlType. All OUT parameters must be registered before a stored procedure is executed.
- *
- * The JDBC type specified by sqlType for an OUT parameter determines the Java type that must be used in the get method to read the value of that parameter.
- *
- * @param parameterName the name of the parameter
- * @param sqlType the JDBC type code defined by SQLType to use to register the OUT Parameter.
- * @param precision the sum of the desired number of digits to the left and right of the decimal point. It must be greater than or equal to zero.
- * @param scale the desired number of digits to the right of the decimal point. It must be greater than or equal to zero.
- * @throws SQLServerException If any errors occur.
- */
- public void registerOutParameter(String parameterName, SQLType sqlType, int precision, int scale) throws SQLServerException;
+ /**
+ * Registers the parameter in ordinal position index to be of JDBC type sqlType. All OUT parameters must be registered before a stored procedure
+ * is executed.
+ *
+ * The JDBC type specified by sqlType for an OUT parameter determines the Java type that must be used in the get method to read the value of that
+ * parameter.
+ *
+ * @param parameterName
+ * the name of the parameter
+ * @param sqlType
+ * the JDBC type code defined by SQLType to use to register the OUT Parameter.
+ * @param precision
+ * the sum of the desired number of digits to the left and right of the decimal point. It must be greater than or equal to zero.
+ * @param scale
+ * the desired number of digits to the right of the decimal point. It must be greater than or equal to zero.
+ * @throws SQLServerException
+ * If any errors occur.
+ */
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType,
+ int precision,
+ int scale) throws SQLServerException;
- public void registerOutParameter(String parameterName, SQLType sqlType) throws SQLServerException;
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType) throws SQLServerException;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
index 302ab941f..57ea5e94a 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
@@ -1,42 +1,32 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerConnection.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
+
import java.sql.SQLException;
import java.util.UUID;
/**
-*
-* This interface is implemented by SQLServerConnection Class.
-*/
-public interface ISQLServerConnection extends java.sql.Connection
-{
+ *
+ * This interface is implemented by SQLServerConnection Class.
+ */
+public interface ISQLServerConnection extends java.sql.Connection {
// Transaction types.
- // TRANSACTION_SNAPSHOT corresponds to -> SET TRANSACTION ISOLATION LEVEL SNAPSHOT
- public final static int TRANSACTION_SNAPSHOT = 0x1000;
-
- /**
- * Gets the connection ID of the most recent connection attempt, regardless of whether the attempt succeeded or failed.
- * @return 16-byte GUID representing the connection ID of the most recent connection attempt. Or, NULL if there is a
- * failure after the connection request is initiated and the pre-login handshake.
- * @throws SQLException If any errors occur.
- */
- public UUID getClientConnectionId() throws SQLException;
-}
+ // TRANSACTION_SNAPSHOT corresponds to -> SET TRANSACTION ISOLATION LEVEL SNAPSHOT
+ public final static int TRANSACTION_SNAPSHOT = 0x1000;
+ /**
+ * Gets the connection ID of the most recent connection attempt, regardless of whether the attempt succeeded or failed.
+ *
+ * @return 16-byte GUID representing the connection ID of the most recent connection attempt. Or, NULL if there is a failure after the connection
+ * request is initiated and the pre-login handshake.
+ * @throws SQLException
+ * If any errors occur.
+ */
+ public UUID getClientConnectionId() throws SQLException;
+}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataRecord.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataRecord.java
index 98c9ab1ce..79e109b7c 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataRecord.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataRecord.java
@@ -1,61 +1,49 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerDataRecord.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
/**
- * The ISQLServerDataRecord interface can be used to create classes that read in data from any source (such as a file)
- * and allow a structured type to be sent to SQL Server tables.
+ * The ISQLServerDataRecord interface can be used to create classes that read in data from any source (such as a file) and allow a structured type to
+ * be sent to SQL Server tables.
*/
-public interface ISQLServerDataRecord
-{
- /**
- * Get the column meta data
- * @param column the first column is 1, the second is 2, and so on
- * @return SQLServerMetaData of column
- */
- public SQLServerMetaData getColumnMetaData(int column);
-
+public interface ISQLServerDataRecord {
+ /**
+ * Get the column meta data
+ *
+ * @param column
+ * the first column is 1, the second is 2, and so on
+ * @return SQLServerMetaData of column
+ */
+ public SQLServerMetaData getColumnMetaData(int column);
+
/**
* Get the column count.
*
* @return Set of ordinals for the columns.
*/
public int getColumnCount();
-
+
/**
* Gets the data for the current row as an array of Objects.
- *
- * Each Object must match the Java language Type that is used to represent the indicated
- * JDBC data type for the given column. For more information, see
- * 'Understanding the JDBC Driver Data Types' for the appropriate mappings.
+ *
+ * Each Object must match the Java language Type that is used to represent the indicated JDBC data type for the given column. For more
+ * information, see 'Understanding the JDBC Driver Data Types' for the appropriate mappings.
*
* @return The data for the row.
*/
public Object[] getRowData();
-
+
/**
* Advances to the next data row.
*
* @return True if rows are available; false if there are no more rows
*/
- public boolean next();
+ public boolean next();
}
-
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
index d6c7cf471..492559244 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
@@ -1,355 +1,427 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerDataSource.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
-import javax.sql.CommonDataSource;
+import javax.sql.CommonDataSource;
/**
-* A factory to create connections to the data source represented by this object. This interface was added in SQL Server JDBC Driver 3.0.
-*/
-public interface ISQLServerDataSource extends CommonDataSource
-{
- /**
- * Sets the application intent.
- * @param applicationIntent A String that contains the application intent.
- */
+ * A factory to create connections to the data source represented by this object. This interface was added in SQL Server JDBC Driver 3.0.
+ */
+public interface ISQLServerDataSource extends CommonDataSource {
+ /**
+ * Sets the application intent.
+ *
+ * @param applicationIntent
+ * A String that contains the application intent.
+ */
public void setApplicationIntent(String applicationIntent);
-
- /**
+
+ /**
* Returns the application intent.
+ *
* @return A String that contains the application intent.
*/
public String getApplicationIntent();
-
- /**
+
+ /**
* Sets the application name.
- * @param applicationName A String that contains the name of the application.
- */
- public void setApplicationName(String applicationName);
-
- /**
- * Returns the application name.
- * @return A String that contains the application name, or "Microsoft JDBC Driver for SQL Server" if no value is set.
- */
+ *
+ * @param applicationName
+ * A String that contains the name of the application.
+ */
+ public void setApplicationName(String applicationName);
+
+ /**
+ * Returns the application name.
+ *
+ * @return A String that contains the application name, or "Microsoft JDBC Driver for SQL Server" if no value is set.
+ */
public String getApplicationName();
-
- /**
+
+ /**
* Sets the database name to connect to.
- * @param databaseName A String that contains the database name.
+ *
+ * @param databaseName
+ * A String that contains the database name.
*/
public void setDatabaseName(String databaseName);
-
- /**
+
+ /**
* Returns the database name.
+ *
* @return A String that contains the database name or null if no value is set.
*/
public String getDatabaseName();
-
- /**
+
+ /**
* Sets the SQL Server instance name.
- * @param instanceName A String that contains the instance name.
+ *
+ * @param instanceName
+ * A String that contains the instance name.
*/
public void setInstanceName(String instanceName);
-
- /**
+
+ /**
* Returns the SQL Server instance name.
+ *
* @return A String that contains the instance name, or null if no value is set.
*/
public String getInstanceName();
-
- /**
+
+ /**
* Sets a Boolean value that indicates if the integratedSecurity property is enabled.
- * @param enable true if integratedSecurity is enabled. Otherwise, false.
+ *
+ * @param enable
+ * true if integratedSecurity is enabled. Otherwise, false.
*/
public void setIntegratedSecurity(boolean enable);
-
- /**
+
+ /**
* Sets a Boolean value that indicates if the lastUpdateCount property is enabled.
- * @param lastUpdateCount true if lastUpdateCount is enabled. Otherwise, false.
+ *
+ * @param lastUpdateCount
+ * true if lastUpdateCount is enabled. Otherwise, false.
*/
public void setLastUpdateCount(boolean lastUpdateCount);
-
- /**
+
+ /**
* Returns a Boolean value that indicates if the lastUpdateCount property is enabled.
+ *
* @return true if lastUpdateCount is enabled. Otherwise, false.
*/
public boolean getLastUpdateCount();
-
- /**
+
+ /**
* Sets a Boolean value that indicates if the encrypt property is enabled.
- * @param encrypt true if the Secure Sockets Layer (SSL) encryption is enabled between the client and the SQL Server. Otherwise, false.
+ *
+ * @param encrypt
+ * true if the Secure Sockets Layer (SSL) encryption is enabled between the client and the SQL Server. Otherwise, false.
*/
public void setEncrypt(boolean encrypt);
-
- /**
+
+ /**
* Returns a Boolean value that indicates if the encrypt property is enabled.
+ *
* @return true if encrypt is enabled. Otherwise, false.
*/
public boolean getEncrypt();
-
- /**
+
+ /**
* Sets a Boolean value that indicates if the trustServerCertificate property is enabled.
- * @param e true if the server Secure Sockets Layer (SSL) certificate should be automatically trusted when the communication layer is encrypted using SSL. Otherwise, false.
+ *
+ * @param e
+ * true if the server Secure Sockets Layer (SSL) certificate should be automatically trusted when the communication layer is encrypted
+ * using SSL. Otherwise, false.
*/
public void setTrustServerCertificate(boolean e);
-
- /**
+
+ /**
* Returns a Boolean value that indicates if the trustServerCertificate property is enabled.
+ *
* @return true if trustServerCertificate is enabled. Otherwise, false.
*/
public boolean getTrustServerCertificate();
-
- /**
+
+ /**
* Sets the path (including file name) to the certificate trustStore file.
- * @param st A String that contains the path (including file name) to the certificate trustStore file.
+ *
+ * @param st
+ * A String that contains the path (including file name) to the certificate trustStore file.
*/
public void setTrustStore(String st);
-
- /**
+
+ /**
* Returns the path (including file name) to the certificate trustStore file.
+ *
* @return A String that contains the path (including file name) to the certificate trustStore file, or null if no value is set.
*/
public String getTrustStore();
-
- /**
+
+ /**
* Sets the password that is used to check the integrity of the trustStore data.
- * @param p A String that contains the password that is used to check the integrity of the trustStore data.
+ *
+ * @param p
+ * A String that contains the password that is used to check the integrity of the trustStore data.
*/
public void setTrustStorePassword(String p);
-
- /**
+
+ /**
* Sets the host name to be used in validating the SQL Server Secure Sockets Layer (SSL) certificate.
- * @param host A String that contains the host name.
+ *
+ * @param host
+ * A String that contains the host name.
*/
public void setHostNameInCertificate(String host);
-
- /**
+
+ /**
* Returns the host name used in validating the SQL Server Secure Sockets Layer (SSL) certificate.
+ *
* @return A String that contains the host name, or null if no value is set.
*/
public String getHostNameInCertificate();
-
- /**
+
+ /**
* Sets an int value that indicates the number of milliseconds to wait before the database reports a lock time out.
- * @param lockTimeout An int value that contains the number of milliseconds to wait.
+ *
+ * @param lockTimeout
+ * An int value that contains the number of milliseconds to wait.
*/
public void setLockTimeout(int lockTimeout);
-
- /**
+
+ /**
* Returns an int value that indicates the number of milliseconds that the database will wait before reporting a lock time out.
+ *
* @return An int value that contains the number of milliseconds that the database will wait.
*/
public int getLockTimeout();
-
- /**
+
+ /**
* Sets the password that will be used to connect to SQL Server.
- * @param password A String that contains the password.
+ *
+ * @param password
+ * A String that contains the password.
*/
public void setPassword(String password);
-
- /**
+
+ /**
* Sets the port number to be used to communicate with SQL Server.
- * @param portNumber An int value that contains the port number.
+ *
+ * @param portNumber
+ * An int value that contains the port number.
*/
public void setPortNumber(int portNumber);
-
- /**
+
+ /**
* Returns the current port number that is used to communicate with SQL Server.
+ *
* @return An int value that contains the current port number.
*/
public int getPortNumber();
-
- /**
+
+ /**
* Sets the default cursor type that is used for all result sets that are created by using this SQLServerDataSource object.
- * @param selectMethod A String value that contains the default cursor type.
+ *
+ * @param selectMethod
+ * A String value that contains the default cursor type.
*/
public void setSelectMethod(String selectMethod);
-
- /**
+
+ /**
* Returns the default cursor type used for all result sets that are created by using this SQLServerDataSource object.
+ *
* @return A String value that contains the default cursor type.
*/
public String getSelectMethod();
-
- /**
+
+ /**
* Sets the response buffering mode for connections created by using this SQLServerDataSource object.
- * @param respo A String that contains the buffering and streaming mode. The valid mode can be one of the following case-insensitive Strings: full or adaptive.
+ *
+ * @param respo
+ * A String that contains the buffering and streaming mode. The valid mode can be one of the following case-insensitive Strings: full
+ * or adaptive.
*/
public void setResponseBuffering(String respo);
-
- /**
+
+ /**
* Returns the response buffering mode for this SQLServerDataSource object.
+ *
* @return A String that contains a lower-case full or adaptive.
*/
public String getResponseBuffering();
-
- /**
+
+ /**
* Modifies the setting of the sendTimeAsDatetime connection property.
- * @param sendTimeAsDatetime A Boolean value. When true, causes java.sql.Time values to be sent to the server as SQL Server datetime types.
- * When false, causes java.sql.Time values to be sent to the server as SQL Server time types.
+ *
+ * @param sendTimeAsDatetime
+ * A Boolean value. When true, causes java.sql.Time values to be sent to the server as SQL Server datetime types. When false, causes
+ * java.sql.Time values to be sent to the server as SQL Server time types.
*/
public void setSendTimeAsDatetime(boolean sendTimeAsDatetime);
-
- /**
+
+ /**
* This method was added in SQL Server JDBC Driver 3.0. Returns the setting of the sendTimeAsDatetime connection property.
- * @return true if java.sql.Time values will be sent to the server as a SQL Server datetime type. false if java.sql.Time values will be sent
- * to the server as a SQL Server time type.
+ *
+ * @return true if java.sql.Time values will be sent to the server as a SQL Server datetime type. false if java.sql.Time values will be sent to
+ * the server as a SQL Server time type.
*/
public boolean getSendTimeAsDatetime();
-
- /**
+
+ /**
* Sets a boolean value that indicates if sending string parameters to the server in UNICODE format is enabled.
- * @param sendStringParametersAsUnicode true if string parameters are sent to the server in UNICODE format. Otherwise, false.
+ *
+ * @param sendStringParametersAsUnicode
+ * true if string parameters are sent to the server in UNICODE format. Otherwise, false.
*/
public void setSendStringParametersAsUnicode(boolean sendStringParametersAsUnicode);
-
- /**
+
+ /**
* Returns a boolean value that indicates if sending string parameters to the server in UNICODE format is enabled.
+ *
* @return true if string parameters are sent to the server in UNICODE format. Otherwise, false.
*/
public boolean getSendStringParametersAsUnicode();
-
- /**
+
+ /**
* Sets the name of the computer that is running SQL Server.
- * @param serverName A String that contains the server name.
+ *
+ * @param serverName
+ * A String that contains the server name.
*/
public void setServerName(String serverName);
-
- /**
+
+ /**
* Returns the name of the SQL Server instance.
+ *
* @return A String that contains the server name or null if no value is set.
*/
public String getServerName();
-
- /**
+
+ /**
* Sets the name of the failover server that is used in a database mirroring configuration.
- * @param serverName A String that contains the failover server name.
+ *
+ * @param serverName
+ * A String that contains the failover server name.
*/
public void setFailoverPartner(String serverName);
-
- /**
+
+ /**
* Returns the name of the failover server that is used in a database mirroring configuration.
+ *
* @return A String that contains the name of the failover partner, or null if none is set.
*/
public String getFailoverPartner();
-
- /**
+
+ /**
* Sets the value of the multiSubnetFailover connection property.
- * @param multiSubnetFailover The new value of the multiSubnetFailover connection property.
+ *
+ * @param multiSubnetFailover
+ * The new value of the multiSubnetFailover connection property.
*/
public void setMultiSubnetFailover(boolean multiSubnetFailover);
-
- /**
+
+ /**
* Returns the value of the multiSubnetFailover connection property.
+ *
* @return Returns true or false, depending on the current setting of the connection property.
*/
public boolean getMultiSubnetFailover();
-
- /**
+
+ /**
* Sets the user name that is used to connect the data source.
- * @param user A String that contains the user name.
+ *
+ * @param user
+ * A String that contains the user name.
*/
public void setUser(String user);
-
- /**
+
+ /**
* Returns the user name that is used to connect the data source.
+ *
* @return A String that contains the user name.
*/
public String getUser();
-
- /**
+
+ /**
* Sets the name of the client computer name that is used to connect to the data source.
- * @param workstationID A String that contains the client computer name.
+ *
+ * @param workstationID
+ * A String that contains the client computer name.
*/
public void setWorkstationID(String workstationID);
-
- /**
+
+ /**
* Returns the name of the client computer name that is used to connect to the data source.
+ *
* @return A String that contains the client computer name.
*/
public String getWorkstationID();
-
- /**
+
+ /**
* Sets a Boolean value that indicates if converting SQL states to XOPEN compliant states is enabled.
- * @param xopenStates true if converting SQL states to XOPEN compliant states is enabled. Otherwise, false.
+ *
+ * @param xopenStates
+ * true if converting SQL states to XOPEN compliant states is enabled. Otherwise, false.
*/
public void setXopenStates(boolean xopenStates);
-
- /**
+
+ /**
* Returns a boolean value that indicates if converting SQL states to XOPEN compliant states is enabled.
+ *
* @return true if converting SQL states to XOPEN compliant states is enabled. Otherwise, false.
*/
public boolean getXopenStates();
-
- /**
+
+ /**
* Sets the URL that is used to connect to the data source.
- * @param url A String that contains the URL.
+ *
+ * @param url
+ * A String that contains the URL.
*/
public void setURL(String url);
-
- /**
+
+ /**
* Returns the URL that is used to connect to the data source.
+ *
* @return A String that contains the URL.
*/
public String getURL();
-
- /**
+
+ /**
* Sets the description of the data source.
- * @param description A String that contains the description.
+ *
+ * @param description
+ * A String that contains the description.
*/
public void setDescription(String description);
-
- /**
+
+ /**
* Returns a description of the data source.
+ *
* @return A String that contains the data source description or null if no value is set.
*/
public String getDescription();
-
- /**
+
+ /**
* Sets the current network packet size used to communicate with SQL Server, specified in bytes.
- * @param packetSize An int value containing the network packet size.
+ *
+ * @param packetSize
+ * An int value containing the network packet size.
*/
public void setPacketSize(int packetSize);
-
- /**
+
+ /**
* Returns the current network packet size used to communicate with SQL Server, specified in bytes.
+ *
* @return An int value containing the current network packet size.
*/
public int getPacketSize();
-
- /**
+
+ /**
* Indicates the kind of integrated security you want your application to use.
- * @param authenticationScheme Values are "JavaKerberos" and the default "NativeAuthentication".
- */
- public void setAuthenticationScheme(String authenticationScheme);
-
- /**
- * Sets the server spn
- * @param serverSpn A String that contains the server spn
- */
- public void setServerSpn(String serverSpn);
-
- /**
- * Returns the server spn
- * @return A String that contains the server spn
- */
- public String getServerSpn();
-}
+ *
+ * @param authenticationScheme
+ * Values are "JavaKerberos" and the default "NativeAuthentication".
+ */
+ public void setAuthenticationScheme(String authenticationScheme);
+ /**
+ * Sets the server spn
+ *
+ * @param serverSpn
+ * A String that contains the server spn
+ */
+ public void setServerSpn(String serverSpn);
+
+ /**
+ * Returns the server spn
+ *
+ * @return A String that contains the server spn
+ */
+ public String getServerSpn();
+}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement.java
index b9ecf9d87..ee954f151 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement.java
@@ -1,37 +1,27 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerPreparedStatement.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.sql.SQLException;
-public interface ISQLServerPreparedStatement extends java.sql.PreparedStatement, ISQLServerStatement
-{
- /**
- * Sets the designated parameter to the given microsoft.sql.DateTimeOffset
value.
- *
- * @param parameterIndex the first parameter is 1, the second is 2, ...
- * @param x the parameter value
- * @throws SQLException if parameterIndex does not correspond to a parameter
- * marker in the SQL statement; if a database access error occurs or
- * this method is called on a closed PreparedStatement
- */
- public void setDateTimeOffset(int parameterIndex, microsoft.sql.DateTimeOffset x) throws SQLException;
+public interface ISQLServerPreparedStatement extends java.sql.PreparedStatement, ISQLServerStatement {
+ /**
+ * Sets the designated parameter to the given microsoft.sql.DateTimeOffset
value.
+ *
+ * @param parameterIndex
+ * the first parameter is 1, the second is 2, ...
+ * @param x
+ * the parameter value
+ * @throws SQLException
+ * if parameterIndex does not correspond to a parameter marker in the SQL statement; if a database access error occurs or this method
+ * is called on a closed PreparedStatement
+ */
+ public void setDateTimeOffset(int parameterIndex,
+ microsoft.sql.DateTimeOffset x) throws SQLException;
}
-
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement42.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement42.java
index 55f448304..229af83ee 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement42.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerPreparedStatement42.java
@@ -1,21 +1,10 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerPreparedStatement42.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
@@ -25,51 +14,75 @@
* This interface requires all the PreparedStatement methods including those are specific to JDBC 4.2
*
*/
-public interface ISQLServerPreparedStatement42 extends ISQLServerPreparedStatement{
+public interface ISQLServerPreparedStatement42 extends ISQLServerPreparedStatement {
- public void setObject(int index, Object obj, SQLType jdbcType) throws SQLServerException;
+ public void setObject(int index,
+ Object obj,
+ SQLType jdbcType) throws SQLServerException;
- public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLServerException;
+ public void setObject(int parameterIndex,
+ Object x,
+ SQLType targetSqlType,
+ int scaleOrLength) throws SQLServerException;
- /**
+ /**
* Sets the value of the designated parameter with the given object.
*
- * This method is similar to {@link #setObject(int parameterIndex,
- * Object x, SQLType targetSqlType, int scaleOrLength)},
- * except that it assumes a scale of zero.
- *
+ * This method is similar to {@link #setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength)}, except that it assumes a
+ * scale of zero.
+ *
* The default implementation will throw {@code SQLFeatureNotSupportedException}
*
- * @param parameterIndex the first parameter is 1, the second is 2, ...
- * @param x the object containing the input parameter value
- * @param targetSqlType the SQL type to be sent to the database
- * @param precision the precision of the column
- * @param scale the scale of the column
- * @throws SQLServerException if parameterIndex does not correspond to a
- * parameter marker in the SQL statement; if a database access error occurs
- * or this method is called on a closed {@code PreparedStatement}
+ * @param parameterIndex
+ * the first parameter is 1, the second is 2, ...
+ * @param x
+ * the object containing the input parameter value
+ * @param targetSqlType
+ * the SQL type to be sent to the database
+ * @param precision
+ * the precision of the column
+ * @param scale
+ * the scale of the column
+ * @throws SQLServerException
+ * if parameterIndex does not correspond to a parameter marker in the SQL statement; if a database access error occurs or this method
+ * is called on a closed {@code PreparedStatement}
*/
- public void setObject(int parameterIndex, Object x, SQLType targetSqlType, Integer precision, Integer scale) throws SQLServerException;
+ public void setObject(int parameterIndex,
+ Object x,
+ SQLType targetSqlType,
+ Integer precision,
+ Integer scale) throws SQLServerException;
- /**
+ /**
* Sets the value of the designated parameter with the given object.
*
- * This method is similar to {@link #setObject(int parameterIndex,
- * Object x, SQLType targetSqlType, int scaleOrLength)},
- * except that it assumes a scale of zero.
- *
+ * This method is similar to {@link #setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength)}, except that it assumes a
+ * scale of zero.
+ *
* The default implementation will throw {@code SQLFeatureNotSupportedException}
*
- * @param parameterIndex the first parameter is 1, the second is 2, ...
- * @param x the object containing the input parameter value
- * @param targetSqlType the SQL type to be sent to the database
- * @param precision the precision of the column
- * @param scale the scale of the column
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterIndex does not correspond to a
- * parameter marker in the SQL statement; if a database access error occurs
- * or this method is called on a closed {@code PreparedStatement}
+ * @param parameterIndex
+ * the first parameter is 1, the second is 2, ...
+ * @param x
+ * the object containing the input parameter value
+ * @param targetSqlType
+ * the SQL type to be sent to the database
+ * @param precision
+ * the precision of the column
+ * @param scale
+ * the scale of the column
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterIndex does not correspond to a parameter marker in the SQL statement; if a database access error occurs or this method
+ * is called on a closed {@code PreparedStatement}
*/
- public void setObject(int parameterIndex, Object x, SQLType targetSqlType, Integer precision, Integer scale, boolean forceEncrypt) throws SQLServerException;
+ public void setObject(int parameterIndex,
+ Object x,
+ SQLType targetSqlType,
+ Integer precision,
+ Integer scale,
+ boolean forceEncrypt) throws SQLServerException;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet.java
index edd6a725e..d0bab83cf 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet.java
@@ -1,70 +1,74 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerResultSet.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.sql.SQLException;
-public interface ISQLServerResultSet extends java.sql.ResultSet
-{
-
- public static final int TYPE_SS_DIRECT_FORWARD_ONLY = 2003; // TYPE_FORWARD_ONLY + 1000
+public interface ISQLServerResultSet extends java.sql.ResultSet {
+
+ public static final int TYPE_SS_DIRECT_FORWARD_ONLY = 2003; // TYPE_FORWARD_ONLY + 1000
public static final int TYPE_SS_SERVER_CURSOR_FORWARD_ONLY = 2004; // TYPE_FORWARD_ONLY + 1001
- public static final int TYPE_SS_SCROLL_STATIC = 1004; // TYPE_SCROLL_INSENSITIVE
- public static final int TYPE_SS_SCROLL_KEYSET = 1005; // TYPE_SCROLL_SENSITIVE
- public static final int TYPE_SS_SCROLL_DYNAMIC = 1006; // TYPE_SCROLL_SENSITIVE + 1
+ public static final int TYPE_SS_SCROLL_STATIC = 1004; // TYPE_SCROLL_INSENSITIVE
+ public static final int TYPE_SS_SCROLL_KEYSET = 1005; // TYPE_SCROLL_SENSITIVE
+ public static final int TYPE_SS_SCROLL_DYNAMIC = 1006; // TYPE_SCROLL_SENSITIVE + 1
/* SQL Server concurrency values */
- public static final int CONCUR_SS_OPTIMISTIC_CC = 1008; // CONCUR_UPDATABLE
- public static final int CONCUR_SS_SCROLL_LOCKS = 1009; // CONCUR_UPDATABLE + 1
+ public static final int CONCUR_SS_OPTIMISTIC_CC = 1008; // CONCUR_UPDATABLE
+ public static final int CONCUR_SS_SCROLL_LOCKS = 1009; // CONCUR_UPDATABLE + 1
public static final int CONCUR_SS_OPTIMISTIC_CCVAL = 1010; // CONCUR_UPDATABLE + 2
- /**
+ /**
* Retrieves the value of the designated column as a microsoft.sql.DateTimeOffset object, given a zero-based column ordinal.
- * @param columnIndex The zero-based ordinal of a column.
+ *
+ * @param columnIndex
+ * The zero-based ordinal of a column.
* @return A DateTimeOffset Class object.
- * @throws SQLException when an error occurs
+ * @throws SQLException
+ * when an error occurs
*/
public microsoft.sql.DateTimeOffset getDateTimeOffset(int columnIndex) throws SQLException;
-
- /**
+
+ /**
* Retrieves the value of the column specified as a microsoft.sql.DateTimeOffset object, given a column name.
- * @param columnName The name of a column.
+ *
+ * @param columnName
+ * The name of a column.
* @return A DateTimeOffset Class object.
- * @throws SQLException when an error occurs
+ * @throws SQLException
+ * when an error occurs
*/
- public microsoft.sql.DateTimeOffset getDateTimeOffset(String columnName) throws SQLException;
-
- /**
+ public microsoft.sql.DateTimeOffset getDateTimeOffset(String columnName) throws SQLException;
+
+ /**
* Updates the value of the column specified to the DateTimeOffset Class value, given a zero-based column ordinal.
- * @param index The zero-based ordinal of a column.
- * @param x A DateTimeOffset Class object.
- * @throws SQLException when an error occurs
+ *
+ * @param index
+ * The zero-based ordinal of a column.
+ * @param x
+ * A DateTimeOffset Class object.
+ * @throws SQLException
+ * when an error occurs
*/
- public void updateDateTimeOffset(int index, microsoft.sql.DateTimeOffset x) throws SQLException;
-
- /**
+ public void updateDateTimeOffset(int index,
+ microsoft.sql.DateTimeOffset x) throws SQLException;
+
+ /**
* Updates the value of the column specified to the DateTimeOffset Class value, given a column name.
- * @param columnName The name of a column.
- * @param x A DateTimeOffset Class object.
- * @throws SQLException when an error occurs
+ *
+ * @param columnName
+ * The name of a column.
+ * @param x
+ * A DateTimeOffset Class object.
+ * @throws SQLException
+ * when an error occurs
*/
- public void updateDateTimeOffset(String columnName, microsoft.sql.DateTimeOffset x) throws SQLException;
-
+ public void updateDateTimeOffset(String columnName,
+ microsoft.sql.DateTimeOffset x) throws SQLException;
+
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet42.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet42.java
index 87bb7e088..1f75ec99f 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet42.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerResultSet42.java
@@ -1,21 +1,10 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerResultSet42.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
@@ -25,52 +14,83 @@
* This interface requires all the ResultSet methods including those are specific to JDBC 4.2
*
*/
-public interface ISQLServerResultSet42 extends ISQLServerResultSet{
+public interface ISQLServerResultSet42 extends ISQLServerResultSet {
- public void updateObject(int index, Object obj, SQLType targetSqlType) throws SQLServerException;
+ public void updateObject(int index,
+ Object obj,
+ SQLType targetSqlType) throws SQLServerException;
- public void updateObject(int index, Object obj, SQLType targetSqlType, int scale) throws SQLServerException;
+ public void updateObject(int index,
+ Object obj,
+ SQLType targetSqlType,
+ int scale) throws SQLServerException;
- /**
- * Updates the designated column with an Object value. The updater methods are used to update column values in the current row or the insert row.
- * The updater methods do not update the underlying database; instead the updateRow or insertRow methods are called to update the database.
- * If the second argument is an InputStream then the stream must contain the number of bytes specified by scaleOrLength. If the second argument
- * is a Reader then the reader must contain the number of characters specified by scaleOrLength. If these conditions are not true the driver will
- * generate a SQLException when the statement is executed.
- * The default implementation will throw SQLFeatureNotSupportedException
- * @param index the first column is 1, the second is 2, ...
- * @param obj the new column value
- * @param targetSqlType the SQL type to be sent to the database
- * @param scale for an object of java.math.BigDecimal , this is the number of digits after the decimal point. For Java Object types InputStream and Reader,
- * this is the length of the data in the stream or reader. For all other types, this value will be ignored.
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement.If the boolean forceEncrypt is set to false, the driver will not force
- * encryption on parameters.
- * @throws SQLServerException If any errors occur.
- */
- public void updateObject(int index, Object obj, SQLType targetSqlType, int scale, boolean forceEncrypt) throws SQLServerException;
+ /**
+ * Updates the designated column with an Object value. The updater methods are used to update column values in the current row or the insert row.
+ * The updater methods do not update the underlying database; instead the updateRow or insertRow methods are called to update the database. If the
+ * second argument is an InputStream then the stream must contain the number of bytes specified by scaleOrLength. If the second argument is a
+ * Reader then the reader must contain the number of characters specified by scaleOrLength. If these conditions are not true the driver will
+ * generate a SQLException when the statement is executed. The default implementation will throw SQLFeatureNotSupportedException
+ *
+ * @param index
+ * the first column is 1, the second is 2, ...
+ * @param obj
+ * the new column value
+ * @param targetSqlType
+ * the SQL type to be sent to the database
+ * @param scale
+ * for an object of java.math.BigDecimal , this is the number of digits after the decimal point. For Java Object types InputStream and
+ * Reader, this is the length of the data in the stream or reader. For all other types, this value will be ignored.
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement.If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * If any errors occur.
+ */
+ public void updateObject(int index,
+ Object obj,
+ SQLType targetSqlType,
+ int scale,
+ boolean forceEncrypt) throws SQLServerException;
- public void updateObject(String columnName, Object obj, SQLType targetSqlType, int scale) throws SQLServerException;
+ public void updateObject(String columnName,
+ Object obj,
+ SQLType targetSqlType,
+ int scale) throws SQLServerException;
- /**
- *
- * Updates the designated column with an Object value. The updater methods are used to update column values in the current row or the insert row.
- * The updater methods do not update the underlying database; instead the updateRow or insertRow methods are called to update the database.
- * If the second argument is an InputStream then the stream must contain the number of bytes specified by scaleOrLength. If the second argument
- * is a Reader then the reader must contain the number of characters specified by scaleOrLength. If these conditions are not true the driver will
- * generate a SQLException when the statement is executed.
- * The default implementation will throw SQLFeatureNotSupportedException
- * @param columnName the label for the column specified with the SQL AS clause. If the SQL AS clause was not specified, then the label is the name of the column
- * @param obj the new column value
- * @param targetSqlType the SQL type to be sent to the database
- * @param scale for an object of java.math.BigDecimal , this is the number of digits after the decimal point. For Java Object types InputStream and Reader,
- * this is the length of the data in the stream or reader. For all other types, this value will be ignored.
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement.If the boolean forceEncrypt is set to false, the driver will not force
- * encryption on parameters.
- * @throws SQLServerException If any errors occur.
- */
- public void updateObject(String columnName, Object obj, SQLType targetSqlType, int scale, boolean forceEncrypt) throws SQLServerException;
+ /**
+ *
+ * Updates the designated column with an Object value. The updater methods are used to update column values in the current row or the insert row.
+ * The updater methods do not update the underlying database; instead the updateRow or insertRow methods are called to update the database. If the
+ * second argument is an InputStream then the stream must contain the number of bytes specified by scaleOrLength. If the second argument is a
+ * Reader then the reader must contain the number of characters specified by scaleOrLength. If these conditions are not true the driver will
+ * generate a SQLException when the statement is executed. The default implementation will throw SQLFeatureNotSupportedException
+ *
+ * @param columnName
+ * the label for the column specified with the SQL AS clause. If the SQL AS clause was not specified, then the label is the name of the
+ * column
+ * @param obj
+ * the new column value
+ * @param targetSqlType
+ * the SQL type to be sent to the database
+ * @param scale
+ * for an object of java.math.BigDecimal , this is the number of digits after the decimal point. For Java Object types InputStream and
+ * Reader, this is the length of the data in the stream or reader. For all other types, this value will be ignored.
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement.If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * If any errors occur.
+ */
+ public void updateObject(String columnName,
+ Object obj,
+ SQLType targetSqlType,
+ int scale,
+ boolean forceEncrypt) throws SQLServerException;
- public void updateObject(String columnName, Object obj, SQLType targetSqlType) throws SQLServerException;
+ public void updateObject(String columnName,
+ Object obj,
+ SQLType targetSqlType) throws SQLServerException;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java
index 3d102061d..518a74cb2 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerStatement.java
@@ -1,28 +1,15 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ISQLServerStatement.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
-package com.microsoft.sqlserver.jdbc;
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+package com.microsoft.sqlserver.jdbc;
-public interface ISQLServerStatement extends java.sql.Statement
-{
- /**
+public interface ISQLServerStatement extends java.sql.Statement {
+ /**
* Sets the response buffering mode for this SQLServerStatement object to case-insensitive String full or adaptive.
*
* Response buffering controls the driver's buffering of responses from SQL Server.
@@ -32,15 +19,21 @@ public interface ISQLServerStatement extends java.sql.Statement
* "full" - Fully buffer the response at execution time.
*
* "adaptive" - Data Pipe adaptive buffering
- * @param value A String that contains the response buffering mode. The valid mode can be one of the following case-insensitive Strings: full or adaptive.
- * @throws SQLServerException If there are any errors in setting the response buffering mode.
+ *
+ * @param value
+ * A String that contains the response buffering mode. The valid mode can be one of the following case-insensitive Strings: full or
+ * adaptive.
+ * @throws SQLServerException
+ * If there are any errors in setting the response buffering mode.
*/
- public void setResponseBuffering(String value) throws SQLServerException;
-
- /**
+ public void setResponseBuffering(String value) throws SQLServerException;
+
+ /**
* Retrieves the response buffering mode for this SQLServerStatement object.
+ *
* @return A String that contains a lower-case full or adaptive.
- * @throws SQLServerException If there are any errors in retrieving the response buffering mode.
+ * @throws SQLServerException
+ * If there are any errors in retrieving the response buffering mode.
*/
- public String getResponseBuffering() throws SQLServerException;
+ public String getResponseBuffering() throws SQLServerException;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
index 2d3efdaf7..6706d84a6 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
@@ -1,23 +1,13 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: KerbAuthentication.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
+
import java.net.IDN;
import java.security.AccessControlContext;
import java.security.AccessController;
@@ -40,309 +30,261 @@
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
-
/**
-* KerbAuthentication for int auth.
-*/
-final class KerbAuthentication extends SSPIAuthentication
-{
+ * KerbAuthentication for int auth.
+ */
+final class KerbAuthentication extends SSPIAuthentication {
private final static String CONFIGNAME = "SQLJDBCDriver";
- private final static java.util.logging.Logger authLogger = java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.KerbAuthentication");
+ private final static java.util.logging.Logger authLogger = java.util.logging.Logger
+ .getLogger("com.microsoft.sqlserver.jdbc.internals.KerbAuthentication");
- private final SQLServerConnection con;
+ private final SQLServerConnection con;
private final String spn;
private final GSSManager manager = GSSManager.getInstance();
- private LoginContext lc = null;
+ private LoginContext lc = null;
private GSSCredential peerCredentials = null;
- private GSSContext peerContext = null;
+ private GSSContext peerContext = null;
-
- static
- {
- // The driver on load will look to see if there is a configuration set for the SQLJDBCDriver, if not it will install its
- // own configuration. Note it is possible that there is a configuration exists but it does not contain a configuration entry
+ static {
+ // The driver on load will look to see if there is a configuration set for the SQLJDBCDriver, if not it will install its
+ // own configuration. Note it is possible that there is a configuration exists but it does not contain a configuration entry
// for the driver in that case, we will override the configuration but will flow the configuration requests to existing
// config for anything other than SQLJDBCDriver
- //
- class SQLJDBCDriverConfig extends Configuration
- {
- Configuration current = null;
+ //
+ class SQLJDBCDriverConfig extends Configuration {
+ Configuration current = null;
AppConfigurationEntry[] driverConf;
- SQLJDBCDriverConfig()
- {
- try
- {
- current = Configuration.getConfiguration();
+ SQLJDBCDriverConfig() {
+ try {
+ current = Configuration.getConfiguration();
}
- catch (SecurityException e)
- {
+ catch (SecurityException e) {
// if we cant get the configuration, it is likely that no configuration has been specified. So go ahead and set the config
authLogger.finer(toString() + " No configurations provided, setting driver default");
}
AppConfigurationEntry[] config = null;
-
- if(null != current)
- {
+
+ if (null != current) {
config = current.getAppConfigurationEntry(CONFIGNAME);
}
// If there is user provided configuration we leave use that and not install our configuration
- if(null == config)
- {
- if (authLogger.isLoggable(Level.FINER))
+ if (null == config) {
+ if (authLogger.isLoggable(Level.FINER))
authLogger.finer(toString() + " SQLJDBCDriver configuration entry is not provided, setting driver default");
-
+
AppConfigurationEntry appConf;
- if(Util.isIBM())
- {
+ if (Util.isIBM()) {
Map confDetails = new HashMap();
- confDetails.put("useDefaultCcache", "true");
+ confDetails.put("useDefaultCcache", "true");
confDetails.put("moduleBanner", "false");
- appConf = new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, confDetails);
- if (authLogger.isLoggable(Level.FINER))
- authLogger.finer(toString() + " Setting IBM Krb5LoginModule");
+ appConf = new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, confDetails);
+ if (authLogger.isLoggable(Level.FINER))
+ authLogger.finer(toString() + " Setting IBM Krb5LoginModule");
}
- else
- {
+ else {
Map confDetails = new HashMap();
- confDetails.put("useTicketCache", "true");
- confDetails.put("doNotPrompt", "true");
- appConf = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, confDetails);
- if (authLogger.isLoggable(Level.FINER))
- authLogger.finer(toString() + " Setting Sun Krb5LoginModule");
+ confDetails.put("useTicketCache", "true");
+ confDetails.put("doNotPrompt", "true");
+ appConf = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, confDetails);
+ if (authLogger.isLoggable(Level.FINER))
+ authLogger.finer(toString() + " Setting Sun Krb5LoginModule");
}
- driverConf = new AppConfigurationEntry[1];
- driverConf[0] = appConf;
- Configuration.setConfiguration(this);
+ driverConf = new AppConfigurationEntry[1];
+ driverConf[0] = appConf;
+ Configuration.setConfiguration(this);
}
-
+
}
-
- public AppConfigurationEntry[] getAppConfigurationEntry(String name)
- {
- // we should only handle anything that is related to our part, everything else is handled by the configuration
- // already existing configuration if there is one.
- if(name.equals(CONFIGNAME))
- {
+
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ // we should only handle anything that is related to our part, everything else is handled by the configuration
+ // already existing configuration if there is one.
+ if (name.equals(CONFIGNAME)) {
return driverConf;
}
- else
- {
- if(null != current)
+ else {
+ if (null != current)
return current.getAppConfigurationEntry(name);
else
return null;
}
- }
-
- public void refresh()
- {
- if(null != current)
+ }
+
+ public void refresh() {
+ if (null != current)
current.refresh();
- }
- }
+ }
+ }
SQLJDBCDriverConfig driverconfig = new SQLJDBCDriverConfig();
- }
-
- private void intAuthInit() throws SQLServerException
- {
- try
- {
+ }
+
+ private void intAuthInit() throws SQLServerException {
+ try {
// If we need to support NTLM as well, we can use null
// Kerberos OID
- Oid kerberos = new Oid("1.2.840.113554.1.2.2");
- Subject currentSubject=null;
- try
- {
+ Oid kerberos = new Oid("1.2.840.113554.1.2.2");
+ Subject currentSubject = null;
+ try {
AccessControlContext context = AccessController.getContext();
- currentSubject = Subject.getSubject(context);
- if(null == currentSubject)
- {
+ currentSubject = Subject.getSubject(context);
+ if (null == currentSubject) {
lc = new LoginContext(CONFIGNAME);
lc.login();
- // per documentation LoginContext will instantiate a new subject.
+ // per documentation LoginContext will instantiate a new subject.
currentSubject = lc.getSubject();
}
- }
- catch (LoginException le)
- {
+ }
+ catch (LoginException le) {
con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le);
}
-
+
// http://blogs.sun.com/harcey/entry/of_java_kerberos_and_access
- // We pass null to indicate that the system should interpret the SPN as it is.
+ // We pass null to indicate that the system should interpret the SPN as it is.
GSSName remotePeerName = manager.createName(spn, null);
- if (authLogger.isLoggable(Level.FINER))
- {
- authLogger.finer(toString() + " Getting client credentials");
- }
- peerCredentials = getClientCredential(currentSubject,manager, kerberos);
- if (authLogger.isLoggable(Level.FINER))
- {
- authLogger.finer(toString() + " creating security context");
- }
+ if (authLogger.isLoggable(Level.FINER)) {
+ authLogger.finer(toString() + " Getting client credentials");
+ }
+ peerCredentials = getClientCredential(currentSubject, manager, kerberos);
+ if (authLogger.isLoggable(Level.FINER)) {
+ authLogger.finer(toString() + " creating security context");
+ }
- peerContext = manager.createContext(remotePeerName,
- kerberos,
- peerCredentials,
- GSSContext.DEFAULT_LIFETIME);
+ peerContext = manager.createContext(remotePeerName, kerberos, peerCredentials, GSSContext.DEFAULT_LIFETIME);
// The following flags should be inline with our native implementation.
peerContext.requestCredDeleg(true);
peerContext.requestMutualAuth(true);
peerContext.requestInteg(true);
- }
+ }
- catch(GSSException ge)
- {
- authLogger.finer(toString() + "initAuthInit failed GSSException:-" + ge);
- con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);
- }
- catch(PrivilegedActionException ge)
- {
- authLogger.finer(toString() + "initAuthInit failed privileged exception:-" + ge);
- con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);
- }
-
+ catch (GSSException ge) {
+ authLogger.finer(toString() + "initAuthInit failed GSSException:-" + ge);
+ con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);
+ }
+ catch (PrivilegedActionException ge) {
+ authLogger.finer(toString() + "initAuthInit failed privileged exception:-" + ge);
+ con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);
+ }
- }
+ }
// We have to do a privileged action to create the credential of the user in the current context
- private static GSSCredential getClientCredential(final Subject subject, final GSSManager MANAGER, final Oid kerboid)
- throws PrivilegedActionException
- {
- final PrivilegedExceptionAction action =
- new PrivilegedExceptionAction() {
- public GSSCredential run() throws GSSException {
- return MANAGER.createCredential(
- null // use the default principal
- , GSSCredential.DEFAULT_LIFETIME
- ,kerboid
- , GSSCredential.INITIATE_ONLY);
- }
- };
+ private static GSSCredential getClientCredential(final Subject subject,
+ final GSSManager MANAGER,
+ final Oid kerboid) throws PrivilegedActionException {
+ final PrivilegedExceptionAction action = new PrivilegedExceptionAction() {
+ public GSSCredential run() throws GSSException {
+ return MANAGER.createCredential(null // use the default principal
+ , GSSCredential.DEFAULT_LIFETIME, kerboid, GSSCredential.INITIATE_ONLY);
+ }
+ };
// TO support java 5, 6 we have to do this
- // The signature for Java 5 returns an object 6 returns GSSCredential, immediate casting throws
+ // The signature for Java 5 returns an object 6 returns GSSCredential, immediate casting throws
// warning in Java 6.
Object credential = Subject.doAs(subject, action);
- return (GSSCredential)credential;
+ return (GSSCredential) credential;
}
- private byte [] intAuthHandShake(byte[] pin, boolean[] done) throws SQLServerException
- {
- try
- {
- if (authLogger.isLoggable(Level.FINER))
- {
- authLogger.finer(toString() + " Sending token to server over secure context");
- }
- byte [] byteToken = peerContext.initSecContext(pin, 0, pin.length);
-
- if (peerContext.isEstablished())
- {
+
+ private byte[] intAuthHandShake(byte[] pin,
+ boolean[] done) throws SQLServerException {
+ try {
+ if (authLogger.isLoggable(Level.FINER)) {
+ authLogger.finer(toString() + " Sending token to server over secure context");
+ }
+ byte[] byteToken = peerContext.initSecContext(pin, 0, pin.length);
+
+ if (peerContext.isEstablished()) {
done[0] = true;
- if (authLogger.isLoggable(Level.FINER))
+ if (authLogger.isLoggable(Level.FINER))
authLogger.finer(toString() + "Authentication done.");
- }
- else
- if (null == byteToken)
- {
- // The documentation is not clear on when this can happen but it does say this could happen
- authLogger.info(toString() + "byteToken is null in initSecContext.");
- con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"));
- }
- return byteToken;
+ }
+ else if (null == byteToken) {
+ // The documentation is not clear on when this can happen but it does say this could happen
+ authLogger.info(toString() + "byteToken is null in initSecContext.");
+ con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"));
+ }
+ return byteToken;
+ }
+ catch (GSSException ge) {
+ authLogger.finer(toString() + "initSecContext Failed :-" + ge);
+ con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);
}
- catch(GSSException ge)
- {
- authLogger.finer(toString() + "initSecContext Failed :-" + ge);
- con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);
- }
// keep the compiler happy
return null;
}
-
- private String makeSpn(String server, int port) throws SQLServerException
- {
- if (authLogger.isLoggable(Level.FINER))
- {
- authLogger.finer(toString() + " Server: " + server + " port: " + port);
- }
+
+ private String makeSpn(String server,
+ int port) throws SQLServerException {
+ if (authLogger.isLoggable(Level.FINER)) {
+ authLogger.finer(toString() + " Server: " + server + " port: " + port);
+ }
StringBuilder spn = new StringBuilder("MSSQLSvc/");
- //Format is MSSQLSvc/myhost.domain.company.com:1433
- // FQDN must be provided
- if(con.serverNameAsACE())
- {
- spn.append(IDN.toASCII(server));
+ // Format is MSSQLSvc/myhost.domain.company.com:1433
+ // FQDN must be provided
+ if (con.serverNameAsACE()) {
+ spn.append(IDN.toASCII(server));
+ }
+ else {
+ spn.append(server);
+ }
+ spn.append(":");
+ spn.append(port);
+ String strSPN = spn.toString();
+ if (authLogger.isLoggable(Level.FINER)) {
+ authLogger.finer(toString() + " SPN: " + strSPN);
}
- else
- {
- spn.append(server);
- }
- spn.append(":");
- spn.append(port);
- String strSPN = spn.toString();
- if (authLogger.isLoggable(Level.FINER))
- {
- authLogger.finer(toString() + " SPN: " + strSPN);
- }
- return strSPN;
- }
+ return strSPN;
+ }
+
+ // Package visible members below.
+ KerbAuthentication(SQLServerConnection con,
+ String address,
+ int port) throws SQLServerException {
+ this.con = con;
+ // Get user provided SPN string; if not provided then build the generic one
+ String userSuppliedServerSpn = con.activeConnectionProperties.getProperty(SQLServerDriverStringProperty.SERVER_SPN.toString());
- // Package visible members below.
- KerbAuthentication(SQLServerConnection con, String address, int port) throws SQLServerException
- {
- this.con = con;
- // Get user provided SPN string; if not provided then build the generic one
- String userSuppliedServerSpn = con.activeConnectionProperties.
- getProperty(SQLServerDriverStringProperty.SERVER_SPN.toString());
-
- if (null != userSuppliedServerSpn)
- {
- // serverNameAsACE is true, translate the user supplied serverSPN to ASCII
- if(con.serverNameAsACE())
- {
- int slashPos = userSuppliedServerSpn.indexOf("/");
- spn = userSuppliedServerSpn.substring(0,slashPos+1)
- + IDN.toASCII(userSuppliedServerSpn.substring(slashPos+1));
- }
- else
- {
- spn = userSuppliedServerSpn;
- }
- }
- else
- {
- spn = makeSpn(address, port);
+ if (null != userSuppliedServerSpn) {
+ // serverNameAsACE is true, translate the user supplied serverSPN to ASCII
+ if (con.serverNameAsACE()) {
+ int slashPos = userSuppliedServerSpn.indexOf("/");
+ spn = userSuppliedServerSpn.substring(0, slashPos + 1) + IDN.toASCII(userSuppliedServerSpn.substring(slashPos + 1));
+ }
+ else {
+ spn = userSuppliedServerSpn;
+ }
+ }
+ else {
+ spn = makeSpn(address, port);
}
- }
-
- byte[] GenerateClientContext(byte[] pin, boolean[] done ) throws SQLServerException
- {
- if(null == peerContext)
- {
+ }
+
+ byte[] GenerateClientContext(byte[] pin,
+ boolean[] done) throws SQLServerException {
+ if (null == peerContext) {
intAuthInit();
}
return intAuthHandShake(pin, done);
}
- int ReleaseClientContext() throws SQLServerException
- {
- try
- {
- if(null != peerCredentials)
+
+ int ReleaseClientContext() throws SQLServerException {
+ try {
+ if (null != peerCredentials)
peerCredentials.dispose();
- if(null != peerContext)
+ if (null != peerContext)
peerContext.dispose();
- if(null != lc)
+ if (null != lc)
lc.logout();
}
- catch(LoginException e)
- {
+ catch (LoginException e) {
// yes we are eating exceptions here but this should not fail in the normal circumstances and we do not want to eat previous
// login errors if caused before which is more useful to the user than the cleanup errors.
authLogger.fine(toString() + " Release of the credentials failed LoginException: " + e);
}
- catch(GSSException e)
- {
+ catch (GSSException e) {
// yes we are eating exceptions here but this should not fail in the normal circumstances and we do not want to eat previous
// login errors if caused before which is more useful to the user than the cleanup errors.
authLogger.fine(toString() + " Release of the credentials failed GSSException: " + e);
@@ -350,5 +292,3 @@ int ReleaseClientContext() throws SQLServerException
return 0;
}
}
-
-
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KeyStoreProviderCommon.java b/src/main/java/com/microsoft/sqlserver/jdbc/KeyStoreProviderCommon.java
index 0050c6a61..324b1232e 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KeyStoreProviderCommon.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KeyStoreProviderCommon.java
@@ -1,21 +1,10 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: KeyStoreProviderCommon.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
@@ -34,227 +23,159 @@
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
-
/**
*
- *This class holds information about the certificate
+ * This class holds information about the certificate
*
*/
-class CertificateDetails
-{
+class CertificateDetails {
X509Certificate certificate;
Key privateKey;
- CertificateDetails(X509Certificate certificate, Key privateKey)
- {
+ CertificateDetails(X509Certificate certificate,
+ Key privateKey) {
this.certificate = certificate;
this.privateKey = privateKey;
}
}
-
class KeyStoreProviderCommon {
- static final String rsaEncryptionAlgorithmWithOAEP="RSA_OAEP";
- static byte[] version = new byte[] { 0x01 };
+ static final String rsaEncryptionAlgorithmWithOAEP = "RSA_OAEP";
+ static byte[] version = new byte[] {0x01};
- static void validateEncryptionAlgorithm(String encryptionAlgorithm, boolean isEncrypt) throws SQLServerException
- {
+ static void validateEncryptionAlgorithm(String encryptionAlgorithm,
+ boolean isEncrypt) throws SQLServerException {
String errString = isEncrypt ? "R_NullKeyEncryptionAlgorithm" : "R_NullKeyEncryptionAlgorithmInternal";
- if (null == encryptionAlgorithm)
- {
+ if (null == encryptionAlgorithm) {
- throw new SQLServerException(
- null,
- SQLServerException.getErrString(errString),
- null,
- 0,
- false);
+ throw new SQLServerException(null, SQLServerException.getErrString(errString), null, 0, false);
}
-
- errString = isEncrypt ? "R_InvalidKeyEncryptionAlgorithm" : "R_InvalidKeyEncryptionAlgorithmInternal";
- if (!rsaEncryptionAlgorithmWithOAEP.equalsIgnoreCase(encryptionAlgorithm.trim()))
- {
-
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString(errString));
- Object[] msgArgs = { encryptionAlgorithm, rsaEncryptionAlgorithmWithOAEP };
+
+ errString = isEncrypt ? "R_InvalidKeyEncryptionAlgorithm" : "R_InvalidKeyEncryptionAlgorithmInternal";
+ if (!rsaEncryptionAlgorithmWithOAEP.equalsIgnoreCase(encryptionAlgorithm.trim())) {
+
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString(errString));
+ Object[] msgArgs = {encryptionAlgorithm, rsaEncryptionAlgorithmWithOAEP};
throw new SQLServerException(form.format(msgArgs), null);
- }
+ }
}
- static void validateNonEmptyMasterKeyPath(String masterKeyPath) throws SQLServerException
- {
- if (null == masterKeyPath || masterKeyPath.trim().length() == 0)
- {
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_InvalidMasterKeyDetails"),
- null,
- 0,
- false);
- }
- }
-
- static byte[] decryptColumnEncryptionKey(
- String masterKeyPath,
- String encryptionAlgorithm,
- byte[] encryptedColumnEncryptionKey,
- CertificateDetails certificateDetails) throws SQLServerException
- {
- if(null == encryptedColumnEncryptionKey){
-
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"),
- null,
- 0,
- false);
-
+ static void validateNonEmptyMasterKeyPath(String masterKeyPath) throws SQLServerException {
+ if (null == masterKeyPath || masterKeyPath.trim().length() == 0) {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_InvalidMasterKeyDetails"), null, 0, false);
}
- else if (0==encryptedColumnEncryptionKey.length){
-
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_EmptyEncryptedColumnEncryptionKey"),
- null,
- 0,
- false);
-
+ }
+
+ static byte[] decryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] encryptedColumnEncryptionKey,
+ CertificateDetails certificateDetails) throws SQLServerException {
+ if (null == encryptedColumnEncryptionKey) {
+
+ throw new SQLServerException(null, SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"), null, 0, false);
+
}
-
+ else if (0 == encryptedColumnEncryptionKey.length) {
+
+ throw new SQLServerException(null, SQLServerException.getErrString("R_EmptyEncryptedColumnEncryptionKey"), null, 0, false);
+
+ }
+
validateEncryptionAlgorithm(encryptionAlgorithm, false);
-
+
int currentIndex = version.length;
int keyPathLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex);
// We just read 2 bytes
currentIndex += 2;
- // Get ciphertext length
- int cipherTextLength = convertTwoBytesToShort(
- encryptedColumnEncryptionKey,
- currentIndex);
- currentIndex += 2;
-
- currentIndex += keyPathLength;
-
- int signatureLength = encryptedColumnEncryptionKey.length - currentIndex -
- cipherTextLength;
-
- // Get ciphertext
- byte[] cipherText = new byte[cipherTextLength];
- System.arraycopy(
- encryptedColumnEncryptionKey,
- currentIndex,
- cipherText,
- 0,
- cipherTextLength);
- currentIndex += cipherTextLength;
-
- byte[] signature = new byte[signatureLength];
- System.arraycopy(
- encryptedColumnEncryptionKey,
- currentIndex,
- signature,
- 0,
- signatureLength);
-
-
-
- byte[] hash = new byte[encryptedColumnEncryptionKey.length - signature.length];
-
- System.arraycopy(
- encryptedColumnEncryptionKey,
- 0,
- hash,
- 0,
- encryptedColumnEncryptionKey.length - signature.length);
-
- if (!verifyRSASignature(hash, signature, certificateDetails.certificate, masterKeyPath))
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_InvalidCertificateSignature"));
- Object[] msgArgs = { masterKeyPath };
- throw new SQLServerException(form.format(msgArgs), null);
- }
-
- byte[] plainCEK = decryptRSAOAEP(cipherText, certificateDetails);
-
+ // Get ciphertext length
+ int cipherTextLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex);
+ currentIndex += 2;
+
+ currentIndex += keyPathLength;
+
+ int signatureLength = encryptedColumnEncryptionKey.length - currentIndex - cipherTextLength;
+
+ // Get ciphertext
+ byte[] cipherText = new byte[cipherTextLength];
+ System.arraycopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength);
+ currentIndex += cipherTextLength;
+
+ byte[] signature = new byte[signatureLength];
+ System.arraycopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signatureLength);
+
+ byte[] hash = new byte[encryptedColumnEncryptionKey.length - signature.length];
+
+ System.arraycopy(encryptedColumnEncryptionKey, 0, hash, 0, encryptedColumnEncryptionKey.length - signature.length);
+
+ if (!verifyRSASignature(hash, signature, certificateDetails.certificate, masterKeyPath)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidCertificateSignature"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(form.format(msgArgs), null);
+ }
+
+ byte[] plainCEK = decryptRSAOAEP(cipherText, certificateDetails);
+
return plainCEK;
}
-
- private static byte[] decryptRSAOAEP(byte[] cipherText, CertificateDetails certificateDetails)
- throws SQLServerException
- {
- byte[] plainCEK = null;
- try
- {
- Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
- rsa.init(Cipher.DECRYPT_MODE, certificateDetails.privateKey);
- rsa.update(cipherText);
- plainCEK = rsa.doFinal();
- }
- catch (InvalidKeyException |
- NoSuchAlgorithmException |
- NoSuchPaddingException |
- IllegalBlockSizeException |
- BadPaddingException e)
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_CEKDecryptionFailed"));
- Object[] msgArgs = { e.getMessage() };
- throw new SQLServerException(form.format(msgArgs), null);
- }
-
- return plainCEK;
+ private static byte[] decryptRSAOAEP(byte[] cipherText,
+ CertificateDetails certificateDetails) throws SQLServerException {
+ byte[] plainCEK = null;
+ try {
+ Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
+ rsa.init(Cipher.DECRYPT_MODE, certificateDetails.privateKey);
+ rsa.update(cipherText);
+ plainCEK = rsa.doFinal();
+ }
+ catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CEKDecryptionFailed"));
+ Object[] msgArgs = {e.getMessage()};
+ throw new SQLServerException(form.format(msgArgs), null);
}
- private static boolean verifyRSASignature(byte[] hash, byte[] signature, X509Certificate certificate, String masterKeyPath) throws SQLServerException
- {
- Signature signVerify;
- boolean verificationSucess = false;
- try
- {
- signVerify = Signature.getInstance("SHA256withRSA");
- signVerify.initVerify(certificate.getPublicKey());
- signVerify.update(hash);
- verificationSucess = signVerify.verify(signature);
- }
- catch (InvalidKeyException |
- NoSuchAlgorithmException |
- SignatureException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidCertificateSignature"));
- Object[] msgArgs = {masterKeyPath};
- throw new SQLServerException(form.format(msgArgs), null);
- }
-
- return verificationSucess;
+ return plainCEK;
+
+ }
+ private static boolean verifyRSASignature(byte[] hash,
+ byte[] signature,
+ X509Certificate certificate,
+ String masterKeyPath) throws SQLServerException {
+ Signature signVerify;
+ boolean verificationSucess = false;
+ try {
+ signVerify = Signature.getInstance("SHA256withRSA");
+ signVerify.initVerify(certificate.getPublicKey());
+ signVerify.update(hash);
+ verificationSucess = signVerify.verify(signature);
+ }
+ catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidCertificateSignature"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(form.format(msgArgs), null);
}
- private static short convertTwoBytesToShort(byte[] input, int index) throws SQLServerException
- {
-
- short shortVal = -1;
- if (index + 1 >= input.length)
- {
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_ByteToShortConversion"),
- null,
- 0,
- false);
- }
- ByteBuffer byteBuffer = ByteBuffer.allocate(2);
- byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
- byteBuffer.put(input[index]);
- byteBuffer.put(input[index + 1]);
- shortVal = byteBuffer.getShort(0);
- return shortVal;
+ return verificationSucess;
+ }
+
+ private static short convertTwoBytesToShort(byte[] input,
+ int index) throws SQLServerException {
+
+ short shortVal = -1;
+ if (index + 1 >= input.length) {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_ByteToShortConversion"), null, 0, false);
}
+ ByteBuffer byteBuffer = ByteBuffer.allocate(2);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.put(input[index]);
+ byteBuffer.put(input[index + 1]);
+ shortVal = byteBuffer.getShort(0);
+ return shortVal;
+
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultCredential.java b/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultCredential.java
index 690a7ee8f..70d99421a 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultCredential.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KeyVaultCredential.java
@@ -1,21 +1,10 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: KeyVaultCredential.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
@@ -32,40 +21,44 @@
* An implementation of ServiceClientCredentials that supports automatic bearer token refresh.
*
*/
-class KeyVaultCredential extends KeyVaultCredentials{
+class KeyVaultCredential extends KeyVaultCredentials {
+
+ // this is the only supported access token type
+ // https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx
+ private final String accessTokenType = "Bearer";
+
+ SQLServerKeyVaultAuthenticationCallback authenticationCallback = null;
+ String clientId = null;
+ String clientKey = null;
+ String accessToken = null;
- //this is the only supported access token type
- //https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx
- private final String accessTokenType = "Bearer";
+ KeyVaultCredential(SQLServerKeyVaultAuthenticationCallback authenticationCallback) {
+ this.authenticationCallback = authenticationCallback;
+ }
- SQLServerKeyVaultAuthenticationCallback authenticationCallback = null;
- String clientId = null;
- String clientKey = null;
- String accessToken = null;
+ /**
+ * Authenticates the service request
+ *
+ * @param request
+ * the ServiceRequestContext
+ * @param challenge
+ * used to get the accessToken
+ * @return BasicHeader
+ */
+ @Override
+ public Header doAuthenticate(ServiceRequestContext request,
+ Map challenge) {
+ assert null != challenge;
- KeyVaultCredential(SQLServerKeyVaultAuthenticationCallback authenticationCallback) {
- this.authenticationCallback = authenticationCallback;
- }
+ String authorization = challenge.get("authorization");
+ String resource = challenge.get("resource");
- /**
- * Authenticates the service request
- * @param request the ServiceRequestContext
- * @param challenge used to get the accessToken
- * @return BasicHeader
- */
- @Override
- public Header doAuthenticate(ServiceRequestContext request, Map challenge) {
- assert null != challenge;
-
- String authorization = challenge.get("authorization");
- String resource = challenge.get("resource");
+ accessToken = authenticationCallback.getAccessToken(authorization, resource, "");
+ return new BasicHeader("Authorization", accessTokenType + " " + accessToken);
+ }
- accessToken = authenticationCallback.getAccessToken(authorization, resource, "");
- return new BasicHeader("Authorization", accessTokenType + " " + accessToken);
- }
-
- void setAccessToken(String accessToken){
- this.accessToken= accessToken;
- }
+ void setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/PLPInputStream.java b/src/main/java/com/microsoft/sqlserver/jdbc/PLPInputStream.java
index abda2a8c1..ab3679ef0 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/PLPInputStream.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/PLPInputStream.java
@@ -1,44 +1,30 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: PLPInputStream.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
-* PLPInputStream is an InputStream implementation that reads from a TDS PLP stream.
-*
-* Note PLP stands for Partially Length-prefixed Bytes. TDS 7.2 introduced this new streaming
-* format for streaming of large types such as varchar(max), nvarchar(max), varbinary(max) and XML.
-*
-* See TDS specification, 6.3.3 Datatype Dependant Data Streams: Partially Length-prefixed Bytes
-* for more details on the PLP format.
-*/
-
-class PLPInputStream extends BaseInputStream
-{
- static final long PLP_NULL = 0xFFFFFFFFFFFFFFFFL;
+ * PLPInputStream is an InputStream implementation that reads from a TDS PLP stream.
+ *
+ * Note PLP stands for Partially Length-prefixed Bytes. TDS 7.2 introduced this new streaming format for streaming of large types such as
+ * varchar(max), nvarchar(max), varbinary(max) and XML.
+ *
+ * See TDS specification, 6.3.3 Datatype Dependant Data Streams: Partially Length-prefixed Bytes for more details on the PLP format.
+ */
+
+class PLPInputStream extends BaseInputStream {
+ static final long PLP_NULL = 0xFFFFFFFFFFFFFFFFL;
static final long UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFEL;
static final int PLP_TERMINATOR = 0x00000000;
private final static byte[] EMPTY_PLP_BYTES = new byte[0];
-
// Stated length of the PLP stream payload; -1 if unknown length.
int payloadLength;
@@ -47,63 +33,66 @@ class PLPInputStream extends BaseInputStream
private int currentChunkRemain;
private int markedChunkRemain;
- private int leftOverReadLimit =0;
+ private int leftOverReadLimit = 0;
private byte[] oneByteArray = new byte[1];
-
+
/**
* Non-destructive method for checking whether a PLP value at the current TDSReader location is null.
*/
- final static boolean isNull(TDSReader tdsReader) throws SQLServerException
- {
- TDSReaderMark mark = tdsReader.mark();
- try
- {
- PLPInputStream tempPLP = PLPInputStream.makeTempStream(tdsReader, false, null);
- try
- {
- if(null != tempPLP)
- {
- tempPLP.close();
- return false;
- }
- }
- catch (IOException e)
- {
- tdsReader.getConnection().terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, e.getMessage());
- }
- return true;
- }
- finally
- {
- if( null != tdsReader )
- tdsReader.reset(mark);
- }
+ final static boolean isNull(TDSReader tdsReader) throws SQLServerException {
+ TDSReaderMark mark = tdsReader.mark();
+ try {
+ PLPInputStream tempPLP = PLPInputStream.makeTempStream(tdsReader, false, null);
+ try {
+ if (null != tempPLP) {
+ tempPLP.close();
+ return false;
+ }
+ }
+ catch (IOException e) {
+ tdsReader.getConnection().terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, e.getMessage());
+ }
+ return true;
+ }
+ finally {
+ if (null != tdsReader)
+ tdsReader.reset(mark);
+ }
}
/**
* Create a new input stream.
- * @param tdsReader TDS reader pointing at the start of the PLP data
- * @param discardValue boolean to represent if base input stream is adaptive and is streaming
- * @param dtv DTV implementation for values set from the TDS response stream.
+ *
+ * @param tdsReader
+ * TDS reader pointing at the start of the PLP data
+ * @param discardValue
+ * boolean to represent if base input stream is adaptive and is streaming
+ * @param dtv
+ * DTV implementation for values set from the TDS response stream.
* @return PLPInputStream that is created
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- final static PLPInputStream makeTempStream(TDSReader tdsReader, boolean discardValue, ServerDTVImpl dtv) throws SQLServerException
- {
+ final static PLPInputStream makeTempStream(TDSReader tdsReader,
+ boolean discardValue,
+ ServerDTVImpl dtv) throws SQLServerException {
return makeStream(tdsReader, discardValue, discardValue, dtv);
}
- final static PLPInputStream makeStream(TDSReader tdsReader, InputStreamGetterArgs getterArgs, ServerDTVImpl dtv) throws SQLServerException
- {
+ final static PLPInputStream makeStream(TDSReader tdsReader,
+ InputStreamGetterArgs getterArgs,
+ ServerDTVImpl dtv) throws SQLServerException {
PLPInputStream is = makeStream(tdsReader, getterArgs.isAdaptive, getterArgs.isStreaming, dtv);
if (null != is)
is.setLoggingInfo(getterArgs.logContext);
return is;
}
- private static PLPInputStream makeStream(TDSReader tdsReader, boolean isAdaptive, boolean isStreaming, ServerDTVImpl dtv) throws SQLServerException
- {
+ private static PLPInputStream makeStream(TDSReader tdsReader,
+ boolean isAdaptive,
+ boolean isStreaming,
+ ServerDTVImpl dtv) throws SQLServerException {
// Read total length of PLP stream.
long payloadLength = tdsReader.readLong();
@@ -114,48 +103,44 @@ private static PLPInputStream makeStream(TDSReader tdsReader, boolean isAdaptive
return new PLPInputStream(tdsReader, payloadLength, isAdaptive, isStreaming, dtv);
}
-
/**
- * Initializes the input stream.
- */
- PLPInputStream(TDSReader tdsReader, long statedPayloadLength, boolean isAdaptive, boolean isStreaming, ServerDTVImpl dtv) throws SQLServerException
- {
+ * Initializes the input stream.
+ */
+ PLPInputStream(TDSReader tdsReader,
+ long statedPayloadLength,
+ boolean isAdaptive,
+ boolean isStreaming,
+ ServerDTVImpl dtv) throws SQLServerException {
super(tdsReader, isAdaptive, isStreaming, dtv);
this.payloadLength = (UNKNOWN_PLP_LEN != statedPayloadLength) ? ((int) statedPayloadLength) : -1;
this.currentChunkRemain = this.markedChunkRemain = 0;
}
/**
- * Helper function to convert the entire PLP stream into a contiguous byte array.
- * This call is inefficient (in terms of memory usage and run time) for
- * very large PLPs. Use it only if a contiguous byte array is required.
- */
- byte[] getBytes() throws SQLServerException
- {
+ * Helper function to convert the entire PLP stream into a contiguous byte array. This call is inefficient (in terms of memory usage and run time)
+ * for very large PLPs. Use it only if a contiguous byte array is required.
+ */
+ byte[] getBytes() throws SQLServerException {
byte[] value;
// The following 0-byte read just ensures that the number of bytes
// remaining in the current chunk is known.
readBytesInternal(null, 0, 0);
- if (PLP_EOS == currentChunkRemain)
- {
+ if (PLP_EOS == currentChunkRemain) {
value = EMPTY_PLP_BYTES;
}
- else
- {
+ else {
// If the PLP payload length is known, allocate the final byte array now.
- // Otherwise, start with the size of the first chunk. Additional chunks
+ // Otherwise, start with the size of the first chunk. Additional chunks
// will cause the array to be reallocated & copied.
value = new byte[(-1 != payloadLength) ? payloadLength : currentChunkRemain];
int bytesRead = 0;
- while (PLP_EOS != currentChunkRemain)
- {
+ while (PLP_EOS != currentChunkRemain) {
// If the current byte array isn't large enough to hold
// the contents of the current chunk, then make it larger.
- if (value.length == bytesRead)
- {
+ if (value.length == bytesRead) {
byte[] newValue = new byte[bytesRead + currentChunkRemain];
System.arraycopy(value, 0, newValue, 0, bytesRead);
value = newValue;
@@ -166,89 +151,83 @@ byte[] getBytes() throws SQLServerException
}
// Always close the stream after retrieving it
- try
- {
+ try {
close();
}
- catch (IOException e)
- {
- SQLServerException.makeFromDriverError(
- null,
- null,
- e.getMessage(),
- null,
- true);
+ catch (IOException e) {
+ SQLServerException.makeFromDriverError(null, null, e.getMessage(), null, true);
}
return value;
}
/**
- * Skips over and discards n bytes of data from this input stream.
- * @param n the number of bytes to be skipped.
- * @return the actual number of bytes skipped.
- * @exception IOException if an I/O error occurs.
- */
- public long skip(long n) throws IOException
- {
+ * Skips over and discards n bytes of data from this input stream.
+ *
+ * @param n
+ * the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public long skip(long n) throws IOException {
checkClosed();
- if (n < 0) return 0L;
- if(n >Integer.MAX_VALUE)
+ if (n < 0)
+ return 0L;
+ if (n > Integer.MAX_VALUE)
n = Integer.MAX_VALUE;
- long bytesread = readBytes(null,0,(int)n);
+ long bytesread = readBytes(null, 0, (int) n);
- if(-1 == bytesread)
+ if (-1 == bytesread)
return 0;
else
return bytesread;
}
/**
- * Returns the number of bytes that can be read (or skipped over) from this
- * input stream without blocking by the next caller of a method for
- * this input stream.
- * @return the actual number of bytes available.
- * @exception IOException if an I/O error occurs.
- */
- public int available() throws IOException
- {
- checkClosed();
- try
- {
-
- // The following 0-byte read just ensures that the number of bytes
- // remaining in the current chunk is known.
- if (0 == currentChunkRemain)
- readBytesInternal(null, 0, 0);
-
- if (PLP_EOS == currentChunkRemain)
- return 0;
-
- // Return the lesser of the number of bytes available for reading
- // from the underlying TDSReader and the number of bytes left in
- // the current chunk.
- int available = tdsReader.available();
- if (available > currentChunkRemain)
- available = currentChunkRemain;
-
- return available;
- }
- catch (SQLServerException e)
- {
- throw new IOException(e.getMessage());
- }
+ * Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this
+ * input stream.
+ *
+ * @return the actual number of bytes available.
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public int available() throws IOException {
+ checkClosed();
+ try {
+
+ // The following 0-byte read just ensures that the number of bytes
+ // remaining in the current chunk is known.
+ if (0 == currentChunkRemain)
+ readBytesInternal(null, 0, 0);
+ if (PLP_EOS == currentChunkRemain)
+ return 0;
+
+ // Return the lesser of the number of bytes available for reading
+ // from the underlying TDSReader and the number of bytes left in
+ // the current chunk.
+ int available = tdsReader.available();
+ if (available > currentChunkRemain)
+ available = currentChunkRemain;
+
+ return available;
+ }
+ catch (SQLServerException e) {
+ throw new IOException(e.getMessage());
+ }
}
/**
- * Reads the next byte of data from the input stream.
- * @return the byte read or -1 meaning no more bytes.
- * @exception IOException if an I/O error occurs.
- */
- public int read() throws IOException
- {
+ * Reads the next byte of data from the input stream.
+ *
+ * @return the byte read or -1 meaning no more bytes.
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public int read() throws IOException {
checkClosed();
if (-1 != readBytes(oneByteArray, 0, 1))
@@ -257,34 +236,42 @@ public int read() throws IOException
}
/**
- * Reads available data into supplied byte array.
- * @param b array of bytes to fill.
- * @return the number of bytes read or 0 meaning no bytes read.
- * @exception IOException if an I/O error occurs.
- */
- public int read(byte[] b) throws IOException
- {
- // If b is null, a NullPointerException is thrown.
- if (null==b)
+ * Reads available data into supplied byte array.
+ *
+ * @param b
+ * array of bytes to fill.
+ * @return the number of bytes read or 0 meaning no bytes read.
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public int read(byte[] b) throws IOException {
+ // If b is null, a NullPointerException is thrown.
+ if (null == b)
throw new NullPointerException();
checkClosed();
- return readBytes(b,0,b.length);
+ return readBytes(b, 0, b.length);
}
-
+
/**
- * Reads available data into supplied byte array.
- * @param b array of bytes to fill.
- * @param offset the offset into array b where to start writing.
- * @param maxBytes the max number of bytes to write into b.
- * @return the number of bytes read or 0 meaning no bytes read.
- * @exception IOException if an I/O error occurs.
- */
- public int read(byte b[], int offset, int maxBytes) throws IOException
- {
- // If b is null, a NullPointerException is thrown.
- if (null==b)
+ * Reads available data into supplied byte array.
+ *
+ * @param b
+ * array of bytes to fill.
+ * @param offset
+ * the offset into array b where to start writing.
+ * @param maxBytes
+ * the max number of bytes to write into b.
+ * @return the number of bytes read or 0 meaning no bytes read.
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public int read(byte b[],
+ int offset,
+ int maxBytes) throws IOException {
+ // If b is null, a NullPointerException is thrown.
+ if (null == b)
throw new NullPointerException();
// Verify offset and maxBytes against target buffer if we're reading (as opposed to skipping).
@@ -295,59 +282,60 @@ public int read(byte b[], int offset, int maxBytes) throws IOException
checkClosed();
- return readBytes(b,offset,maxBytes);
+ return readBytes(b, offset, maxBytes);
}
/**
- * Reads available data into supplied byte array b.
- * @param b array of bytes to fill. If b is null, method will skip over data.
- * @param offset the offset into array b where to start writing.
- * @param maxBytes the max number of bytes to write into b.
- * @return the number of bytes read or 0 meaning no bytes read or -1 meaning EOS.
- * @exception IOException if an I/O error occurs.
- */
- int readBytes(byte[] b, int offset, int maxBytes) throws IOException
- {
+ * Reads available data into supplied byte array b.
+ *
+ * @param b
+ * array of bytes to fill. If b is null, method will skip over data.
+ * @param offset
+ * the offset into array b where to start writing.
+ * @param maxBytes
+ * the max number of bytes to write into b.
+ * @return the number of bytes read or 0 meaning no bytes read or -1 meaning EOS.
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ int readBytes(byte[] b,
+ int offset,
+ int maxBytes) throws IOException {
// If maxBytes is zero, then no bytes are read and 0 is returned
// This must be done here rather than in readBytesInternal since a 0-byte read
// there may return -1 at EOS.
if (0 == maxBytes)
return 0;
- try
- {
+ try {
return readBytesInternal(b, offset, maxBytes);
}
- catch (SQLServerException e)
- {
+ catch (SQLServerException e) {
throw new IOException(e.getMessage());
}
}
- private int readBytesInternal(byte b[], int offset, int maxBytes) throws SQLServerException
- {
+ private int readBytesInternal(byte b[],
+ int offset,
+ int maxBytes) throws SQLServerException {
// If we're at EOS, say so.
// Note: For back compat, this special case needs to always be handled
// before checking user-supplied arguments below.
if (PLP_EOS == currentChunkRemain)
return -1;
-
// Save off the current TDSReader position, wherever it is, and start reading
// from where we left off last time.
int bytesRead = 0;
- for (;;)
- {
+ for (;;) {
// Check that we have bytes left to read from the current chunk.
// If not then figure out the size of the next chunk or
// determine that we have reached the end of the stream.
- if (0 == currentChunkRemain)
- {
+ if (0 == currentChunkRemain) {
currentChunkRemain = (int) tdsReader.readUnsignedInt();
- assert currentChunkRemain >=0;
- if (0 == currentChunkRemain)
- {
+ assert currentChunkRemain >= 0;
+ if (0 == currentChunkRemain) {
currentChunkRemain = PLP_EOS;
break;
}
@@ -373,12 +361,10 @@ private int readBytesInternal(byte b[], int offset, int maxBytes) throws SQLServ
currentChunkRemain -= bytesToRead;
}
- if (bytesRead > 0)
- {
- if(isReadLimitSet && leftOverReadLimit >0)
- {
- leftOverReadLimit= leftOverReadLimit - bytesRead;
- if (leftOverReadLimit <0)
+ if (bytesRead > 0) {
+ if (isReadLimitSet && leftOverReadLimit > 0) {
+ leftOverReadLimit = leftOverReadLimit - bytesRead;
+ if (leftOverReadLimit < 0)
clearCurrentMark();
}
return bytesRead;
@@ -391,43 +377,45 @@ private int readBytesInternal(byte b[], int offset, int maxBytes) throws SQLServ
}
/**
- * Marks the current position in this input stream.
- * @param readlimit the number of bytes to hold (this implementation ignores this).
- */
- public void mark(int readLimit)
- {
+ * Marks the current position in this input stream.
+ *
+ * @param readlimit
+ * the number of bytes to hold (this implementation ignores this).
+ */
+ public void mark(int readLimit) {
// Save off current position and how much of the current chunk remains
// cant throw if the tdsreader is null
- if(null != tdsReader && readLimit >0)
- {
+ if (null != tdsReader && readLimit > 0) {
currentMark = tdsReader.mark();
markedChunkRemain = currentChunkRemain;
- leftOverReadLimit =readLimit;
+ leftOverReadLimit = readLimit;
setReadLimit(readLimit);
- }
+ }
}
-
+
/**
- * Closes the stream releasing all resources held.
- * @exception IOException if an I/O error occurs.
- */
- public void close() throws IOException
- {
- if (null==tdsReader)
+ * Closes the stream releasing all resources held.
+ *
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public void close() throws IOException {
+ if (null == tdsReader)
return;
- while (skip(tdsReader.getConnection().getTDSPacketSize()) !=0)
+ while (skip(tdsReader.getConnection().getTDSPacketSize()) != 0)
;
// Release ref to tdsReader and parentRS here, shut down stream state.
closeHelper();
}
/**
- * Resets stream to saved mark position.
- * @exception IOException if an I/O error occurs.
- */
- public void reset() throws IOException
- {
+ * Resets stream to saved mark position.
+ *
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public void reset() throws IOException {
resetHelper();
leftOverReadLimit = readLimit;
currentChunkRemain = markedChunkRemain;
@@ -437,18 +425,17 @@ public void reset() throws IOException
/**
* Implements an XML binary stream with BOM header.
*
- * Class extends a normal PLPInputStream class and prepends the XML BOM (0xFFFE) token
- * then steps out of the way and forwards the rest of the InputStream calls to the
- * super class PLPInputStream.
+ * Class extends a normal PLPInputStream class and prepends the XML BOM (0xFFFE) token then steps out of the way and forwards the rest of the
+ * InputStream calls to the super class PLPInputStream.
*/
-final class PLPXMLInputStream extends PLPInputStream
-{
+final class PLPXMLInputStream extends PLPInputStream {
// XML BOM header (the first two header bytes sent to caller).
- private final static byte [] xmlBOM = {(byte)0xFF, (byte)0xFE};
+ private final static byte[] xmlBOM = {(byte) 0xFF, (byte) 0xFE};
private final ByteArrayInputStream bomStream = new ByteArrayInputStream(xmlBOM);
- final static PLPXMLInputStream makeXMLStream(TDSReader tdsReader, InputStreamGetterArgs getterArgs, ServerDTVImpl dtv) throws SQLServerException
- {
+ final static PLPXMLInputStream makeXMLStream(TDSReader tdsReader,
+ InputStreamGetterArgs getterArgs,
+ ServerDTVImpl dtv) throws SQLServerException {
// Read total length of PLP stream.
long payloadLength = tdsReader.readLong();
@@ -463,22 +450,20 @@ final static PLPXMLInputStream makeXMLStream(TDSReader tdsReader, InputStreamGet
return is;
}
- PLPXMLInputStream(
- TDSReader tdsReader,
- long statedPayloadLength,
- InputStreamGetterArgs getterArgs,
- ServerDTVImpl dtv) throws SQLServerException
- {
+ PLPXMLInputStream(TDSReader tdsReader,
+ long statedPayloadLength,
+ InputStreamGetterArgs getterArgs,
+ ServerDTVImpl dtv) throws SQLServerException {
super(tdsReader, statedPayloadLength, getterArgs.isAdaptive, getterArgs.isStreaming, dtv);
}
- public void close() throws IOException
- {
+ public void close() throws IOException {
super.close();
}
- int readBytes(byte[] b, int offset, int maxBytes) throws IOException
- {
+ int readBytes(byte[] b,
+ int offset,
+ int maxBytes) throws IOException {
assert offset >= 0;
assert maxBytes >= 0;
// If maxBytes is zero, then no bytes are read and 0 is returned.
@@ -488,26 +473,20 @@ int readBytes(byte[] b, int offset, int maxBytes) throws IOException
int bytesRead = 0;
int xmlBytesRead = 0;
- // Read/Skip BOM bytes first. When all BOM bytes have been consumed ...
- if (null == b)
- {
- for (int bomBytesSkipped = 0;
- bytesRead < maxBytes && 0 != (bomBytesSkipped = (int) bomStream.skip(maxBytes - bytesRead));
- bytesRead += bomBytesSkipped)
+ // Read/Skip BOM bytes first. When all BOM bytes have been consumed ...
+ if (null == b) {
+ for (int bomBytesSkipped = 0; bytesRead < maxBytes
+ && 0 != (bomBytesSkipped = (int) bomStream.skip(maxBytes - bytesRead)); bytesRead += bomBytesSkipped)
;
}
- else
- {
- for (int bomBytesRead = 0;
- bytesRead < maxBytes && -1 != (bomBytesRead = bomStream.read(b, offset + bytesRead, maxBytes - bytesRead));
- bytesRead += bomBytesRead)
+ else {
+ for (int bomBytesRead = 0; bytesRead < maxBytes
+ && -1 != (bomBytesRead = bomStream.read(b, offset + bytesRead, maxBytes - bytesRead)); bytesRead += bomBytesRead)
;
}
// ... then read/skip bytes from the underlying PLPInputStream
- for (;
- bytesRead < maxBytes && -1 != (xmlBytesRead = super.readBytes(b, offset + bytesRead, maxBytes - bytesRead));
- bytesRead += xmlBytesRead)
+ for (; bytesRead < maxBytes && -1 != (xmlBytesRead = super.readBytes(b, offset + bytesRead, maxBytes - bytesRead)); bytesRead += xmlBytesRead)
;
if (bytesRead > 0)
@@ -518,52 +497,41 @@ int readBytes(byte[] b, int offset, int maxBytes) throws IOException
return -1;
}
- public void mark(int readLimit)
- {
+ public void mark(int readLimit) {
bomStream.mark(xmlBOM.length);
super.mark(readLimit);
}
- public void reset() throws IOException
- {
+ public void reset() throws IOException {
bomStream.reset();
super.reset();
}
/**
- * Helper function to convert the entire PLP stream into a contiguous byte array.
- * This call is inefficient (in terms of memory usage and run time) for
- * very large PLPs. Use it only if a contiguous byte array is required.
+ * Helper function to convert the entire PLP stream into a contiguous byte array. This call is inefficient (in terms of memory usage and run time)
+ * for very large PLPs. Use it only if a contiguous byte array is required.
*/
- byte[] getBytes() throws SQLServerException
- {
+ byte[] getBytes() throws SQLServerException {
// Look to see if the BOM has been read
- byte [] bom = new byte[2];
- try
- {
+ byte[] bom = new byte[2];
+ try {
int bytesread = bomStream.read(bom);
byte[] valueWithoutBOM = super.getBytes();
- if(bytesread >0)
- {
+ if (bytesread > 0) {
assert 2 == bytesread;
byte[] valueWithBOM = new byte[valueWithoutBOM.length + bytesread];
System.arraycopy(bom, 0, valueWithBOM, 0, bytesread);
System.arraycopy(valueWithoutBOM, 0, valueWithBOM, bytesread, valueWithoutBOM.length);
return valueWithBOM;
- }
+ }
else
return valueWithoutBOM;
- }catch (IOException e)
- {
- SQLServerException.makeFromDriverError(
- null,
- null,
- e.getMessage(),
- null,
- true);
}
-
+ catch (IOException e) {
+ SQLServerException.makeFromDriverError(null, null, e.getMessage(), null, true);
+ }
+
return null;
}
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
index e4f007430..6c12683c4 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
@@ -1,23 +1,13 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: Parameter.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
+
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
@@ -37,1287 +27,1180 @@
import java.util.Locale;
/**
- * Parameter represents a JDBC parameter value that is supplied with a prepared or callable
- * statement or an updatable result set. Parameter is JDBC type specific and is capable of
- * representing any Java native type as well as a number of Java object types including
- * binary and character streams.
+ * Parameter represents a JDBC parameter value that is supplied with a prepared or callable statement or an updatable result set. Parameter is JDBC
+ * type specific and is capable of representing any Java native type as well as a number of Java object types including binary and character streams.
*/
-final class Parameter
-{
- // Value type info for OUT parameters (excluding return status)
- private TypeInfo typeInfo;
-
- // For unencrypted paramters cryptometa will be null. For encrypted parameters it will hold encryption metadata.
- CryptoMetadata cryptoMeta = null;
- TypeInfo getTypeInfo() { return typeInfo; }
- final CryptoMetadata getCryptoMetadata() {return cryptoMeta;}
-
- private boolean shouldHonorAEForParameter = false;
- private boolean userProvidesPrecision = false;
- private boolean userProvidesScale = false;
-
- // The parameter type definition
- private String typeDefinition = null;
- boolean renewDefinition = false;
-
- // updated if sendStringParametersAsUnicode=true for setNString, setNCharacterStream, and setNClob methods
- private JDBCType jdbcTypeSetByUser = null;
-
- // set length of value for variable length type (String)
- private int valueLength = 0;
-
- private boolean forceEncryption = false;
-
-
- Parameter(boolean honorAE)
- {
- shouldHonorAEForParameter = honorAE;
- }
-
- // Flag set to true if this is a registered OUTPUT parameter.
- boolean isOutput() { return null != registeredOutDTV; }
-
- // Since a parameter can have only one type definition for both sending its value to the server (IN)
- // and getting its value from the server (OUT), we use the JDBC type of the IN parameter value if there
- // is one; otherwise we use the registered OUT param JDBC type.
- JDBCType getJdbcType() throws SQLServerException
- {
- return (null != inputDTV) ? inputDTV.getJdbcType() : JDBCType.UNKNOWN;
- }
-
- /**
- * Used when sendStringParametersAsUnicode=true to derive the appropriate National Character Set
- * JDBC type corresponding to the specified JDBC type.
- */
- private static JDBCType getSSPAUJDBCType(JDBCType jdbcType)
- {
- switch (jdbcType)
- {
- case CHAR: return JDBCType.NCHAR;
- case VARCHAR: return JDBCType.NVARCHAR;
- case LONGVARCHAR: return JDBCType.LONGNVARCHAR;
- case CLOB: return JDBCType.NCLOB;
- default: return jdbcType;
- }
- }
-
- // For parameters whose underlying type is not represented by a JDBC type
- // the transport type reflects how the value is sent to the
- // server (e.g. JDBCType.CHAR for GUID parameters).
- void registerForOutput(JDBCType jdbcType, SQLServerConnection con) throws SQLServerException
- {
- // DateTimeOffset is not supported with SQL Server versions earlier than Katmai
- if (JDBCType.DATETIMEOFFSET == jdbcType && !con.isKatmaiOrLater())
- {
- throw new SQLServerException(
- SQLServerException.getErrString("R_notSupported"),
- SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
- DriverError.NOT_SET,
- null);
- }
-
- // sendStringParametersAsUnicode
- // If set to true, this connection property tells the driver to send textual parameters
- // to the server as Unicode rather than MBCS. This is accomplished here by re-tagging
- // the value with the appropriate corresponding Unicode type.
- if (con.sendStringParametersAsUnicode())
- {
-
- if(shouldHonorAEForParameter)
- {
- setJdbcTypeSetByUser(jdbcType);
- }
-
- jdbcType = getSSPAUJDBCType(jdbcType);
- }
-
- registeredOutDTV = new DTV();
- registeredOutDTV.setJdbcType(jdbcType);
-
- if (null == setterDTV)
- inputDTV = registeredOutDTV;
-
- resetOutputValue();
- }
-
- int scale = 0;
-
- // Scale requested for a DECIMAL and NUMERIC OUT parameter. If the OUT parameter
- // is also non-null IN parameter, the scale will be the larger of this value and
- // the value of the IN parameter's scale.
- private int outScale = 4;
- int getOutScale() { return outScale; }
-
- void setOutScale(int outScale) {
- this.outScale = outScale;
- userProvidesScale = true;
- }
-
- // The parameter name
- private String name;
- private String schemaName;
-
- /*
- * The different DTVs representing the parameter's value:
- *
- * getterDTV -
- * The OUT value, if set, of the parameter after execution. This is the value
- * retrieved by CallableStatement getter methods.
- *
- * registeredOutDTV -
- * The "IN" value corresponding to a SQL NULL with a JDBC type that was passed to the
- * CallableStatement.registerOutParameter method. Since SQL Server does not
- * directly support OUT-only parameters (just IN and IN/OUT), the driver sends
- * a null IN value for an OUT parameter, unless the application set an input value
- * (setterDTV) as well.
- *
- * setterDTV -
- * The IN value, if set, of the parameter. This is the value set by PreparedStatement
- * and CallableStatement setter methods.
- *
- * inputDTV -
- * If set, refers to either setterDTV or registeredOutDTV depending on whether
- * the parameter is IN, IN/OUT, or OUT-only. If cleared (i.e. set to null),
- * it means that no value is set for the parameter and that execution of the
- * PreparedStatement or CallableStatement should throw a "parameter not set"
- * exception.
- *
- * Note that if the parameter value is a stream, the driver consumes its contents
- * it at execution and clears inputDTV and setterDTV so that the application must
- * reset the parameter prior to the next execution to avoid getting a "parameter not set"
- * exception.
- */
- private DTV getterDTV;
- private DTV registeredOutDTV = null;
- private DTV setterDTV = null;
- private DTV inputDTV = null;
-
- /**
- * Clones this Parameter object for use in a batch.
- *
- * The clone method creates a shallow clone of the Parameter object. That is,
- * the cloned instance references all of the same internal objects and state
- * as the original.
- *
- * Note: this method is purposely NOT the Object.clone() method, as that method
- * has specific requirements and semantics that we don't need here.
- */
- final Parameter cloneForBatch()
- {
- Parameter clonedParam = new Parameter(shouldHonorAEForParameter);
- clonedParam.typeInfo = typeInfo;
- clonedParam.typeDefinition = typeDefinition;
- clonedParam.outScale = outScale;
- clonedParam.name = name;
- clonedParam.getterDTV = getterDTV;
- clonedParam.registeredOutDTV = registeredOutDTV;
- clonedParam.setterDTV = setterDTV;
- clonedParam.inputDTV = inputDTV;
- clonedParam.cryptoMeta = cryptoMeta;
- clonedParam.jdbcTypeSetByUser = jdbcTypeSetByUser;
- clonedParam.valueLength = valueLength;
- clonedParam.userProvidesPrecision = userProvidesPrecision;
- clonedParam.userProvidesScale = userProvidesScale;
- return clonedParam;
- }
-
- /**
- * Skip value.
- */
- final void skipValue(TDSReader tdsReader, boolean isDiscard) throws SQLServerException
- {
- if (null == getterDTV)
- getterDTV = new DTV();
-
- deriveTypeInfo(tdsReader);
-
- getterDTV.skipValue(typeInfo, tdsReader, isDiscard);
- }
-
- /**
- * Skip value.
- */
- final void skipRetValStatus(TDSReader tdsReader) throws SQLServerException
- {
-
- StreamRetValue srv = new StreamRetValue();
- srv.setFromTDS(tdsReader);
- }
-
- // Clear an INPUT parameter value
- void clearInputValue()
- {
- setterDTV = null;
- inputDTV = registeredOutDTV;
- }
-
- // reset output value for re -execution
- // if there was old value reset it to a new DTV
- void resetOutputValue()
- {
- getterDTV = null;
- typeInfo = null;
- }
-
- void deriveTypeInfo( TDSReader tdsReader) throws SQLServerException
- {
- if(null == typeInfo)
- {
- typeInfo = TypeInfo.getInstance(tdsReader, true);
-
- if(shouldHonorAEForParameter && typeInfo.isEncrypted())
- {
- //In this case, method getCryptoMetadata(tdsReader) retrieves baseTypeInfo without cryptoMetadata,
- //so save cryptoMetadata first.
- CekTableEntry cekEntry = cryptoMeta.getCekTableEntry();
- cryptoMeta = (new StreamRetValue()).getCryptoMetadata(tdsReader);
- cryptoMeta.setCekTableEntry(cekEntry);
- }
- }
- }
-
- void setFromReturnStatus(int returnStatus, SQLServerConnection con) throws SQLServerException
- {
- if (null == getterDTV)
- getterDTV = new DTV();
-
- getterDTV.setValue(null, JDBCType.INTEGER, new Integer(returnStatus), JavaType.INTEGER, null, null, null, con, getForceEncryption());
- }
-
- void setValue(
- JDBCType jdbcType,
- Object value,
- JavaType javaType,
- StreamSetterArgs streamSetterArgs,
- Calendar calendar,
- Integer precision,
- Integer scale,
- SQLServerConnection con,
- boolean forceEncrypt, SQLServerStatementColumnEncryptionSetting stmtColumnEncriptionSetting,
- int parameterIndex,
- String userSQL,
- String tvpName) throws SQLServerException
- {
-
- if (shouldHonorAEForParameter)
- {
- userProvidesPrecision = false;
- userProvidesScale = false;
-
- if(null != precision){
- userProvidesPrecision = true;
- }
-
- if(null != scale){
- userProvidesScale = true;
- }
-
- //for encrypted tinyint, we need to convert short value to byte value,
- //otherwise it would be sent as smallint
- //Also, for setters, we are able to send tinyint to smallint
- //However, for output parameter, it might cause error.
- if(!isOutput()){
- if((JavaType.SHORT == javaType) &&
- ((JDBCType.TINYINT == jdbcType) || (JDBCType.SMALLINT == jdbcType))){
- // value falls in the TINYINT range
- if(((Short)value) >= 0 && ((Short)value) <= 255){
- value = ((Short)value).byteValue();
- javaType = JavaType.of(value);
- jdbcType = javaType.getJDBCType(SSType.UNKNOWN, jdbcType);
- }
- // value falls outside tinyint range. Throw an error if the user intends to send as tinyint.
- else
- {
- // This is for cases like setObject(1, Short.valueOf("-1"), java.sql.Types.TINYINT);
- if (JDBCType.TINYINT == jdbcType)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = {javaType.toString().toLowerCase(Locale.ENGLISH), jdbcType.toString().toLowerCase(Locale.ENGLISH)};
- throw new SQLServerException(form.format(msgArgs), null);
- }
- }
- }
- }
- }
-
- //forceEncryption is true, shouldhonorae is false
- if((true == forceEncrypt) && (false == Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, con))){
-
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAEFalse"));
- Object[] msgArgs = {parameterIndex, userSQL};
- SQLServerException.makeFromDriverError(con, this, form.format(msgArgs), null, true);
-
- }
-
- // DateTimeOffset is not supported with SQL Server versions earlier than Katmai
- if ((JDBCType.DATETIMEOFFSET == jdbcType || JavaType.DATETIMEOFFSET == javaType) &&
- !con.isKatmaiOrLater())
- {
- throw new SQLServerException(
- SQLServerException.getErrString("R_notSupported"),
- SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
- DriverError.NOT_SET,
- null);
- }
-
- if( JavaType.TVP == javaType )
- {
- TVP tvpValue = null;
- if (null == value)
- {
- tvpValue = new TVP (tvpName);
- }
- else if (value instanceof SQLServerDataTable)
- {
- tvpValue = new TVP (tvpName, (SQLServerDataTable) value);
- }
- else if (value instanceof ResultSet)
- {
- // if ResultSet and PreparedStatemet/CallableStatement are created from same connection object
- // with property SelectMethod=cursor, TVP is not supported
- if(con.getSelectMethod().equalsIgnoreCase("cursor") && (value instanceof SQLServerResultSet))
- {
- SQLServerStatement stmt = (SQLServerStatement) ((SQLServerResultSet)value).getStatement();
-
- if(con.equals(stmt.connection))
- {
- throw new SQLServerException(
- SQLServerException.getErrString("R_invalidServerCursorForTVP"),
- null);
- }
- }
-
- tvpValue = new TVP (tvpName, (ResultSet) value);
- }
- else if (value instanceof ISQLServerDataRecord)
- {
- tvpValue = new TVP (tvpName, (ISQLServerDataRecord) value);
- }
- else
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPInvalidValue"));
- Object[] msgArgs = {parameterIndex};
- throw new SQLServerException(form.format(msgArgs), null);
- }
-
- if (!tvpValue.isNull() && (0 == tvpValue.getTVPColumnCount()))
- {
- throw new SQLServerException(
- SQLServerException.getErrString("R_TVPEmptyMetadata"),
- null);
- }
- name = (tvpValue).getTVPName();
- schemaName= tvpValue.getOwningSchemaNameTVP();
-
- value = tvpValue;
- }
-
- // setting JDBCType and exact length needed for AE stored procedure
- if (shouldHonorAEForParameter)
- {
- setForceEncryption(forceEncrypt);
-
- //set it if it is not output parameter or jdbcTypeSetByUser is null
- if(!(this.isOutput() && this.jdbcTypeSetByUser != null)){
- setJdbcTypeSetByUser(jdbcType);
- }
-
- //skip it if is (character types or binary type) & is output parameter && value is already set,
- if( (! (jdbcType.isTextual() || jdbcType.isBinary()) ) || !(this.isOutput()) || (this.valueLength == 0) ){
- this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType);
- }
-
- if(null != scale){
- this.outScale = scale;
- }
- }
-
- // sendStringParametersAsUnicode
- // If set to true, this connection property tells the driver to send textual parameters
- // to the server as Unicode rather than MBCS. This is accomplished here by re-tagging
- // the value with the appropriate corresponding Unicode type.
- if (con.sendStringParametersAsUnicode() &&
- (JavaType.STRING == javaType ||
- JavaType.READER == javaType ||
- JavaType.CLOB == javaType))
- {
- jdbcType = getSSPAUJDBCType(jdbcType);
- }
-
- DTV newDTV = new DTV();
- newDTV.setValue(con.getDatabaseCollation(), jdbcType, value, javaType, streamSetterArgs, calendar, scale, con, forceEncrypt);
-
- if(!con.sendStringParametersAsUnicode()){
- newDTV.sendStringParametersAsUnicode = false;
- }
-
- inputDTV = setterDTV = newDTV;
- }
-
- boolean isNull()
- {
- if(null != getterDTV)
- return getterDTV.isNull();
-
- return false;
- }
-
- boolean isValueGotten()
- {
- return (null!=getterDTV)? (true):(false);
-
- }
-
- Object getValue(JDBCType jdbcType, InputStreamGetterArgs getterArgs, Calendar cal, TDSReader tdsReader) throws SQLServerException
- {
- if(null == getterDTV)
- getterDTV = new DTV();
-
- deriveTypeInfo(tdsReader);
- // If the parameter is not encrypted or column encryption is turned off (either at connection or
- // statement level), cryptoMeta would be null.
- return getterDTV.getValue(jdbcType, outScale, getterArgs, cal, typeInfo, cryptoMeta, tdsReader);
- }
-
- int getInt(TDSReader tdsReader) throws SQLServerException
- {
- Integer value = (Integer) getValue(JDBCType.INTEGER, null, null, tdsReader);
- return null != value ? value.intValue() : 0;
- }
-
- /**
- * DTV execute op to determine the parameter type definition.
- */
- final class GetTypeDefinitionOp extends DTVExecuteOp
- {
- private static final String NVARCHAR_MAX = "nvarchar(max)";
- private static final String NVARCHAR_4K = "nvarchar(4000)";
- private static final String NTEXT = "ntext";
-
- private static final String VARCHAR_MAX = "varchar(max)";
- private static final String VARCHAR_8K = "varchar(8000)";
- private static final String TEXT = "text";
-
- private static final String VARBINARY_MAX = "varbinary(max)";
- private static final String VARBINARY_8K = "varbinary(8000)";
- private static final String IMAGE = "image";
-
- private final Parameter param;
- private final SQLServerConnection con;
-
- GetTypeDefinitionOp(
- Parameter param,
- SQLServerConnection con)
- {
- this.param = param;
- this.con = con;
- }
-
- private void setTypeDefinition(DTV dtv)
- {
- switch (dtv.getJdbcType())
- {
- case TINYINT:
- param.typeDefinition = SSType.TINYINT.toString();
- break;
-
- case SMALLINT:
- param.typeDefinition = SSType.SMALLINT.toString();
- break;
-
- case INTEGER:
- param.typeDefinition = SSType.INTEGER.toString();
- break;
-
- case BIGINT:
- param.typeDefinition = SSType.BIGINT.toString();
- break;
-
- case REAL:
- // sp_describe_parameter_encryption must be queried as real for AE
- if(param.shouldHonorAEForParameter &&
- (null!=jdbcTypeSetByUser) &&
- !(null == param.getCryptoMetadata() && param.renewDefinition))
- {
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- param.typeDefinition = SSType.REAL.toString();
- break;
- }
- case FLOAT:
- case DOUBLE:
- param.typeDefinition = SSType.FLOAT.toString();
- break;
-
- case DECIMAL:
- case NUMERIC:
- // First, bound the scale by the maximum allowed by SQL Server
- if (scale > SQLServerConnection.maxDecimalPrecision)
- scale = SQLServerConnection.maxDecimalPrecision;
-
- // Next, prepare with the largest of:
- // - the value's scale (initial value, as limited above)
- // - the specified input scale (if any)
- // - the registered output scale
- Integer inScale = dtv.getScale();
- if (null != inScale && scale < inScale.intValue())
- scale = inScale.intValue();
-
- if (param.isOutput() && scale < param.getOutScale())
- scale = param.getOutScale();
-
- if(param.shouldHonorAEForParameter &&
- (null!=jdbcTypeSetByUser) &&
- !(null == param.getCryptoMetadata() && param.renewDefinition))
- {
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- if(0 == valueLength){
- //for prepared statement and callable statement, There are only two cases where valueLength is 0:
- //1. when the parameter is output parameter
- //2. for input parameter, the value is null
- //so, here, if the decimal parameter is encrypted and it is null and it is not outparameter
- //then we set precision as the default precision instead of max precision
- if(!isOutput()){
- param.typeDefinition = "decimal(" + SQLServerConnection.defaultDecimalPrecision + ", " + scale + ")";
- }
- }
- else{
- if(SQLServerConnection.defaultDecimalPrecision >= valueLength){
- param.typeDefinition = "decimal("+SQLServerConnection.defaultDecimalPrecision+","+scale+")";
-
- if(SQLServerConnection.defaultDecimalPrecision < (valueLength + scale)){
- param.typeDefinition = "decimal(" + (SQLServerConnection.defaultDecimalPrecision + scale) + ","+scale+")";
- }
- }
- else{
- param.typeDefinition = "decimal("+SQLServerConnection.maxDecimalPrecision+","+scale+")";
- }
- }
-
- if(isOutput()){
- param.typeDefinition = "decimal(" + SQLServerConnection.maxDecimalPrecision + ", " + scale + ")";
- }
-
- if(userProvidesPrecision){
- param.typeDefinition = "decimal("+valueLength+","+scale+")";
- }
- }
- else
- param.typeDefinition = "decimal("+SQLServerConnection.maxDecimalPrecision+","+scale+")";
-
- break;
-
- case MONEY:
- param.typeDefinition = SSType.MONEY.toString();
- break;
- case SMALLMONEY:
- param.typeDefinition = SSType.MONEY.toString();
-
- if(param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)){
- param.typeDefinition = SSType.SMALLMONEY.toString();
- }
-
- break;
- case BIT:
- case BOOLEAN:
- param.typeDefinition = SSType.BIT.toString();
- break;
-
- case LONGVARBINARY:
- case BLOB:
- param.typeDefinition = VARBINARY_MAX;
- break;
-
- case BINARY:
- case VARBINARY:
- // To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
- if (VARBINARY_MAX.equals(param.typeDefinition) || IMAGE.equals(param.typeDefinition))
- break;
- if(param.shouldHonorAEForParameter &&
- (null!=jdbcTypeSetByUser) &&
- !(null == param.getCryptoMetadata() && param.renewDefinition))
- {
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- if(0 == valueLength){
- param.typeDefinition = "varbinary";
- }
- else{
- param.typeDefinition = "varbinary("+valueLength+")";
- }
-
- if(JDBCType.LONGVARBINARY== jdbcTypeSetByUser){
- param.typeDefinition = VARBINARY_MAX;
- }
- }
- else
- param.typeDefinition = VARBINARY_8K;
- break;
-
- case DATE:
- // Bind DATE values to pre-Katmai servers as DATETIME (which has no DATE-only type).
- param.typeDefinition = con.isKatmaiOrLater() ? SSType.DATE.toString() : SSType.DATETIME.toString();
- break;
-
- case TIME:
- if(param.shouldHonorAEForParameter
- && !(null == param.getCryptoMetadata() && param.renewDefinition)
- ){
-
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
-
- if(userProvidesScale){
- param.typeDefinition = (SSType.TIME.toString() + "(" + outScale + ")");
- }
- else{
- param.typeDefinition =
- param.typeDefinition = SSType.TIME.toString() + "(" + valueLength + ")";
- }
- }
- else{
- param.typeDefinition =
- con.getSendTimeAsDatetime() ?
- SSType.DATETIME.toString() :
- SSType.TIME.toString();
- }
- break;
-
- case TIMESTAMP:
- // Bind TIMESTAMP values to pre-Katmai servers as DATETIME. Bind TIMESTAMP values to
- // Katmai and later servers as DATETIME2 to take advantage of increased precision.
- if(param.shouldHonorAEForParameter
- && !(null == param.getCryptoMetadata() && param.renewDefinition)
- ){
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- if(userProvidesScale){
- param.typeDefinition =
- con.isKatmaiOrLater() ? (SSType.DATETIME2.toString() + "(" + outScale + ")") : (SSType.DATETIME.toString());
- }
- else{
- param.typeDefinition =
- con.isKatmaiOrLater() ? (SSType.DATETIME2.toString() + "(" + valueLength + ")") : SSType.DATETIME.toString();
- }
- }
- else{
- param.typeDefinition =
- con.isKatmaiOrLater() ? SSType.DATETIME2.toString() : SSType.DATETIME.toString();
- }
- break;
-
- case DATETIME:
- //send as Datetime by default
- param.typeDefinition = SSType.DATETIME2.toString();
-
- if(param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)){
- param.typeDefinition = SSType.DATETIME.toString();
- }
-
- if(!param.shouldHonorAEForParameter){
- //if AE is off and it is output parameter of stored procedure, sent it as datetime2(3)
- //otherwise it returns incorrect milliseconds.
- if(param.isOutput()){
- param.typeDefinition = SSType.DATETIME2.toString() + "(" + outScale + ")";
- }
- }
- else{
- //when AE is on, set it to Datetime by default,
- //However, if column is not encrypted and it is output parameter of stored procedure,
- //renew it to datetime2(3)
- if(null == param.getCryptoMetadata() && param.renewDefinition){
- if(param.isOutput()){
- param.typeDefinition = SSType.DATETIME2.toString() + "(" + outScale + ")";
- }
- break;
- }
- }
- break;
-
- case SMALLDATETIME:
- param.typeDefinition = SSType.DATETIME2.toString();
-
- if(param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)){
- param.typeDefinition = SSType.SMALLDATETIME.toString();
- }
-
- break;
-
- case TIME_WITH_TIMEZONE:
- case TIMESTAMP_WITH_TIMEZONE:
- case DATETIMEOFFSET:
- if(param.shouldHonorAEForParameter
- && !(null == param.getCryptoMetadata() && param.renewDefinition)
- ){
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- if(userProvidesScale){
- param.typeDefinition = SSType.DATETIMEOFFSET.toString() + "(" + outScale + ")";
- }
- else{
- param.typeDefinition = SSType.DATETIMEOFFSET.toString() + "(" + valueLength + ")";
- }
- }
- else{
- param.typeDefinition = SSType.DATETIMEOFFSET.toString();
- }
- break;
-
- case LONGVARCHAR:
- case CLOB:
- param.typeDefinition = VARCHAR_MAX;
- break;
-
- case CHAR:
- case VARCHAR:
- // To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
- if (VARCHAR_MAX.equals(param.typeDefinition) || TEXT.equals(param.typeDefinition))
- break;
-
- // Adding for case useColumnEncryption=true & sendStringParametersAsUnicode=false
- if(param.shouldHonorAEForParameter &&
- (null!=jdbcTypeSetByUser) &&
- !(null == param.getCryptoMetadata() && param.renewDefinition))
- {
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- if(0 == valueLength){
- param.typeDefinition = "varchar";
- }
- else{
- param.typeDefinition = "varchar("+valueLength+")";
-
- if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
- param.typeDefinition = VARCHAR_MAX;
- }
- }
- }
- else
- param.typeDefinition = VARCHAR_8K;
- break;
-
- case LONGNVARCHAR:
- if(param.shouldHonorAEForParameter &&
- !(null == param.getCryptoMetadata() && param.renewDefinition))
- {
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- if((null!=jdbcTypeSetByUser) &&
- ((jdbcTypeSetByUser == JDBCType.VARCHAR) || (jdbcTypeSetByUser == JDBCType.CHAR) || (jdbcTypeSetByUser == JDBCType.LONGVARCHAR))){
- if(0 == valueLength){
- param.typeDefinition = "varchar";
- }
- else if(DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength){
- param.typeDefinition = VARCHAR_MAX;
- }
- else{
- param.typeDefinition = "varchar("+valueLength+")";
- }
-
- if(jdbcTypeSetByUser == JDBCType.LONGVARCHAR){
- param.typeDefinition = VARCHAR_MAX;
- }
- }
- else if((null!=jdbcTypeSetByUser)
- && (jdbcTypeSetByUser == JDBCType.NVARCHAR || jdbcTypeSetByUser == JDBCType.LONGNVARCHAR)){
- if(0 == valueLength){
- param.typeDefinition = "nvarchar";
- }
- else if(DataTypes.SHORT_VARTYPE_MAX_CHARS < valueLength){
- param.typeDefinition = NVARCHAR_MAX;
- }
- else{
- param.typeDefinition = "nvarchar("+valueLength+")";
- }
-
- if(jdbcTypeSetByUser == JDBCType.LONGNVARCHAR){
- param.typeDefinition = NVARCHAR_MAX;
- }
- }
- else{ // used if setNull() is called with java.sql.Types.NCHAR
- if(0 == valueLength){
- param.typeDefinition = "nvarchar";
- }
- else{
- param.typeDefinition = "nvarchar("+valueLength+")";
-
- if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
- param.typeDefinition = NVARCHAR_MAX;
- }
- }
- }
- break;
- }
- else
- param.typeDefinition = NVARCHAR_MAX;
- break;
-
- case NCLOB:
- //do not need to check if AE is enabled or not,
- //because NCLOB does not work with it
- param.typeDefinition = NVARCHAR_MAX;
- break;
-
- case NCHAR:
- case NVARCHAR:
- // To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
- if (NVARCHAR_MAX.equals(param.typeDefinition) || NTEXT.equals(param.typeDefinition))
- break;
-
- if(param.shouldHonorAEForParameter &&
- !(null == param.getCryptoMetadata() && param.renewDefinition))
- {
- /*
- * This means AE is ON in the connection, and
- * (1) this is either the first round to SQL Server to get encryption meta data, or
- * (2) this is the second round of renewing meta data and parameter is encrypted
- * In both of these cases we need to send specific type info, otherwise generic type info can be used as before.
- */
- if((null!=jdbcTypeSetByUser) &&
- ((jdbcTypeSetByUser == JDBCType.VARCHAR)|| (jdbcTypeSetByUser == JDBCType.CHAR)
- || (JDBCType.LONGVARCHAR == jdbcTypeSetByUser))){
- if(0 == valueLength){
- param.typeDefinition = "varchar";
- }
- else{
- param.typeDefinition = "varchar("+valueLength+")";
-
- if( DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength){
- param.typeDefinition = VARCHAR_MAX;
- }
- }
-
- if(JDBCType.LONGVARCHAR == jdbcTypeSetByUser){
- param.typeDefinition = VARCHAR_MAX;
- }
- }
- else if((null!=jdbcTypeSetByUser) &&
- ((jdbcTypeSetByUser == JDBCType.NVARCHAR) || (jdbcTypeSetByUser == JDBCType.NCHAR)
- || (JDBCType.LONGNVARCHAR == jdbcTypeSetByUser))){
- if(0 == valueLength){
- param.typeDefinition = "nvarchar";
- }
- else{
- param.typeDefinition = "nvarchar("+valueLength+")";
-
- if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
- param.typeDefinition = NVARCHAR_MAX;
- }
- }
-
- if(JDBCType.LONGNVARCHAR == jdbcTypeSetByUser){
- param.typeDefinition = NVARCHAR_MAX;
- }
- }
- else{ // used if setNull() is called with java.sql.Types.NCHAR
- if(0 == valueLength){
- param.typeDefinition = "nvarchar";
- }
- else{
- param.typeDefinition = "nvarchar("+valueLength+")";
-
- if( DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength){
- param.typeDefinition = NVARCHAR_MAX;
- }
- }
- }
- break;
- }
- else
- param.typeDefinition = NVARCHAR_4K;
- break;
- case SQLXML:
- param.typeDefinition = SSType.XML.toString();
- break;
-
- case TVP:
- // definition should contain the TVP name and the keyword READONLY
- String schema = param.schemaName;
-
- if(null != schema){
- param.typeDefinition = "[" + schema + "].[" + param.name + "] READONLY";
- }
- else{
- param.typeDefinition = "[" + param.name + "] READONLY";
- }
-
- break;
-
- case GUID:
- param.typeDefinition = SSType.GUID.toString();
- break;
-
- default:
- assert false : "Unexpected JDBC type " + dtv.getJdbcType();
- break;
- }
- }
-
- void execute(DTV dtv, String strValue) throws SQLServerException
- {
- if (null != strValue && strValue.length() > DataTypes.SHORT_VARTYPE_MAX_CHARS)
- dtv.setJdbcType(JDBCType.LONGNVARCHAR);
-
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Clob clobValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Byte byteValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Integer intValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, java.sql.Time timeValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, java.sql.Date dateValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, java.sql.Timestamp timestampValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, java.util.Date utildateValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, java.util.Calendar calendarValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, LocalDate localDateValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, LocalTime localTimeValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, LocalDateTime localDateTimeValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, OffsetTime offsetTimeValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, OffsetDateTime OffsetDateTimeValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, microsoft.sql.DateTimeOffset dtoValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Float floatValue) throws SQLServerException
- {
- scale = 4;
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Double doubleValue) throws SQLServerException
- {
- scale = 4;
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, BigDecimal bigDecimalValue) throws SQLServerException
- {
- if (null != bigDecimalValue)
- {
- scale = bigDecimalValue.scale();
-
- // BigDecimal in JRE 1.5 and later JVMs exposes an implementation detail
- // that allows representation of large values in small space by interpreting
- // a negative value for scale to imply scientific notation (e.g. 1 E 10^n)
- // would have a scale of -n. A BigDecimal value with a negative scale has
- // no fractional component.
- if (scale < 0)
- scale = 0;
- }
-
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Long longValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, java.math.BigInteger bigIntegerValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Short shortValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Boolean booleanValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, byte[] byteArrayValue) throws SQLServerException
- {
- if (null != byteArrayValue && byteArrayValue.length > DataTypes.SHORT_VARTYPE_MAX_BYTES)
- dtv.setJdbcType(dtv.getJdbcType().isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);
-
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Blob blobValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, InputStream inputStreamValue) throws SQLServerException
- {
- StreamSetterArgs streamSetterArgs = dtv.getStreamSetterArgs();
-
- JDBCType jdbcType = dtv.getJdbcType();
-
- // If the JDBC type is currently a "short" type, then figure out if needs to be bumped up to a "long" type
- if (JDBCType.CHAR == jdbcType ||
- JDBCType.VARCHAR == jdbcType ||
- JDBCType.BINARY == jdbcType ||
- JDBCType.VARBINARY == jdbcType)
- {
- // If we know the length is too long for a "short" type, then convert to a "long" type.
- if (streamSetterArgs.getLength() > DataTypes.SHORT_VARTYPE_MAX_BYTES)
- dtv.setJdbcType(jdbcType.isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);
-
- // If the length of the value is unknown, then figure out whether it is at least longer
- // than what will fit into a "short" type.
- else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamSetterArgs.getLength())
- {
- byte[] vartypeBytes = new byte[1 + DataTypes.SHORT_VARTYPE_MAX_BYTES];
- BufferedInputStream bufferedStream = new BufferedInputStream(inputStreamValue, vartypeBytes.length);
-
- int bytesRead = 0;
-
- try
- {
- bufferedStream.mark(vartypeBytes.length);
-
- bytesRead = bufferedStream.read(vartypeBytes, 0, vartypeBytes.length);
-
- if (-1 == bytesRead)
- bytesRead = 0;
-
- bufferedStream.reset();
- }
- catch (IOException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {e.toString()};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
- }
-
- dtv.setValue(bufferedStream, JavaType.INPUTSTREAM);
-
- // If the stream is longer than what can fit into the "short" type, then use the "long" type instead.
- // Otherwise, we know the exact stream length since we reached end of stream before reading SHORT_VARTYPE_MAX_BYTES + 1
- // bytes. So adjust the setter args to reflect the known length to avoid unnecessarily copying the
- // stream again in SendByRPCOp.
- if (bytesRead > DataTypes.SHORT_VARTYPE_MAX_BYTES)
- dtv.setJdbcType(jdbcType.isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);
- else
- streamSetterArgs.setLength(bytesRead);
- }
- }
-
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, Reader readerValue) throws SQLServerException
- {
- // If the JDBC type is currently a "short" type, then figure out if needs to be bumped up to a "long" type
- if (JDBCType.NCHAR == dtv.getJdbcType() || JDBCType.NVARCHAR == dtv.getJdbcType())
- {
- StreamSetterArgs streamSetterArgs = dtv.getStreamSetterArgs();
-
- // If we know the length is too long for a "short" type, then convert to a "long" type.
- if (streamSetterArgs.getLength() > DataTypes.SHORT_VARTYPE_MAX_CHARS)
- dtv.setJdbcType(JDBCType.LONGNVARCHAR);
-
- // If the length of the value is unknown, then figure out whether it is at least longer
- // than what will fit into a "short" type.
- else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamSetterArgs.getLength())
- {
- char[] vartypeChars = new char[1 + DataTypes.SHORT_VARTYPE_MAX_CHARS];
- BufferedReader bufferedReader = new BufferedReader(readerValue, vartypeChars.length);
-
- int charsRead = 0;
-
- try
- {
- bufferedReader.mark(vartypeChars.length);
-
- charsRead = bufferedReader.read(vartypeChars, 0, vartypeChars.length);
-
- if (-1 == charsRead)
- charsRead = 0;
-
- bufferedReader.reset();
- }
- catch (IOException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
- Object[] msgArgs = {e.toString()};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
- }
-
- dtv.setValue(bufferedReader, JavaType.READER);
-
- if (charsRead > DataTypes.SHORT_VARTYPE_MAX_CHARS)
- dtv.setJdbcType(JDBCType.LONGNVARCHAR);
- else
- streamSetterArgs.setLength(charsRead);
- }
- }
-
- setTypeDefinition(dtv);
- }
- void execute(DTV dtv, SQLServerSQLXML xmlValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- void execute(DTV dtv, com.microsoft.sqlserver.jdbc.TVP tvpValue) throws SQLServerException
- {
- setTypeDefinition(dtv);
- }
-
- }
-
- /**
- * Returns a string used to define the parameter type for the server;
- * null if no value for the parameter has been set or registered.
- */
- String getTypeDefinition(SQLServerConnection con, TDSReader tdsReader) throws SQLServerException
- {
- if (null == inputDTV)
- return null;
-
- inputDTV.executeOp(new GetTypeDefinitionOp(this, con));
- return typeDefinition;
- }
-
- void sendByRPC(
- TDSWriter tdsWriter,
- SQLServerConnection conn) throws SQLServerException
- {
- assert null != inputDTV : "Parameter was neither set nor registered";
-
- try
- {
- inputDTV.sendCryptoMetaData(this.cryptoMeta, tdsWriter);
- inputDTV.jdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength());
- inputDTV.sendByRPC(
- name,
- null,
- conn.getDatabaseCollation(),
- valueLength,
- isOutput()?outScale:scale,
- isOutput(),
- tdsWriter,
- conn);
- }
- finally
- {
- // reset the cryptoMeta in IOBuffer
- inputDTV.sendCryptoMetaData(null, tdsWriter);
- }
- // Per JDBC spec:
- // "If in the execution of a PreparedStatement object, the JDBC driver reads values set
- // for the parameter markers by the methods setAsciiStream, setBinaryStream, setCharacterStream,
- // setNCharacterStream, or setUnicodeStream, those parameters must be reset prior to the next
- // execution of the PreparedStatement object otherwise a SQLException will be thrown."
- //
- // Clear the input and setter DTVs to relinquish their hold on the stream resource and ensure
- // that the next call to execute will throw a SQLException (from getTypeDefinitionOp).
- // Don't clear the registered output DTV so that the parameter will still be an OUT (IN/OUT) parameter.
- if (JavaType.INPUTSTREAM == inputDTV.getJavaType() ||
- JavaType.READER == inputDTV.getJavaType())
- {
- inputDTV = setterDTV = null;
- }
- }
- JDBCType getJdbcTypeSetByUser() {
- return jdbcTypeSetByUser;
- }
- void setJdbcTypeSetByUser(JDBCType jdbcTypeSetByUser) {
- this.jdbcTypeSetByUser = jdbcTypeSetByUser;
- }
- int getValueLength() {
- return valueLength;
- }
- void setValueLength(int valueLength) {
- this.valueLength = valueLength;
- userProvidesPrecision = true;
- }
- boolean getForceEncryption() {
- return forceEncryption;
- }
- void setForceEncryption(boolean forceEncryption) {
- this.forceEncryption = forceEncryption;
- }
+final class Parameter {
+ // Value type info for OUT parameters (excluding return status)
+ private TypeInfo typeInfo;
+
+ // For unencrypted paramters cryptometa will be null. For encrypted parameters it will hold encryption metadata.
+ CryptoMetadata cryptoMeta = null;
+
+ TypeInfo getTypeInfo() {
+ return typeInfo;
+ }
+
+ final CryptoMetadata getCryptoMetadata() {
+ return cryptoMeta;
+ }
+
+ private boolean shouldHonorAEForParameter = false;
+ private boolean userProvidesPrecision = false;
+ private boolean userProvidesScale = false;
+
+ // The parameter type definition
+ private String typeDefinition = null;
+ boolean renewDefinition = false;
+
+ // updated if sendStringParametersAsUnicode=true for setNString, setNCharacterStream, and setNClob methods
+ private JDBCType jdbcTypeSetByUser = null;
+
+ // set length of value for variable length type (String)
+ private int valueLength = 0;
+
+ private boolean forceEncryption = false;
+
+ Parameter(boolean honorAE) {
+ shouldHonorAEForParameter = honorAE;
+ }
+
+ // Flag set to true if this is a registered OUTPUT parameter.
+ boolean isOutput() {
+ return null != registeredOutDTV;
+ }
+
+ // Since a parameter can have only one type definition for both sending its value to the server (IN)
+ // and getting its value from the server (OUT), we use the JDBC type of the IN parameter value if there
+ // is one; otherwise we use the registered OUT param JDBC type.
+ JDBCType getJdbcType() throws SQLServerException {
+ return (null != inputDTV) ? inputDTV.getJdbcType() : JDBCType.UNKNOWN;
+ }
+
+ /**
+ * Used when sendStringParametersAsUnicode=true to derive the appropriate National Character Set JDBC type corresponding to the specified JDBC
+ * type.
+ */
+ private static JDBCType getSSPAUJDBCType(JDBCType jdbcType) {
+ switch (jdbcType) {
+ case CHAR:
+ return JDBCType.NCHAR;
+ case VARCHAR:
+ return JDBCType.NVARCHAR;
+ case LONGVARCHAR:
+ return JDBCType.LONGNVARCHAR;
+ case CLOB:
+ return JDBCType.NCLOB;
+ default:
+ return jdbcType;
+ }
+ }
+
+ // For parameters whose underlying type is not represented by a JDBC type
+ // the transport type reflects how the value is sent to the
+ // server (e.g. JDBCType.CHAR for GUID parameters).
+ void registerForOutput(JDBCType jdbcType,
+ SQLServerConnection con) throws SQLServerException {
+ // DateTimeOffset is not supported with SQL Server versions earlier than Katmai
+ if (JDBCType.DATETIMEOFFSET == jdbcType && !con.isKatmaiOrLater()) {
+ throw new SQLServerException(SQLServerException.getErrString("R_notSupported"), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET,
+ null);
+ }
+
+ // sendStringParametersAsUnicode
+ // If set to true, this connection property tells the driver to send textual parameters
+ // to the server as Unicode rather than MBCS. This is accomplished here by re-tagging
+ // the value with the appropriate corresponding Unicode type.
+ if (con.sendStringParametersAsUnicode()) {
+
+ if (shouldHonorAEForParameter) {
+ setJdbcTypeSetByUser(jdbcType);
+ }
+
+ jdbcType = getSSPAUJDBCType(jdbcType);
+ }
+
+ registeredOutDTV = new DTV();
+ registeredOutDTV.setJdbcType(jdbcType);
+
+ if (null == setterDTV)
+ inputDTV = registeredOutDTV;
+
+ resetOutputValue();
+ }
+
+ int scale = 0;
+
+ // Scale requested for a DECIMAL and NUMERIC OUT parameter. If the OUT parameter
+ // is also non-null IN parameter, the scale will be the larger of this value and
+ // the value of the IN parameter's scale.
+ private int outScale = 4;
+
+ int getOutScale() {
+ return outScale;
+ }
+
+ void setOutScale(int outScale) {
+ this.outScale = outScale;
+ userProvidesScale = true;
+ }
+
+ // The parameter name
+ private String name;
+ private String schemaName;
+
+ /*
+ * The different DTVs representing the parameter's value:
+ *
+ * getterDTV - The OUT value, if set, of the parameter after execution. This is the value retrieved by CallableStatement getter methods.
+ *
+ * registeredOutDTV - The "IN" value corresponding to a SQL NULL with a JDBC type that was passed to the CallableStatement.registerOutParameter
+ * method. Since SQL Server does not directly support OUT-only parameters (just IN and IN/OUT), the driver sends a null IN value for an OUT
+ * parameter, unless the application set an input value (setterDTV) as well.
+ *
+ * setterDTV - The IN value, if set, of the parameter. This is the value set by PreparedStatement and CallableStatement setter methods.
+ *
+ * inputDTV - If set, refers to either setterDTV or registeredOutDTV depending on whether the parameter is IN, IN/OUT, or OUT-only. If cleared
+ * (i.e. set to null), it means that no value is set for the parameter and that execution of the PreparedStatement or CallableStatement should
+ * throw a "parameter not set" exception.
+ *
+ * Note that if the parameter value is a stream, the driver consumes its contents it at execution and clears inputDTV and setterDTV so that the
+ * application must reset the parameter prior to the next execution to avoid getting a "parameter not set" exception.
+ */
+ private DTV getterDTV;
+ private DTV registeredOutDTV = null;
+ private DTV setterDTV = null;
+ private DTV inputDTV = null;
+
+ /**
+ * Clones this Parameter object for use in a batch.
+ *
+ * The clone method creates a shallow clone of the Parameter object. That is, the cloned instance references all of the same internal objects and
+ * state as the original.
+ *
+ * Note: this method is purposely NOT the Object.clone() method, as that method has specific requirements and semantics that we don't need here.
+ */
+ final Parameter cloneForBatch() {
+ Parameter clonedParam = new Parameter(shouldHonorAEForParameter);
+ clonedParam.typeInfo = typeInfo;
+ clonedParam.typeDefinition = typeDefinition;
+ clonedParam.outScale = outScale;
+ clonedParam.name = name;
+ clonedParam.getterDTV = getterDTV;
+ clonedParam.registeredOutDTV = registeredOutDTV;
+ clonedParam.setterDTV = setterDTV;
+ clonedParam.inputDTV = inputDTV;
+ clonedParam.cryptoMeta = cryptoMeta;
+ clonedParam.jdbcTypeSetByUser = jdbcTypeSetByUser;
+ clonedParam.valueLength = valueLength;
+ clonedParam.userProvidesPrecision = userProvidesPrecision;
+ clonedParam.userProvidesScale = userProvidesScale;
+ return clonedParam;
+ }
+
+ /**
+ * Skip value.
+ */
+ final void skipValue(TDSReader tdsReader,
+ boolean isDiscard) throws SQLServerException {
+ if (null == getterDTV)
+ getterDTV = new DTV();
+
+ deriveTypeInfo(tdsReader);
+
+ getterDTV.skipValue(typeInfo, tdsReader, isDiscard);
+ }
+
+ /**
+ * Skip value.
+ */
+ final void skipRetValStatus(TDSReader tdsReader) throws SQLServerException {
+
+ StreamRetValue srv = new StreamRetValue();
+ srv.setFromTDS(tdsReader);
+ }
+
+ // Clear an INPUT parameter value
+ void clearInputValue() {
+ setterDTV = null;
+ inputDTV = registeredOutDTV;
+ }
+
+ // reset output value for re -execution
+ // if there was old value reset it to a new DTV
+ void resetOutputValue() {
+ getterDTV = null;
+ typeInfo = null;
+ }
+
+ void deriveTypeInfo(TDSReader tdsReader) throws SQLServerException {
+ if (null == typeInfo) {
+ typeInfo = TypeInfo.getInstance(tdsReader, true);
+
+ if (shouldHonorAEForParameter && typeInfo.isEncrypted()) {
+ // In this case, method getCryptoMetadata(tdsReader) retrieves baseTypeInfo without cryptoMetadata,
+ // so save cryptoMetadata first.
+ CekTableEntry cekEntry = cryptoMeta.getCekTableEntry();
+ cryptoMeta = (new StreamRetValue()).getCryptoMetadata(tdsReader);
+ cryptoMeta.setCekTableEntry(cekEntry);
+ }
+ }
+ }
+
+ void setFromReturnStatus(int returnStatus,
+ SQLServerConnection con) throws SQLServerException {
+ if (null == getterDTV)
+ getterDTV = new DTV();
+
+ getterDTV.setValue(null, JDBCType.INTEGER, new Integer(returnStatus), JavaType.INTEGER, null, null, null, con, getForceEncryption());
+ }
+
+ void setValue(JDBCType jdbcType,
+ Object value,
+ JavaType javaType,
+ StreamSetterArgs streamSetterArgs,
+ Calendar calendar,
+ Integer precision,
+ Integer scale,
+ SQLServerConnection con,
+ boolean forceEncrypt,
+ SQLServerStatementColumnEncryptionSetting stmtColumnEncriptionSetting,
+ int parameterIndex,
+ String userSQL,
+ String tvpName) throws SQLServerException {
+
+ if (shouldHonorAEForParameter) {
+ userProvidesPrecision = false;
+ userProvidesScale = false;
+
+ if (null != precision) {
+ userProvidesPrecision = true;
+ }
+
+ if (null != scale) {
+ userProvidesScale = true;
+ }
+
+ // for encrypted tinyint, we need to convert short value to byte value,
+ // otherwise it would be sent as smallint
+ // Also, for setters, we are able to send tinyint to smallint
+ // However, for output parameter, it might cause error.
+ if (!isOutput()) {
+ if ((JavaType.SHORT == javaType) && ((JDBCType.TINYINT == jdbcType) || (JDBCType.SMALLINT == jdbcType))) {
+ // value falls in the TINYINT range
+ if (((Short) value) >= 0 && ((Short) value) <= 255) {
+ value = ((Short) value).byteValue();
+ javaType = JavaType.of(value);
+ jdbcType = javaType.getJDBCType(SSType.UNKNOWN, jdbcType);
+ }
+ // value falls outside tinyint range. Throw an error if the user intends to send as tinyint.
+ else {
+ // This is for cases like setObject(1, Short.valueOf("-1"), java.sql.Types.TINYINT);
+ if (JDBCType.TINYINT == jdbcType) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {javaType.toString().toLowerCase(Locale.ENGLISH), jdbcType.toString().toLowerCase(Locale.ENGLISH)};
+ throw new SQLServerException(form.format(msgArgs), null);
+ }
+ }
+ }
+ }
+ }
+
+ // forceEncryption is true, shouldhonorae is false
+ if ((true == forceEncrypt) && (false == Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, con))) {
+
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAEFalse"));
+ Object[] msgArgs = {parameterIndex, userSQL};
+ SQLServerException.makeFromDriverError(con, this, form.format(msgArgs), null, true);
+
+ }
+
+ // DateTimeOffset is not supported with SQL Server versions earlier than Katmai
+ if ((JDBCType.DATETIMEOFFSET == jdbcType || JavaType.DATETIMEOFFSET == javaType) && !con.isKatmaiOrLater()) {
+ throw new SQLServerException(SQLServerException.getErrString("R_notSupported"), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET,
+ null);
+ }
+
+ if (JavaType.TVP == javaType) {
+ TVP tvpValue = null;
+ if (null == value) {
+ tvpValue = new TVP(tvpName);
+ }
+ else if (value instanceof SQLServerDataTable) {
+ tvpValue = new TVP(tvpName, (SQLServerDataTable) value);
+ }
+ else if (value instanceof ResultSet) {
+ // if ResultSet and PreparedStatemet/CallableStatement are created from same connection object
+ // with property SelectMethod=cursor, TVP is not supported
+ if (con.getSelectMethod().equalsIgnoreCase("cursor") && (value instanceof SQLServerResultSet)) {
+ SQLServerStatement stmt = (SQLServerStatement) ((SQLServerResultSet) value).getStatement();
+
+ if (con.equals(stmt.connection)) {
+ throw new SQLServerException(SQLServerException.getErrString("R_invalidServerCursorForTVP"), null);
+ }
+ }
+
+ tvpValue = new TVP(tvpName, (ResultSet) value);
+ }
+ else if (value instanceof ISQLServerDataRecord) {
+ tvpValue = new TVP(tvpName, (ISQLServerDataRecord) value);
+ }
+ else {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPInvalidValue"));
+ Object[] msgArgs = {parameterIndex};
+ throw new SQLServerException(form.format(msgArgs), null);
+ }
+
+ if (!tvpValue.isNull() && (0 == tvpValue.getTVPColumnCount())) {
+ throw new SQLServerException(SQLServerException.getErrString("R_TVPEmptyMetadata"), null);
+ }
+ name = (tvpValue).getTVPName();
+ schemaName = tvpValue.getOwningSchemaNameTVP();
+
+ value = tvpValue;
+ }
+
+ // setting JDBCType and exact length needed for AE stored procedure
+ if (shouldHonorAEForParameter) {
+ setForceEncryption(forceEncrypt);
+
+ // set it if it is not output parameter or jdbcTypeSetByUser is null
+ if (!(this.isOutput() && this.jdbcTypeSetByUser != null)) {
+ setJdbcTypeSetByUser(jdbcType);
+ }
+
+ // skip it if is (character types or binary type) & is output parameter && value is already set,
+ if ((!(jdbcType.isTextual() || jdbcType.isBinary())) || !(this.isOutput()) || (this.valueLength == 0)) {
+ this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType);
+ }
+
+ if (null != scale) {
+ this.outScale = scale;
+ }
+ }
+
+ // sendStringParametersAsUnicode
+ // If set to true, this connection property tells the driver to send textual parameters
+ // to the server as Unicode rather than MBCS. This is accomplished here by re-tagging
+ // the value with the appropriate corresponding Unicode type.
+ if (con.sendStringParametersAsUnicode() && (JavaType.STRING == javaType || JavaType.READER == javaType || JavaType.CLOB == javaType)) {
+ jdbcType = getSSPAUJDBCType(jdbcType);
+ }
+
+ DTV newDTV = new DTV();
+ newDTV.setValue(con.getDatabaseCollation(), jdbcType, value, javaType, streamSetterArgs, calendar, scale, con, forceEncrypt);
+
+ if (!con.sendStringParametersAsUnicode()) {
+ newDTV.sendStringParametersAsUnicode = false;
+ }
+
+ inputDTV = setterDTV = newDTV;
+ }
+
+ boolean isNull() {
+ if (null != getterDTV)
+ return getterDTV.isNull();
+
+ return false;
+ }
+
+ boolean isValueGotten() {
+ return (null != getterDTV) ? (true) : (false);
+
+ }
+
+ Object getValue(JDBCType jdbcType,
+ InputStreamGetterArgs getterArgs,
+ Calendar cal,
+ TDSReader tdsReader) throws SQLServerException {
+ if (null == getterDTV)
+ getterDTV = new DTV();
+
+ deriveTypeInfo(tdsReader);
+ // If the parameter is not encrypted or column encryption is turned off (either at connection or
+ // statement level), cryptoMeta would be null.
+ return getterDTV.getValue(jdbcType, outScale, getterArgs, cal, typeInfo, cryptoMeta, tdsReader);
+ }
+
+ int getInt(TDSReader tdsReader) throws SQLServerException {
+ Integer value = (Integer) getValue(JDBCType.INTEGER, null, null, tdsReader);
+ return null != value ? value.intValue() : 0;
+ }
+
+ /**
+ * DTV execute op to determine the parameter type definition.
+ */
+ final class GetTypeDefinitionOp extends DTVExecuteOp {
+ private static final String NVARCHAR_MAX = "nvarchar(max)";
+ private static final String NVARCHAR_4K = "nvarchar(4000)";
+ private static final String NTEXT = "ntext";
+
+ private static final String VARCHAR_MAX = "varchar(max)";
+ private static final String VARCHAR_8K = "varchar(8000)";
+ private static final String TEXT = "text";
+
+ private static final String VARBINARY_MAX = "varbinary(max)";
+ private static final String VARBINARY_8K = "varbinary(8000)";
+ private static final String IMAGE = "image";
+
+ private final Parameter param;
+ private final SQLServerConnection con;
+
+ GetTypeDefinitionOp(Parameter param,
+ SQLServerConnection con) {
+ this.param = param;
+ this.con = con;
+ }
+
+ private void setTypeDefinition(DTV dtv) {
+ switch (dtv.getJdbcType()) {
+ case TINYINT:
+ param.typeDefinition = SSType.TINYINT.toString();
+ break;
+
+ case SMALLINT:
+ param.typeDefinition = SSType.SMALLINT.toString();
+ break;
+
+ case INTEGER:
+ param.typeDefinition = SSType.INTEGER.toString();
+ break;
+
+ case BIGINT:
+ param.typeDefinition = SSType.BIGINT.toString();
+ break;
+
+ case REAL:
+ // sp_describe_parameter_encryption must be queried as real for AE
+ if (param.shouldHonorAEForParameter && (null != jdbcTypeSetByUser)
+ && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ param.typeDefinition = SSType.REAL.toString();
+ break;
+ }
+ case FLOAT:
+ case DOUBLE:
+ param.typeDefinition = SSType.FLOAT.toString();
+ break;
+
+ case DECIMAL:
+ case NUMERIC:
+ // First, bound the scale by the maximum allowed by SQL Server
+ if (scale > SQLServerConnection.maxDecimalPrecision)
+ scale = SQLServerConnection.maxDecimalPrecision;
+
+ // Next, prepare with the largest of:
+ // - the value's scale (initial value, as limited above)
+ // - the specified input scale (if any)
+ // - the registered output scale
+ Integer inScale = dtv.getScale();
+ if (null != inScale && scale < inScale.intValue())
+ scale = inScale.intValue();
+
+ if (param.isOutput() && scale < param.getOutScale())
+ scale = param.getOutScale();
+
+ if (param.shouldHonorAEForParameter && (null != jdbcTypeSetByUser)
+ && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ if (0 == valueLength) {
+ // for prepared statement and callable statement, There are only two cases where valueLength is 0:
+ // 1. when the parameter is output parameter
+ // 2. for input parameter, the value is null
+ // so, here, if the decimal parameter is encrypted and it is null and it is not outparameter
+ // then we set precision as the default precision instead of max precision
+ if (!isOutput()) {
+ param.typeDefinition = "decimal(" + SQLServerConnection.defaultDecimalPrecision + ", " + scale + ")";
+ }
+ }
+ else {
+ if (SQLServerConnection.defaultDecimalPrecision >= valueLength) {
+ param.typeDefinition = "decimal(" + SQLServerConnection.defaultDecimalPrecision + "," + scale + ")";
+
+ if (SQLServerConnection.defaultDecimalPrecision < (valueLength + scale)) {
+ param.typeDefinition = "decimal(" + (SQLServerConnection.defaultDecimalPrecision + scale) + "," + scale + ")";
+ }
+ }
+ else {
+ param.typeDefinition = "decimal(" + SQLServerConnection.maxDecimalPrecision + "," + scale + ")";
+ }
+ }
+
+ if (isOutput()) {
+ param.typeDefinition = "decimal(" + SQLServerConnection.maxDecimalPrecision + ", " + scale + ")";
+ }
+
+ if (userProvidesPrecision) {
+ param.typeDefinition = "decimal(" + valueLength + "," + scale + ")";
+ }
+ }
+ else
+ param.typeDefinition = "decimal(" + SQLServerConnection.maxDecimalPrecision + "," + scale + ")";
+
+ break;
+
+ case MONEY:
+ param.typeDefinition = SSType.MONEY.toString();
+ break;
+ case SMALLMONEY:
+ param.typeDefinition = SSType.MONEY.toString();
+
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ param.typeDefinition = SSType.SMALLMONEY.toString();
+ }
+
+ break;
+ case BIT:
+ case BOOLEAN:
+ param.typeDefinition = SSType.BIT.toString();
+ break;
+
+ case LONGVARBINARY:
+ case BLOB:
+ param.typeDefinition = VARBINARY_MAX;
+ break;
+
+ case BINARY:
+ case VARBINARY:
+ // To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
+ if (VARBINARY_MAX.equals(param.typeDefinition) || IMAGE.equals(param.typeDefinition))
+ break;
+ if (param.shouldHonorAEForParameter && (null != jdbcTypeSetByUser)
+ && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ if (0 == valueLength) {
+ param.typeDefinition = "varbinary";
+ }
+ else {
+ param.typeDefinition = "varbinary(" + valueLength + ")";
+ }
+
+ if (JDBCType.LONGVARBINARY == jdbcTypeSetByUser) {
+ param.typeDefinition = VARBINARY_MAX;
+ }
+ }
+ else
+ param.typeDefinition = VARBINARY_8K;
+ break;
+
+ case DATE:
+ // Bind DATE values to pre-Katmai servers as DATETIME (which has no DATE-only type).
+ param.typeDefinition = con.isKatmaiOrLater() ? SSType.DATE.toString() : SSType.DATETIME.toString();
+ break;
+
+ case TIME:
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+
+ if (userProvidesScale) {
+ param.typeDefinition = (SSType.TIME.toString() + "(" + outScale + ")");
+ }
+ else {
+ param.typeDefinition = param.typeDefinition = SSType.TIME.toString() + "(" + valueLength + ")";
+ }
+ }
+ else {
+ param.typeDefinition = con.getSendTimeAsDatetime() ? SSType.DATETIME.toString() : SSType.TIME.toString();
+ }
+ break;
+
+ case TIMESTAMP:
+ // Bind TIMESTAMP values to pre-Katmai servers as DATETIME. Bind TIMESTAMP values to
+ // Katmai and later servers as DATETIME2 to take advantage of increased precision.
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ if (userProvidesScale) {
+ param.typeDefinition = con.isKatmaiOrLater() ? (SSType.DATETIME2.toString() + "(" + outScale + ")")
+ : (SSType.DATETIME.toString());
+ }
+ else {
+ param.typeDefinition = con.isKatmaiOrLater() ? (SSType.DATETIME2.toString() + "(" + valueLength + ")")
+ : SSType.DATETIME.toString();
+ }
+ }
+ else {
+ param.typeDefinition = con.isKatmaiOrLater() ? SSType.DATETIME2.toString() : SSType.DATETIME.toString();
+ }
+ break;
+
+ case DATETIME:
+ // send as Datetime by default
+ param.typeDefinition = SSType.DATETIME2.toString();
+
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ param.typeDefinition = SSType.DATETIME.toString();
+ }
+
+ if (!param.shouldHonorAEForParameter) {
+ // if AE is off and it is output parameter of stored procedure, sent it as datetime2(3)
+ // otherwise it returns incorrect milliseconds.
+ if (param.isOutput()) {
+ param.typeDefinition = SSType.DATETIME2.toString() + "(" + outScale + ")";
+ }
+ }
+ else {
+ // when AE is on, set it to Datetime by default,
+ // However, if column is not encrypted and it is output parameter of stored procedure,
+ // renew it to datetime2(3)
+ if (null == param.getCryptoMetadata() && param.renewDefinition) {
+ if (param.isOutput()) {
+ param.typeDefinition = SSType.DATETIME2.toString() + "(" + outScale + ")";
+ }
+ break;
+ }
+ }
+ break;
+
+ case SMALLDATETIME:
+ param.typeDefinition = SSType.DATETIME2.toString();
+
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ param.typeDefinition = SSType.SMALLDATETIME.toString();
+ }
+
+ break;
+
+ case TIME_WITH_TIMEZONE:
+ case TIMESTAMP_WITH_TIMEZONE:
+ case DATETIMEOFFSET:
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ if (userProvidesScale) {
+ param.typeDefinition = SSType.DATETIMEOFFSET.toString() + "(" + outScale + ")";
+ }
+ else {
+ param.typeDefinition = SSType.DATETIMEOFFSET.toString() + "(" + valueLength + ")";
+ }
+ }
+ else {
+ param.typeDefinition = SSType.DATETIMEOFFSET.toString();
+ }
+ break;
+
+ case LONGVARCHAR:
+ case CLOB:
+ param.typeDefinition = VARCHAR_MAX;
+ break;
+
+ case CHAR:
+ case VARCHAR:
+ // To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
+ if (VARCHAR_MAX.equals(param.typeDefinition) || TEXT.equals(param.typeDefinition))
+ break;
+
+ // Adding for case useColumnEncryption=true & sendStringParametersAsUnicode=false
+ if (param.shouldHonorAEForParameter && (null != jdbcTypeSetByUser)
+ && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ if (0 == valueLength) {
+ param.typeDefinition = "varchar";
+ }
+ else {
+ param.typeDefinition = "varchar(" + valueLength + ")";
+
+ if (DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength) {
+ param.typeDefinition = VARCHAR_MAX;
+ }
+ }
+ }
+ else
+ param.typeDefinition = VARCHAR_8K;
+ break;
+
+ case LONGNVARCHAR:
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ if ((null != jdbcTypeSetByUser) && ((jdbcTypeSetByUser == JDBCType.VARCHAR) || (jdbcTypeSetByUser == JDBCType.CHAR)
+ || (jdbcTypeSetByUser == JDBCType.LONGVARCHAR))) {
+ if (0 == valueLength) {
+ param.typeDefinition = "varchar";
+ }
+ else if (DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength) {
+ param.typeDefinition = VARCHAR_MAX;
+ }
+ else {
+ param.typeDefinition = "varchar(" + valueLength + ")";
+ }
+
+ if (jdbcTypeSetByUser == JDBCType.LONGVARCHAR) {
+ param.typeDefinition = VARCHAR_MAX;
+ }
+ }
+ else if ((null != jdbcTypeSetByUser)
+ && (jdbcTypeSetByUser == JDBCType.NVARCHAR || jdbcTypeSetByUser == JDBCType.LONGNVARCHAR)) {
+ if (0 == valueLength) {
+ param.typeDefinition = "nvarchar";
+ }
+ else if (DataTypes.SHORT_VARTYPE_MAX_CHARS < valueLength) {
+ param.typeDefinition = NVARCHAR_MAX;
+ }
+ else {
+ param.typeDefinition = "nvarchar(" + valueLength + ")";
+ }
+
+ if (jdbcTypeSetByUser == JDBCType.LONGNVARCHAR) {
+ param.typeDefinition = NVARCHAR_MAX;
+ }
+ }
+ else { // used if setNull() is called with java.sql.Types.NCHAR
+ if (0 == valueLength) {
+ param.typeDefinition = "nvarchar";
+ }
+ else {
+ param.typeDefinition = "nvarchar(" + valueLength + ")";
+
+ if (DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength) {
+ param.typeDefinition = NVARCHAR_MAX;
+ }
+ }
+ }
+ break;
+ }
+ else
+ param.typeDefinition = NVARCHAR_MAX;
+ break;
+
+ case NCLOB:
+ // do not need to check if AE is enabled or not,
+ // because NCLOB does not work with it
+ param.typeDefinition = NVARCHAR_MAX;
+ break;
+
+ case NCHAR:
+ case NVARCHAR:
+ // To avoid the server side cost of re-preparing, once a "long" type, always a "long" type...
+ if (NVARCHAR_MAX.equals(param.typeDefinition) || NTEXT.equals(param.typeDefinition))
+ break;
+
+ if (param.shouldHonorAEForParameter && !(null == param.getCryptoMetadata() && param.renewDefinition)) {
+ /*
+ * This means AE is ON in the connection, and (1) this is either the first round to SQL Server to get encryption meta data, or
+ * (2) this is the second round of renewing meta data and parameter is encrypted In both of these cases we need to send
+ * specific type info, otherwise generic type info can be used as before.
+ */
+ if ((null != jdbcTypeSetByUser) && ((jdbcTypeSetByUser == JDBCType.VARCHAR) || (jdbcTypeSetByUser == JDBCType.CHAR)
+ || (JDBCType.LONGVARCHAR == jdbcTypeSetByUser))) {
+ if (0 == valueLength) {
+ param.typeDefinition = "varchar";
+ }
+ else {
+ param.typeDefinition = "varchar(" + valueLength + ")";
+
+ if (DataTypes.SHORT_VARTYPE_MAX_BYTES < valueLength) {
+ param.typeDefinition = VARCHAR_MAX;
+ }
+ }
+
+ if (JDBCType.LONGVARCHAR == jdbcTypeSetByUser) {
+ param.typeDefinition = VARCHAR_MAX;
+ }
+ }
+ else if ((null != jdbcTypeSetByUser) && ((jdbcTypeSetByUser == JDBCType.NVARCHAR) || (jdbcTypeSetByUser == JDBCType.NCHAR)
+ || (JDBCType.LONGNVARCHAR == jdbcTypeSetByUser))) {
+ if (0 == valueLength) {
+ param.typeDefinition = "nvarchar";
+ }
+ else {
+ param.typeDefinition = "nvarchar(" + valueLength + ")";
+
+ if (DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength) {
+ param.typeDefinition = NVARCHAR_MAX;
+ }
+ }
+
+ if (JDBCType.LONGNVARCHAR == jdbcTypeSetByUser) {
+ param.typeDefinition = NVARCHAR_MAX;
+ }
+ }
+ else { // used if setNull() is called with java.sql.Types.NCHAR
+ if (0 == valueLength) {
+ param.typeDefinition = "nvarchar";
+ }
+ else {
+ param.typeDefinition = "nvarchar(" + valueLength + ")";
+
+ if (DataTypes.SHORT_VARTYPE_MAX_BYTES <= valueLength) {
+ param.typeDefinition = NVARCHAR_MAX;
+ }
+ }
+ }
+ break;
+ }
+ else
+ param.typeDefinition = NVARCHAR_4K;
+ break;
+ case SQLXML:
+ param.typeDefinition = SSType.XML.toString();
+ break;
+
+ case TVP:
+ // definition should contain the TVP name and the keyword READONLY
+ String schema = param.schemaName;
+
+ if (null != schema) {
+ param.typeDefinition = "[" + schema + "].[" + param.name + "] READONLY";
+ }
+ else {
+ param.typeDefinition = "[" + param.name + "] READONLY";
+ }
+
+ break;
+
+ case GUID:
+ param.typeDefinition = SSType.GUID.toString();
+ break;
+
+ default:
+ assert false : "Unexpected JDBC type " + dtv.getJdbcType();
+ break;
+ }
+ }
+
+ void execute(DTV dtv,
+ String strValue) throws SQLServerException {
+ if (null != strValue && strValue.length() > DataTypes.SHORT_VARTYPE_MAX_CHARS)
+ dtv.setJdbcType(JDBCType.LONGNVARCHAR);
+
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Clob clobValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Byte byteValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Integer intValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ java.sql.Time timeValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ java.sql.Date dateValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ java.sql.Timestamp timestampValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ java.util.Date utildateValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ java.util.Calendar calendarValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ LocalDate localDateValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ LocalTime localTimeValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ LocalDateTime localDateTimeValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ OffsetTime offsetTimeValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ OffsetDateTime OffsetDateTimeValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ microsoft.sql.DateTimeOffset dtoValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Float floatValue) throws SQLServerException {
+ scale = 4;
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Double doubleValue) throws SQLServerException {
+ scale = 4;
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ BigDecimal bigDecimalValue) throws SQLServerException {
+ if (null != bigDecimalValue) {
+ scale = bigDecimalValue.scale();
+
+ // BigDecimal in JRE 1.5 and later JVMs exposes an implementation detail
+ // that allows representation of large values in small space by interpreting
+ // a negative value for scale to imply scientific notation (e.g. 1 E 10^n)
+ // would have a scale of -n. A BigDecimal value with a negative scale has
+ // no fractional component.
+ if (scale < 0)
+ scale = 0;
+ }
+
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Long longValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ java.math.BigInteger bigIntegerValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Short shortValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Boolean booleanValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ byte[] byteArrayValue) throws SQLServerException {
+ if (null != byteArrayValue && byteArrayValue.length > DataTypes.SHORT_VARTYPE_MAX_BYTES)
+ dtv.setJdbcType(dtv.getJdbcType().isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);
+
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Blob blobValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ InputStream inputStreamValue) throws SQLServerException {
+ StreamSetterArgs streamSetterArgs = dtv.getStreamSetterArgs();
+
+ JDBCType jdbcType = dtv.getJdbcType();
+
+ // If the JDBC type is currently a "short" type, then figure out if needs to be bumped up to a "long" type
+ if (JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType || JDBCType.BINARY == jdbcType || JDBCType.VARBINARY == jdbcType) {
+ // If we know the length is too long for a "short" type, then convert to a "long" type.
+ if (streamSetterArgs.getLength() > DataTypes.SHORT_VARTYPE_MAX_BYTES)
+ dtv.setJdbcType(jdbcType.isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);
+
+ // If the length of the value is unknown, then figure out whether it is at least longer
+ // than what will fit into a "short" type.
+ else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamSetterArgs.getLength()) {
+ byte[] vartypeBytes = new byte[1 + DataTypes.SHORT_VARTYPE_MAX_BYTES];
+ BufferedInputStream bufferedStream = new BufferedInputStream(inputStreamValue, vartypeBytes.length);
+
+ int bytesRead = 0;
+
+ try {
+ bufferedStream.mark(vartypeBytes.length);
+
+ bytesRead = bufferedStream.read(vartypeBytes, 0, vartypeBytes.length);
+
+ if (-1 == bytesRead)
+ bytesRead = 0;
+
+ bufferedStream.reset();
+ }
+ catch (IOException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {e.toString()};
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
+ }
+
+ dtv.setValue(bufferedStream, JavaType.INPUTSTREAM);
+
+ // If the stream is longer than what can fit into the "short" type, then use the "long" type instead.
+ // Otherwise, we know the exact stream length since we reached end of stream before reading SHORT_VARTYPE_MAX_BYTES + 1
+ // bytes. So adjust the setter args to reflect the known length to avoid unnecessarily copying the
+ // stream again in SendByRPCOp.
+ if (bytesRead > DataTypes.SHORT_VARTYPE_MAX_BYTES)
+ dtv.setJdbcType(jdbcType.isBinary() ? JDBCType.LONGVARBINARY : JDBCType.LONGVARCHAR);
+ else
+ streamSetterArgs.setLength(bytesRead);
+ }
+ }
+
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ Reader readerValue) throws SQLServerException {
+ // If the JDBC type is currently a "short" type, then figure out if needs to be bumped up to a "long" type
+ if (JDBCType.NCHAR == dtv.getJdbcType() || JDBCType.NVARCHAR == dtv.getJdbcType()) {
+ StreamSetterArgs streamSetterArgs = dtv.getStreamSetterArgs();
+
+ // If we know the length is too long for a "short" type, then convert to a "long" type.
+ if (streamSetterArgs.getLength() > DataTypes.SHORT_VARTYPE_MAX_CHARS)
+ dtv.setJdbcType(JDBCType.LONGNVARCHAR);
+
+ // If the length of the value is unknown, then figure out whether it is at least longer
+ // than what will fit into a "short" type.
+ else if (DataTypes.UNKNOWN_STREAM_LENGTH == streamSetterArgs.getLength()) {
+ char[] vartypeChars = new char[1 + DataTypes.SHORT_VARTYPE_MAX_CHARS];
+ BufferedReader bufferedReader = new BufferedReader(readerValue, vartypeChars.length);
+
+ int charsRead = 0;
+
+ try {
+ bufferedReader.mark(vartypeChars.length);
+
+ charsRead = bufferedReader.read(vartypeChars, 0, vartypeChars.length);
+
+ if (-1 == charsRead)
+ charsRead = 0;
+
+ bufferedReader.reset();
+ }
+ catch (IOException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
+ Object[] msgArgs = {e.toString()};
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), "", true);
+ }
+
+ dtv.setValue(bufferedReader, JavaType.READER);
+
+ if (charsRead > DataTypes.SHORT_VARTYPE_MAX_CHARS)
+ dtv.setJdbcType(JDBCType.LONGNVARCHAR);
+ else
+ streamSetterArgs.setLength(charsRead);
+ }
+ }
+
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ SQLServerSQLXML xmlValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ void execute(DTV dtv,
+ com.microsoft.sqlserver.jdbc.TVP tvpValue) throws SQLServerException {
+ setTypeDefinition(dtv);
+ }
+
+ }
+
+ /**
+ * Returns a string used to define the parameter type for the server; null if no value for the parameter has been set or registered.
+ */
+ String getTypeDefinition(SQLServerConnection con,
+ TDSReader tdsReader) throws SQLServerException {
+ if (null == inputDTV)
+ return null;
+
+ inputDTV.executeOp(new GetTypeDefinitionOp(this, con));
+ return typeDefinition;
+ }
+
+ void sendByRPC(TDSWriter tdsWriter,
+ SQLServerConnection conn) throws SQLServerException {
+ assert null != inputDTV : "Parameter was neither set nor registered";
+
+ try {
+ inputDTV.sendCryptoMetaData(this.cryptoMeta, tdsWriter);
+ inputDTV.jdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength());
+ inputDTV.sendByRPC(name, null, conn.getDatabaseCollation(), valueLength, isOutput() ? outScale : scale, isOutput(), tdsWriter, conn);
+ }
+ finally {
+ // reset the cryptoMeta in IOBuffer
+ inputDTV.sendCryptoMetaData(null, tdsWriter);
+ }
+ // Per JDBC spec:
+ // "If in the execution of a PreparedStatement object, the JDBC driver reads values set
+ // for the parameter markers by the methods setAsciiStream, setBinaryStream, setCharacterStream,
+ // setNCharacterStream, or setUnicodeStream, those parameters must be reset prior to the next
+ // execution of the PreparedStatement object otherwise a SQLException will be thrown."
+ //
+ // Clear the input and setter DTVs to relinquish their hold on the stream resource and ensure
+ // that the next call to execute will throw a SQLException (from getTypeDefinitionOp).
+ // Don't clear the registered output DTV so that the parameter will still be an OUT (IN/OUT) parameter.
+ if (JavaType.INPUTSTREAM == inputDTV.getJavaType() || JavaType.READER == inputDTV.getJavaType()) {
+ inputDTV = setterDTV = null;
+ }
+ }
+
+ JDBCType getJdbcTypeSetByUser() {
+ return jdbcTypeSetByUser;
+ }
+
+ void setJdbcTypeSetByUser(JDBCType jdbcTypeSetByUser) {
+ this.jdbcTypeSetByUser = jdbcTypeSetByUser;
+ }
+
+ int getValueLength() {
+ return valueLength;
+ }
+
+ void setValueLength(int valueLength) {
+ this.valueLength = valueLength;
+ userProvidesPrecision = true;
+ }
+
+ boolean getForceEncryption() {
+ return forceEncryption;
+ }
+
+ void setForceEncryption(boolean forceEncryption) {
+ this.forceEncryption = forceEncryption;
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java b/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java
index bfeeeb711..4cd7f2b84 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ParameterUtils.java
@@ -1,165 +1,129 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ParameterUtils.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
/**
-* ParameterUtils provides utility a set of methods to manipulate parameter values.
-*/
-
-final class ParameterUtils
-{
- static byte[] HexToBin(String hexV) throws SQLServerException
- {
- int len = hexV.length();
- char orig[] = hexV.toCharArray();
- if ((len% 2) !=0)
- SQLServerException.makeFromDriverError(null, null,
- SQLServerException.getErrString("R_stringNotInHex"), null, false);
- byte[] bin = new byte[len/2];
- for (int i=0; i= 'A' && CTX <= 'F' )
- {
- ret = (byte)(CTX - 'A' + 10);
- }
- else if(CTX >= 'a' && CTX <= 'f' )
- {
- ret = (byte)(CTX - 'a' + 10);
- }
- else
- if(CTX >= '0' && CTX <= '9' )
- {
- ret = (byte)(CTX - '0');
- }
- else
- {
- SQLServerException.makeFromDriverError(null, null,
- SQLServerException.getErrString("R_stringNotInHex"), null, false);
- }
- return ret;
- }
+ * ParameterUtils provides utility a set of methods to manipulate parameter values.
+ */
+final class ParameterUtils {
+ static byte[] HexToBin(String hexV) throws SQLServerException {
+ int len = hexV.length();
+ char orig[] = hexV.toCharArray();
+ if ((len % 2) != 0)
+ SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_stringNotInHex"), null, false);
+ byte[] bin = new byte[len / 2];
+ for (int i = 0; i < len / 2; i++) {
+ bin[i] = (byte) ((CharToHex(orig[2 * i]) << 4) + CharToHex(orig[2 * i + 1]));
+ }
+ return bin;
+ }
+ // conversion routine valid values 0-9 a-f A-F
+ // throws exception when failed to convert
+ //
+ static byte CharToHex(char CTX) throws SQLServerException {
+ byte ret = 0;
+ if (CTX >= 'A' && CTX <= 'F') {
+ ret = (byte) (CTX - 'A' + 10);
+ }
+ else if (CTX >= 'a' && CTX <= 'f') {
+ ret = (byte) (CTX - 'a' + 10);
+ }
+ else if (CTX >= '0' && CTX <= '9') {
+ ret = (byte) (CTX - '0');
+ }
+ else {
+ SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_stringNotInHex"), null, false);
+ }
+ return ret;
+ }
- /**
- * Locates the first occurrence of [c] in [sql] starting at [offset],
- * where [sql] is a SQL statement string, which may contain any
- * combination of:
- *
- * - Literals, enclosed in single quotes (')
- * - Literals, enclosed in double quotes (")
- * - Escape sequences, enclosed in square brackets ([])
- * - Escaped escapes or literal delimiters (i.e. '', "", or ]]) in the above
- * - Single-line comments, beginning in -- and continuing to EOL
- * - Multi-line comments, enclosed in C-style comment delimiters
- *
- * and [c] is not contained any of the above.
- *
- * @param c the character to search for
- * @param sql the SQL string to search in
- * @param offset the offset into [sql] to start searching from
- * @return Offset into [sql] where [c] occurs, or sql.length if [c] is not found.
- * @throws SQLServerException when [sql] does not follow
- */
- @SuppressWarnings({"fallthrough"})
- static int scanSQLForChar(char ch, String sql, int offset)
- {
- char chQuote;
- char chTmp;
- final int len = sql.length();
+ /**
+ * Locates the first occurrence of [c] in [sql] starting at [offset], where [sql] is a SQL statement string, which may contain any combination of:
+ *
+ * - Literals, enclosed in single quotes (') - Literals, enclosed in double quotes (") - Escape sequences, enclosed in square brackets ([]) -
+ * Escaped escapes or literal delimiters (i.e. '', "", or ]]) in the above - Single-line comments, beginning in -- and continuing to EOL -
+ * Multi-line comments, enclosed in C-style comment delimiters
+ *
+ * and [c] is not contained any of the above.
+ *
+ * @param c
+ * the character to search for
+ * @param sql
+ * the SQL string to search in
+ * @param offset
+ * the offset into [sql] to start searching from
+ * @return Offset into [sql] where [c] occurs, or sql.length if [c] is not found.
+ * @throws SQLServerException
+ * when [sql] does not follow
+ */
+ @SuppressWarnings({"fallthrough"})
+ static int scanSQLForChar(char ch,
+ String sql,
+ int offset) {
+ char chQuote;
+ char chTmp;
+ final int len = sql.length();
- while (offset < len)
- {
- switch (chTmp = sql.charAt(offset++))
- {
- case '/':
- if (offset == len)
- break;
+ while (offset < len) {
+ switch (chTmp = sql.charAt(offset++)) {
+ case '/':
+ if (offset == len)
+ break;
- if (sql.charAt(offset) == '*')
- { // If '/* ... */' comment
- while (++offset < len)
- { // Go thru comment.
- if (sql.charAt(offset) == '*' &&
- offset+1 < len &&
- sql.charAt(offset+1) == '/')
- { // If end of comment
- offset += 2;
+ if (sql.charAt(offset) == '*') { // If '/* ... */' comment
+ while (++offset < len) { // Go thru comment.
+ if (sql.charAt(offset) == '*' && offset + 1 < len && sql.charAt(offset + 1) == '/') { // If end of comment
+ offset += 2;
+ break;
+ }
+ }
break;
}
- }
- break;
- }
- else if (sql.charAt(offset) == '-')
- break;
+ else if (sql.charAt(offset) == '-')
+ break;
- // Fall through - will fail next if and end up in default case
- case '-':
- if (sql.charAt(offset) == '-')
- { // If '-- ... \n' comment
- while (++offset < len)
- { // Go thru comment.
- if (sql.charAt(offset) == '\n' || sql.charAt(offset) == '\r')
- { // If end of comment
- offset++;
+ // Fall through - will fail next if and end up in default case
+ case '-':
+ if (sql.charAt(offset) == '-') { // If '-- ... \n' comment
+ while (++offset < len) { // Go thru comment.
+ if (sql.charAt(offset) == '\n' || sql.charAt(offset) == '\r') { // If end of comment
+ offset++;
+ break;
+ }
+ }
break;
}
- }
- break;
- }
- // Fall through to test character
- default:
- if (ch == chTmp)
- return offset - 1;
- break;
+ // Fall through to test character
+ default:
+ if (ch == chTmp)
+ return offset - 1;
+ break;
- case '[':
- chTmp = ']';
- case '\'':
- case '"':
- chQuote = chTmp;
- while (offset < len)
- {
- if (sql.charAt(offset++) == chQuote)
- {
- if (len == offset || sql.charAt(offset) != chQuote)
- break;
+ case '[':
+ chTmp = ']';
+ case '\'':
+ case '"':
+ chQuote = chTmp;
+ while (offset < len) {
+ if (sql.charAt(offset++) == chQuote) {
+ if (len == offset || sql.charAt(offset) != chQuote)
+ break;
- ++offset;
- }
+ ++offset;
+ }
+ }
+ break;
}
- break;
}
+
+ return len;
}
-
- return len;
- }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java b/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java
index 431ca2374..2667d5af0 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java
@@ -1,23 +1,13 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: ReaderInputStream.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
@@ -29,20 +19,16 @@
/**
* InputStream adapter for Readers.
*
- * This class implements an InputStream whose bytes are encoded
- * character values that are read on demand from a wrapped Reader
- * using the suplied Charset.
+ * This class implements an InputStream whose bytes are encoded character values that are read on demand from a wrapped Reader using the suplied
+ * Charset.
*
- * Character values pass through through the following
- * in their transformation to bytes:
- * Reader .. CharBuffer .. Charset (CharsetEncoder) .. ByteBuffer .. InputStream
+ * Character values pass through through the following in their transformation to bytes: Reader .. CharBuffer .. Charset (CharsetEncoder) ..
+ * ByteBuffer .. InputStream
*
- * To minimize memory usage, the CharBuffer and ByteBuffer instances
- * used by this class are created on demand when InputStream read
- * methods are called.
+ * To minimize memory usage, the CharBuffer and ByteBuffer instances used by this class are created on demand when InputStream read methods are
+ * called.
*/
-class ReaderInputStream extends InputStream
-{
+class ReaderInputStream extends InputStream {
// The Reader that this ReaderInputStream adapts.
private final Reader reader;
@@ -69,8 +55,9 @@ class ReaderInputStream extends InputStream
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private ByteBuffer encodedChars = EMPTY_BUFFER;
- ReaderInputStream(Reader reader, Charset charset, long readerLength)
- {
+ ReaderInputStream(Reader reader,
+ Charset charset,
+ long readerLength) {
assert reader != null;
assert charset != null;
assert DataTypes.UNKNOWN_STREAM_LENGTH == readerLength || readerLength >= 0;
@@ -81,14 +68,14 @@ class ReaderInputStream extends InputStream
}
/**
- * Returns the number of bytes that can be read (or skipped over) from this input stream without blocking
- * by the next caller of a method for this input stream.
+ * Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this
+ * input stream.
*
* @return - the number of bytes that can be read from this input stream without blocking
- * @throws IOException - if an I/O error occurs
+ * @throws IOException
+ * - if an I/O error occurs
*/
- public int available() throws IOException
- {
+ public int available() throws IOException {
assert null != reader;
assert null != encodedChars;
@@ -101,7 +88,7 @@ public int available() throws IOException
return encodedChars.remaining();
// If there are no encoded characters left in the buffer (or the buffer hasn't yet been populated)
- // then ask the Reader whether a call to its read() method would block. If reading wouldn't block,
+ // then ask the Reader whether a call to its read() method would block. If reading wouldn't block,
// then there's at least 1 byte that can be encoded, possibly more.
if (reader.ready())
return 1;
@@ -112,23 +99,24 @@ public int available() throws IOException
}
private final byte[] oneByte = new byte[1];
- public int read() throws IOException
- {
+
+ public int read() throws IOException {
return (-1 == readInternal(oneByte, 0, oneByte.length)) ? -1 : oneByte[0];
}
- public int read(byte[] b) throws IOException
- {
+ public int read(byte[] b) throws IOException {
return readInternal(b, 0, b.length);
}
- public int read(byte[] b, int off, int len) throws IOException
- {
+ public int read(byte[] b,
+ int off,
+ int len) throws IOException {
return readInternal(b, off, len);
}
- private int readInternal(byte[] b, int off, int len) throws IOException
- {
+ private int readInternal(byte[] b,
+ int off,
+ int len) throws IOException {
assert null != b;
assert 0 <= off && off <= b.length;
assert 0 <= len && len <= b.length;
@@ -138,8 +126,7 @@ private int readInternal(byte[] b, int off, int len) throws IOException
return 0;
int bytesRead = 0;
- while (bytesRead < len && encodeChars())
- {
+ while (bytesRead < len && encodeChars()) {
// Read the lesser of the number of bytes remaining
// in the encoded character buffer and the number
// of bytes remaining for this read request.
@@ -161,44 +148,39 @@ private int readInternal(byte[] b, int off, int len) throws IOException
}
/**
- * Determines whether encoded characters are available, encoding them on demand
- * by reading them from the reader as necessary.
+ * Determines whether encoded characters are available, encoding them on demand by reading them from the reader as necessary.
*
* @return true when encoded characters are available
* @return false when no more encoded characters are available (i.e. end of stream)
- * @exception IOException if an I/O error occurs reading from the reader or encoding the characters
- */
- private boolean encodeChars() throws IOException
- {
+ * @exception IOException
+ * if an I/O error occurs reading from the reader or encoding the characters
+ */
+ private boolean encodeChars() throws IOException {
// Once at the end of the stream, no more characters can be encoded.
if (atEndOfStream)
return false;
// Not at end of stream; check whether there are any encoded characters
- // remaining in the byte buffer. If there are, don't encode any more
+ // remaining in the byte buffer. If there are, don't encode any more
// characters this time.
if (encodedChars.hasRemaining())
return true;
// Encoded byte buffer is either exhausted or has never been filled
- // (i.e. first time through). In that case, we need to repopulate
+ // (i.e. first time through). In that case, we need to repopulate
// the encoded character buffer by encoding raw characters.
//
// To do that, there needs to be raw characters available to encode.
// If there are no raw characters available (because the raw character
// buffer has been exhausted or never filled), then try to read in
// raw characters from the reader.
- if (null == rawChars || !rawChars.hasRemaining())
- {
- if (null == rawChars)
- {
+ if (null == rawChars || !rawChars.hasRemaining()) {
+ if (null == rawChars) {
assert MAX_CHAR_BUFFER_SIZE <= Integer.MAX_VALUE;
- rawChars = CharBuffer.allocate(
- (DataTypes.UNKNOWN_STREAM_LENGTH == readerLength || readerLength > MAX_CHAR_BUFFER_SIZE) ?
- MAX_CHAR_BUFFER_SIZE : Math.max((int) readerLength, 1));
+ rawChars = CharBuffer.allocate((DataTypes.UNKNOWN_STREAM_LENGTH == readerLength || readerLength > MAX_CHAR_BUFFER_SIZE)
+ ? MAX_CHAR_BUFFER_SIZE : Math.max((int) readerLength, 1));
}
- else
- {
+ else {
// Flip the buffer to be ready for put (reader read) operations.
rawChars.clear();
}
@@ -211,21 +193,18 @@ private boolean encodeChars() throws IOException
// - the reader reaches end-of-stream
// - the reader throws any kind of Exception (driver throws an IOException)
// - the reader violates its interface contract (driver throws an IOException)
- while (rawChars.hasRemaining())
- {
+ while (rawChars.hasRemaining()) {
int lastPosition = rawChars.position();
int charsRead = 0;
-
+
// Try reading from the app-supplied Reader
- try
- {
+ try {
charsRead = reader.read(rawChars);
}
// Catch any kind of exception and translate it to an IOException.
// The app-supplied reader cannot be trusted just to throw IOExceptions...
- catch (Exception e)
- {
+ catch (Exception e) {
String detailMessage = e.getMessage();
if (null == detailMessage)
detailMessage = SQLServerException.getErrString("R_streamReadReturnedInvalidValue");
@@ -237,22 +216,19 @@ private boolean encodeChars() throws IOException
if (charsRead < -1 || 0 == charsRead)
throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue"));
- if (-1 == charsRead)
- {
+ if (-1 == charsRead) {
// If the reader violates its interface contract then throw an exception.
if (rawChars.position() != lastPosition)
throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue"));
// Check that the reader has returned exactly the amount of data we expect
- if (DataTypes.UNKNOWN_STREAM_LENGTH != readerLength && 0 != readerLength - readerCharsRead)
- {
+ if (DataTypes.UNKNOWN_STREAM_LENGTH != readerLength && 0 != readerLength - readerCharsRead) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
throw new IOException(form.format(new Object[] {readerLength, readerCharsRead}));
}
// If there are no characters left to encode then we're done.
- if (0 == rawChars.position())
- {
+ if (0 == rawChars.position()) {
rawChars = null;
atEndOfStream = true;
return false;
@@ -269,8 +245,7 @@ private boolean encodeChars() throws IOException
throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue"));
// Check that the reader isn't trying to return more data than we expect
- if (DataTypes.UNKNOWN_STREAM_LENGTH != readerLength && charsRead > readerLength - readerCharsRead)
- {
+ if (DataTypes.UNKNOWN_STREAM_LENGTH != readerLength && charsRead > readerLength - readerCharsRead) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
throw new IOException(form.format(new Object[] {readerLength, readerCharsRead}));
}
@@ -284,7 +259,7 @@ private boolean encodeChars() throws IOException
}
// If the raw character buffer remains empty, despite our efforts to (re)populate it,
- // then no characters can be encoded at this time. This can happen if the reader reports
+ // then no characters can be encoded at this time. This can happen if the reader reports
// that no characters were ready to be read.
if (!rawChars.hasRemaining())
return false;
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java
index 3ba0ef633..46f08e9c2 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLCollation.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.io.UnsupportedEncodingException;
@@ -312,14 +301,13 @@ enum WindowsLocale
private final int langID;
private final Encoding encoding;
- WindowsLocale(int langID, Encoding encoding)
- {
+ WindowsLocale(int langID,
+ Encoding encoding) {
this.langID = langID;
this.encoding = encoding;
}
- final Encoding getEncoding() throws UnsupportedEncodingException
- {
+ final Encoding getEncoding() throws UnsupportedEncodingException {
return encoding.checkSupported();
}
}
@@ -328,23 +316,19 @@ final Encoding getEncoding() throws UnsupportedEncodingException
// of encodings associated with various SQL collations
private static final Map localeIndex;
- private Encoding encodingFromLCID() throws UnsupportedEncodingException
- {
+ private Encoding encodingFromLCID() throws UnsupportedEncodingException {
WindowsLocale locale = localeIndex.get(langID());
- if (null == locale)
- {
+ if (null == locale) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unknownLCID"));
Object[] msgArgs = {Integer.toHexString(langID()).toUpperCase()};
throw new UnsupportedEncodingException(form.format(msgArgs));
}
- try
- {
+ try {
return locale.getEncoding();
}
- catch (UnsupportedEncodingException inner)
- {
+ catch (UnsupportedEncodingException inner) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unknownLCID"));
Object[] msgArgs = {locale};
UnsupportedEncodingException e = new UnsupportedEncodingException(form.format(msgArgs));
@@ -356,168 +340,167 @@ private Encoding encodingFromLCID() throws UnsupportedEncodingException
/**
* Enumeration of original SQL Server sort orders recognized by SQL Server.
*
- * If SQL collation has a non-zero sortId, then use this enum to determine the encoding.
- * From sql_main\sql\common\src\sqlscol.cpp (SQLServer code base).
+ * If SQL collation has a non-zero sortId, then use this enum to determine the encoding. From sql_main\sql\common\src\sqlscol.cpp (SQLServer code
+ * base).
*/
enum SortOrder
{
- BIN_CP437 (30, "SQL_Latin1_General_CP437_BIN", Encoding.CP437),
- DICTIONARY_437 (31, "SQL_Latin1_General_CP437_CS_AS", Encoding.CP437),
- NOCASE_437 (32, "SQL_Latin1_General_CP437_CI_AS", Encoding.CP437),
- NOCASEPREF_437 (33, "SQL_Latin1_General_Pref_CP437_CI_AS", Encoding.CP437),
- NOACCENTS_437 (34, "SQL_Latin1_General_CP437_CI_AI", Encoding.CP437),
- BIN2_CP437 (35, "SQL_Latin1_General_CP437_BIN2", Encoding.CP437),
-
- BIN_CP850 (40, "SQL_Latin1_General_CP850_BIN", Encoding.CP850),
- DICTIONARY_850 (41, "SQL_Latin1_General_CP850_CS_AS", Encoding.CP850),
- NOCASE_850 (42, "SQL_Latin1_General_CP850_CI_AS", Encoding.CP850),
- NOCASEPREF_850 (43, "SQL_Latin1_General_Pref_CP850_CI_AS", Encoding.CP850),
- NOACCENTS_850 (44, "SQL_Latin1_General_CP850_CI_AI", Encoding.CP850),
- BIN2_CP850 (45, "SQL_Latin1_General_CP850_BIN2", Encoding.CP850),
-
- CASELESS_34 (49, "SQL_1xCompat_CP850_CI_AS", Encoding.CP850),
- BIN_ISO_1 (50, "bin_iso_1", Encoding.CP1252),
- DICTIONARY_ISO (51, "SQL_Latin1_General_CP1_CS_AS", Encoding.CP1252),
- NOCASE_ISO (52, "SQL_Latin1_General_CP1_CI_AS", Encoding.CP1252),
- NOCASEPREF_ISO (53, "SQL_Latin1_General_Pref_CP1_CI_AS", Encoding.CP1252),
- NOACCENTS_ISO (54, "SQL_Latin1_General_CP1_CI_AI", Encoding.CP1252),
- ALT_DICTIONARY (55, "SQL_AltDiction_CP850_CS_AS", Encoding.CP850),
- ALT_NOCASEPREF (56, "SQL_AltDiction_Pref_CP850_CI_AS", Encoding.CP850),
- ALT_NOACCENTS (57, "SQL_AltDiction_CP850_CI_AI", Encoding.CP850),
- SCAND_NOCASEPREF (58, "SQL_Scandinavian_Pref_CP850_CI_AS", Encoding.CP850),
- SCAND_DICTIONARY (59, "SQL_Scandinavian_CP850_CS_AS", Encoding.CP850),
- SCAND_NOCASE (60, "SQL_Scandinavian_CP850_CI_AS", Encoding.CP850),
- ALT_NOCASE (61, "SQL_AltDiction_CP850_CI_AS", Encoding.CP850),
-
- DICTIONARY_1252 (71, "dictionary_1252", Encoding.CP1252),
- NOCASE_1252 (72, "nocase_1252", Encoding.CP1252),
- DNK_NOR_DICTIONARY (73, "dnk_nor_dictionary", Encoding.CP1252),
- FIN_SWE_DICTIONARY (74, "fin_swe_dictionary", Encoding.CP1252),
- ISL_DICTIONARY (75, "isl_dictionary", Encoding.CP1252),
-
- BIN_CP1250 (80, "bin_cp1250", Encoding.CP1250),
- DICTIONARY_1250 (81, "SQL_Latin1_General_CP1250_CS_AS", Encoding.CP1250),
- NOCASE_1250 (82, "SQL_Latin1_General_CP1250_CI_AS", Encoding.CP1250),
- CSYDIC (83, "SQL_Czech_CP1250_CS_AS", Encoding.CP1250),
- CSYNC (84, "SQL_Czech_CP1250_CI_AS", Encoding.CP1250),
- HUNDIC (85, "SQL_Hungarian_CP1250_CS_AS", Encoding.CP1250),
- HUNNC (86, "SQL_Hungarian_CP1250_CI_AS", Encoding.CP1250),
- PLKDIC (87, "SQL_Polish_CP1250_CS_AS", Encoding.CP1250),
- PLKNC (88, "SQL_Polish_CP1250_CI_AS", Encoding.CP1250),
- ROMDIC (89, "SQL_Romanian_CP1250_CS_AS", Encoding.CP1250),
- ROMNC (90, "SQL_Romanian_CP1250_CI_AS", Encoding.CP1250),
- SHLDIC (91, "SQL_Croatian_CP1250_CS_AS", Encoding.CP1250),
- SHLNC (92, "SQL_Croatian_CP1250_CI_AS", Encoding.CP1250),
- SKYDIC (93, "SQL_Slovak_CP1250_CS_AS", Encoding.CP1250),
- SKYNC (94, "SQL_Slovak_CP1250_CI_AS", Encoding.CP1250),
- SLVDIC (95, "SQL_Slovenian_CP1250_CS_AS", Encoding.CP1250),
- SLVNC (96, "SQL_Slovenian_CP1250_CI_AS", Encoding.CP1250),
- POLISH_CS (97, "polish_cs", Encoding.CP1250),
- POLISH_CI (98, "polish_ci", Encoding.CP1250),
-
- BIN_CP1251 (104, "bin_cp1251", Encoding.CP1251),
- DICTIONARY_1251 (105, "SQL_Latin1_General_CP1251_CS_AS", Encoding.CP1251),
- NOCASE_1251 (106, "SQL_Latin1_General_CP1251_CI_AS", Encoding.CP1251),
- UKRDIC (107, "SQL_Ukrainian_CP1251_CS_AS", Encoding.CP1251),
- UKRNC (108, "SQL_Ukrainian_CP1251_CI_AS", Encoding.CP1251),
-
- BIN_CP1253 (112, "bin_cp1253", Encoding.CP1253),
- DICTIONARY_1253 (113, "SQL_Latin1_General_CP1253_CS_AS", Encoding.CP1253),
- NOCASE_1253 (114, "SQL_Latin1_General_CP1253_CI_AS", Encoding.CP1253),
-
- GREEK_MIXEDDICTIONARY (120, "SQL_MixDiction_CP1253_CS_AS", Encoding.CP1253),
- GREEK_ALTDICTIONARY (121, "SQL_AltDiction_CP1253_CS_AS", Encoding.CP1253),
- GREEK_ALTDICTIONARY2 (122, "SQL_AltDiction2_CP1253_CS_AS", Encoding.CP1253),
- GREEK_NOCASEDICT (124, "SQL_Latin1_General_CP1253_CI_AI", Encoding.CP1253),
- BIN_CP1254 (128, "bin_cp1254", Encoding.CP1254),
- DICTIONARY_1254 (129, "SQL_Latin1_General_CP1254_CS_AS", Encoding.CP1254),
- NOCASE_1254 (130, "SQL_Latin1_General_CP1254_CI_AS", Encoding.CP1254),
-
- BIN_CP1255 (136, "bin_cp1255", Encoding.CP1255),
- DICTIONARY_1255 (137, "SQL_Latin1_General_CP1255_CS_AS", Encoding.CP1255),
- NOCASE_1255 (138, "SQL_Latin1_General_CP1255_CI_AS", Encoding.CP1255),
-
- BIN_CP1256 (144, "bin_cp1256", Encoding.CP1256),
- DICTIONARY_1256 (145, "SQL_Latin1_General_CP1256_CS_AS", Encoding.CP1256),
- NOCASE_1256 (146, "SQL_Latin1_General_CP1256_CI_AS", Encoding.CP1256),
-
- BIN_CP1257 (152, "bin_cp1257", Encoding.CP1257),
- DICTIONARY_1257 (153, "SQL_Latin1_General_CP1257_CS_AS", Encoding.CP1257),
- NOCASE_1257 (154, "SQL_Latin1_General_CP1257_CI_AS", Encoding.CP1257),
- ETIDIC (155, "SQL_Estonian_CP1257_CS_AS", Encoding.CP1257),
- ETINC (156, "SQL_Estonian_CP1257_CI_AS", Encoding.CP1257),
- LVIDIC (157, "SQL_Latvian_CP1257_CS_AS", Encoding.CP1257),
- LVINC (158, "SQL_Latvian_CP1257_CI_AS", Encoding.CP1257),
- LTHDIC (159, "SQL_Lithuanian_CP1257_CS_AS", Encoding.CP1257),
- LTHNC (160, "SQL_Lithuanian_CP1257_CI_AS", Encoding.CP1257),
-
- DANNO_NOCASEPREF (183, "SQL_Danish_Pref_CP1_CI_AS", Encoding.CP1252),
- SVFI1_NOCASEPREF (184, "SQL_SwedishPhone_Pref_CP1_CI_AS", Encoding.CP1252),
- SVFI2_NOCASEPREF (185, "SQL_SwedishStd_Pref_CP1_CI_AS", Encoding.CP1252),
- ISLAN_NOCASEPREF (186, "SQL_Icelandic_Pref_CP1_CI_AS", Encoding.CP1252),
-
- BIN_CP932 (192, "bin_cp932", Encoding.CP932),
- NLS_CP932 (193, "nls_cp932", Encoding.CP932),
- BIN_CP949 (194, "bin_cp949", Encoding.CP949),
- NLS_CP949 (195, "nls_cp949", Encoding.CP949),
- BIN_CP950 (196, "bin_cp950", Encoding.CP950),
- NLS_CP950 (197, "nls_cp950", Encoding.CP950),
- BIN_CP936 (198, "bin_cp936", Encoding.CP936),
- NLS_CP936 (199, "nls_cp936", Encoding.CP936),
- NLS_CP932_CS (200, "nls_cp932_cs", Encoding.CP932),
- NLS_CP949_CS (201, "nls_cp949_cs", Encoding.CP949),
- NLS_CP950_CS (202, "nls_cp950_cs", Encoding.CP950),
- NLS_CP936_CS (203, "nls_cp936_cs", Encoding.CP936),
- BIN_CP874 (204, "bin_cp874", Encoding.CP874),
- NLS_CP874 (205, "nls_cp874", Encoding.CP874),
- NLS_CP874_CS (206, "nls_cp874_cs", Encoding.CP874),
-
- EBCDIC_037 (210, "SQL_EBCDIC037_CP1_CS_AS", Encoding.CP1252),
- EBCDIC_273 (211, "SQL_EBCDIC273_CP1_CS_AS", Encoding.CP1252),
- EBCDIC_277 (212, "SQL_EBCDIC277_CP1_CS_AS", Encoding.CP1252),
- EBCDIC_278 (213, "SQL_EBCDIC278_CP1_CS_AS", Encoding.CP1252),
- EBCDIC_280 (214, "SQL_EBCDIC280_CP1_CS_AS", Encoding.CP1252),
- EBCDIC_284 (215, "SQL_EBCDIC284_CP1_CS_AS", Encoding.CP1252),
- EBCDIC_285 (216, "SQL_EBCDIC285_CP1_CS_AS", Encoding.CP1252),
- EBCDIC_297 (217, "SQL_EBCDIC297_CP1_CS_AS", Encoding.CP1252);
+ BIN_CP437 (30, "SQL_Latin1_General_CP437_BIN", Encoding.CP437),
+ DICTIONARY_437 (31, "SQL_Latin1_General_CP437_CS_AS", Encoding.CP437),
+ NOCASE_437 (32, "SQL_Latin1_General_CP437_CI_AS", Encoding.CP437),
+ NOCASEPREF_437 (33, "SQL_Latin1_General_Pref_CP437_CI_AS", Encoding.CP437),
+ NOACCENTS_437 (34, "SQL_Latin1_General_CP437_CI_AI", Encoding.CP437),
+ BIN2_CP437 (35, "SQL_Latin1_General_CP437_BIN2", Encoding.CP437),
+
+ BIN_CP850 (40, "SQL_Latin1_General_CP850_BIN", Encoding.CP850),
+ DICTIONARY_850 (41, "SQL_Latin1_General_CP850_CS_AS", Encoding.CP850),
+ NOCASE_850 (42, "SQL_Latin1_General_CP850_CI_AS", Encoding.CP850),
+ NOCASEPREF_850 (43, "SQL_Latin1_General_Pref_CP850_CI_AS", Encoding.CP850),
+ NOACCENTS_850 (44, "SQL_Latin1_General_CP850_CI_AI", Encoding.CP850),
+ BIN2_CP850 (45, "SQL_Latin1_General_CP850_BIN2", Encoding.CP850),
+
+ CASELESS_34 (49, "SQL_1xCompat_CP850_CI_AS", Encoding.CP850),
+ BIN_ISO_1 (50, "bin_iso_1", Encoding.CP1252),
+ DICTIONARY_ISO (51, "SQL_Latin1_General_CP1_CS_AS", Encoding.CP1252),
+ NOCASE_ISO (52, "SQL_Latin1_General_CP1_CI_AS", Encoding.CP1252),
+ NOCASEPREF_ISO (53, "SQL_Latin1_General_Pref_CP1_CI_AS", Encoding.CP1252),
+ NOACCENTS_ISO (54, "SQL_Latin1_General_CP1_CI_AI", Encoding.CP1252),
+ ALT_DICTIONARY (55, "SQL_AltDiction_CP850_CS_AS", Encoding.CP850),
+ ALT_NOCASEPREF (56, "SQL_AltDiction_Pref_CP850_CI_AS", Encoding.CP850),
+ ALT_NOACCENTS (57, "SQL_AltDiction_CP850_CI_AI", Encoding.CP850),
+ SCAND_NOCASEPREF (58, "SQL_Scandinavian_Pref_CP850_CI_AS", Encoding.CP850),
+ SCAND_DICTIONARY (59, "SQL_Scandinavian_CP850_CS_AS", Encoding.CP850),
+ SCAND_NOCASE (60, "SQL_Scandinavian_CP850_CI_AS", Encoding.CP850),
+ ALT_NOCASE (61, "SQL_AltDiction_CP850_CI_AS", Encoding.CP850),
+
+ DICTIONARY_1252 (71, "dictionary_1252", Encoding.CP1252),
+ NOCASE_1252 (72, "nocase_1252", Encoding.CP1252),
+ DNK_NOR_DICTIONARY (73, "dnk_nor_dictionary", Encoding.CP1252),
+ FIN_SWE_DICTIONARY (74, "fin_swe_dictionary", Encoding.CP1252),
+ ISL_DICTIONARY (75, "isl_dictionary", Encoding.CP1252),
+
+ BIN_CP1250 (80, "bin_cp1250", Encoding.CP1250),
+ DICTIONARY_1250 (81, "SQL_Latin1_General_CP1250_CS_AS", Encoding.CP1250),
+ NOCASE_1250 (82, "SQL_Latin1_General_CP1250_CI_AS", Encoding.CP1250),
+ CSYDIC (83, "SQL_Czech_CP1250_CS_AS", Encoding.CP1250),
+ CSYNC (84, "SQL_Czech_CP1250_CI_AS", Encoding.CP1250),
+ HUNDIC (85, "SQL_Hungarian_CP1250_CS_AS", Encoding.CP1250),
+ HUNNC (86, "SQL_Hungarian_CP1250_CI_AS", Encoding.CP1250),
+ PLKDIC (87, "SQL_Polish_CP1250_CS_AS", Encoding.CP1250),
+ PLKNC (88, "SQL_Polish_CP1250_CI_AS", Encoding.CP1250),
+ ROMDIC (89, "SQL_Romanian_CP1250_CS_AS", Encoding.CP1250),
+ ROMNC (90, "SQL_Romanian_CP1250_CI_AS", Encoding.CP1250),
+ SHLDIC (91, "SQL_Croatian_CP1250_CS_AS", Encoding.CP1250),
+ SHLNC (92, "SQL_Croatian_CP1250_CI_AS", Encoding.CP1250),
+ SKYDIC (93, "SQL_Slovak_CP1250_CS_AS", Encoding.CP1250),
+ SKYNC (94, "SQL_Slovak_CP1250_CI_AS", Encoding.CP1250),
+ SLVDIC (95, "SQL_Slovenian_CP1250_CS_AS", Encoding.CP1250),
+ SLVNC (96, "SQL_Slovenian_CP1250_CI_AS", Encoding.CP1250),
+ POLISH_CS (97, "polish_cs", Encoding.CP1250),
+ POLISH_CI (98, "polish_ci", Encoding.CP1250),
+
+ BIN_CP1251 (104, "bin_cp1251", Encoding.CP1251),
+ DICTIONARY_1251 (105, "SQL_Latin1_General_CP1251_CS_AS", Encoding.CP1251),
+ NOCASE_1251 (106, "SQL_Latin1_General_CP1251_CI_AS", Encoding.CP1251),
+ UKRDIC (107, "SQL_Ukrainian_CP1251_CS_AS", Encoding.CP1251),
+ UKRNC (108, "SQL_Ukrainian_CP1251_CI_AS", Encoding.CP1251),
+
+ BIN_CP1253 (112, "bin_cp1253", Encoding.CP1253),
+ DICTIONARY_1253 (113, "SQL_Latin1_General_CP1253_CS_AS", Encoding.CP1253),
+ NOCASE_1253 (114, "SQL_Latin1_General_CP1253_CI_AS", Encoding.CP1253),
+
+ GREEK_MIXEDDICTIONARY (120, "SQL_MixDiction_CP1253_CS_AS", Encoding.CP1253),
+ GREEK_ALTDICTIONARY (121, "SQL_AltDiction_CP1253_CS_AS", Encoding.CP1253),
+ GREEK_ALTDICTIONARY2 (122, "SQL_AltDiction2_CP1253_CS_AS", Encoding.CP1253),
+ GREEK_NOCASEDICT (124, "SQL_Latin1_General_CP1253_CI_AI", Encoding.CP1253),
+ BIN_CP1254 (128, "bin_cp1254", Encoding.CP1254),
+ DICTIONARY_1254 (129, "SQL_Latin1_General_CP1254_CS_AS", Encoding.CP1254),
+ NOCASE_1254 (130, "SQL_Latin1_General_CP1254_CI_AS", Encoding.CP1254),
+
+ BIN_CP1255 (136, "bin_cp1255", Encoding.CP1255),
+ DICTIONARY_1255 (137, "SQL_Latin1_General_CP1255_CS_AS", Encoding.CP1255),
+ NOCASE_1255 (138, "SQL_Latin1_General_CP1255_CI_AS", Encoding.CP1255),
+
+ BIN_CP1256 (144, "bin_cp1256", Encoding.CP1256),
+ DICTIONARY_1256 (145, "SQL_Latin1_General_CP1256_CS_AS", Encoding.CP1256),
+ NOCASE_1256 (146, "SQL_Latin1_General_CP1256_CI_AS", Encoding.CP1256),
+
+ BIN_CP1257 (152, "bin_cp1257", Encoding.CP1257),
+ DICTIONARY_1257 (153, "SQL_Latin1_General_CP1257_CS_AS", Encoding.CP1257),
+ NOCASE_1257 (154, "SQL_Latin1_General_CP1257_CI_AS", Encoding.CP1257),
+ ETIDIC (155, "SQL_Estonian_CP1257_CS_AS", Encoding.CP1257),
+ ETINC (156, "SQL_Estonian_CP1257_CI_AS", Encoding.CP1257),
+ LVIDIC (157, "SQL_Latvian_CP1257_CS_AS", Encoding.CP1257),
+ LVINC (158, "SQL_Latvian_CP1257_CI_AS", Encoding.CP1257),
+ LTHDIC (159, "SQL_Lithuanian_CP1257_CS_AS", Encoding.CP1257),
+ LTHNC (160, "SQL_Lithuanian_CP1257_CI_AS", Encoding.CP1257),
+
+ DANNO_NOCASEPREF (183, "SQL_Danish_Pref_CP1_CI_AS", Encoding.CP1252),
+ SVFI1_NOCASEPREF (184, "SQL_SwedishPhone_Pref_CP1_CI_AS", Encoding.CP1252),
+ SVFI2_NOCASEPREF (185, "SQL_SwedishStd_Pref_CP1_CI_AS", Encoding.CP1252),
+ ISLAN_NOCASEPREF (186, "SQL_Icelandic_Pref_CP1_CI_AS", Encoding.CP1252),
+
+ BIN_CP932 (192, "bin_cp932", Encoding.CP932),
+ NLS_CP932 (193, "nls_cp932", Encoding.CP932),
+ BIN_CP949 (194, "bin_cp949", Encoding.CP949),
+ NLS_CP949 (195, "nls_cp949", Encoding.CP949),
+ BIN_CP950 (196, "bin_cp950", Encoding.CP950),
+ NLS_CP950 (197, "nls_cp950", Encoding.CP950),
+ BIN_CP936 (198, "bin_cp936", Encoding.CP936),
+ NLS_CP936 (199, "nls_cp936", Encoding.CP936),
+ NLS_CP932_CS (200, "nls_cp932_cs", Encoding.CP932),
+ NLS_CP949_CS (201, "nls_cp949_cs", Encoding.CP949),
+ NLS_CP950_CS (202, "nls_cp950_cs", Encoding.CP950),
+ NLS_CP936_CS (203, "nls_cp936_cs", Encoding.CP936),
+ BIN_CP874 (204, "bin_cp874", Encoding.CP874),
+ NLS_CP874 (205, "nls_cp874", Encoding.CP874),
+ NLS_CP874_CS (206, "nls_cp874_cs", Encoding.CP874),
+
+ EBCDIC_037 (210, "SQL_EBCDIC037_CP1_CS_AS", Encoding.CP1252),
+ EBCDIC_273 (211, "SQL_EBCDIC273_CP1_CS_AS", Encoding.CP1252),
+ EBCDIC_277 (212, "SQL_EBCDIC277_CP1_CS_AS", Encoding.CP1252),
+ EBCDIC_278 (213, "SQL_EBCDIC278_CP1_CS_AS", Encoding.CP1252),
+ EBCDIC_280 (214, "SQL_EBCDIC280_CP1_CS_AS", Encoding.CP1252),
+ EBCDIC_284 (215, "SQL_EBCDIC284_CP1_CS_AS", Encoding.CP1252),
+ EBCDIC_285 (216, "SQL_EBCDIC285_CP1_CS_AS", Encoding.CP1252),
+ EBCDIC_297 (217, "SQL_EBCDIC297_CP1_CS_AS", Encoding.CP1252);
private final int sortId;
private final String name;
private final Encoding encoding;
- final Encoding getEncoding() throws UnsupportedEncodingException
- {
+
+ final Encoding getEncoding() throws UnsupportedEncodingException {
return encoding.checkSupported();
}
- SortOrder(int sortId, String name, Encoding encoding)
- {
+ SortOrder(int sortId,
+ String name,
+ Encoding encoding) {
this.sortId = sortId;
this.name = name;
this.encoding = encoding;
}
- public final String toString() { return name; }
+ public final String toString() {
+ return name;
+ }
}
private static final HashMap sortOrderIndex;
- private Encoding encodingFromSortId() throws UnsupportedEncodingException
- {
+ private Encoding encodingFromSortId() throws UnsupportedEncodingException {
SortOrder sortOrder = sortOrderIndex.get(sortId);
- if (null == sortOrder)
- {
+ if (null == sortOrder) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unknownSortId"));
Object[] msgArgs = {sortId};
throw new UnsupportedEncodingException(form.format(msgArgs));
}
- try
- {
+ try {
return sortOrder.getEncoding();
}
- catch (UnsupportedEncodingException inner)
- {
+ catch (UnsupportedEncodingException inner) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unknownSortId"));
Object[] msgArgs = {sortOrder};
UnsupportedEncodingException e = new UnsupportedEncodingException(form.format(msgArgs));
@@ -526,8 +509,7 @@ private Encoding encodingFromSortId() throws UnsupportedEncodingException
}
}
- static
- {
+ static {
// Populate the windows locale and sort order indices
localeIndex = new HashMap();
@@ -540,12 +522,11 @@ private Encoding encodingFromSortId() throws UnsupportedEncodingException
}
}
-
/**
* Enumeration of encodings that are supported by SQL Server (and hopefully the JVM).
*
- * See, for example, https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html
- * for a complete list of supported encodings with their canonical names.
+ * See, for example, https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html for a complete list of supported encodings with
+ * their canonical names.
*/
enum Encoding
{
@@ -573,24 +554,19 @@ enum Encoding
private boolean jvmSupportConfirmed = false;
private Charset charset;
- private Encoding(
- String charsetName,
- boolean supportsAsciiConversion,
- boolean hasAsciiCompatibleSBCS)
- {
+ private Encoding(String charsetName,
+ boolean supportsAsciiConversion,
+ boolean hasAsciiCompatibleSBCS) {
this.charsetName = charsetName;
this.supportsAsciiConversion = supportsAsciiConversion;
this.hasAsciiCompatibleSBCS = hasAsciiCompatibleSBCS;
}
- final Encoding checkSupported() throws UnsupportedEncodingException
- {
- if (!jvmSupportConfirmed)
- {
+ final Encoding checkSupported() throws UnsupportedEncodingException {
+ if (!jvmSupportConfirmed) {
// Checks for support by converting a java.lang.String
// This works for all of the code pages above in SE 5 and later.
- if (!Charset.isSupported(charsetName))
- {
+ if (!Charset.isSupported(charsetName)) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_codePageNotSupported"));
Object[] msgArgs = {charsetName};
throw new UnsupportedEncodingException(form.format(msgArgs));
@@ -602,17 +578,14 @@ final Encoding checkSupported() throws UnsupportedEncodingException
return this;
}
- final Charset charset() throws SQLServerException
- {
- try
- {
+ final Charset charset() throws SQLServerException {
+ try {
checkSupported();
- if (charset == null)
- {
+ if (charset == null) {
charset = Charset.forName(charsetName);
}
- } catch (UnsupportedEncodingException e)
- {
+ }
+ catch (UnsupportedEncodingException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_codePageNotSupported"));
Object[] msgArgs = {charsetName};
throw new SQLServerException(form.format(msgArgs), e);
@@ -623,20 +596,20 @@ final Charset charset() throws SQLServerException
/**
* Returns true if the collation supports conversion to ascii.
*
- * Per discussions with richards and michkap on UNICODE alias ->
- * ASCII range is 0x00 to 0x7F.
- * The range of 0x00 to 0x7F of 1250-1258, 874, 932, 936, 949, and 950 are identical to ASCII.
- * See also -> http://blogs.msdn.com/michkap/archive/2005/11/23/495193.aspx
+ * Per discussions with richards and michkap on UNICODE alias -> ASCII range is 0x00 to 0x7F. The range of 0x00 to 0x7F of 1250-1258, 874, 932,
+ * 936, 949, and 950 are identical to ASCII. See also -> http://blogs.msdn.com/michkap/archive/2005/11/23/495193.aspx
*/
- boolean supportsAsciiConversion() { return supportsAsciiConversion; }
+ boolean supportsAsciiConversion() {
+ return supportsAsciiConversion;
+ }
/**
* Returns true if the collation supports conversion to ascii AND it uses a single-byte character set.
*
- * Per discussions with richards and michkap on UNICODE alias ->
- * ASCII range is 0x00 to 0x7F.
- * The range of 0x00 to 0x7F of 1250-1258 and 874 are identical to ASCII for these SBCS character sets.
- * See also -> http://blogs.msdn.com/michkap/archive/2005/11/23/495193.aspx
+ * Per discussions with richards and michkap on UNICODE alias -> ASCII range is 0x00 to 0x7F. The range of 0x00 to 0x7F of 1250-1258 and 874 are
+ * identical to ASCII for these SBCS character sets. See also -> http://blogs.msdn.com/michkap/archive/2005/11/23/495193.aspx
*/
- boolean hasAsciiCompatibleSBCS() { return hasAsciiCompatibleSBCS; }
+ boolean hasAsciiCompatibleSBCS() {
+ return hasAsciiCompatibleSBCS;
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
index 5a3e7119f..fa32cc006 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java
@@ -1,30 +1,16 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLJdbcVersion.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+package com.microsoft.sqlserver.jdbc;
-/*
-* generated file do not modify
-*/
-package com.microsoft.sqlserver.jdbc;
-final class SQLJdbcVersion
- {
-static final int major=6;
-static final int minor=1;
-static final int patch=0;
-static final int build=0;
+final class SQLJdbcVersion {
+ static final int major = 6;
+ static final int minor = 1;
+ static final int patch = 0;
+ static final int build = 0;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Algorithm.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Algorithm.java
index 2ef640f2b..0f45edbec 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Algorithm.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Algorithm.java
@@ -1,25 +1,13 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerAeadAes256CbcHmac256Algorithm.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
-
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -37,351 +25,335 @@
/**
*
- * This class implements authenticated encryption with associated data (AEAD_AES_256_CBC_HMAC_SHA256)
- * algorithm specified at
- * http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
+ * This class implements authenticated encryption with associated data (AEAD_AES_256_CBC_HMAC_SHA256) algorithm specified at
+ * http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05
*
*/
- class SQLServerAeadAes256CbcHmac256Algorithm extends SQLServerEncryptionAlgorithm {
-
- static final private java.util.logging.Logger aeLogger =
- java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.SQLServerAeadAes256CbcHmac256Algorithm");
-
- final static String algorithmName="AEAD_AES_256_CBC_HMAC_SHA256";
+class SQLServerAeadAes256CbcHmac256Algorithm extends SQLServerEncryptionAlgorithm {
+
+ static final private java.util.logging.Logger aeLogger = java.util.logging.Logger
+ .getLogger("com.microsoft.sqlserver.jdbc.SQLServerAeadAes256CbcHmac256Algorithm");
+
+ final static String algorithmName = "AEAD_AES_256_CBC_HMAC_SHA256";
// Stores column encryption key which includes root key and derived keys
- private SQLServerAeadAes256CbcHmac256EncryptionKey columnEncryptionkey;
- private byte algorithmVersion;
+ private SQLServerAeadAes256CbcHmac256EncryptionKey columnEncryptionkey;
+ private byte algorithmVersion;
// This variable indicate whether encryption type is deterministic (if true)
// or random (if false)
private boolean isDeterministic = false;
- // Each block in the AES is 128 bits
- private int blockSizeInBytes=16;
- private int keySizeInBytes = SQLServerAeadAes256CbcHmac256EncryptionKey.keySize / 8;
- private byte[] version = new byte[] {0x01};
+ // Each block in the AES is 128 bits
+ private int blockSizeInBytes = 16;
+ private int keySizeInBytes = SQLServerAeadAes256CbcHmac256EncryptionKey.keySize / 8;
+ private byte[] version = new byte[] {0x01};
// Added so that java hashing algorithm is similar to c#
- private byte[] versionSize = new byte[] { 1 };
-
- /*
- * Minimum Length of cipherText without authentication tag. This value is 1
- * (version byte) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
- */
- private int minimumCipherTextLengthInBytesNoAuthenticationTag = 1 + blockSizeInBytes + blockSizeInBytes;
+ private byte[] versionSize = new byte[] {1};
/*
- * Minimum Length of cipherText. This value is 1 (version byte) + 32
- * (authentication tag) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
- */
- private int minimumCipherTextLengthInBytesWithAuthenticationTag = minimumCipherTextLengthInBytesNoAuthenticationTag + keySizeInBytes;
-
- /**
- * Initializes a new instance of SQLServerAeadAes256CbcHmac256Algorithm with
- * a given key, encryption type and algorithm version
- *
- * @param columnEncryptionkey
- * Root encryption key from which three other keys will be
- * derived
- * @param encryptionType
- * Encryption Type, accepted values are Deterministic and
- * Randomized.
- * @param algorithmVersion
- * Algorithm version
- */
- SQLServerAeadAes256CbcHmac256Algorithm(
- SQLServerAeadAes256CbcHmac256EncryptionKey columnEncryptionkey,
- SQLServerEncryptionType encryptionType,
- byte algorithmVersion)
- {
+ * Minimum Length of cipherText without authentication tag. This value is 1 (version byte) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
+ */
+ private int minimumCipherTextLengthInBytesNoAuthenticationTag = 1 + blockSizeInBytes + blockSizeInBytes;
+
+ /*
+ * Minimum Length of cipherText. This value is 1 (version byte) + 32 (authentication tag) + 16 (IV) + 16 (minimum of 1 block of cipher Text)
+ */
+ private int minimumCipherTextLengthInBytesWithAuthenticationTag = minimumCipherTextLengthInBytesNoAuthenticationTag + keySizeInBytes;
+
+ /**
+ * Initializes a new instance of SQLServerAeadAes256CbcHmac256Algorithm with a given key, encryption type and algorithm version
+ *
+ * @param columnEncryptionkey
+ * Root encryption key from which three other keys will be derived
+ * @param encryptionType
+ * Encryption Type, accepted values are Deterministic and Randomized.
+ * @param algorithmVersion
+ * Algorithm version
+ */
+ SQLServerAeadAes256CbcHmac256Algorithm(SQLServerAeadAes256CbcHmac256EncryptionKey columnEncryptionkey,
+ SQLServerEncryptionType encryptionType,
+ byte algorithmVersion) {
this.columnEncryptionkey = columnEncryptionkey;
- if (encryptionType == SQLServerEncryptionType.Deterministic)
- {
+ if (encryptionType == SQLServerEncryptionType.Deterministic) {
this.isDeterministic = true;
}
this.algorithmVersion = algorithmVersion;
version[0] = algorithmVersion;
}
- @Override
- byte[] encryptData(byte[] plainText) throws SQLServerException {
- // hasAuthenticationTag is true for this algorithm
- return encryptData(plainText, true);
- }
-
+ @Override
+ byte[] encryptData(byte[] plainText) throws SQLServerException {
+ // hasAuthenticationTag is true for this algorithm
+ return encryptData(plainText, true);
+ }
+
/**
- * Performs encryption of plain text
- * @param plainText text to be encrypted
- * @param hasAuthenticationTag specify if encryption needs authentication
- * @return cipher text
- * @throws SQLServerException
+ * Performs encryption of plain text
+ *
+ * @param plainText
+ * text to be encrypted
+ * @param hasAuthenticationTag
+ * specify if encryption needs authentication
+ * @return cipher text
+ * @throws SQLServerException
*/
- protected byte[] encryptData(byte[] plainText,boolean hasAuthenticationTag) throws SQLServerException{
- aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(), "Encrypting data.");
- // we will generate this initialization vector based whether
- // this encryption type is deterministic
- assert(plainText!=null);
- byte[] iv = new byte[blockSizeInBytes];
- // Secret/private key to be used in AES encryption
- SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(),
- "AES");
-
- if (isDeterministic)
- {
- // this method makes sure this is 16 bytes key
- try
- {
- iv = SQLServerSecurityUtility.getHMACWithSHA256(
- plainText,
- columnEncryptionkey.getIVKey(),
- blockSizeInBytes);
+ protected byte[] encryptData(byte[] plainText,
+ boolean hasAuthenticationTag) throws SQLServerException {
+ aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
+ "Encrypting data.");
+ // we will generate this initialization vector based whether
+ // this encryption type is deterministic
+ assert (plainText != null);
+ byte[] iv = new byte[blockSizeInBytes];
+ // Secret/private key to be used in AES encryption
+ SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(), "AES");
+
+ if (isDeterministic) {
+ // this method makes sure this is 16 bytes key
+ try {
+ iv = SQLServerSecurityUtility.getHMACWithSHA256(plainText, columnEncryptionkey.getIVKey(), blockSizeInBytes);
}
- catch (InvalidKeyException | NoSuchAlgorithmException e)
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_EncryptionFailed"));
- Object[] msgArgs = { e.getMessage() };
+ catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
+ Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
- } else {
+ }
+ else {
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
}
-
int numBlocks = plainText.length / blockSizeInBytes + 1;
-
- int hmacStartIndex = 1;
- int authenticationTagLen = hasAuthenticationTag ? keySizeInBytes : 0;
- int ivStartIndex = hmacStartIndex + authenticationTagLen;
- int cipherStartIndex = ivStartIndex + blockSizeInBytes;
-
- // Output buffer size = size of VersionByte + Authentication Tag + IV + cipher Text blocks.
- int outputBufSize = 1 + authenticationTagLen + iv.length + (numBlocks*blockSizeInBytes);
- byte[] outBuffer = new byte[outputBufSize];
-
- // Copying the version to output buffer
- outBuffer[0]=algorithmVersion;
- // Coping IV to the output buffer
- System.arraycopy(iv, 0, outBuffer, ivStartIndex, iv.length);
-
+
+ int hmacStartIndex = 1;
+ int authenticationTagLen = hasAuthenticationTag ? keySizeInBytes : 0;
+ int ivStartIndex = hmacStartIndex + authenticationTagLen;
+ int cipherStartIndex = ivStartIndex + blockSizeInBytes;
+
+ // Output buffer size = size of VersionByte + Authentication Tag + IV + cipher Text blocks.
+ int outputBufSize = 1 + authenticationTagLen + iv.length + (numBlocks * blockSizeInBytes);
+ byte[] outBuffer = new byte[outputBufSize];
+
+ // Copying the version to output buffer
+ outBuffer[0] = algorithmVersion;
+ // Coping IV to the output buffer
+ System.arraycopy(iv, 0, outBuffer, ivStartIndex, iv.length);
+
// Start the AES encryption
-
- try {
+
+ try {
// initialization vector
- IvParameterSpec ivector = new IvParameterSpec(iv);
- Cipher encryptCipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
- encryptCipher.init(Cipher.ENCRYPT_MODE,skeySpec,ivector);
-
- int count = 0;
+ IvParameterSpec ivector = new IvParameterSpec(iv);
+ Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ encryptCipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivector);
+
+ int count = 0;
int cipherIndex = cipherStartIndex; // this is where cipherText starts
-
- if(numBlocks>1){
- count = (numBlocks - 1) * blockSizeInBytes;
- cipherIndex+=encryptCipher.update(plainText, 0, count, outBuffer,cipherIndex);
+
+ if (numBlocks > 1) {
+ count = (numBlocks - 1) * blockSizeInBytes;
+ cipherIndex += encryptCipher.update(plainText, 0, count, outBuffer, cipherIndex);
}
- // doFinal will complete the encryption
- byte[] buffTmp = encryptCipher.doFinal(plainText, count, plainText.length - count);
- //Encryption completed
+ // doFinal will complete the encryption
+ byte[] buffTmp = encryptCipher.doFinal(plainText, count, plainText.length - count);
+ // Encryption completed
System.arraycopy(buffTmp, 0, outBuffer, cipherIndex, buffTmp.length);
-
+
if (hasAuthenticationTag) {
-
- Mac hmac = Mac.getInstance("HmacSHA256");
- SecretKeySpec initkey = new SecretKeySpec(columnEncryptionkey.getMacKey(), "HmacSHA256");
- hmac.init(initkey);
- hmac.update(version, 0, version.length);
- hmac.update(iv, 0, iv.length);
- hmac.update(outBuffer, cipherStartIndex, numBlocks * blockSizeInBytes);
- hmac.update(versionSize, 0, version.length);
- byte [] hash=hmac.doFinal();
- //coping the authentication tag in the output buffer which holds cipher text
- System.arraycopy(hash, 0, outBuffer, hmacStartIndex, authenticationTagLen);
-
+
+ Mac hmac = Mac.getInstance("HmacSHA256");
+ SecretKeySpec initkey = new SecretKeySpec(columnEncryptionkey.getMacKey(), "HmacSHA256");
+ hmac.init(initkey);
+ hmac.update(version, 0, version.length);
+ hmac.update(iv, 0, iv.length);
+ hmac.update(outBuffer, cipherStartIndex, numBlocks * blockSizeInBytes);
+ hmac.update(versionSize, 0, version.length);
+ byte[] hash = hmac.doFinal();
+ // coping the authentication tag in the output buffer which holds cipher text
+ System.arraycopy(hash, 0, outBuffer, hmacStartIndex, authenticationTagLen);
+
}
- } catch (NoSuchAlgorithmException |
- InvalidAlgorithmParameterException |
- InvalidKeyException |
- NoSuchPaddingException |
- IllegalBlockSizeException |
- BadPaddingException|
- ShortBufferException e)
- {
-
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_EncryptionFailed"));
- Object[] msgArgs = { e.getMessage() };
+ }
+ catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException | ShortBufferException e) {
+
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
+ Object[] msgArgs = {e.getMessage()};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+
+ aeLogger.exiting(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
+ "Data encrypted.");
+ return outBuffer;
+
+ }
+
+ @Override
+ byte[] decryptData(byte[] cipherText) throws SQLServerException {
+ return decryptData(cipherText, true);
+
+ }
+
+ /**
+ * Decrypt the cipher text and return plain text
+ *
+ * @param cipherText
+ * data to be decrypted
+ * @param hasAuthenticationTag
+ * tells whether cipher text contain authentication tag
+ * @return plain text
+ * @throws SQLServerException
+ */
+ private byte[] decryptData(byte[] cipherText,
+ boolean hasAuthenticationTag) throws SQLServerException {
+ assert (cipherText != null);
+
+ byte[] iv = new byte[blockSizeInBytes];
+
+ int minimumCipherTextLength = hasAuthenticationTag ? minimumCipherTextLengthInBytesWithAuthenticationTag
+ : minimumCipherTextLengthInBytesNoAuthenticationTag;
+
+ // Here we check if length of cipher text is more than minimum value,
+ // if not exception is thrown
+ if (cipherText.length < minimumCipherTextLength) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidCipherTextSize"));
+ Object[] msgArgs = {cipherText.length, minimumCipherTextLength};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+
+ }
+
+ // Validate the version byte
+ int startIndex = 0;
+ if (cipherText[startIndex] != algorithmVersion) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidAlgorithmVersion"));
+ // converting byte to Hexa Decimal
+ Object[] msgArgs = {String.format("%02X ", cipherText[startIndex]), String.format("%02X ", algorithmVersion)};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+
}
-
- aeLogger.exiting(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(), "Data encrypted.");
- return outBuffer;
-
-
- }
-
- @Override
- byte[] decryptData(byte[] cipherText) throws SQLServerException {
- return decryptData(cipherText, true);
-
- }
-
- /**
- * Decrypt the cipher text and return plain text
- * @param cipherText data to be decrypted
- * @param hasAuthenticationTag tells whether cipher text contain authentication tag
- * @return plain text
- * @throws SQLServerException
- */
- private byte [] decryptData(byte[] cipherText,boolean hasAuthenticationTag) throws SQLServerException{
- assert(cipherText!=null);
-
- byte[] iv = new byte[blockSizeInBytes];
-
- int minimumCipherTextLength = hasAuthenticationTag ? minimumCipherTextLengthInBytesWithAuthenticationTag : minimumCipherTextLengthInBytesNoAuthenticationTag;
-
- // Here we check if length of cipher text is more than minimum value,
- // if not exception is thrown
- if(cipherText.length < minimumCipherTextLength){
- MessageFormat form =new MessageFormat(SQLServerException.getErrString("R_InvalidCipherTextSize"));
- Object[] msgArgs={cipherText.length,minimumCipherTextLength};
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
-
- }
-
- // Validate the version byte
- int startIndex = 0;
- if (cipherText[startIndex] != algorithmVersion) {
- MessageFormat form =new MessageFormat(SQLServerException.getErrString("R_InvalidAlgorithmVersion"));
- // converting byte to Hexa Decimal
- Object[] msgArgs={String.format("%02X ",cipherText[startIndex]),String.format("%02X ",algorithmVersion)};
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
-
- }
-
- startIndex += 1;
- int authenticationTagOffset = 0;
-
- // Read authentication tag
- if (hasAuthenticationTag) {
- authenticationTagOffset = startIndex;
- // authentication tag size is keySizeInBytes
- startIndex += keySizeInBytes;
- }
-
- // Read IV from cipher text
- System.arraycopy(cipherText, startIndex, iv, 0, iv.length);
- startIndex += iv.length;
-
- // To read encrypted text from cipher
- int cipherTextOffset = startIndex;
- // All data after IV is encrypted data
- int cipherTextCount = cipherText.length - startIndex;
-
- if (hasAuthenticationTag) {
- byte[] authenticationTag;
- try
- {
+
+ startIndex += 1;
+ int authenticationTagOffset = 0;
+
+ // Read authentication tag
+ if (hasAuthenticationTag) {
+ authenticationTagOffset = startIndex;
+ // authentication tag size is keySizeInBytes
+ startIndex += keySizeInBytes;
+ }
+
+ // Read IV from cipher text
+ System.arraycopy(cipherText, startIndex, iv, 0, iv.length);
+ startIndex += iv.length;
+
+ // To read encrypted text from cipher
+ int cipherTextOffset = startIndex;
+ // All data after IV is encrypted data
+ int cipherTextCount = cipherText.length - startIndex;
+
+ if (hasAuthenticationTag) {
+ byte[] authenticationTag;
+ try {
authenticationTag = prepareAuthenticationTag(iv, cipherText, cipherTextOffset, cipherTextCount);
}
- catch (InvalidKeyException | NoSuchAlgorithmException e)
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_DecryptionFailed"));
- Object[] msgArgs = { e.getMessage() };
+ catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed"));
+ Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
-
+
+ }
+ if (!(SQLServerSecurityUtility.compareBytes(authenticationTag, cipherText, authenticationTagOffset, cipherTextCount))) {
+
+ throw new SQLServerException(this, SQLServerException.getErrString("R_InvalidAuthenticationTag"), null, 0, false);
+
}
- if(!(SQLServerSecurityUtility.compareBytes(authenticationTag, cipherText, authenticationTagOffset, cipherTextCount))){
-
- throw new SQLServerException(this, SQLServerException.getErrString("R_InvalidAuthenticationTag"), null, 0, false);
-
- }
-
- }
-
- // Decrypt the text and return
- return decryptData(iv, cipherText, cipherTextOffset, cipherTextCount);
- }
-
- /**
- * Decrypt data with specified IV
- * @param iv initialization vector
- * @param cipherText text to be decrypted
- * @param offset of cipher text
- * @param count length of cipher text
- * @return plain text
- * @throws SQLServerException
- */
- private byte [] decryptData(byte [] iv ,byte [] cipherText,int offset,int count ) throws SQLServerException{
- aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(), "Decrypting data.");
- assert(cipherText!=null);
- assert(iv!=null);
- byte [] plainText = null;
- // key to be used for decryption
- SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(),
- "AES");
- IvParameterSpec ivector = new IvParameterSpec(iv);
- Cipher decryptCipher;
- try
- {
+
+ }
+
+ // Decrypt the text and return
+ return decryptData(iv, cipherText, cipherTextOffset, cipherTextCount);
+ }
+
+ /**
+ * Decrypt data with specified IV
+ *
+ * @param iv
+ * initialization vector
+ * @param cipherText
+ * text to be decrypted
+ * @param offset
+ * of cipher text
+ * @param count
+ * length of cipher text
+ * @return plain text
+ * @throws SQLServerException
+ */
+ private byte[] decryptData(byte[] iv,
+ byte[] cipherText,
+ int offset,
+ int count) throws SQLServerException {
+ aeLogger.entering(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
+ "Decrypting data.");
+ assert (cipherText != null);
+ assert (iv != null);
+ byte[] plainText = null;
+ // key to be used for decryption
+ SecretKeySpec skeySpec = new SecretKeySpec(columnEncryptionkey.getEncryptionKey(), "AES");
+ IvParameterSpec ivector = new IvParameterSpec(iv);
+ Cipher decryptCipher;
+ try {
// AES encryption CBC mode and PKCS5 padding
decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, skeySpec, ivector);
plainText = decryptCipher.doFinal(cipherText, offset, count);
}
- catch (
- NoSuchAlgorithmException |
- InvalidAlgorithmParameterException |
- InvalidKeyException |
- NoSuchPaddingException |
- IllegalBlockSizeException |
- BadPaddingException e)
- {
-
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_DecryptionFailed"));
- Object[] msgArgs = { e.getMessage() };
+ catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException
+ | IllegalBlockSizeException | BadPaddingException e) {
+
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed"));
+ Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
-
- aeLogger.exiting(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(), "Data decrypted.");
- return plainText;
-
- }
-
- /**
- * Prepare the authentication tag
- * @param iv initialization vector
- * @param cipherText
- * @param offset
- * @param length length of cipher text
- * @return authentication tag
- * @throws NoSuchAlgorithmException
- * @throws InvalidKeyException
- */
- private byte[] prepareAuthenticationTag(byte[] iv, byte[] cipherText,
- int offset, int length) throws NoSuchAlgorithmException, InvalidKeyException {
- assert(cipherText!=null);
- byte[] computedHash;
- byte[] authenticationTag = new byte[keySizeInBytes];
-
- Mac hmac = Mac.getInstance("HmacSHA256");
- SecretKeySpec key = new SecretKeySpec(
- columnEncryptionkey.getMacKey(), "HmacSHA256");
- hmac.init(key);
- hmac.update(version, 0, version.length);
- hmac.update(iv, 0, iv.length);
- hmac.update(cipherText, offset, length);
- hmac.update(versionSize, 0, version.length);
- computedHash = hmac.doFinal();
- System.arraycopy(computedHash, 0, authenticationTag, 0,
- authenticationTag.length);
-
-
- return authenticationTag;
-
- }
-
-
-
+
+ aeLogger.exiting(SQLServerAeadAes256CbcHmac256Algorithm.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(),
+ "Data decrypted.");
+ return plainText;
+
+ }
+
+ /**
+ * Prepare the authentication tag
+ *
+ * @param iv
+ * initialization vector
+ * @param cipherText
+ * @param offset
+ * @param length
+ * length of cipher text
+ * @return authentication tag
+ * @throws NoSuchAlgorithmException
+ * @throws InvalidKeyException
+ */
+ private byte[] prepareAuthenticationTag(byte[] iv,
+ byte[] cipherText,
+ int offset,
+ int length) throws NoSuchAlgorithmException, InvalidKeyException {
+ assert (cipherText != null);
+ byte[] computedHash;
+ byte[] authenticationTag = new byte[keySizeInBytes];
+
+ Mac hmac = Mac.getInstance("HmacSHA256");
+ SecretKeySpec key = new SecretKeySpec(columnEncryptionkey.getMacKey(), "HmacSHA256");
+ hmac.init(key);
+ hmac.update(version, 0, version.length);
+ hmac.update(iv, 0, iv.length);
+ hmac.update(cipherText, offset, length);
+ hmac.update(versionSize, 0, version.length);
+ computedHash = hmac.doFinal();
+ System.arraycopy(computedHash, 0, authenticationTag, 0, authenticationTag.length);
+
+ return authenticationTag;
+
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256EncryptionKey.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256EncryptionKey.java
index 07a4bf92c..b2e6826ab 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256EncryptionKey.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256EncryptionKey.java
@@ -1,39 +1,25 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerAeadAes256CbcHmac256EncryptionKey.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
+import static java.nio.charset.StandardCharsets.UTF_16LE;
+
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
-import static java.nio.charset.StandardCharsets.UTF_16LE;
-
/**
- * Encryption key class which consist of following 4 keys :
- * 1) root key - Main key which is used to derive following keys
- * 2) encryption key - A derived key that is used to encrypt the plain text and generate cipher text
- * 3) mac_key - A derived key that is used to compute HMAC of the cipher text
- * 4) iv_key - A derived key that is used to generate a synthetic IV from plain text data.
+ * Encryption key class which consist of following 4 keys : 1) root key - Main key which is used to derive following keys 2) encryption key - A
+ * derived key that is used to encrypt the plain text and generate cipher text 3) mac_key - A derived key that is used to compute HMAC of the cipher
+ * text 4) iv_key - A derived key that is used to generate a synthetic IV from plain text data.
*/
- class SQLServerAeadAes256CbcHmac256EncryptionKey extends SQLServerSymmetricKey
-{
+class SQLServerAeadAes256CbcHmac256EncryptionKey extends SQLServerSymmetricKey {
// This is the key size in the bits, since we are using AES256, it will 256
static final int keySize = 256;
@@ -51,77 +37,63 @@ class SQLServerAeadAes256CbcHmac256EncryptionKey extends SQLServerSymmetricKey
/**
* Derive all the keys from the root key
- * @param rootKey key used to derive other keys
- * @param algorithmName name of the algorithm associated with keys
- * @throws SQLServerException
+ *
+ * @param rootKey
+ * key used to derive other keys
+ * @param algorithmName
+ * name of the algorithm associated with keys
+ * @throws SQLServerException
*/
- SQLServerAeadAes256CbcHmac256EncryptionKey(byte[] rootKey, String algorithmName) throws SQLServerException
- {
+ SQLServerAeadAes256CbcHmac256EncryptionKey(byte[] rootKey,
+ String algorithmName) throws SQLServerException {
super(rootKey);
this.algorithmName = algorithmName;
- encryptionKeySaltFormat = "Microsoft SQL Server cell encryption key with encryption algorithm:" +
- this.algorithmName + " and key length:" + keySize;
- macKeySaltFormat = "Microsoft SQL Server cell MAC key with encryption algorithm:" +
- this.algorithmName + " and key length:" + keySize;
- ivKeySaltFormat = "Microsoft SQL Server cell IV key with encryption algorithm:" +
- this.algorithmName + " and key length:" + keySize;
+ encryptionKeySaltFormat = "Microsoft SQL Server cell encryption key with encryption algorithm:" + this.algorithmName + " and key length:"
+ + keySize;
+ macKeySaltFormat = "Microsoft SQL Server cell MAC key with encryption algorithm:" + this.algorithmName + " and key length:" + keySize;
+ ivKeySaltFormat = "Microsoft SQL Server cell IV key with encryption algorithm:" + this.algorithmName + " and key length:" + keySize;
int keySizeInBytes = (keySize / 8);
- if (rootKey.length != keySizeInBytes)
- {
- MessageFormat form =new MessageFormat(SQLServerException.getErrString("R_InvalidKeySize"));
- Object[] msgArgs={rootKey.length,keySizeInBytes,this.algorithmName};
+ if (rootKey.length != keySizeInBytes) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidKeySize"));
+ Object[] msgArgs = {rootKey.length, keySizeInBytes, this.algorithmName};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
-
+
}
// Derive encryption key
byte[] encKeyBuff = new byte[keySizeInBytes];
- try
- {
+ try {
// By default Java is big endian, we are getting bytes in little endian(LE in UTF-16LE)
// to make it compatible with C# driver which is little endian
- encKeyBuff = SQLServerSecurityUtility.getHMACWithSHA256(
- encryptionKeySaltFormat.getBytes(UTF_16LE),
- rootKey,
- encKeyBuff.length);
+ encKeyBuff = SQLServerSecurityUtility.getHMACWithSHA256(encryptionKeySaltFormat.getBytes(UTF_16LE), rootKey, encKeyBuff.length);
encryptionKey = new SQLServerSymmetricKey(encKeyBuff);
// Derive mac key from root key
byte[] macKeyBuff = new byte[keySizeInBytes];
- macKeyBuff = SQLServerSecurityUtility.getHMACWithSHA256(
- macKeySaltFormat.getBytes(UTF_16LE),
- rootKey,
- macKeyBuff.length);
+ macKeyBuff = SQLServerSecurityUtility.getHMACWithSHA256(macKeySaltFormat.getBytes(UTF_16LE), rootKey, macKeyBuff.length);
macKey = new SQLServerSymmetricKey(macKeyBuff);
// Derive the initialization vector from root key
byte[] ivKeyBuff = new byte[keySizeInBytes];
- ivKeyBuff = SQLServerSecurityUtility.getHMACWithSHA256(
- ivKeySaltFormat.getBytes(UTF_16LE),
- rootKey,
- ivKeyBuff.length);
+ ivKeyBuff = SQLServerSecurityUtility.getHMACWithSHA256(ivKeySaltFormat.getBytes(UTF_16LE), rootKey, ivKeyBuff.length);
ivKey = new SQLServerSymmetricKey(ivKeyBuff);
}
- catch (InvalidKeyException | NoSuchAlgorithmException e)
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_KeyExtractionFailed"));
- Object[] msgArgs = { e.getMessage() };
+ catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_KeyExtractionFailed"));
+ Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
-
}
/**
*
- * @return encryption key
+ * @return encryption key
*/
- byte[] getEncryptionKey()
- {
+ byte[] getEncryptionKey() {
return encryptionKey.getRootKey();
}
@@ -129,17 +101,15 @@ byte[] getEncryptionKey()
*
* @return mac key
*/
- byte[] getMacKey()
- {
+ byte[] getMacKey() {
return macKey.getRootKey();
}
/**
*
- * @return iv key
+ * @return iv key
*/
- byte[] getIVKey()
- {
+ byte[] getIVKey() {
return ivKey.getRootKey();
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java
index 5fc02ca5f..7ee7ab9f0 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java
@@ -1,81 +1,63 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerAeadAes256CbcHmac256Factory.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.text.MessageFormat;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.bind.DatatypeConverter;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
/**
* Factory for SQLServerAeadAes256CbcHmac256Algorithm
*/
- class SQLServerAeadAes256CbcHmac256Factory extends SQLServerEncryptionAlgorithmFactory{
- // In future we can have more
- private byte algorithmVersion=0x1;
- private ConcurrentHashMap encryptionAlgorithms=new ConcurrentHashMap();
-
- @Override
- SQLServerEncryptionAlgorithm create(
- SQLServerSymmetricKey columnEncryptionKey,
- SQLServerEncryptionType encryptionType, String encryptionAlgorithm) throws SQLServerException {
-
- assert(columnEncryptionKey!=null);
- if(encryptionType!=SQLServerEncryptionType.Deterministic && encryptionType!=SQLServerEncryptionType.Randomized){
- MessageFormat form =new MessageFormat(SQLServerException.getErrString("R_InvalidEncryptionType"));
- Object[] msgArgs={encryptionType,encryptionAlgorithm,"'"+SQLServerEncryptionType.Deterministic+","+SQLServerEncryptionType.Randomized+"'"};
+class SQLServerAeadAes256CbcHmac256Factory extends SQLServerEncryptionAlgorithmFactory {
+ // In future we can have more
+ private byte algorithmVersion = 0x1;
+ private ConcurrentHashMap encryptionAlgorithms = new ConcurrentHashMap();
+
+ @Override
+ SQLServerEncryptionAlgorithm create(SQLServerSymmetricKey columnEncryptionKey,
+ SQLServerEncryptionType encryptionType,
+ String encryptionAlgorithm) throws SQLServerException {
+
+ assert (columnEncryptionKey != null);
+ if (encryptionType != SQLServerEncryptionType.Deterministic && encryptionType != SQLServerEncryptionType.Randomized) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidEncryptionType"));
+ Object[] msgArgs = {encryptionType, encryptionAlgorithm,
+ "'" + SQLServerEncryptionType.Deterministic + "," + SQLServerEncryptionType.Randomized + "'"};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
-
- }
- String factoryKey="";
-
- StringBuffer factoryKeyBuilder=new StringBuffer();
- factoryKeyBuilder.append(
- DatatypeConverter.printBase64Binary(
- new String(
- columnEncryptionKey.getRootKey(),
- UTF_8
- ).getBytes()
- )
- );
- factoryKeyBuilder.append(":");
- factoryKeyBuilder.append(encryptionType);
- factoryKeyBuilder.append(":");
- factoryKeyBuilder.append(algorithmVersion);
-
- factoryKey =factoryKeyBuilder.toString();
-
- SQLServerAeadAes256CbcHmac256Algorithm aesAlgorithm;
-
- if(!encryptionAlgorithms.containsKey(factoryKey)){
- SQLServerAeadAes256CbcHmac256EncryptionKey encryptedKey = new SQLServerAeadAes256CbcHmac256EncryptionKey(columnEncryptionKey.getRootKey(), SQLServerAeadAes256CbcHmac256Algorithm.algorithmName);
- aesAlgorithm = new SQLServerAeadAes256CbcHmac256Algorithm(encryptedKey, encryptionType, algorithmVersion);
- encryptionAlgorithms.putIfAbsent(factoryKey, aesAlgorithm);
- }
-
-
-
- return encryptionAlgorithms.get(factoryKey);
- }
+ }
+ String factoryKey = "";
+
+ StringBuffer factoryKeyBuilder = new StringBuffer();
+ factoryKeyBuilder.append(DatatypeConverter.printBase64Binary(new String(columnEncryptionKey.getRootKey(), UTF_8).getBytes()));
+
+ factoryKeyBuilder.append(":");
+ factoryKeyBuilder.append(encryptionType);
+ factoryKeyBuilder.append(":");
+ factoryKeyBuilder.append(algorithmVersion);
+
+ factoryKey = factoryKeyBuilder.toString();
+
+ SQLServerAeadAes256CbcHmac256Algorithm aesAlgorithm;
+
+ if (!encryptionAlgorithms.containsKey(factoryKey)) {
+ SQLServerAeadAes256CbcHmac256EncryptionKey encryptedKey = new SQLServerAeadAes256CbcHmac256EncryptionKey(columnEncryptionKey.getRootKey(),
+ SQLServerAeadAes256CbcHmac256Algorithm.algorithmName);
+ aesAlgorithm = new SQLServerAeadAes256CbcHmac256Algorithm(encryptedKey, encryptionType, algorithmVersion);
+ encryptionAlgorithms.putIfAbsent(factoryKey, aesAlgorithm);
+ }
+
+ return encryptionAlgorithms.get(factoryKey);
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java
index 9d2a29c2a..9b4bcd882 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java
@@ -1,23 +1,13 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerBlob.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
package com.microsoft.sqlserver.jdbc;
+
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
@@ -32,14 +22,13 @@
import java.util.logging.Logger;
/**
-* SQLServerBlob represents a binary LOB object and implements a java.sql.Blob.
-*/
+ * SQLServerBlob represents a binary LOB object and implements a java.sql.Blob.
+ */
-public final class SQLServerBlob implements java.sql.Blob, java.io.Serializable
-{
- private static final long serialVersionUID = -3526170228097889085L;
+public final class SQLServerBlob implements java.sql.Blob, java.io.Serializable {
+ private static final long serialVersionUID = -3526170228097889085L;
- // The value of the BLOB that this Blob object represents.
+ // The value of the BLOB that this Blob object represents.
// This value is never null unless/until the free() method is called.
private byte[] value;
@@ -49,37 +38,39 @@ public final class SQLServerBlob implements java.sql.Blob, java.io.Serializable
// Active streams which must be closed when the Blob is closed
//
// Initial size of the array is based on an assumption that a Blob object is
- // typically used either for input or output, and then only once. The array size
+ // typically used either for input or output, and then only once. The array size
// grows automatically if multiple streams are used.
ArrayList activeStreams = new ArrayList(1);
static private final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerBlob");
- static private final AtomicInteger baseID = new AtomicInteger(0); // Unique id generator for each instance (used for logging).
+ static private final AtomicInteger baseID = new AtomicInteger(0); // Unique id generator for each instance (used for logging).
final private String traceID;
- final public String toString()
- {
+
+ final public String toString() {
return traceID;
}
// Returns unique id for each instance.
- private static int nextInstanceID()
- {
+ private static int nextInstanceID() {
return baseID.incrementAndGet();
}
/**
* Create a new BLOB
*
- * @param connection the database connection this blob is implemented on
- * @param data the BLOB's data
+ * @param connection
+ * the database connection this blob is implemented on
+ * @param data
+ * the BLOB's data
*/
- @Deprecated public SQLServerBlob(SQLServerConnection connection, byte data[])
- {
- traceID = " SQLServerBlob:" + nextInstanceID();
+ @Deprecated
+ public SQLServerBlob(SQLServerConnection connection,
+ byte data[]) {
+ traceID = " SQLServerBlob:" + nextInstanceID();
con = connection;
- // Disallow Blobs with internal null values. We throw a NullPointerException here
+ // Disallow Blobs with internal null values. We throw a NullPointerException here
// because the method signature of the public constructor does not permit a SQLException
// to be thrown.
if (null == data)
@@ -87,54 +78,44 @@ private static int nextInstanceID()
value = data;
- if(logger.isLoggable(Level.FINE))
- {
- String loggingInfo = (null !=connection)? connection.toString(): "null connection";
- logger.fine(toString() + " created by (" + loggingInfo+ ")");
+ if (logger.isLoggable(Level.FINE)) {
+ String loggingInfo = (null != connection) ? connection.toString() : "null connection";
+ logger.fine(toString() + " created by (" + loggingInfo + ")");
}
}
- SQLServerBlob(SQLServerConnection connection)
- {
- traceID = " SQLServerBlob:" + nextInstanceID();
+ SQLServerBlob(SQLServerConnection connection) {
+ traceID = " SQLServerBlob:" + nextInstanceID();
con = connection;
value = new byte[0];
- if(logger.isLoggable(Level.FINE))
+ if (logger.isLoggable(Level.FINE))
logger.fine(toString() + " created by (" + connection.toString() + ")");
}
- SQLServerBlob(BaseInputStream stream) throws SQLServerException
- {
- traceID = " SQLServerBlob:" + nextInstanceID();
+ SQLServerBlob(BaseInputStream stream) throws SQLServerException {
+ traceID = " SQLServerBlob:" + nextInstanceID();
value = stream.getBytes();
- if(logger.isLoggable(Level.FINE))
+ if (logger.isLoggable(Level.FINE))
logger.fine(toString() + " created by (null connection)");
}
/**
* Frees this Blob object and releases the resources that it holds.
*
- * After free() has been called, any attempt to invoke a method other than free() will
- * result in a SQLException being thrown. If free() is called multiple times, the subsequent
- * calls to free are treated as a no-op.
+ * After free() has been called, any attempt to invoke a method other than free() will result in a SQLException being thrown. If free() is called
+ * multiple times, the subsequent calls to free are treated as a no-op.
*/
- public void free() throws SQLException
- {
+ public void free() throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if (!isClosed)
- {
+ if (!isClosed) {
// Close active streams, ignoring any errors, since nothing can be done with them after that point anyway.
- if (null != activeStreams)
- {
- for (Closeable stream : activeStreams)
- {
- try
- {
+ if (null != activeStreams) {
+ for (Closeable stream : activeStreams) {
+ try {
stream.close();
}
- catch (IOException ioException)
- {
+ catch (IOException ioException) {
logger.fine(toString() + " ignored IOException closing stream " + stream + ": " + ioException.getMessage());
}
}
@@ -151,32 +132,29 @@ public void free() throws SQLException
/**
* Throws a SQLException if the LOB has been freed.
*/
- private void checkClosed() throws SQLServerException
- {
- if (isClosed)
- {
+ private void checkClosed() throws SQLServerException {
+ if (isClosed) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_isFreed"));
SQLServerException.makeFromDriverError(con, null, form.format(new Object[] {"Blob"}), null, true);
}
}
- public InputStream getBinaryStream() throws SQLException
- {
+ public InputStream getBinaryStream() throws SQLException {
checkClosed();
return getBinaryStreamInternal(0, value.length);
}
- public InputStream getBinaryStream(long pos, long length) throws SQLException
- {
+ public InputStream getBinaryStream(long pos,
+ long length) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
// Not implemented - partial materialization
throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
}
- private InputStream getBinaryStreamInternal(int pos, int length)
- {
+ private InputStream getBinaryStreamInternal(int pos,
+ int length) {
assert null != value;
assert pos >= 0;
assert 0 <= length && length <= value.length - pos;
@@ -188,36 +166,37 @@ private InputStream getBinaryStreamInternal(int pos, int length)
}
/**
- * Retrieves all or part of the BLOB value that this Blob object represents, as an array of bytes.
- * This byte array contains up to length consecutive bytes starting at position pos.
+ * Retrieves all or part of the BLOB value that this Blob object represents, as an array of bytes. This byte array contains up to length
+ * consecutive bytes starting at position pos.
*
- * @param pos - the ordinal position of the first byte in the BLOB value to be extracted; the first byte is at position 1
- * @param length - the number of consecutive bytes to be copied; the value for length must be 0 or greater
- * @return a byte array containing up to length consecutive bytes from the BLOB value designated by this Blob object,
- * starting with the byte at position pos
- * @throws SQLException - if there is an error accessing the BLOB value; if pos is less than 1 or length is less than 0
+ * @param pos
+ * - the ordinal position of the first byte in the BLOB value to be extracted; the first byte is at position 1
+ * @param length
+ * - the number of consecutive bytes to be copied; the value for length must be 0 or greater
+ * @return a byte array containing up to length consecutive bytes from the BLOB value designated by this Blob object, starting with the byte at
+ * position pos
+ * @throws SQLException
+ * - if there is an error accessing the BLOB value; if pos is less than 1 or length is less than 0
*/
- public byte[] getBytes(long pos, int length) throws SQLException
- {
+ public byte[] getBytes(long pos,
+ int length) throws SQLException {
checkClosed();
- if (pos < 1)
- {
+ if (pos < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(pos)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
}
- if (length < 0)
- {
+ if (length < 0) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
- Object[] msgArgs = { new Integer(length) };
+ Object[] msgArgs = {new Integer(length)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
}
// Adjust pos to zero based.
pos--;
-
+
// Bound the starting position if necessary
if (pos > value.length)
pos = value.length;
@@ -227,37 +206,39 @@ public byte[] getBytes(long pos, int length) throws SQLException
length = (int) (value.length - pos);
byte bTemp[] = new byte[length];
- System.arraycopy(value, (int)pos, bTemp, 0, length);
+ System.arraycopy(value, (int) pos, bTemp, 0, length);
return bTemp;
}
/**
* Return the length of the BLOB
- * @throws SQLException when an error occurs
+ *
+ * @throws SQLException
+ * when an error occurs
* @return the data length
*/
- public long length() throws SQLException
- {
+ public long length() throws SQLException {
checkClosed();
return value.length;
}
/**
- * Retrieves the byte position in the BLOB value designated by this Blob object
- * at which pattern begins. The search begins at position start.
+ * Retrieves the byte position in the BLOB value designated by this Blob object at which pattern begins. The search begins at position start.
*
- * @param pattern - the Blob object designating the BLOB value for which to search
- * @param start - the position in the BLOB value at which to begin searching; the first position is 1
+ * @param pattern
+ * - the Blob object designating the BLOB value for which to search
+ * @param start
+ * - the position in the BLOB value at which to begin searching; the first position is 1
* @return the postion at which the pattern begins, else -1
- * @throws SQLException - if there is an error accessing the BLOB value or if start is less than 1
+ * @throws SQLException
+ * - if there is an error accessing the BLOB value or if start is less than 1
*/
- public long position(Blob pattern, long start) throws SQLException
- {
+ public long position(Blob pattern,
+ long start) throws SQLException {
checkClosed();
- if (start < 1)
- {
+ if (start < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(start)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -270,20 +251,22 @@ public long position(Blob pattern, long start) throws SQLException
}
/**
- * Retrieves the byte position at which the specified byte array pattern begins within the
- * BLOB value that this Blob object represents. The search for pattern begins at position start.
+ * Retrieves the byte position at which the specified byte array pattern begins within the BLOB value that this Blob object represents. The search
+ * for pattern begins at position start.
*
- * @param bPattern - the byte array for which to search
- * @param start - the position at which to begin searching; the first position is 1
+ * @param bPattern
+ * - the byte array for which to search
+ * @param start
+ * - the position at which to begin searching; the first position is 1
* @return the position at which the pattern appears, else -1
- * @throws SQLException - if there is an error accessing the BLOB or if start is less than 1
+ * @throws SQLException
+ * - if there is an error accessing the BLOB or if start is less than 1
*/
- public long position(byte[] bPattern, long start) throws SQLException
- {
+ public long position(byte[] bPattern,
+ long start) throws SQLException {
checkClosed();
- if (start < 1)
- {
+ if (start < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(start)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -298,13 +281,10 @@ public long position(byte[] bPattern, long start) throws SQLException
start--;
// Search for pattern in value.
- for (int pos = (int) start; pos <= value.length - bPattern.length; ++pos)
- {
+ for (int pos = (int) start; pos <= value.length - bPattern.length; ++pos) {
boolean match = true;
- for (int i = 0; i < bPattern.length; ++i)
- {
- if (value[pos + i] != bPattern[i])
- {
+ for (int i = 0; i < bPattern.length; ++i) {
+ if (value[pos + i] != bPattern[i]) {
match = false;
break;
}
@@ -321,57 +301,61 @@ public long position(byte[] bPattern, long start) throws SQLException
/**
* Truncate a BLOB
- * @param len the new length for the BLOB
- * @throws SQLException when an error occurs
+ *
+ * @param len
+ * the new length for the BLOB
+ * @throws SQLException
+ * when an error occurs
*/
- public void truncate(long len) throws SQLException
- {
+ public void truncate(long len) throws SQLException {
checkClosed();
- if (len<0)
- {
+ if (len < 0) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
Object[] msgArgs = {new Long(len)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
}
- if (value.length > len)
- {
- byte bNew[] = new byte[(int)len];
- System.arraycopy(value, 0, bNew, 0, (int)len);
+ if (value.length > len) {
+ byte bNew[] = new byte[(int) len];
+ System.arraycopy(value, 0, bNew, 0, (int) len);
value = bNew;
}
}
/**
* Retrieves a stream that can be used to write to the BLOB value that this Blob object represents
- * @param pos - the position in the BLOB value at which to start writing; the first position is 1
+ *
+ * @param pos
+ * - the position in the BLOB value at which to start writing; the first position is 1
* @return a java.io.OutputStream object to which data can be written
- * @throws SQLException - if there is an error accessing the BLOB value or if pos is less than 1
+ * @throws SQLException
+ * - if there is an error accessing the BLOB value or if pos is less than 1
*/
- public java.io.OutputStream setBinaryStream(long pos) throws SQLException
- {
+ public java.io.OutputStream setBinaryStream(long pos) throws SQLException {
checkClosed();
- if (pos < 1)
- {
+ if (pos < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
SQLServerException.makeFromDriverError(con, null, form.format(new Object[] {pos}), null, true);
}
- return new SQLServerBlobOutputStream(this,pos);
+ return new SQLServerBlobOutputStream(this, pos);
}
/**
- * Writes the given array of bytes into the Blob starting at position pos,
- * and returns the number of bytes written.
- * @param pos the position (1 based) in the Blob object at which to start writing the data.
- * @param bytes the array of bytes to be written into the Blob.
- * @throws SQLException if there is an error accessing the BLOB value.
+ * Writes the given array of bytes into the Blob starting at position pos, and returns the number of bytes written.
+ *
+ * @param pos
+ * the position (1 based) in the Blob object at which to start writing the data.
+ * @param bytes
+ * the array of bytes to be written into the Blob.
+ * @throws SQLException
+ * if there is an error accessing the BLOB value.
* @return the number of bytes written.
*/
- public int setBytes(long pos, byte[] bytes) throws SQLException
- {
+ public int setBytes(long pos,
+ byte[] bytes) throws SQLException {
checkClosed();
if (null == bytes)
@@ -381,42 +365,43 @@ public int setBytes(long pos, byte[] bytes) throws SQLException
}
/**
- * Writes all or part of the given byte array to the BLOB value that this Blob object represents
- * and returns the number of bytes written. Writing starts at position pos in the BLOB value;
- * len bytes from the given byte wrray are written. The array of bytes will overwrite the existing
- * bytes in the Blob object starting at the position pos. If the end of the Blob value is reached
- * while writing the array bytes, then the length of the Blob value will be increased to accomodate
- * the extra bytes.
+ * Writes all or part of the given byte array to the BLOB value that this Blob object represents and returns the number of bytes written. Writing
+ * starts at position pos in the BLOB value; len bytes from the given byte wrray are written. The array of bytes will overwrite the existing bytes
+ * in the Blob object starting at the position pos. If the end of the Blob value is reached while writing the array bytes, then the length of the
+ * Blob value will be increased to accomodate the extra bytes.
*
- * SQL Server behavior:
- * If the value specified for pos is greater than the length+1 of the BLOB value then a SQLException
- * is thrown.
+ * SQL Server behavior: If the value specified for pos is greater than the length+1 of the BLOB value then a SQLException is thrown.
*
- * @param pos - the position in the BLOB object at which to start writing; the first position is 1
- * @param bytes - the array of bytes to be written to this BLOB object.
- * @param offset - the offset (0-based) into the array bytes at which to start reading the bytes to set
- * @param len - the number of bytes to be written to the BLOB value from the array of bytes bytes
+ * @param pos
+ * - the position in the BLOB object at which to start writing; the first position is 1
+ * @param bytes
+ * - the array of bytes to be written to this BLOB object.
+ * @param offset
+ * - the offset (0-based) into the array bytes at which to start reading the bytes to set
+ * @param len
+ * - the number of bytes to be written to the BLOB value from the array of bytes bytes
* @return the number of bytes written.
- * @throws SQLException - if there is an error accessing the BLOB value or if pos is less than 1
+ * @throws SQLException
+ * - if there is an error accessing the BLOB value or if pos is less than 1
*/
- public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException
- {
+ public int setBytes(long pos,
+ byte[] bytes,
+ int offset,
+ int len) throws SQLException {
checkClosed();
if (null == bytes)
SQLServerException.makeFromDriverError(con, null, SQLServerException.getErrString("R_cantSetNull"), null, true);
// Offset must be within incoming bytes boundary.
- if (offset < 0 || offset > bytes.length)
- {
+ if (offset < 0 || offset > bytes.length) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidOffset"));
Object[] msgArgs = {new Integer(offset)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
}
// len must be within incoming bytes boundary.
- if (len < 0 || len > bytes.length - offset)
- {
+ if (len < 0 || len > bytes.length - offset) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
Object[] msgArgs = {new Integer(len)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -425,8 +410,7 @@ public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLExcep
// Note position for Blob.setBytes is 1 based not zero based.
// Position must be in range of existing Blob data or exactly 1 byte
// past the end of data to request "append" mode.
- if (pos <= 0 || pos > value.length + 1)
- {
+ if (pos <= 0 || pos > value.length + 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(pos)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -436,75 +420,70 @@ public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLExcep
pos--;
// Overwrite past end of value case.
- if (len >= value.length - pos)
- {
+ if (len >= value.length - pos) {
// Make sure the new value length wouldn't exceed the maximum allowed
DataTypes.getCheckedLength(con, JDBCType.BLOB, pos + len, false);
assert pos + len <= Integer.MAX_VALUE;
// Start with the original value, up to the starting position
- byte combinedValue[] = new byte[(int)pos+len];
- System.arraycopy(value, 0, combinedValue, 0, (int)pos);
+ byte combinedValue[] = new byte[(int) pos + len];
+ System.arraycopy(value, 0, combinedValue, 0, (int) pos);
// Copy rest of data.
- System.arraycopy(bytes, offset, combinedValue, (int)pos, len);
+ System.arraycopy(bytes, offset, combinedValue, (int) pos, len);
value = combinedValue;
}
- else
- {
+ else {
// Overwrite internal to value case.
- System.arraycopy(bytes, offset, value, (int)pos, len);
+ System.arraycopy(bytes, offset, value, (int) pos, len);
}
return len;
}
}
-
/**
- * SQLServerBlobOutputStream is a simple java.io.OutputStream interface implementing class that
- * forwards all calls to SQLServerBlob.setBytes. This class is returned to caller by
- * SQLServerBlob class when setBinaryStream is called.
+ * SQLServerBlobOutputStream is a simple java.io.OutputStream interface implementing class that forwards all calls to SQLServerBlob.setBytes. This
+ * class is returned to caller by SQLServerBlob class when setBinaryStream is called.
*
- * SQLServerBlobOutputStream starts writing at postion startPos and continues to write
- * in a forward only manner. Reset/mark are not supported.
+ * SQLServerBlobOutputStream starts writing at postion startPos and continues to write in a forward only manner. Reset/mark are not supported.
*/
-final class SQLServerBlobOutputStream extends java.io.OutputStream
-{
- private SQLServerBlob parentBlob = null;
- private long currentPos;
- SQLServerBlobOutputStream(SQLServerBlob parentBlob, long startPos)
- {
- this.parentBlob = parentBlob;
- this.currentPos = startPos;
- }
-
- // java.io.OutputStream interface methods.
-
- public void write(byte[] b) throws IOException
- {
- if (null == b) return;
- write(b,0,b.length);
- }
- public void write(byte[] b, int off, int len) throws IOException
- {
- try
- {
- // Call parent's setBytes and update position.
- // setBytes can throw a SQLServerException, we translate
- // this to an IOException here.
- int bytesWritten = parentBlob.setBytes(currentPos, b, off, len);
- currentPos += bytesWritten;
- }
- catch (SQLException ex)
- {
- throw new IOException(ex.getMessage());
- }
- }
- public void write(int b) throws java.io.IOException
- {
- byte [] bTemp = new byte[1];
- bTemp[0] = (byte)(b&0xFF);
- write(bTemp,0,bTemp.length);
- }
+final class SQLServerBlobOutputStream extends java.io.OutputStream {
+ private SQLServerBlob parentBlob = null;
+ private long currentPos;
+
+ SQLServerBlobOutputStream(SQLServerBlob parentBlob,
+ long startPos) {
+ this.parentBlob = parentBlob;
+ this.currentPos = startPos;
+ }
+
+ // java.io.OutputStream interface methods.
+
+ public void write(byte[] b) throws IOException {
+ if (null == b)
+ return;
+ write(b, 0, b.length);
+ }
+
+ public void write(byte[] b,
+ int off,
+ int len) throws IOException {
+ try {
+ // Call parent's setBytes and update position.
+ // setBytes can throw a SQLServerException, we translate
+ // this to an IOException here.
+ int bytesWritten = parentBlob.setBytes(currentPos, b, off, len);
+ currentPos += bytesWritten;
+ }
+ catch (SQLException ex) {
+ throw new IOException(ex.getMessage());
+ }
+ }
+
+ public void write(int b) throws java.io.IOException {
+ byte[] bTemp = new byte[1];
+ bTemp[0] = (byte) (b & 0xFF);
+ write(bTemp, 0, bTemp.length);
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java
index d7f97ba80..56190a1d7 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerBulkCSVFileRecord.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.io.BufferedReader;
@@ -38,346 +27,399 @@
import java.util.Set;
/**
- * A simple implementation of the ISQLServerBulkRecord interface that can be used to read in the
- * basic Java data types from a delimited file where each line represents a row of data.
+ * A simple implementation of the ISQLServerBulkRecord interface that can be used to read in the basic Java data types from a delimited file where
+ * each line represents a row of data.
*/
-public class SQLServerBulkCSVFileRecord implements ISQLServerBulkRecord , java.lang.AutoCloseable
-{
+public class SQLServerBulkCSVFileRecord implements ISQLServerBulkRecord, java.lang.AutoCloseable {
/*
* Class to represent the column metadata
*/
- private class ColumnMetadata{
+ private class ColumnMetadata {
String columnName;
int columnType;
int precision;
int scale;
DateTimeFormatter dateTimeFormatter = null;
-
- ColumnMetadata(String name, int type, int precision, int scale, DateTimeFormatter dateTimeFormatter)
- {
- columnName = name;
+
+ ColumnMetadata(String name,
+ int type,
+ int precision,
+ int scale,
+ DateTimeFormatter dateTimeFormatter) {
+ columnName = name;
columnType = type;
this.precision = precision;
this.scale = scale;
this.dateTimeFormatter = dateTimeFormatter;
}
}
-
+
/*
* Resources associated with reading in the file
*/
private BufferedReader fileReader;
private InputStreamReader sr;
private FileInputStream fis;
-
+
/*
- * Metadata to represent the columns in the file. Each column should be mapped to its corresponding
- * position within the file (from position 1 and onwards)
+ * Metadata to represent the columns in the file. Each column should be mapped to its corresponding position within the file (from position 1 and
+ * onwards)
*/
- private Map columnMetadata;
-
+ private Map columnMetadata;
+
/*
* Current line of data to parse.
*/
private String currentLine = null;
-
+
/*
* Delimiter to parse lines with.
*/
private final String delimiter;
-
+
/*
* Contains all the column names if firstLineIsColumnNames is true
*/
private String[] columnNames = null;
-
+
/*
* Contains the format that java.sql.Types.TIMESTAMP_WITH_TIMEZONE data should be read in as.
*/
private DateTimeFormatter dateTimeFormatter = null;
-
+
/*
* Contains the format that java.sql.Types.TIME_WITH_TIMEZONE data should be read in as.
*/
private DateTimeFormatter timeFormatter = null;
-
+
/*
* Class name for logging.
*/
private static final String loggerClassName = "com.microsoft.sqlserver.jdbc.SQLServerBulkCSVFileRecord";
-
+
/*
* Logger
*/
- private static final java.util.logging.Logger loggerExternal =
- java.util.logging.Logger.getLogger(loggerClassName);
-
+ private static final java.util.logging.Logger loggerExternal = java.util.logging.Logger.getLogger(loggerClassName);
+
/**
* Creates a simple reader to parse data from a delimited file with the given encoding.
*
- * @param fileToParse File to parse data from
- * @param encoding Charset encoding to use for reading the file, or NULL for the default encoding.
- * @param delimiter Delimiter to used to separate each column
- * @param firstLineIsColumnNames True if the first line of the file should be parsed as column names; false otherwise
- * @throws SQLServerException If the arguments are invalid, there are any errors in reading the file, or the file is empty
+ * @param fileToParse
+ * File to parse data from
+ * @param encoding
+ * Charset encoding to use for reading the file, or NULL for the default encoding.
+ * @param delimiter
+ * Delimiter to used to separate each column
+ * @param firstLineIsColumnNames
+ * True if the first line of the file should be parsed as column names; false otherwise
+ * @throws SQLServerException
+ * If the arguments are invalid, there are any errors in reading the file, or the file is empty
*/
- public SQLServerBulkCSVFileRecord(String fileToParse, String encoding, String delimiter, boolean firstLineIsColumnNames) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "SQLServerBulkCSVFileRecord",
- new Object[] { fileToParse, encoding, delimiter,
- firstLineIsColumnNames });
-
- if (null == fileToParse) {
+ public SQLServerBulkCSVFileRecord(String fileToParse,
+ String encoding,
+ String delimiter,
+ boolean firstLineIsColumnNames) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "SQLServerBulkCSVFileRecord",
+ new Object[] {fileToParse, encoding, delimiter, firstLineIsColumnNames});
+
+ if (null == fileToParse) {
throwInvalidArgument("fileToParse");
- } else if (null == delimiter) {
+ }
+ else if (null == delimiter) {
throwInvalidArgument("delimiter");
}
-
+
this.delimiter = delimiter;
- try
- {
- //Create the file reader
+ try {
+ // Create the file reader
fis = new FileInputStream(fileToParse);
- if (null == encoding || 0 == encoding.length())
- {
+ if (null == encoding || 0 == encoding.length()) {
sr = new InputStreamReader(fis);
}
- else
- {
+ else {
sr = new InputStreamReader(fis, encoding);
}
-
+
fileReader = new BufferedReader(sr);
-
- if (firstLineIsColumnNames)
- {
+
+ if (firstLineIsColumnNames) {
currentLine = fileReader.readLine();
- if(null != currentLine)
- {
- columnNames = currentLine.split(delimiter, -1);
+ if (null != currentLine) {
+ columnNames = currentLine.split(delimiter, -1);
}
}
}
- catch(UnsupportedEncodingException unsupportedEncoding)
- {
+ catch (UnsupportedEncodingException unsupportedEncoding) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedEncoding"));
throw new SQLServerException(form.format(new Object[] {encoding}), null, 0, null);
- }
- catch (Exception e)
- {
- throw new SQLServerException(null , e.getMessage() , null , 0 , false);
}
- columnMetadata = new HashMap();
-
+ catch (Exception e) {
+ throw new SQLServerException(null, e.getMessage(), null, 0, false);
+ }
+ columnMetadata = new HashMap();
+
loggerExternal.exiting(loggerClassName, "SQLServerBulkCSVFileRecord");
}
-
+
/**
* Creates a simple reader to parse data from a CSV file with the given encoding.
*
- * @param fileToParse File to parse data from
- * @param encoding Charset encoding to use for reading the file.
- * @param firstLineIsColumnNames True if the first line of the file should be parsed as column names; false otherwise
- * @throws SQLServerException If the arguments are invalid, there are any errors in reading the file, or the file is empty
+ * @param fileToParse
+ * File to parse data from
+ * @param encoding
+ * Charset encoding to use for reading the file.
+ * @param firstLineIsColumnNames
+ * True if the first line of the file should be parsed as column names; false otherwise
+ * @throws SQLServerException
+ * If the arguments are invalid, there are any errors in reading the file, or the file is empty
*/
- public SQLServerBulkCSVFileRecord(String fileToParse, String encoding, boolean firstLineIsColumnNames) throws SQLServerException
- {
- this(fileToParse,encoding,",", firstLineIsColumnNames);
+ public SQLServerBulkCSVFileRecord(String fileToParse,
+ String encoding,
+ boolean firstLineIsColumnNames) throws SQLServerException {
+ this(fileToParse, encoding, ",", firstLineIsColumnNames);
}
-
+
/**
* Creates a simple reader to parse data from a CSV file with the default encoding.
*
- * @param fileToParse File to parse data from
- * @param firstLineIsColumnNames True if the first line of the file should be parsed as column names; false otherwise
- * @throws SQLServerException If the arguments are invalid, there are any errors in reading the file, or the file is empty
+ * @param fileToParse
+ * File to parse data from
+ * @param firstLineIsColumnNames
+ * True if the first line of the file should be parsed as column names; false otherwise
+ * @throws SQLServerException
+ * If the arguments are invalid, there are any errors in reading the file, or the file is empty
*/
- public SQLServerBulkCSVFileRecord(String fileToParse, boolean firstLineIsColumnNames) throws SQLServerException
- {
- this(fileToParse,null,",", firstLineIsColumnNames);
+ public SQLServerBulkCSVFileRecord(String fileToParse,
+ boolean firstLineIsColumnNames) throws SQLServerException {
+ this(fileToParse, null, ",", firstLineIsColumnNames);
}
- /**
+ /**
* Adds metadata for the given column in the file.
*
- * @param positionInFile Indicates which column the metadata is for. Columns start at 1.
- * @param name Name for the column (optional if only using column ordinal in a mapping for SQLServerBulkCopy operation)
- * @param jdbcType JDBC data type of the column
- * @param precision Precision for the column (ignored for the appropriate data types)
- * @param scale Scale for the column (ignored for the appropriate data types)
- * @param dateTimeFormatter format to parse data that is sent
- * @throws SQLServerException when an error occurs
+ * @param positionInFile
+ * Indicates which column the metadata is for. Columns start at 1.
+ * @param name
+ * Name for the column (optional if only using column ordinal in a mapping for SQLServerBulkCopy operation)
+ * @param jdbcType
+ * JDBC data type of the column
+ * @param precision
+ * Precision for the column (ignored for the appropriate data types)
+ * @param scale
+ * Scale for the column (ignored for the appropriate data types)
+ * @param dateTimeFormatter
+ * format to parse data that is sent
+ * @throws SQLServerException
+ * when an error occurs
*/
- public void addColumnMetadata(int positionInFile, String name, int jdbcType, int precision, int scale, DateTimeFormatter dateTimeFormatter) throws SQLServerException
- {
- addColumnMetadataInternal(positionInFile, name, jdbcType, precision, scale, dateTimeFormatter);
+ public void addColumnMetadata(int positionInFile,
+ String name,
+ int jdbcType,
+ int precision,
+ int scale,
+ DateTimeFormatter dateTimeFormatter) throws SQLServerException {
+ addColumnMetadataInternal(positionInFile, name, jdbcType, precision, scale, dateTimeFormatter);
}
-
- /**
+
+ /**
* Adds metadata for the given column in the file.
*
- * @param positionInFile Indicates which column the metadata is for. Columns start at 1.
- * @param name Name for the column (optional if only using column ordinal in a mapping for SQLServerBulkCopy operation)
- * @param jdbcType JDBC data type of the column
- * @param precision Precision for the column (ignored for the appropriate data types)
- * @param scale Scale for the column (ignored for the appropriate data types)
- * @throws SQLServerException when an error occurs
+ * @param positionInFile
+ * Indicates which column the metadata is for. Columns start at 1.
+ * @param name
+ * Name for the column (optional if only using column ordinal in a mapping for SQLServerBulkCopy operation)
+ * @param jdbcType
+ * JDBC data type of the column
+ * @param precision
+ * Precision for the column (ignored for the appropriate data types)
+ * @param scale
+ * Scale for the column (ignored for the appropriate data types)
+ * @throws SQLServerException
+ * when an error occurs
*/
- public void addColumnMetadata(int positionInFile, String name, int jdbcType, int precision, int scale) throws SQLServerException
- {
- addColumnMetadataInternal(positionInFile, name, jdbcType, precision, scale, null);
+ public void addColumnMetadata(int positionInFile,
+ String name,
+ int jdbcType,
+ int precision,
+ int scale) throws SQLServerException {
+ addColumnMetadataInternal(positionInFile, name, jdbcType, precision, scale, null);
}
-
+
/**
* Adds metadata for the given column in the file.
*
- * @param positionInFile Indicates which column the metadata is for. Columns start at 1.
- * @param name Name for the column (optional if only using column ordinal in a mapping for SQLServerBulkCopy operation)
- * @param jdbcType JDBC data type of the column
- * @param precision Precision for the column (ignored for the appropriate data types)
- * @param scale Scale for the column (ignored for the appropriate data types)
- * @param dateTimeFormatter format to parse data that is sent
- * @throws SQLServerException when an error occurs
+ * @param positionInFile
+ * Indicates which column the metadata is for. Columns start at 1.
+ * @param name
+ * Name for the column (optional if only using column ordinal in a mapping for SQLServerBulkCopy operation)
+ * @param jdbcType
+ * JDBC data type of the column
+ * @param precision
+ * Precision for the column (ignored for the appropriate data types)
+ * @param scale
+ * Scale for the column (ignored for the appropriate data types)
+ * @param dateTimeFormatter
+ * format to parse data that is sent
+ * @throws SQLServerException
+ * when an error occurs
*/
- void addColumnMetadataInternal(int positionInFile, String name, int jdbcType, int precision, int scale, DateTimeFormatter dateTimeFormatter) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "addColumnMetadata", new Object[] {
- positionInFile, name, jdbcType, precision, scale });
-
- String colName = "";
-
- if(0 >= positionInFile)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumnOrdinal"));
- Object[] msgArgs = {positionInFile};
- throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
+ void addColumnMetadataInternal(int positionInFile,
+ String name,
+ int jdbcType,
+ int precision,
+ int scale,
+ DateTimeFormatter dateTimeFormatter) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "addColumnMetadata", new Object[] {positionInFile, name, jdbcType, precision, scale});
+
+ String colName = "";
+
+ if (0 >= positionInFile) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumnOrdinal"));
+ Object[] msgArgs = {positionInFile};
+ throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
}
-
+
if (null != name)
colName = name.trim();
- else if((columnNames!=null) && (columnNames.length >= positionInFile))
- colName = columnNames[positionInFile-1];
-
- if((columnNames!=null) && (positionInFile >columnNames.length))
- {
+ else if ((columnNames != null) && (columnNames.length >= positionInFile))
+ colName = columnNames[positionInFile - 1];
+
+ if ((columnNames != null) && (positionInFile > columnNames.length)) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
Object[] msgArgs = {positionInFile};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
- }
-
- checkDuplicateColumnName(positionInFile,name);
- switch (jdbcType)
- {
- /*
- * SQL Server supports numerous string literal formats for temporal types, hence sending them as varchar
- * with approximate precision(length) needed to send supported string literals.
- * string literal formats supported by temporal types are available in MSDN page on data types.
- */
- case java.sql.Types.DATE:
- case java.sql.Types.TIME:
- case java.sql.Types.TIMESTAMP:
- case microsoft.sql.Types.DATETIMEOFFSET:
- // The precision is just a number long enough to hold all types of temporal data, doesn't need to be exact precision.
- columnMetadata.put(positionInFile, new ColumnMetadata(colName, jdbcType, 50, scale, dateTimeFormatter));
- break;
-
- // Redirect SQLXML as LONGNVARCHAR
- // SQLXML is not valid type in TDS
- case java.sql.Types.SQLXML:
- columnMetadata.put(positionInFile, new ColumnMetadata(colName, java.sql.Types.LONGNVARCHAR, precision, scale, dateTimeFormatter));
- break;
-
- // Redirecting Float as Double based on data type mapping
- // https://msdn.microsoft.com/en-us/library/ms378878%28v=sql.110%29.aspx
- case java.sql.Types.FLOAT:
- columnMetadata.put(positionInFile, new ColumnMetadata(colName, java.sql.Types.DOUBLE, precision, scale, dateTimeFormatter));
- break;
-
- // redirecting BOOLEAN as BIT
- case java.sql.Types.BOOLEAN:
- columnMetadata.put(positionInFile, new ColumnMetadata(colName, java.sql.Types.BIT, precision, scale, dateTimeFormatter));
- break;
-
- default:
- columnMetadata.put(positionInFile, new ColumnMetadata(colName, jdbcType, precision, scale, dateTimeFormatter));
}
-
+
+ checkDuplicateColumnName(positionInFile, name);
+ switch (jdbcType) {
+ /*
+ * SQL Server supports numerous string literal formats for temporal types, hence sending them as varchar with approximate
+ * precision(length) needed to send supported string literals. string literal formats supported by temporal types are available in MSDN
+ * page on data types.
+ */
+ case java.sql.Types.DATE:
+ case java.sql.Types.TIME:
+ case java.sql.Types.TIMESTAMP:
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ // The precision is just a number long enough to hold all types of temporal data, doesn't need to be exact precision.
+ columnMetadata.put(positionInFile, new ColumnMetadata(colName, jdbcType, 50, scale, dateTimeFormatter));
+ break;
+
+ // Redirect SQLXML as LONGNVARCHAR
+ // SQLXML is not valid type in TDS
+ case java.sql.Types.SQLXML:
+ columnMetadata.put(positionInFile, new ColumnMetadata(colName, java.sql.Types.LONGNVARCHAR, precision, scale, dateTimeFormatter));
+ break;
+
+ // Redirecting Float as Double based on data type mapping
+ // https://msdn.microsoft.com/en-us/library/ms378878%28v=sql.110%29.aspx
+ case java.sql.Types.FLOAT:
+ columnMetadata.put(positionInFile, new ColumnMetadata(colName, java.sql.Types.DOUBLE, precision, scale, dateTimeFormatter));
+ break;
+
+ // redirecting BOOLEAN as BIT
+ case java.sql.Types.BOOLEAN:
+ columnMetadata.put(positionInFile, new ColumnMetadata(colName, java.sql.Types.BIT, precision, scale, dateTimeFormatter));
+ break;
+
+ default:
+ columnMetadata.put(positionInFile, new ColumnMetadata(colName, jdbcType, precision, scale, dateTimeFormatter));
+ }
+
loggerExternal.exiting(loggerClassName, "addColumnMetadata");
}
-
+
/**
* Set the format for reading in dates from the file.
- * @param dateTimeFormat format to parse data sent as java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ *
+ * @param dateTimeFormat
+ * format to parse data sent as java.sql.Types.TIMESTAMP_WITH_TIMEZONE
*/
- public void setTimestampWithTimezoneFormat(String dateTimeFormat)
- {
- DriverJDBCVersion.checkSupportsJDBC42();
- loggerExternal.entering(loggerClassName, "setTimestampWithTimezoneFormat",dateTimeFormat);
-
- this.dateTimeFormatter = DateTimeFormatter.ofPattern(dateTimeFormat);
-
- loggerExternal.exiting(loggerClassName, "setTimestampWithTimezoneFormat");
+ public void setTimestampWithTimezoneFormat(String dateTimeFormat) {
+ DriverJDBCVersion.checkSupportsJDBC42();
+ loggerExternal.entering(loggerClassName, "setTimestampWithTimezoneFormat", dateTimeFormat);
+
+ this.dateTimeFormatter = DateTimeFormatter.ofPattern(dateTimeFormat);
+
+ loggerExternal.exiting(loggerClassName, "setTimestampWithTimezoneFormat");
}
-
+
/**
* Set the format for reading in dates from the file.
- * @param dateTimeFormatter format to parse data sent as java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ *
+ * @param dateTimeFormatter
+ * format to parse data sent as java.sql.Types.TIMESTAMP_WITH_TIMEZONE
*/
- public void setTimestampWithTimezoneFormat(DateTimeFormatter dateTimeFormatter)
- {
- loggerExternal.entering(loggerClassName,
- "setTimestampWithTimezoneFormat",
- new Object[] { dateTimeFormatter });
-
- this.dateTimeFormatter = dateTimeFormatter;
-
- loggerExternal.exiting(loggerClassName, "setTimestampWithTimezoneFormat");
+ public void setTimestampWithTimezoneFormat(DateTimeFormatter dateTimeFormatter) {
+ loggerExternal.entering(loggerClassName, "setTimestampWithTimezoneFormat", new Object[] {dateTimeFormatter});
+
+ this.dateTimeFormatter = dateTimeFormatter;
+
+ loggerExternal.exiting(loggerClassName, "setTimestampWithTimezoneFormat");
}
-
+
/**
* Set the format for reading in dates from the file.
- * @param timeFormat format to parse data sent as java.sql.Types.TIME_WITH_TIMEZONE
+ *
+ * @param timeFormat
+ * format to parse data sent as java.sql.Types.TIME_WITH_TIMEZONE
*/
- public void setTimeWithTimezoneFormat(String timeFormat)
- {
- DriverJDBCVersion.checkSupportsJDBC42();
- loggerExternal.entering(loggerClassName, "setTimeWithTimezoneFormat",timeFormat);
-
- this.timeFormatter = DateTimeFormatter.ofPattern(timeFormat);
-
- loggerExternal.exiting(loggerClassName, "setTimeWithTimezoneFormat");
+ public void setTimeWithTimezoneFormat(String timeFormat) {
+ DriverJDBCVersion.checkSupportsJDBC42();
+ loggerExternal.entering(loggerClassName, "setTimeWithTimezoneFormat", timeFormat);
+
+ this.timeFormatter = DateTimeFormatter.ofPattern(timeFormat);
+
+ loggerExternal.exiting(loggerClassName, "setTimeWithTimezoneFormat");
}
-
+
/**
* Set the format for reading in dates from the file.
- * @param dateTimeFormatter format to parse data sent as java.sql.Types.TIME_WITH_TIMEZONE
+ *
+ * @param dateTimeFormatter
+ * format to parse data sent as java.sql.Types.TIME_WITH_TIMEZONE
*/
- public void setTimeWithTimezoneFormat(DateTimeFormatter dateTimeFormatter)
- {
- loggerExternal.entering(loggerClassName, "setTimeWithTimezoneFormat",
- new Object[] { dateTimeFormatter });
-
- this.timeFormatter = dateTimeFormatter;
-
- loggerExternal.exiting(loggerClassName, "setTimeWithTimezoneFormat");
+ public void setTimeWithTimezoneFormat(DateTimeFormatter dateTimeFormatter) {
+ loggerExternal.entering(loggerClassName, "setTimeWithTimezoneFormat", new Object[] {dateTimeFormatter});
+
+ this.timeFormatter = dateTimeFormatter;
+
+ loggerExternal.exiting(loggerClassName, "setTimeWithTimezoneFormat");
}
-
+
/**
* Releases any resources associated with the file reader.
- * @throws SQLServerException when an error occurs
+ *
+ * @throws SQLServerException
+ * when an error occurs
*/
- public void close() throws SQLServerException
- {
+ public void close() throws SQLServerException {
loggerExternal.entering(loggerClassName, "close");
-
- // Ignore errors since we are only cleaning up here
- if (fileReader != null) try { fileReader.close(); } catch(Exception e) {}
- if (sr != null) try { sr.close(); } catch(Exception e) {}
- if (fis != null) try { fis.close(); } catch(Exception e) {}
-
+
+ // Ignore errors since we are only cleaning up here
+ if (fileReader != null)
+ try {
+ fileReader.close();
+ }
+ catch (Exception e) {
+ }
+ if (sr != null)
+ try {
+ sr.close();
+ }
+ catch (Exception e) {
+ }
+ if (fis != null)
+ try {
+ fis.close();
+ }
+ catch (Exception e) {
+ }
+
loggerExternal.exiting(loggerClassName, "close");
}
-
+
public DateTimeFormatter getColumnDateTimeFormatter(int column) {
return columnMetadata.get(column).dateTimeFormatter;
}
@@ -412,297 +454,243 @@ public boolean isAutoIncrement(int column) {
return false;
}
- @Override
- public Object[] getRowData() throws SQLServerException
- {
- if (null == currentLine)
- return null;
- else
- {
- // Binary data may be corrupted
- // The limit in split() function should be a negative value, otherwise trailing empty strings are discarded.
- // Empty string is returned if there is no value.
- String[] data = currentLine.split(delimiter, -1);
-
- // Cannot go directly from String[] to Object[] and expect it to act as an array.
- Object[] dataRow = new Object[data.length];
-
- Iterator> it = columnMetadata.entrySet().iterator();
- while (it.hasNext())
- {
- Entry pair = it.next();
- ColumnMetadata cm = pair.getValue();
-
- // Reading a column not available in csv
- // positionInFile > number of columns retrieved after split
- if (data.length < pair.getKey()-1)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
+ @Override
+ public Object[] getRowData() throws SQLServerException {
+ if (null == currentLine)
+ return null;
+ else {
+ // Binary data may be corrupted
+ // The limit in split() function should be a negative value, otherwise trailing empty strings are discarded.
+ // Empty string is returned if there is no value.
+ String[] data = currentLine.split(delimiter, -1);
+
+ // Cannot go directly from String[] to Object[] and expect it to act as an array.
+ Object[] dataRow = new Object[data.length];
+
+ Iterator> it = columnMetadata.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry pair = it.next();
+ ColumnMetadata cm = pair.getValue();
+
+ // Reading a column not available in csv
+ // positionInFile > number of columns retrieved after split
+ if (data.length < pair.getKey() - 1) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
Object[] msgArgs = {pair.getKey()};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
- }
-
- // Source header has more columns than current line read
- if (columnNames!=null && (columnNames.length > data.length))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkCSVDataSchemaMismatch"));
+ }
+
+ // Source header has more columns than current line read
+ if (columnNames != null && (columnNames.length > data.length)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkCSVDataSchemaMismatch"));
Object[] msgArgs = {};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
- }
-
- try
- {
- if (0 == data[pair.getKey()-1].length())
- {
- dataRow[pair.getKey()-1] = null;
- continue;
- }
-
- switch (cm.columnType)
- {
- /*
- * Both BCP and BULK INSERT considers double quotes as part of the data and throws error if any data
- * (say "10") is to be inserted into an numeric column. Our implementation does the same.
- */
- case java.sql.Types.INTEGER:
- {
- // Formatter to remove the decimal part as SQL Server floors the decimal in integer types
- DecimalFormat decimalFormatter = new DecimalFormat("#");
- String formatedfInput = decimalFormatter.format(Double.parseDouble(data[pair.getKey()-1]));
- dataRow[pair.getKey()-1] = Integer.valueOf(formatedfInput);
- break;
- }
-
- case java.sql.Types.TINYINT:
- case java.sql.Types.SMALLINT:
- {
- // Formatter to remove the decimal part as SQL Server floors the decimal in integer types
- DecimalFormat decimalFormatter = new DecimalFormat("#");
- String formatedfInput = decimalFormatter.format(Double.parseDouble(data[pair.getKey()-1]));
- dataRow[pair.getKey()-1] = Short.valueOf(formatedfInput);
- break;
- }
-
- case java.sql.Types.BIGINT:
- {
- BigDecimal bd = new BigDecimal(data[pair.getKey()-1].trim());
- try
- {
- dataRow[pair.getKey()-1] = bd.setScale(0,BigDecimal.ROUND_DOWN).longValueExact();
- }
- catch(ArithmeticException ex)
- {
- String value = "'" + data[pair.getKey()-1] + "'";
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
- throw new SQLServerException(form.format(new Object[] {value, JDBCType.of(cm.columnType)}), null, 0, null);
- }
- break;
- }
-
- case java.sql.Types.DECIMAL:
- case java.sql.Types.NUMERIC:
- {
- BigDecimal bd = new BigDecimal(data[pair.getKey()-1].trim());
- dataRow[pair.getKey()-1] = bd.setScale(cm.scale, RoundingMode.HALF_UP);
- break;
- }
-
- case java.sql.Types.BIT:
- {
- // "true" => 1, "false" => 0
- // Any non-zero value (integer/double) => 1, 0/0.0 => 0
- try
- {
- dataRow[pair.getKey()-1] =
- (0 == Double.parseDouble(data[pair.getKey()-1])) ?
- Boolean.FALSE : Boolean.TRUE;
- }
- catch(NumberFormatException e)
- {
- dataRow[pair.getKey()-1] = Boolean.parseBoolean(data[pair.getKey()-1]);
- }
- break;
- }
-
- case java.sql.Types.REAL:
- {
- dataRow[pair.getKey()-1] = Float.parseFloat(data[pair.getKey()-1]);
- break;
- }
-
- case java.sql.Types.DOUBLE:
- {
- dataRow[pair.getKey()-1] = Double.parseDouble(data[pair.getKey()-1]);
- break;
- }
-
- case java.sql.Types.BINARY:
- case java.sql.Types.VARBINARY:
- case java.sql.Types.LONGVARBINARY:
- case java.sql.Types.BLOB:
- {
- /*
- * For binary data, the value in file may or may not have the '0x' prefix.
- * We will try to match our implementation with 'BULK INSERT' except that we will
- * allow 0x prefix whereas 'BULK INSERT' command does not allow 0x prefix.
- * A BULK INSERT example:
- * A sample csv file containing data for 2 binary columns and 1 row:
- * 61,62
- * Table definition: create table t1(c1 varbinary(10), c2 varbinary(10))
- * BULK INSERT command: bulk insert t1 from 'C:\in.csv' with(DATAFILETYPE='char',firstrow=1,FIELDTERMINATOR=',')
- * select * from t1 shows 1 row with columns: 0x61, 0x62
- */
- // Strip off 0x if present.
- String binData = data[pair.getKey()-1].trim();
- if (binData.startsWith("0x") ||
- binData.startsWith("0X"))
- {
- dataRow[pair.getKey()-1] = binData.substring(2);
- }
- else
- {
- dataRow[pair.getKey()-1] = binData;
- }
- break;
- }
-
- case 2013: // java.sql.Types.TIME_WITH_TIMEZONE
- {
- DriverJDBCVersion.checkSupportsJDBC42();
- OffsetTime offsetTimeValue = null;
-
- // The per-column DateTimeFormatter gets priority.
- if (null != cm.dateTimeFormatter)
- offsetTimeValue= OffsetTime.parse(data[pair.getKey()-1], cm.dateTimeFormatter);
- else if(timeFormatter != null)
- offsetTimeValue= OffsetTime.parse(data[pair.getKey()-1], timeFormatter);
- else
- offsetTimeValue= OffsetTime.parse(data[pair.getKey()-1]);
-
- dataRow[pair.getKey()-1] = offsetTimeValue;
- break;
- }
-
- case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE
- {
- DriverJDBCVersion.checkSupportsJDBC42();
- OffsetDateTime offsetDateTimeValue = null;
-
- // The per-column DateTimeFormatter gets priority.
- if (null != cm.dateTimeFormatter)
- offsetDateTimeValue= OffsetDateTime.parse(data[pair.getKey()-1], cm.dateTimeFormatter);
- else if(dateTimeFormatter != null)
- offsetDateTimeValue= OffsetDateTime.parse(data[pair.getKey()-1], dateTimeFormatter);
- else
- offsetDateTimeValue= OffsetDateTime.parse(data[pair.getKey()-1]);
-
- dataRow[pair.getKey()-1] = offsetDateTimeValue;
- break;
- }
-
- case java.sql.Types.NULL:
- {
- dataRow[pair.getKey()-1] = null;
- break;
- }
-
- case java.sql.Types.DATE:
- case java.sql.Types.CHAR:
- case java.sql.Types.NCHAR:
- case java.sql.Types.VARCHAR:
- case java.sql.Types.NVARCHAR:
- case java.sql.Types.LONGVARCHAR:
- case java.sql.Types.LONGNVARCHAR:
- case java.sql.Types.CLOB:
- default:
- {
- // The string is copied as is.
- /* Handling double quotes:
- * Both BCP (without a format file) and BULK INSERT behaves the same way for double quotes.
- * They treat double quotes as part of the data. For a CSV file as follows, data is inserted as is:
- ""abc""
- "abc"
- abc
- a"b"c
- a""b""c
- * Excel on the other hand, shows data as follows. It strips off beginning and ending quotes, and sometimes quotes get messed up.
- * When the same CSV is saved from Excel again, Excel adds additional quotes.
- abc""
- abc
- abc
- a"b"c
- a""b""c
- * In our implementation we will match the behavior with BCP and BULK INSERT.
- * BCP command: bcp table1 in in.csv -c -t , -r 0x0A -S localhost -U sa -P
- * BULK INSERT command: bulk insert table1 from 'in.csv' with (FIELDTERMINATOR=',')
- *
- * Handling delimiters in data:
- * Excel allows comma in data when data is surrounded with quotes. For example,
- * "Hello, world" is treated as one cell. BCP and BULK INSERT deos not allow field
- * terminators in data:
- * https://technet.microsoft.com/en-us/library/aa196735%28v=sql.80%29.aspx?f=255&MSPPError=-2147217396
- */
- dataRow[pair.getKey()-1] = data[pair.getKey()-1];
- break;
- }
- }
- }
- catch(IllegalArgumentException e)
- {
- String value = "'" + data[pair.getKey()-1] + "'";
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
- throw new SQLServerException(form.format(new Object[] {value, JDBCType.of(cm.columnType)}), null, 0, null);
- }
- catch(ArrayIndexOutOfBoundsException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_BulkCSVDataSchemaMismatch"), null);
- }
-
- }
- return dataRow;
- }
- }
-
- @Override
+ }
+
+ try {
+ if (0 == data[pair.getKey() - 1].length()) {
+ dataRow[pair.getKey() - 1] = null;
+ continue;
+ }
+
+ switch (cm.columnType) {
+ /*
+ * Both BCP and BULK INSERT considers double quotes as part of the data and throws error if any data (say "10") is to be
+ * inserted into an numeric column. Our implementation does the same.
+ */
+ case java.sql.Types.INTEGER: {
+ // Formatter to remove the decimal part as SQL Server floors the decimal in integer types
+ DecimalFormat decimalFormatter = new DecimalFormat("#");
+ String formatedfInput = decimalFormatter.format(Double.parseDouble(data[pair.getKey() - 1]));
+ dataRow[pair.getKey() - 1] = Integer.valueOf(formatedfInput);
+ break;
+ }
+
+ case java.sql.Types.TINYINT:
+ case java.sql.Types.SMALLINT: {
+ // Formatter to remove the decimal part as SQL Server floors the decimal in integer types
+ DecimalFormat decimalFormatter = new DecimalFormat("#");
+ String formatedfInput = decimalFormatter.format(Double.parseDouble(data[pair.getKey() - 1]));
+ dataRow[pair.getKey() - 1] = Short.valueOf(formatedfInput);
+ break;
+ }
+
+ case java.sql.Types.BIGINT: {
+ BigDecimal bd = new BigDecimal(data[pair.getKey() - 1].trim());
+ try {
+ dataRow[pair.getKey() - 1] = bd.setScale(0, BigDecimal.ROUND_DOWN).longValueExact();
+ }
+ catch (ArithmeticException ex) {
+ String value = "'" + data[pair.getKey() - 1] + "'";
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
+ throw new SQLServerException(form.format(new Object[] {value, JDBCType.of(cm.columnType)}), null, 0, null);
+ }
+ break;
+ }
+
+ case java.sql.Types.DECIMAL:
+ case java.sql.Types.NUMERIC: {
+ BigDecimal bd = new BigDecimal(data[pair.getKey() - 1].trim());
+ dataRow[pair.getKey() - 1] = bd.setScale(cm.scale, RoundingMode.HALF_UP);
+ break;
+ }
+
+ case java.sql.Types.BIT: {
+ // "true" => 1, "false" => 0
+ // Any non-zero value (integer/double) => 1, 0/0.0 => 0
+ try {
+ dataRow[pair.getKey() - 1] = (0 == Double.parseDouble(data[pair.getKey() - 1])) ? Boolean.FALSE : Boolean.TRUE;
+ }
+ catch (NumberFormatException e) {
+ dataRow[pair.getKey() - 1] = Boolean.parseBoolean(data[pair.getKey() - 1]);
+ }
+ break;
+ }
+
+ case java.sql.Types.REAL: {
+ dataRow[pair.getKey() - 1] = Float.parseFloat(data[pair.getKey() - 1]);
+ break;
+ }
+
+ case java.sql.Types.DOUBLE: {
+ dataRow[pair.getKey() - 1] = Double.parseDouble(data[pair.getKey() - 1]);
+ break;
+ }
+
+ case java.sql.Types.BINARY:
+ case java.sql.Types.VARBINARY:
+ case java.sql.Types.LONGVARBINARY:
+ case java.sql.Types.BLOB: {
+ /*
+ * For binary data, the value in file may or may not have the '0x' prefix. We will try to match our implementation with
+ * 'BULK INSERT' except that we will allow 0x prefix whereas 'BULK INSERT' command does not allow 0x prefix. A BULK INSERT
+ * example: A sample csv file containing data for 2 binary columns and 1 row: 61,62 Table definition: create table t1(c1
+ * varbinary(10), c2 varbinary(10)) BULK INSERT command: bulk insert t1 from 'C:\in.csv'
+ * with(DATAFILETYPE='char',firstrow=1,FIELDTERMINATOR=',') select * from t1 shows 1 row with columns: 0x61, 0x62
+ */
+ // Strip off 0x if present.
+ String binData = data[pair.getKey() - 1].trim();
+ if (binData.startsWith("0x") || binData.startsWith("0X")) {
+ dataRow[pair.getKey() - 1] = binData.substring(2);
+ }
+ else {
+ dataRow[pair.getKey() - 1] = binData;
+ }
+ break;
+ }
+
+ case 2013: // java.sql.Types.TIME_WITH_TIMEZONE
+ {
+ DriverJDBCVersion.checkSupportsJDBC42();
+ OffsetTime offsetTimeValue = null;
+
+ // The per-column DateTimeFormatter gets priority.
+ if (null != cm.dateTimeFormatter)
+ offsetTimeValue = OffsetTime.parse(data[pair.getKey() - 1], cm.dateTimeFormatter);
+ else if (timeFormatter != null)
+ offsetTimeValue = OffsetTime.parse(data[pair.getKey() - 1], timeFormatter);
+ else
+ offsetTimeValue = OffsetTime.parse(data[pair.getKey() - 1]);
+
+ dataRow[pair.getKey() - 1] = offsetTimeValue;
+ break;
+ }
+
+ case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ {
+ DriverJDBCVersion.checkSupportsJDBC42();
+ OffsetDateTime offsetDateTimeValue = null;
+
+ // The per-column DateTimeFormatter gets priority.
+ if (null != cm.dateTimeFormatter)
+ offsetDateTimeValue = OffsetDateTime.parse(data[pair.getKey() - 1], cm.dateTimeFormatter);
+ else if (dateTimeFormatter != null)
+ offsetDateTimeValue = OffsetDateTime.parse(data[pair.getKey() - 1], dateTimeFormatter);
+ else
+ offsetDateTimeValue = OffsetDateTime.parse(data[pair.getKey() - 1]);
+
+ dataRow[pair.getKey() - 1] = offsetDateTimeValue;
+ break;
+ }
+
+ case java.sql.Types.NULL: {
+ dataRow[pair.getKey() - 1] = null;
+ break;
+ }
+
+ case java.sql.Types.DATE:
+ case java.sql.Types.CHAR:
+ case java.sql.Types.NCHAR:
+ case java.sql.Types.VARCHAR:
+ case java.sql.Types.NVARCHAR:
+ case java.sql.Types.LONGVARCHAR:
+ case java.sql.Types.LONGNVARCHAR:
+ case java.sql.Types.CLOB:
+ default: {
+ // The string is copied as is.
+ /*
+ * Handling double quotes: Both BCP (without a format file) and BULK INSERT behaves the same way for double quotes. They
+ * treat double quotes as part of the data. For a CSV file as follows, data is inserted as is: ""abc"" "abc" abc a"b"c
+ * a""b""c Excel on the other hand, shows data as follows. It strips off beginning and ending quotes, and sometimes quotes
+ * get messed up. When the same CSV is saved from Excel again, Excel adds additional quotes. abc"" abc abc a"b"c a""b""c
+ * In our implementation we will match the behavior with BCP and BULK INSERT. BCP command: bcp table1 in in.csv -c -t , -r
+ * 0x0A -S localhost -U sa -P BULK INSERT command: bulk insert table1 from 'in.csv' with (FIELDTERMINATOR=',')
+ *
+ * Handling delimiters in data: Excel allows comma in data when data is surrounded with quotes. For example,
+ * "Hello, world" is treated as one cell. BCP and BULK INSERT deos not allow field terminators in data:
+ * https://technet.microsoft.com/en-us/library/aa196735%28v=sql.80%29.aspx?f=255&MSPPError=-2147217396
+ */
+ dataRow[pair.getKey() - 1] = data[pair.getKey() - 1];
+ break;
+ }
+ }
+ }
+ catch (IllegalArgumentException e) {
+ String value = "'" + data[pair.getKey() - 1] + "'";
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
+ throw new SQLServerException(form.format(new Object[] {value, JDBCType.of(cm.columnType)}), null, 0, null);
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_BulkCSVDataSchemaMismatch"), null);
+ }
+
+ }
+ return dataRow;
+ }
+ }
+
+ @Override
public boolean next() throws SQLServerException {
try {
currentLine = fileReader.readLine();
- } catch (IOException e) {
- throw new SQLServerException(null , e.getMessage() , null , 0 , false);
+ }
+ catch (IOException e) {
+ throw new SQLServerException(null, e.getMessage(), null, 0, false);
}
return (null != currentLine);
}
-
+
/*
* Helper method to throw a SQLServerExeption with the invalidArgument message and given argument.
*/
- private void throwInvalidArgument(String argument) throws SQLServerException
- {
+ private void throwInvalidArgument(String argument) throws SQLServerException {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidArgument"));
Object[] msgArgs = {argument};
SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, false);
}
-
+
/*
- * Method to throw a SQLServerExeption for duplicate column names
+ * Method to throw a SQLServerExeption for duplicate column names
*/
- private void checkDuplicateColumnName(int positionInFile, String colName)
- throws SQLServerException
- {
-
- if (null != colName && colName.trim().length() != 0)
- {
- for (Entry entry : columnMetadata.entrySet())
- {
+ private void checkDuplicateColumnName(int positionInFile,
+ String colName) throws SQLServerException {
+
+ if (null != colName && colName.trim().length() != 0) {
+ for (Entry entry : columnMetadata.entrySet()) {
// duplicate check is not performed in case of same positionInFile value
- if (null != entry && entry.getKey() != positionInFile)
- {
- if (null != entry.getValue() &&
- colName.trim().equalsIgnoreCase(entry.getValue().columnName))
- {
- throw new SQLServerException(
- SQLServerException.getErrString("R_BulkCSVDataDuplicateColumn"),
- null);
+ if (null != entry && entry.getKey() != positionInFile) {
+ if (null != entry.getValue() && colName.trim().equalsIgnoreCase(entry.getValue().columnName)) {
+ throw new SQLServerException(SQLServerException.getErrString("R_BulkCSVDataDuplicateColumn"), null);
}
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
index 38d8c9eca..0a8adae96 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerBulkCopy.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import static java.nio.charset.StandardCharsets.UTF_16LE;
@@ -58,111 +47,104 @@
import javax.sql.RowSet;
/**
- * Lets you efficiently bulk load a SQL Server table with data from another source.
- *
- * Microsoft SQL Server includes a popular command-prompt utility named bcp for moving data from one table to
- * another, whether on a single server or between servers. The SQLServerBulkCopy class lets you write code
- * solutions in Java that provide similar functionality. There are other ways to load data into a SQL Server
- * table (INSERT statements, for example), but SQLServerBulkCopy offers a significant performance advantage over them.
+ * Lets you efficiently bulk load a SQL Server table with data from another source.
*
- * The SQLServerBulkCopy class can be used to write data only to SQL Server tables. However, the data source is not
- * limited to SQL Server; any data source can be used, as long as the data can be read with a ResultSet or ISQLServerBulkRecord instance.
+ * Microsoft SQL Server includes a popular command-prompt utility named bcp for moving data from one table to another, whether on a single server or
+ * between servers. The SQLServerBulkCopy class lets you write code solutions in Java that provide similar functionality. There are other ways to load
+ * data into a SQL Server table (INSERT statements, for example), but SQLServerBulkCopy offers a significant performance advantage over them.
+ * The SQLServerBulkCopy class can be used to write data only to SQL Server tables. However, the data source is not limited to SQL Server; any data
+ * source can be used, as long as the data can be read with a ResultSet or ISQLServerBulkRecord instance.
*/
-public class SQLServerBulkCopy implements java.lang.AutoCloseable
-{
+public class SQLServerBulkCopy implements java.lang.AutoCloseable {
/*
* Class to represent the column mappings between the source and destination table
*/
- private class ColumnMapping
- {
+ private class ColumnMapping {
String sourceColumnName = null;
int sourceColumnOrdinal = -1;
String destinationColumnName = null;
int destinationColumnOrdinal = -1;
-
- ColumnMapping(String source, String dest)
- {
+
+ ColumnMapping(String source,
+ String dest) {
this.sourceColumnName = source;
this.destinationColumnName = dest;
}
-
- ColumnMapping(String source, int dest)
- {
+
+ ColumnMapping(String source,
+ int dest) {
this.sourceColumnName = source;
this.destinationColumnOrdinal = dest;
}
-
- ColumnMapping(int source, String dest)
- {
+
+ ColumnMapping(int source,
+ String dest) {
this.sourceColumnOrdinal = source;
this.destinationColumnName = dest;
}
-
- ColumnMapping(int source, int dest)
- {
+
+ ColumnMapping(int source,
+ int dest) {
this.sourceColumnOrdinal = source;
this.destinationColumnOrdinal = dest;
}
}
-
+
/*
* Class name for logging.
*/
private static final String loggerClassName = "com.microsoft.sqlserver.jdbc.SQLServerBulkCopy";
-
+
private static final int SQL_SERVER_2016_VERSION = 13;
-
+
/*
* Logger
*/
- private static final java.util.logging.Logger loggerExternal =
- java.util.logging.Logger.getLogger(loggerClassName);
-
+ private static final java.util.logging.Logger loggerExternal = java.util.logging.Logger.getLogger(loggerClassName);
+
/*
* Destination server connection.
*/
private SQLServerConnection connection;
-
+
/*
* Options to control how the WriteToServer methods behave.
*/
private SQLServerBulkCopyOptions copyOptions;
-
+
/*
* Mappings between columns in the data source and columns in the destination
*/
private List columnMappings;
-
+
/*
* Flag if SQLServerBulkCopy owns the connection and should close it when Close is called
*/
private boolean ownsConnection;
-
+
/*
* Name of destination table on server.
*
- * If destinationTable has not been set when WriteToServer is called, an Exception is thrown.
+ * If destinationTable has not been set when WriteToServer is called, an Exception is thrown.
*
- * destinationTable is a three-part name (..). You can qualify
- * the table name with its database and owning schema if you choose. However, if the table name uses
- * an underscore ("_") or any other special characters, you must escape the name using surrounding
- * brackets. For more information, see "Identifiers" in SQL Server Books Online.
+ * destinationTable is a three-part name (..). You can qualify the table name with its database and owning schema if
+ * you choose. However, if the table name uses an underscore ("_") or any other special characters, you must escape the name using surrounding
+ * brackets. For more information, see "Identifiers" in SQL Server Books Online.
*
- * You can bulk-copy data to a temporary table by using a value such as
- * tempdb..#table or tempdb..#table for the destinationTable property.
+ * You can bulk-copy data to a temporary table by using a value such as tempdb..#table or tempdb..#table for the destinationTable property.
*/
private String destinationTableName;
-
+
/*
- * Source data (from a Record). Is null unless the corresponding version of writeToServer is called.
+ * Source data (from a Record). Is null unless the corresponding version of writeToServer is called.
*/
private ISQLServerBulkRecord sourceBulkRecord;
-
+
/*
* Source data (from ResultSet). Is null unless the corresponding version of writeToServer is called.
*/
private ResultSet sourceResultSet;
-
+
/*
* Metadata for the source table columns
*/
@@ -170,70 +152,72 @@ private class ColumnMapping
/* The CekTable for the destination table. */
private CekTable destCekTable = null;
-
+
/*
* Metadata for the destination table columns
*/
- class BulkColumnMetaData
- {
+ class BulkColumnMetaData {
String columnName;
SSType ssType = null;
- int jdbcType;
+ int jdbcType;
int precision, scale;
- SQLCollation collation;
+ SQLCollation collation;
byte[] flags = new byte[2];
boolean isIdentity = false;
boolean isNullable;
- String collationName;
- CryptoMetadata cryptoMeta = null;
- DateTimeFormatter dateTimeFormatter = null;
-
- // used when allowEncryptedValueModifications is on and encryption is turned off in connection
- String encryptionType = null;
-
- BulkColumnMetaData(Column column) throws SQLServerException
- {
- this.cryptoMeta = column.getCryptoMetadata();
- TypeInfo typeInfo = column.getTypeInfo();
- this.columnName = column.getColumnName();
- this.ssType = typeInfo.getSSType();
- this.flags = typeInfo.getFlags();
- this.isIdentity = typeInfo.isIdentity();
- this.isNullable = typeInfo.isNullable();
- precision = typeInfo.getPrecision();
- this.scale = typeInfo.getScale();
- collation = typeInfo.getSQLCollation();
- this.jdbcType = ssType.getJDBCType().getIntValue();
- }
-
- // This constructor is needed for the source meta data.
- BulkColumnMetaData(
- String colName, boolean isNullable, int precision, int scale, int jdbcType, DateTimeFormatter dateTimeFormatter) throws SQLServerException
- {
- this.columnName = colName;
- this.isNullable = isNullable;
- this.precision = precision;
- this.scale = scale;
- this.jdbcType = jdbcType;
- this.dateTimeFormatter = dateTimeFormatter;
- }
-
- BulkColumnMetaData(Column column, String collationName, String encryptionType) throws SQLServerException
- {
+ String collationName;
+ CryptoMetadata cryptoMeta = null;
+ DateTimeFormatter dateTimeFormatter = null;
+
+ // used when allowEncryptedValueModifications is on and encryption is turned off in connection
+ String encryptionType = null;
+
+ BulkColumnMetaData(Column column) throws SQLServerException {
+ this.cryptoMeta = column.getCryptoMetadata();
+ TypeInfo typeInfo = column.getTypeInfo();
+ this.columnName = column.getColumnName();
+ this.ssType = typeInfo.getSSType();
+ this.flags = typeInfo.getFlags();
+ this.isIdentity = typeInfo.isIdentity();
+ this.isNullable = typeInfo.isNullable();
+ precision = typeInfo.getPrecision();
+ this.scale = typeInfo.getScale();
+ collation = typeInfo.getSQLCollation();
+ this.jdbcType = ssType.getJDBCType().getIntValue();
+ }
+
+ // This constructor is needed for the source meta data.
+ BulkColumnMetaData(String colName,
+ boolean isNullable,
+ int precision,
+ int scale,
+ int jdbcType,
+ DateTimeFormatter dateTimeFormatter) throws SQLServerException {
+ this.columnName = colName;
+ this.isNullable = isNullable;
+ this.precision = precision;
+ this.scale = scale;
+ this.jdbcType = jdbcType;
+ this.dateTimeFormatter = dateTimeFormatter;
+ }
+
+ BulkColumnMetaData(Column column,
+ String collationName,
+ String encryptionType) throws SQLServerException {
this(column);
this.collationName = collationName;
this.encryptionType = encryptionType;
- }
+ }
// update the cryptoMeta of source when reading from forward only resultset
- BulkColumnMetaData(BulkColumnMetaData bulkColumnMetaData, CryptoMetadata cryptoMeta)
- {
- this.columnName = bulkColumnMetaData.columnName;
- this.isNullable = bulkColumnMetaData.isNullable;
- this.precision = bulkColumnMetaData.precision;
- this.scale = bulkColumnMetaData.scale;
- this.jdbcType = bulkColumnMetaData.jdbcType;
- this.cryptoMeta = cryptoMeta;
+ BulkColumnMetaData(BulkColumnMetaData bulkColumnMetaData,
+ CryptoMetadata cryptoMeta) {
+ this.columnName = bulkColumnMetaData.columnName;
+ this.isNullable = bulkColumnMetaData.isNullable;
+ this.precision = bulkColumnMetaData.precision;
+ this.scale = bulkColumnMetaData.scale;
+ this.jdbcType = bulkColumnMetaData.jdbcType;
+ this.cryptoMeta = cryptoMeta;
}
};
@@ -256,318 +240,313 @@ class BulkColumnMetaData
* Variable to store source column count.
*/
private int srcColumnCount;
-
+
/*
- * Timer for the bulk copy operation. The other timeout timers in the TDS layer only measure
- * the response of the first packet from SQL Server.
+ * Timer for the bulk copy operation. The other timeout timers in the TDS layer only measure the response of the first packet from SQL Server.
*/
- private final class BulkTimeoutTimer implements Runnable
- {
- private final int timeoutSeconds;
- private int secondsRemaining;
- private final TDSCommand command;
- private Thread timerThread;
- private volatile boolean canceled = false;
-
- BulkTimeoutTimer(int timeoutSeconds, TDSCommand command)
- {
- assert timeoutSeconds > 0;
- assert null != command;
-
- this.timeoutSeconds = timeoutSeconds;
- this.secondsRemaining = timeoutSeconds;
- this.command = command;
- }
-
- final void start()
- {
- timerThread = new Thread(this);
- timerThread.setDaemon(true);
- timerThread.start();
- }
-
- final void stop()
- {
- canceled = true;
- timerThread.interrupt();
- }
-
- final boolean expired()
- {
- return (secondsRemaining <= 0);
- }
-
- public void run()
- {
- try
- {
- // Poll every second while time is left on the timer.
- // Return if/when the timer is canceled.
- do
- {
- if (canceled)
- return;
-
- Thread.sleep(1000);
- }
- while (--secondsRemaining > 0);
- }
- catch (InterruptedException e)
- {
- return;
- }
-
- // If the timer wasn't canceled before it ran out of
- // time then interrupt the registered command.
- try
- {
- command.interrupt(SQLServerException.getErrString("R_queryTimedOut"));
- }
- catch (SQLServerException e)
- {
- // Unfortunately, there's nothing we can do if we
- // fail to time out the request. There is no way
- // to report back what happened.
- command.log(Level.FINE, "Command could not be timed out. Reason: " + e.getMessage());
- }
- }
+ private final class BulkTimeoutTimer implements Runnable {
+ private final int timeoutSeconds;
+ private int secondsRemaining;
+ private final TDSCommand command;
+ private Thread timerThread;
+ private volatile boolean canceled = false;
+
+ BulkTimeoutTimer(int timeoutSeconds,
+ TDSCommand command) {
+ assert timeoutSeconds > 0;
+ assert null != command;
+
+ this.timeoutSeconds = timeoutSeconds;
+ this.secondsRemaining = timeoutSeconds;
+ this.command = command;
+ }
+
+ final void start() {
+ timerThread = new Thread(this);
+ timerThread.setDaemon(true);
+ timerThread.start();
+ }
+
+ final void stop() {
+ canceled = true;
+ timerThread.interrupt();
+ }
+
+ final boolean expired() {
+ return (secondsRemaining <= 0);
+ }
+
+ public void run() {
+ try {
+ // Poll every second while time is left on the timer.
+ // Return if/when the timer is canceled.
+ do {
+ if (canceled)
+ return;
+
+ Thread.sleep(1000);
+ }
+ while (--secondsRemaining > 0);
+ }
+ catch (InterruptedException e) {
+ return;
+ }
+
+ // If the timer wasn't canceled before it ran out of
+ // time then interrupt the registered command.
+ try {
+ command.interrupt(SQLServerException.getErrString("R_queryTimedOut"));
+ }
+ catch (SQLServerException e) {
+ // Unfortunately, there's nothing we can do if we
+ // fail to time out the request. There is no way
+ // to report back what happened.
+ command.log(Level.FINE, "Command could not be timed out. Reason: " + e.getMessage());
+ }
+ }
}
-
+
private BulkTimeoutTimer timeoutTimer = null;
-
+
/**
* Initializes a new instance of the SQLServerBulkCopy class using the specified open instance of SQLServerConnection.
*
- * @param connection Open instance of Connection to destination server. Must be from the Microsoft JDBC driver for SQL Server.
- * @throws SQLServerException If the supplied type is not a connection from the Microsoft JDBC driver for SQL Server.
+ * @param connection
+ * Open instance of Connection to destination server. Must be from the Microsoft JDBC driver for SQL Server.
+ * @throws SQLServerException
+ * If the supplied type is not a connection from the Microsoft JDBC driver for SQL Server.
*/
- public SQLServerBulkCopy(Connection connection) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "SQLServerBulkCopy", connection);
-
- if (null == connection || !connection.getClass().equals(SQLServerConnection.class))
- {
+ public SQLServerBulkCopy(Connection connection) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "SQLServerBulkCopy", connection);
+
+ if (null == connection || !connection.getClass().equals(SQLServerConnection.class)) {
SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDestConnection"), null, false);
}
-
- if (connection instanceof SQLServerConnection)
- {
- this.connection = (SQLServerConnection) connection;
+
+ if (connection instanceof SQLServerConnection) {
+ this.connection = (SQLServerConnection) connection;
}
- else
- {
- SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDestConnection"), null, false);
+ else {
+ SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDestConnection"), null, false);
}
ownsConnection = false;
-
+
// useInternalTransaction will be false by default. When a connection object is passed in, bulk copy
// will use that connection object's transaction, i.e. no transaction management is done by bulk copy.
copyOptions = new SQLServerBulkCopyOptions();
-
+
initializeDefaults();
-
+
loggerExternal.exiting(loggerClassName, "SQLServerBulkCopy");
}
-
+
/**
- * Initializes and opens a new instance of SQLServerConnection based on the supplied connectionString.
+ * Initializes and opens a new instance of SQLServerConnection based on the supplied connectionString.
*
- * @param connectionUrl Connection string for the destination server.
- * @throws SQLServerException If a connection cannot be established.
+ * @param connectionUrl
+ * Connection string for the destination server.
+ * @throws SQLServerException
+ * If a connection cannot be established.
*/
- public SQLServerBulkCopy(String connectionUrl) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "SQLServerBulkCopy", "connectionUrl not traced.");
- if((connectionUrl == null ) || connectionUrl.trim().equals(""))
- {
- throw new SQLServerException(null , SQLServerException.getErrString("R_nullConnection"), null , 0 , false);
- }
-
+ public SQLServerBulkCopy(String connectionUrl) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "SQLServerBulkCopy", "connectionUrl not traced.");
+ if ((connectionUrl == null) || connectionUrl.trim().equals("")) {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_nullConnection"), null, 0, false);
+ }
+
ownsConnection = true;
SQLServerDriver driver = new SQLServerDriver();
connection = (SQLServerConnection) driver.connect(connectionUrl, null);
- if(null == connection)
- {
- throw new SQLServerException(null , SQLServerException.getErrString("R_invalidConnection"), null , 0 , false);
+ if (null == connection) {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_invalidConnection"), null, 0, false);
}
-
- copyOptions = new SQLServerBulkCopyOptions();
+
+ copyOptions = new SQLServerBulkCopyOptions();
initializeDefaults();
-
+
loggerExternal.exiting(loggerClassName, "SQLServerBulkCopy");
}
-
+
/**
* Adds a new column mapping, using ordinals to specify both the source and destination columns.
*
- * @param sourceColumn Source column ordinal.
- * @param destinationColumn Destination column ordinal.
- * @throws SQLServerException If the column mapping is invalid
+ * @param sourceColumn
+ * Source column ordinal.
+ * @param destinationColumn
+ * Destination column ordinal.
+ * @throws SQLServerException
+ * If the column mapping is invalid
*/
- public void addColumnMapping(int sourceColumn, int destinationColumn) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[]{sourceColumn, destinationColumn});
-
+ public void addColumnMapping(int sourceColumn,
+ int destinationColumn) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[] {sourceColumn, destinationColumn});
+
if (0 >= sourceColumn) {
throwInvalidArgument("sourceColumn");
- } else if (0 >= destinationColumn){
+ }
+ else if (0 >= destinationColumn) {
throwInvalidArgument("destinationColumn");
}
- columnMappings.add( new ColumnMapping(sourceColumn, destinationColumn) );
-
+ columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn));
+
loggerExternal.exiting(loggerClassName, "addColumnMapping");
}
-
+
/**
* Adds a new column mapping, using an ordinal for the source column and a string for the destination column.
*
- * @param sourceColumn Source column ordinal.
- * @param destinationColumn Destination column name.
- * @throws SQLServerException If the column mapping is invalid
+ * @param sourceColumn
+ * Source column ordinal.
+ * @param destinationColumn
+ * Destination column name.
+ * @throws SQLServerException
+ * If the column mapping is invalid
*/
- public void addColumnMapping(int sourceColumn, String destinationColumn) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[]{sourceColumn, destinationColumn});
-
+ public void addColumnMapping(int sourceColumn,
+ String destinationColumn) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[] {sourceColumn, destinationColumn});
+
if (0 >= sourceColumn) {
throwInvalidArgument("sourceColumn");
- } else if (null == destinationColumn || destinationColumn.isEmpty()){
+ }
+ else if (null == destinationColumn || destinationColumn.isEmpty()) {
throwInvalidArgument("destinationColumn");
}
- columnMappings.add( new ColumnMapping(sourceColumn, destinationColumn.trim()) );
-
+ columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn.trim()));
+
loggerExternal.exiting(loggerClassName, "addColumnMapping");
}
-
+
/**
* Adds a new column mapping, using a column name to describe the source column and an ordinal to specify the destination column.
*
- * @param sourceColumn Source column name.
- * @param destinationColumn Destination column ordinal.
- * @throws SQLServerException If the column mapping is invalid
+ * @param sourceColumn
+ * Source column name.
+ * @param destinationColumn
+ * Destination column ordinal.
+ * @throws SQLServerException
+ * If the column mapping is invalid
*/
- public void addColumnMapping(String sourceColumn, int destinationColumn) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[]{sourceColumn, destinationColumn});
-
+ public void addColumnMapping(String sourceColumn,
+ int destinationColumn) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[] {sourceColumn, destinationColumn});
+
if (0 >= destinationColumn) {
throwInvalidArgument("destinationColumn");
- } else if (null == sourceColumn || sourceColumn.isEmpty()){
+ }
+ else if (null == sourceColumn || sourceColumn.isEmpty()) {
throwInvalidArgument("sourceColumn");
}
- columnMappings.add( new ColumnMapping(sourceColumn.trim(), destinationColumn) );
-
+ columnMappings.add(new ColumnMapping(sourceColumn.trim(), destinationColumn));
+
loggerExternal.exiting(loggerClassName, "addColumnMapping");
}
-
+
/**
* Adds a new column mapping, using column names to specify both source and destination columns.
*
- * @param sourceColumn Source column name.
- * @param destinationColumn Destination column name.
- * @throws SQLServerException If the column mapping is invalid
+ * @param sourceColumn
+ * Source column name.
+ * @param destinationColumn
+ * Destination column name.
+ * @throws SQLServerException
+ * If the column mapping is invalid
*/
- public void addColumnMapping(String sourceColumn, String destinationColumn) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[]{sourceColumn, destinationColumn});
-
+ public void addColumnMapping(String sourceColumn,
+ String destinationColumn) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "addColumnMapping", new Object[] {sourceColumn, destinationColumn});
+
if (null == sourceColumn || sourceColumn.isEmpty()) {
throwInvalidArgument("sourceColumn");
- } else if (null == destinationColumn || destinationColumn.isEmpty()){
+ }
+ else if (null == destinationColumn || destinationColumn.isEmpty()) {
throwInvalidArgument("destinationColumn");
}
- columnMappings.add( new ColumnMapping(sourceColumn.trim(), destinationColumn.trim()) );
-
+ columnMappings.add(new ColumnMapping(sourceColumn.trim(), destinationColumn.trim()));
+
loggerExternal.exiting(loggerClassName, "addColumnMapping");
}
-
+
/**
* Clears the contents of the column mappings
*/
- public void clearColumnMappings()
- {
+ public void clearColumnMappings() {
loggerExternal.entering(loggerClassName, "clearColumnMappings");
-
+
columnMappings.clear();
-
+
loggerExternal.exiting(loggerClassName, "clearColumnMappings");
}
-
+
/**
* Closes the SQLServerBulkCopy instance
*/
- public void close()
- {
+ public void close() {
loggerExternal.entering(loggerClassName, "close");
-
- if(ownsConnection)
- {
+
+ if (ownsConnection) {
try {
connection.close();
- } catch (SQLException e) {
+ }
+ catch (SQLException e) {
// Ignore this exception
}
}
-
+
loggerExternal.exiting(loggerClassName, "close");
}
-
+
/**
* Gets the name of the destination table on the server.
*
* @return Destination table name.
*/
- public String getDestinationTableName()
- {
+ public String getDestinationTableName() {
return destinationTableName;
}
-
+
/**
* Sets the name of the destination table on the server.
*
- * @param tableName Destination table name.
- * @throws SQLServerException If the table name is null
+ * @param tableName
+ * Destination table name.
+ * @throws SQLServerException
+ * If the table name is null
*/
- public void setDestinationTableName(String tableName) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "setDestinationTableName", tableName);
-
- if (null == tableName || 0 == tableName.trim().length())
- {
+ public void setDestinationTableName(String tableName) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "setDestinationTableName", tableName);
+
+ if (null == tableName || 0 == tableName.trim().length()) {
throwInvalidArgument("tableName");
}
-
+
destinationTableName = tableName.trim();
-
- loggerExternal.exiting(loggerClassName, "setDestinationTableName");
+
+ loggerExternal.exiting(loggerClassName, "setDestinationTableName");
}
-
+
/**
* Gets the current SQLServerBulkCopyOptions.
*
* @return Current SQLServerBulkCopyOptions settings.
*/
- public SQLServerBulkCopyOptions getBulkCopyOptions()
- {
+ public SQLServerBulkCopyOptions getBulkCopyOptions() {
return copyOptions;
}
-
+
/**
- * Update the behavior of the SQLServerBulkCopy instance according to the
- * options supplied, if supplied SQLServerBulkCopyOption is not null.
+ * Update the behavior of the SQLServerBulkCopy instance according to the options supplied, if supplied SQLServerBulkCopyOption is not null.
*
* @param copyOptions
* Settings to change how the WriteToServer methods behave.
* @throws SQLServerException
- * If the SQLServerBulkCopyOption class was constructed using an
- * existing Connection and the UseInternalTransaction option is
+ * If the SQLServerBulkCopyOption class was constructed using an existing Connection and the UseInternalTransaction option is
* specified.
*/
- public void setBulkCopyOptions(SQLServerBulkCopyOptions copyOptions) throws SQLServerException
- {
- loggerExternal.entering(loggerClassName, "updateBulkCopyOptions", copyOptions);
-
+ public void setBulkCopyOptions(SQLServerBulkCopyOptions copyOptions) throws SQLServerException {
+ loggerExternal.entering(loggerClassName, "updateBulkCopyOptions", copyOptions);
+
if (null != copyOptions) {
// Verify that copyOptions does not have useInternalTransaction set.
// UseInternalTrasnaction can only be used with a connection string.
@@ -579,114 +558,115 @@ public void setBulkCopyOptions(SQLServerBulkCopyOptions copyOptions) throws SQLS
this.copyOptions = copyOptions;
}
- loggerExternal.exiting(loggerClassName, "updateBulkCopyOptions");
+ loggerExternal.exiting(loggerClassName, "updateBulkCopyOptions");
}
-
+
/**
- * Copies all rows in the supplied ResultSet to a destination table specified by the destinationTableName property of the SQLServerBulkCopy object.
+ * Copies all rows in the supplied ResultSet to a destination table specified by the destinationTableName property of the SQLServerBulkCopy
+ * object.
*
- * @param sourceData ResultSet to read data rows from.
- * @throws SQLServerException If there are any issues encountered when performing the bulk copy operation
+ * @param sourceData
+ * ResultSet to read data rows from.
+ * @throws SQLServerException
+ * If there are any issues encountered when performing the bulk copy operation
*/
- public void writeToServer(ResultSet sourceData) throws SQLServerException
- {
+ public void writeToServer(ResultSet sourceData) throws SQLServerException {
writeResultSet(sourceData, false);
}
-
+
/**
* Copies all rows in the supplied RowSet to a destination table specified by the destinationTableName property of the SQLServerBulkCopy object.
*
- * @param sourceData RowSet to read data rows from.
- * @throws SQLServerException If there are any issues encountered when performing the bulk copy operation
+ * @param sourceData
+ * RowSet to read data rows from.
+ * @throws SQLServerException
+ * If there are any issues encountered when performing the bulk copy operation
*/
- public void writeToServer(RowSet sourceData) throws SQLServerException
- {
+ public void writeToServer(RowSet sourceData) throws SQLServerException {
writeResultSet(sourceData, true);
}
-
+
/**
- * Copies all rows in the supplied ResultSet to a destination table specified by the destinationTableName property of the SQLServerBulkCopy object.
+ * Copies all rows in the supplied ResultSet to a destination table specified by the destinationTableName property of the SQLServerBulkCopy
+ * object.
*
- * @param sourceData ResultSet to read data rows from.
- * @param isRowSet
- * @throws SQLServerException If there are any issues encountered when performing the bulk copy operation
+ * @param sourceData
+ * ResultSet to read data rows from.
+ * @param isRowSet
+ * @throws SQLServerException
+ * If there are any issues encountered when performing the bulk copy operation
*/
- private void writeResultSet(ResultSet sourceData, boolean isRowSet) throws SQLServerException
- {
+ private void writeResultSet(ResultSet sourceData,
+ boolean isRowSet) throws SQLServerException {
loggerExternal.entering(loggerClassName, "writeToServer");
-
- if (null == sourceData)
- {
+
+ if (null == sourceData) {
throwInvalidArgument("sourceData");
}
-
+
try {
if (isRowSet) // Default RowSet implementation in Java doesn't have isClosed() implemented so need to do an alternate check instead.
{
- if (!sourceData.isBeforeFirst())
- {
+ if (!sourceData.isBeforeFirst()) {
sourceData.beforeFirst();
}
}
- else
- {
- if (sourceData.isClosed())
- {
+ else {
+ if (sourceData.isClosed()) {
SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_resultsetClosed"), null, false);
}
}
- } catch (SQLException e) {
-
- throw new SQLServerException(null , e.getMessage() , null , 0 , false);
}
+ catch (SQLException e) {
+
+ throw new SQLServerException(null, e.getMessage(), null, 0, false);
+ }
+
+ sourceResultSet = sourceData;
+
+ sourceBulkRecord = null;
- sourceResultSet = sourceData;
-
- sourceBulkRecord = null;
-
// Save the resultset metadata as it is used in many places.
- try
- {
+ try {
sourceResultSetMetaData = sourceResultSet.getMetaData();
}
- catch(SQLException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
+ catch (SQLException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
}
-
+
writeToServer();
-
+
loggerExternal.exiting(loggerClassName, "writeToServer");
}
-
+
/**
- * Copies all rows from the supplied ISQLServerBulkRecord to a destination table specified by the destinationTableName property of the SQLServerBulkCopy object.
+ * Copies all rows from the supplied ISQLServerBulkRecord to a destination table specified by the destinationTableName property of the
+ * SQLServerBulkCopy object.
*
- * @param sourceData SQLServerBulkReader to read data rows from.
- * @throws SQLServerException If there are any issues encountered when performing the bulk copy operation
+ * @param sourceData
+ * SQLServerBulkReader to read data rows from.
+ * @throws SQLServerException
+ * If there are any issues encountered when performing the bulk copy operation
*/
- public void writeToServer(ISQLServerBulkRecord sourceData) throws SQLServerException
- {
+ public void writeToServer(ISQLServerBulkRecord sourceData) throws SQLServerException {
loggerExternal.entering(loggerClassName, "writeToServer");
-
- if (null == sourceData)
- {
+
+ if (null == sourceData) {
throwInvalidArgument("sourceData");
}
-
+
sourceBulkRecord = sourceData;
- sourceResultSet = null;
-
+ sourceResultSet = null;
+
writeToServer();
loggerExternal.exiting(loggerClassName, "writeToServer");
}
-
+
/*
* Initializes the defaults for member variables that require it.
*/
- private void initializeDefaults()
- {
+ private void initializeDefaults() {
columnMappings = new LinkedList();
destinationTableName = null;
sourceBulkRecord = null;
@@ -697,1392 +677,1137 @@ private void initializeDefaults()
destColumnMetadata = null;
destColumnCount = 0;
}
-
- private void sendBulkLoadBCP() throws SQLServerException
- {
- final class InsertBulk extends TDSCommand
- {
- InsertBulk()
- {
+
+ private void sendBulkLoadBCP() throws SQLServerException {
+ final class InsertBulk extends TDSCommand {
+ InsertBulk() {
super("InsertBulk", 0);
int timeoutSeconds = copyOptions.getBulkCopyTimeout();
timeoutTimer = (timeoutSeconds > 0) ? (new BulkTimeoutTimer(timeoutSeconds, this)) : null;
}
- final boolean doExecute() throws SQLServerException
- {
- if (null != timeoutTimer)
- {
+ final boolean doExecute() throws SQLServerException {
+ if (null != timeoutTimer) {
if (logger.isLoggable(Level.FINEST))
logger.finest(this.toString() + ": Starting bulk timer...");
timeoutTimer.start();
}
-
- // doInsertBulk inserts the rows in one batch. It returns true if there are more rows in
- // the resultset, false otherwise. We keep sending rows, one batch at a time until the
- // resultset is done.
- try
- {
- while(doInsertBulk(this));
+
+ // doInsertBulk inserts the rows in one batch. It returns true if there are more rows in
+ // the resultset, false otherwise. We keep sending rows, one batch at a time until the
+ // resultset is done.
+ try {
+ while (doInsertBulk(this))
+ ;
}
- catch(SQLServerException topLevelException)
- {
- // Get to the root of this exception.
- Throwable rootCause = topLevelException;
- while(null != rootCause.getCause())
- {
- rootCause = rootCause.getCause();
- }
-
- // Check whether it is a timeout exception.
- if (rootCause instanceof SQLException)
- {
- checkForTimeoutException((SQLException) rootCause, timeoutTimer);
- }
-
- // It is not a timeout exception. Re-throw.
- throw topLevelException;
- }
-
- if (null != timeoutTimer)
- {
+ catch (SQLServerException topLevelException) {
+ // Get to the root of this exception.
+ Throwable rootCause = topLevelException;
+ while (null != rootCause.getCause()) {
+ rootCause = rootCause.getCause();
+ }
+
+ // Check whether it is a timeout exception.
+ if (rootCause instanceof SQLException) {
+ checkForTimeoutException((SQLException) rootCause, timeoutTimer);
+ }
+
+ // It is not a timeout exception. Re-throw.
+ throw topLevelException;
+ }
+
+ if (null != timeoutTimer) {
if (logger.isLoggable(Level.FINEST))
logger.finest(this.toString() + ": Stopping bulk timer...");
timeoutTimer.stop();
}
-
+
return true;
}
}
-
+
connection.executeCommand(new InsertBulk());
- }
-
+ }
+
/*
* write ColumnData token in COLMETADATA header
*/
- private void writeColumnMetaDataColumnData(TDSWriter tdsWriter, int idx) throws SQLServerException
- {
- int srcColumnIndex = 0, destPrecision = 0;
- int bulkJdbcType = 0, bulkPrecision = 0 , bulkScale = 0;
- SQLCollation collation = null;
- SSType destSSType = null;
- boolean isStreaming, srcNullable;
- // For varchar, precision is the size of the varchar type.
- /*
- * UserType USHORT/ULONG; (Changed to ULONG in TDS 7.2) The user
- * type ID of the data type of the column. The value will be 0x0000
- * with the exceptions of TIMESTAMP (0x0050)
- * and alias types (greater than 0x00FF)
- */
- byte[] userType = new byte[4];
- userType[0] = (byte) 0x00;
- userType[1] = (byte) 0x00;
- userType[2] = (byte) 0x00;
- userType[3] = (byte) 0x00;
- tdsWriter.writeBytes(userType);
-
-
- /*
- * Flags token - Bit flags in least significant bit order
- * https://msdn.microsoft.com/en-us/library/dd357363.aspx
- * flags[0] = (byte) 0x05;
- * flags[1] = (byte) 0x00;
- */
- int destColumnIndex = columnMappings.get(idx).destinationColumnOrdinal;
-
- /*
- * TYPE_INFO
- * FIXEDLENTYPE
- * Example INT: tdsWriter.writeByte((byte) 0x38);
- */
- srcColumnIndex = columnMappings.get(idx).sourceColumnOrdinal;
-
- byte flags[] = destColumnMetadata.get(destColumnIndex).flags;
- //If AllowEncryptedValueModification is set to true (and of course AE is off),
- //the driver will not sent AE information, so, we need to set Encryption bit flag to 0.
- if(null == srcColumnMetadata.get(srcColumnIndex).cryptoMeta
- && null == destColumnMetadata.get(destColumnIndex).cryptoMeta
- && true == copyOptions.isAllowEncryptedValueModifications()){
-
- //flags[1]>>3 & 0x01 is the encryption bit flag.
- //it is the 4th least significant bit in this byte, so minus 8 to set it to 0.
- if(1 == (flags[1]>>3 & 0x01)){
- flags[1] = (byte) (flags[1] - 8);
- }
- }
- tdsWriter.writeBytes(flags);
-
- bulkJdbcType = srcColumnMetadata.get(srcColumnIndex).jdbcType;
- bulkPrecision = srcColumnMetadata.get(srcColumnIndex).precision;
- bulkScale = srcColumnMetadata.get(srcColumnIndex).scale;
- srcNullable = srcColumnMetadata.get(srcColumnIndex).isNullable;
-
-
- destSSType = destColumnMetadata.get(destColumnIndex).ssType;
- destPrecision = destColumnMetadata.get(destColumnIndex).precision;
-
- bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType, destPrecision);
-
- collation = destColumnMetadata.get(destColumnIndex).collation;
- if (null == collation)
- collation = connection.getDatabaseCollation();
-
- if ((java.sql.Types.NCHAR == bulkJdbcType) ||
- (java.sql.Types.NVARCHAR == bulkJdbcType) ||
- (java.sql.Types.LONGNVARCHAR == bulkJdbcType))
- {
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < bulkPrecision) ||
- (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
- }
- else
- {
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < bulkPrecision) ||
- (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision);
- }
-
- /*
- * if source is encrypted and destination is unenecrypted use destination sql type to send
- * since there is no way of finding if source is encrypted without accessing the resultset, send destination type if
- * source resultset set is of type SQLServer and encryption is enabled
- */
- if((sourceResultSet instanceof SQLServerResultSet) &&
- (connection.isColumnEncryptionSettingEnabled()))
- {
- bulkJdbcType = destColumnMetadata.get(destColumnIndex).jdbcType;
- bulkPrecision = destPrecision;
- bulkScale = destColumnMetadata.get(destColumnIndex).scale;
- }
-
- // use varbinary to send if destination is encrypted
- if(((null != destColumnMetadata.get(destColumnIndex).encryptionType)
- && copyOptions.isAllowEncryptedValueModifications())
- || (null != destColumnMetadata.get(destColumnIndex).cryptoMeta))
- {
- tdsWriter.writeByte((byte)0xA5);
-
- if (isStreaming)
- {
- tdsWriter.writeShort((short) 0xFFFF);
- }
- else
- {
- tdsWriter.writeShort((short) (bulkPrecision));
- }
-
- }
- // In this case we will explicitly send binary value.
- else if (((java.sql.Types.CHAR == bulkJdbcType) || (java.sql.Types.VARCHAR == bulkJdbcType) || (java.sql.Types.LONGVARCHAR == bulkJdbcType))
- && (SSType.BINARY == destSSType || SSType.VARBINARY == destSSType || SSType.VARBINARYMAX == destSSType || SSType.IMAGE == destSSType))
- {
- if(isStreaming)
- {
- // Send as VARBINARY if streaming is enabled
- tdsWriter.writeByte((byte)0xA5);
- }
- else
- {
- tdsWriter.writeByte((byte)
- ((SSType.BINARY == destSSType) ?
- 0xAD :
- 0xA5
- ));
- }
- tdsWriter.writeShort((short) (bulkPrecision));
- }
- else
- {
- writeTypeInfo(tdsWriter,bulkJdbcType,
- bulkScale,
- bulkPrecision,
- destSSType,
- collation,
- isStreaming,
- srcNullable,
- false);
- }
-
- CryptoMetadata destCryptoMeta = null;
- if((null != (destCryptoMeta = destColumnMetadata.get(destColumnIndex).cryptoMeta)))
- {
- int baseDestJDBCType = destCryptoMeta.baseTypeInfo.getSSType().getJDBCType().asJavaSqlType();
- int baseDestPrecision = destCryptoMeta.baseTypeInfo.getPrecision();
-
- if ((java.sql.Types.NCHAR == baseDestJDBCType) ||
- (java.sql.Types.NVARCHAR == baseDestJDBCType) ||
- (java.sql.Types.LONGNVARCHAR == baseDestJDBCType))
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < baseDestPrecision);
- else
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < baseDestPrecision);
-
- // Send CryptoMetaData
- tdsWriter.writeShort(destCryptoMeta.getOrdinal()); // Ordinal
- tdsWriter.writeBytes(userType); // usertype
- // BaseTypeInfo
- writeTypeInfo(tdsWriter,
- baseDestJDBCType,
- destCryptoMeta.baseTypeInfo.getScale(),
- baseDestPrecision,
- destCryptoMeta.baseTypeInfo.getSSType(),
- collation,
- isStreaming,
- srcNullable,
- true);
- tdsWriter.writeByte(destCryptoMeta.cipherAlgorithmId);
- tdsWriter.writeByte(destCryptoMeta.encryptionType.getValue());
- tdsWriter.writeByte(destCryptoMeta.normalizationRuleVersion);
- }
-
- /*
- * ColName token The column name. Contains the column name length and column name.
- * see: SQLServerConnection.java toUCS16(String s)
- */
- int destColNameLen = columnMappings.get(idx).destinationColumnName.length();
- String destColName = columnMappings.get(idx).destinationColumnName;
- byte colName[] = new byte[2*destColNameLen];
-
- for (int i = 0; i < destColNameLen; ++i) {
- int c = destColName.charAt(i);
- colName[2*i] = (byte) (c & 0xFF);
- colName[2*i+1] = (byte) ((c >> 8) & 0xFF);
- }
-
- tdsWriter.writeByte((byte) destColNameLen);
+ private void writeColumnMetaDataColumnData(TDSWriter tdsWriter,
+ int idx) throws SQLServerException {
+ int srcColumnIndex = 0, destPrecision = 0;
+ int bulkJdbcType = 0, bulkPrecision = 0, bulkScale = 0;
+ SQLCollation collation = null;
+ SSType destSSType = null;
+ boolean isStreaming, srcNullable;
+ // For varchar, precision is the size of the varchar type.
+ /*
+ * UserType USHORT/ULONG; (Changed to ULONG in TDS 7.2) The user type ID of the data type of the column. The value will be 0x0000 with the
+ * exceptions of TIMESTAMP (0x0050) and alias types (greater than 0x00FF)
+ */
+ byte[] userType = new byte[4];
+ userType[0] = (byte) 0x00;
+ userType[1] = (byte) 0x00;
+ userType[2] = (byte) 0x00;
+ userType[3] = (byte) 0x00;
+ tdsWriter.writeBytes(userType);
+
+ /*
+ * Flags token - Bit flags in least significant bit order https://msdn.microsoft.com/en-us/library/dd357363.aspx flags[0] = (byte) 0x05;
+ * flags[1] = (byte) 0x00;
+ */
+ int destColumnIndex = columnMappings.get(idx).destinationColumnOrdinal;
+
+ /*
+ * TYPE_INFO FIXEDLENTYPE Example INT: tdsWriter.writeByte((byte) 0x38);
+ */
+ srcColumnIndex = columnMappings.get(idx).sourceColumnOrdinal;
+
+ byte flags[] = destColumnMetadata.get(destColumnIndex).flags;
+ // If AllowEncryptedValueModification is set to true (and of course AE is off),
+ // the driver will not sent AE information, so, we need to set Encryption bit flag to 0.
+ if (null == srcColumnMetadata.get(srcColumnIndex).cryptoMeta && null == destColumnMetadata.get(destColumnIndex).cryptoMeta
+ && true == copyOptions.isAllowEncryptedValueModifications()) {
+
+ // flags[1]>>3 & 0x01 is the encryption bit flag.
+ // it is the 4th least significant bit in this byte, so minus 8 to set it to 0.
+ if (1 == (flags[1] >> 3 & 0x01)) {
+ flags[1] = (byte) (flags[1] - 8);
+ }
+ }
+ tdsWriter.writeBytes(flags);
+
+ bulkJdbcType = srcColumnMetadata.get(srcColumnIndex).jdbcType;
+ bulkPrecision = srcColumnMetadata.get(srcColumnIndex).precision;
+ bulkScale = srcColumnMetadata.get(srcColumnIndex).scale;
+ srcNullable = srcColumnMetadata.get(srcColumnIndex).isNullable;
+
+ destSSType = destColumnMetadata.get(destColumnIndex).ssType;
+ destPrecision = destColumnMetadata.get(destColumnIndex).precision;
+
+ bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType, destPrecision);
+
+ collation = destColumnMetadata.get(destColumnIndex).collation;
+ if (null == collation)
+ collation = connection.getDatabaseCollation();
+
+ if ((java.sql.Types.NCHAR == bulkJdbcType) || (java.sql.Types.NVARCHAR == bulkJdbcType) || (java.sql.Types.LONGNVARCHAR == bulkJdbcType)) {
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < bulkPrecision) || (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
+ }
+ else {
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < bulkPrecision) || (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision);
+ }
+
+ /*
+ * if source is encrypted and destination is unenecrypted use destination sql type to send since there is no way of finding if source is
+ * encrypted without accessing the resultset, send destination type if source resultset set is of type SQLServer and encryption is enabled
+ */
+ if ((sourceResultSet instanceof SQLServerResultSet) && (connection.isColumnEncryptionSettingEnabled())) {
+ bulkJdbcType = destColumnMetadata.get(destColumnIndex).jdbcType;
+ bulkPrecision = destPrecision;
+ bulkScale = destColumnMetadata.get(destColumnIndex).scale;
+ }
+
+ // use varbinary to send if destination is encrypted
+ if (((null != destColumnMetadata.get(destColumnIndex).encryptionType) && copyOptions.isAllowEncryptedValueModifications())
+ || (null != destColumnMetadata.get(destColumnIndex).cryptoMeta)) {
+ tdsWriter.writeByte((byte) 0xA5);
+
+ if (isStreaming) {
+ tdsWriter.writeShort((short) 0xFFFF);
+ }
+ else {
+ tdsWriter.writeShort((short) (bulkPrecision));
+ }
+
+ }
+ // In this case we will explicitly send binary value.
+ else if (((java.sql.Types.CHAR == bulkJdbcType) || (java.sql.Types.VARCHAR == bulkJdbcType) || (java.sql.Types.LONGVARCHAR == bulkJdbcType))
+ && (SSType.BINARY == destSSType || SSType.VARBINARY == destSSType || SSType.VARBINARYMAX == destSSType
+ || SSType.IMAGE == destSSType)) {
+ if (isStreaming) {
+ // Send as VARBINARY if streaming is enabled
+ tdsWriter.writeByte((byte) 0xA5);
+ }
+ else {
+ tdsWriter.writeByte((byte) ((SSType.BINARY == destSSType) ? 0xAD : 0xA5));
+ }
+ tdsWriter.writeShort((short) (bulkPrecision));
+ }
+ else {
+ writeTypeInfo(tdsWriter, bulkJdbcType, bulkScale, bulkPrecision, destSSType, collation, isStreaming, srcNullable, false);
+ }
+
+ CryptoMetadata destCryptoMeta = null;
+ if ((null != (destCryptoMeta = destColumnMetadata.get(destColumnIndex).cryptoMeta))) {
+ int baseDestJDBCType = destCryptoMeta.baseTypeInfo.getSSType().getJDBCType().asJavaSqlType();
+ int baseDestPrecision = destCryptoMeta.baseTypeInfo.getPrecision();
+
+ if ((java.sql.Types.NCHAR == baseDestJDBCType) || (java.sql.Types.NVARCHAR == baseDestJDBCType)
+ || (java.sql.Types.LONGNVARCHAR == baseDestJDBCType))
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < baseDestPrecision);
+ else
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < baseDestPrecision);
+
+ // Send CryptoMetaData
+ tdsWriter.writeShort(destCryptoMeta.getOrdinal()); // Ordinal
+ tdsWriter.writeBytes(userType); // usertype
+ // BaseTypeInfo
+ writeTypeInfo(tdsWriter, baseDestJDBCType, destCryptoMeta.baseTypeInfo.getScale(), baseDestPrecision,
+ destCryptoMeta.baseTypeInfo.getSSType(), collation, isStreaming, srcNullable, true);
+ tdsWriter.writeByte(destCryptoMeta.cipherAlgorithmId);
+ tdsWriter.writeByte(destCryptoMeta.encryptionType.getValue());
+ tdsWriter.writeByte(destCryptoMeta.normalizationRuleVersion);
+ }
+
+ /*
+ * ColName token The column name. Contains the column name length and column name. see: SQLServerConnection.java toUCS16(String s)
+ */
+ int destColNameLen = columnMappings.get(idx).destinationColumnName.length();
+ String destColName = columnMappings.get(idx).destinationColumnName;
+ byte colName[] = new byte[2 * destColNameLen];
+
+ for (int i = 0; i < destColNameLen; ++i) {
+ int c = destColName.charAt(i);
+ colName[2 * i] = (byte) (c & 0xFF);
+ colName[2 * i + 1] = (byte) ((c >> 8) & 0xFF);
+ }
+
+ tdsWriter.writeByte((byte) destColNameLen);
tdsWriter.writeBytes(colName);
}
-
- private void writeTypeInfo(TDSWriter tdsWriter, int srcJdbcType, int srcScale, int srcPrecision,
- SSType destSSType, SQLCollation collation, boolean isStreaming, boolean srcNullable,
- boolean isBaseType) throws SQLServerException
- {
- switch (srcJdbcType)
- {
- case java.sql.Types.INTEGER: // 0x38
- if (!srcNullable)
- {
- tdsWriter.writeByte(TDSType.INT4.byteValue());
- }
- else
- {
- tdsWriter.writeByte(TDSType.INTN.byteValue());
- tdsWriter.writeByte((byte) 0x04);
- }
- break;
-
- case java.sql.Types.BIGINT: // 0x7f
- if (!srcNullable)
- {
- tdsWriter.writeByte(TDSType.INT8.byteValue());
- }
- else
- {
- tdsWriter.writeByte(TDSType.INTN.byteValue());
- tdsWriter.writeByte((byte) 0x08);
- }
- break;
-
- case java.sql.Types.BIT: // 0x32
- if (!srcNullable)
- {
- tdsWriter.writeByte(TDSType.BIT1.byteValue());
- }
- else
- {
- tdsWriter.writeByte(TDSType.BITN.byteValue());
- tdsWriter.writeByte((byte) 0x01);
- }
- break;
-
- case java.sql.Types.SMALLINT: // 0x34
- if (!srcNullable)
- {
- tdsWriter.writeByte(TDSType.INT2.byteValue());
- }
- else
- {
- tdsWriter.writeByte(TDSType.INTN.byteValue());
- tdsWriter.writeByte((byte) 0x02);
- }
- break;
-
- case java.sql.Types.TINYINT: // 0x30
- if (!srcNullable)
- {
- tdsWriter.writeByte(TDSType.INT1.byteValue());
- }
- else
- {
- tdsWriter.writeByte(TDSType.INTN.byteValue());
- tdsWriter.writeByte((byte) 0x01);
- }
- break;
-
- case java.sql.Types.DOUBLE: // (FLT8TYPE) 0x3E
- if (!srcNullable)
- {
- tdsWriter.writeByte(TDSType.FLOAT8.byteValue());
- }
- else
- {
- tdsWriter.writeByte(TDSType.FLOATN.byteValue());
- tdsWriter.writeByte((byte) 0x08);
- }
- break;
-
- case java.sql.Types.REAL: // (FLT4TYPE) 0x3B
- if (!srcNullable)
- {
- tdsWriter.writeByte(TDSType.FLOAT4.byteValue());
- }
- else
- {
- tdsWriter.writeByte(TDSType.FLOATN.byteValue());
- tdsWriter.writeByte((byte) 0x04);
- }
- break;
-
- case microsoft.sql.Types.MONEY:
- case microsoft.sql.Types.SMALLMONEY:
- case java.sql.Types.NUMERIC:
- case java.sql.Types.DECIMAL:
- if(isBaseType && ((SSType.MONEY == destSSType ) || (SSType.SMALLMONEY == destSSType )))
- {
- tdsWriter.writeByte(TDSType.MONEYN.byteValue()); // 0x6E
- if(SSType.MONEY == destSSType )
- tdsWriter.writeByte((byte) 8);
- else
- tdsWriter.writeByte((byte) 4);
- }
- else
- {
- if (java.sql.Types.DECIMAL == srcJdbcType)
- tdsWriter.writeByte(TDSType.DECIMALN.byteValue()); // 0x6A
- else
- tdsWriter.writeByte(TDSType.NUMERICN.byteValue()); // 0x6C
- tdsWriter.writeByte((byte) TDSWriter.BIGDECIMAL_MAX_LENGTH); // maximum length
- tdsWriter.writeByte((byte) srcPrecision); // unsigned byte
- tdsWriter.writeByte((byte) srcScale); // unsigned byte
- }
- break;
-
- case microsoft.sql.Types.GUID:
- case java.sql.Types.CHAR: // 0xAF
- if(isBaseType && (SSType.GUID == destSSType))
- {
- tdsWriter.writeByte(TDSType.GUID.byteValue());
- tdsWriter.writeByte((byte) 0x10);
- }
- else
- {
- // BIGCHARTYPE
- tdsWriter.writeByte(TDSType.BIGCHAR.byteValue());
-
- tdsWriter.writeShort((short) (srcPrecision));
-
- collation.writeCollation(tdsWriter);
- }
- break;
-
- case java.sql.Types.NCHAR: // 0xEF
- tdsWriter.writeByte(TDSType.NCHAR.byteValue());
- tdsWriter.writeShort( isBaseType ? (short) (srcPrecision) : (short) (2*srcPrecision));
- collation.writeCollation(tdsWriter);
- break;
-
- case java.sql.Types.LONGVARCHAR:
- case java.sql.Types.VARCHAR: // 0xA7
- // BIGVARCHARTYPE
- tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
- if (isStreaming)
- {
- tdsWriter.writeShort((short) 0xFFFF);
- }
- else
- {
- tdsWriter.writeShort((short) (srcPrecision));
- }
- collation.writeCollation(tdsWriter);
- break;
-
- case java.sql.Types.LONGNVARCHAR:
- case java.sql.Types.NVARCHAR: // 0xE7
- tdsWriter.writeByte(TDSType.NVARCHAR.byteValue());
- if (isStreaming)
- {
- tdsWriter.writeShort((short) 0xFFFF);
- }
- else
- {
- tdsWriter.writeShort( isBaseType ? (short) (srcPrecision) : (short) (2*srcPrecision));
- }
- collation.writeCollation(tdsWriter);
- break;
-
- case java.sql.Types.BINARY: // 0xAD
- tdsWriter.writeByte(TDSType.BIGBINARY.byteValue());
- tdsWriter.writeShort((short) (srcPrecision));
- break;
-
- case java.sql.Types.LONGVARBINARY:
- case java.sql.Types.VARBINARY: // 0xA5
- // BIGVARBINARY
- tdsWriter.writeByte(TDSType.BIGVARBINARY.byteValue());
- if (isStreaming)
- {
- tdsWriter.writeShort((short) 0xFFFF);
- }
- else
- {
- tdsWriter.writeShort((short) (srcPrecision));
- }
- break;
-
- case microsoft.sql.Types.DATETIME:
- case microsoft.sql.Types.SMALLDATETIME:
- case java.sql.Types.TIMESTAMP:
- if((!isBaseType) && (null != sourceBulkRecord))
- {
- tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
- tdsWriter.writeShort((short) (srcPrecision));
- collation.writeCollation(tdsWriter);
- }
- else
- {
- switch(destSSType)
- {
- case SMALLDATETIME:
- if (!srcNullable)
- tdsWriter.writeByte(TDSType.DATETIME4.byteValue());
- else
- {
- tdsWriter.writeByte(TDSType.DATETIMEN.byteValue());
- tdsWriter.writeByte((byte) 4);
- }
- break;
- case DATETIME:
- if (!srcNullable)
- tdsWriter.writeByte(TDSType.DATETIME8.byteValue());
- else
- {
- tdsWriter.writeByte(TDSType.DATETIMEN.byteValue());
- tdsWriter.writeByte((byte) 8);
- }
- break;
- default:
- //DATETIME2 0x2A
- tdsWriter.writeByte(TDSType.DATETIME2N.byteValue());
- tdsWriter.writeByte((byte) srcScale);
- break;
- }
- }
- break;
-
- case java.sql.Types.DATE: // 0x28
- /*
- * SQL Server supports numerous string literal formats for temporal types, hence sending them as varchar
- * with approximate precision(length) needed to send supported string literals if destination is unencrypted.
- * string literal formats supported by temporal types are available in MSDN page on data types.
- */
- if((!isBaseType) && (null != sourceBulkRecord))
- {
- tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
- tdsWriter.writeShort((short) (srcPrecision));
- collation.writeCollation(tdsWriter);
- }
- else
- {
- tdsWriter.writeByte(TDSType.DATEN.byteValue());
- }
- break;
-
- case java.sql.Types.TIME: // 0x29
- if((!isBaseType) && (null != sourceBulkRecord))
- {
- tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
- tdsWriter.writeShort((short) (srcPrecision));
- collation.writeCollation(tdsWriter);
- }
- else
- {
- tdsWriter.writeByte(TDSType.TIMEN.byteValue());
- tdsWriter.writeByte((byte) srcScale);
- }
- break;
-
- // Send as DATETIMEOFFSET for TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE
- case 2013: //java.sql.Types.TIME_WITH_TIMEZONE
- case 2014: //java.sql.Types.TIMESTAMP_WITH_TIMEZONE
- tdsWriter.writeByte(TDSType.DATETIMEOFFSETN.byteValue());
- tdsWriter.writeByte((byte) srcScale);
- break;
-
- case microsoft.sql.Types.DATETIMEOFFSET: // 0x2B
- if((!isBaseType) && (null != sourceBulkRecord))
- {
- tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
- tdsWriter.writeShort((short) (srcPrecision));
- collation.writeCollation(tdsWriter);
- }
- else
- {
- tdsWriter.writeByte(TDSType.DATETIMEOFFSETN.byteValue());
- tdsWriter.writeByte((byte) srcScale);
- }
- break;
-
- default:
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
- String unsupportedDataType = JDBCType.of(srcJdbcType).toString().toLowerCase(Locale.ENGLISH);
- throw new SQLServerException(form.format(new Object[] {unsupportedDataType}), null, 0, null);
+
+ private void writeTypeInfo(TDSWriter tdsWriter,
+ int srcJdbcType,
+ int srcScale,
+ int srcPrecision,
+ SSType destSSType,
+ SQLCollation collation,
+ boolean isStreaming,
+ boolean srcNullable,
+ boolean isBaseType) throws SQLServerException {
+ switch (srcJdbcType) {
+ case java.sql.Types.INTEGER: // 0x38
+ if (!srcNullable) {
+ tdsWriter.writeByte(TDSType.INT4.byteValue());
+ }
+ else {
+ tdsWriter.writeByte(TDSType.INTN.byteValue());
+ tdsWriter.writeByte((byte) 0x04);
+ }
+ break;
+
+ case java.sql.Types.BIGINT: // 0x7f
+ if (!srcNullable) {
+ tdsWriter.writeByte(TDSType.INT8.byteValue());
+ }
+ else {
+ tdsWriter.writeByte(TDSType.INTN.byteValue());
+ tdsWriter.writeByte((byte) 0x08);
+ }
+ break;
+
+ case java.sql.Types.BIT: // 0x32
+ if (!srcNullable) {
+ tdsWriter.writeByte(TDSType.BIT1.byteValue());
+ }
+ else {
+ tdsWriter.writeByte(TDSType.BITN.byteValue());
+ tdsWriter.writeByte((byte) 0x01);
+ }
+ break;
+
+ case java.sql.Types.SMALLINT: // 0x34
+ if (!srcNullable) {
+ tdsWriter.writeByte(TDSType.INT2.byteValue());
+ }
+ else {
+ tdsWriter.writeByte(TDSType.INTN.byteValue());
+ tdsWriter.writeByte((byte) 0x02);
+ }
+ break;
+
+ case java.sql.Types.TINYINT: // 0x30
+ if (!srcNullable) {
+ tdsWriter.writeByte(TDSType.INT1.byteValue());
+ }
+ else {
+ tdsWriter.writeByte(TDSType.INTN.byteValue());
+ tdsWriter.writeByte((byte) 0x01);
+ }
+ break;
+
+ case java.sql.Types.DOUBLE: // (FLT8TYPE) 0x3E
+ if (!srcNullable) {
+ tdsWriter.writeByte(TDSType.FLOAT8.byteValue());
+ }
+ else {
+ tdsWriter.writeByte(TDSType.FLOATN.byteValue());
+ tdsWriter.writeByte((byte) 0x08);
+ }
+ break;
+
+ case java.sql.Types.REAL: // (FLT4TYPE) 0x3B
+ if (!srcNullable) {
+ tdsWriter.writeByte(TDSType.FLOAT4.byteValue());
+ }
+ else {
+ tdsWriter.writeByte(TDSType.FLOATN.byteValue());
+ tdsWriter.writeByte((byte) 0x04);
+ }
+ break;
+
+ case microsoft.sql.Types.MONEY:
+ case microsoft.sql.Types.SMALLMONEY:
+ case java.sql.Types.NUMERIC:
+ case java.sql.Types.DECIMAL:
+ if (isBaseType && ((SSType.MONEY == destSSType) || (SSType.SMALLMONEY == destSSType))) {
+ tdsWriter.writeByte(TDSType.MONEYN.byteValue()); // 0x6E
+ if (SSType.MONEY == destSSType)
+ tdsWriter.writeByte((byte) 8);
+ else
+ tdsWriter.writeByte((byte) 4);
+ }
+ else {
+ if (java.sql.Types.DECIMAL == srcJdbcType)
+ tdsWriter.writeByte(TDSType.DECIMALN.byteValue()); // 0x6A
+ else
+ tdsWriter.writeByte(TDSType.NUMERICN.byteValue()); // 0x6C
+ tdsWriter.writeByte((byte) TDSWriter.BIGDECIMAL_MAX_LENGTH); // maximum length
+ tdsWriter.writeByte((byte) srcPrecision); // unsigned byte
+ tdsWriter.writeByte((byte) srcScale); // unsigned byte
+ }
+ break;
+
+ case microsoft.sql.Types.GUID:
+ case java.sql.Types.CHAR: // 0xAF
+ if (isBaseType && (SSType.GUID == destSSType)) {
+ tdsWriter.writeByte(TDSType.GUID.byteValue());
+ tdsWriter.writeByte((byte) 0x10);
+ }
+ else {
+ // BIGCHARTYPE
+ tdsWriter.writeByte(TDSType.BIGCHAR.byteValue());
+
+ tdsWriter.writeShort((short) (srcPrecision));
+
+ collation.writeCollation(tdsWriter);
+ }
+ break;
+
+ case java.sql.Types.NCHAR: // 0xEF
+ tdsWriter.writeByte(TDSType.NCHAR.byteValue());
+ tdsWriter.writeShort(isBaseType ? (short) (srcPrecision) : (short) (2 * srcPrecision));
+ collation.writeCollation(tdsWriter);
+ break;
+
+ case java.sql.Types.LONGVARCHAR:
+ case java.sql.Types.VARCHAR: // 0xA7
+ // BIGVARCHARTYPE
+ tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
+ if (isStreaming) {
+ tdsWriter.writeShort((short) 0xFFFF);
+ }
+ else {
+ tdsWriter.writeShort((short) (srcPrecision));
+ }
+ collation.writeCollation(tdsWriter);
+ break;
+
+ case java.sql.Types.LONGNVARCHAR:
+ case java.sql.Types.NVARCHAR: // 0xE7
+ tdsWriter.writeByte(TDSType.NVARCHAR.byteValue());
+ if (isStreaming) {
+ tdsWriter.writeShort((short) 0xFFFF);
+ }
+ else {
+ tdsWriter.writeShort(isBaseType ? (short) (srcPrecision) : (short) (2 * srcPrecision));
+ }
+ collation.writeCollation(tdsWriter);
+ break;
+
+ case java.sql.Types.BINARY: // 0xAD
+ tdsWriter.writeByte(TDSType.BIGBINARY.byteValue());
+ tdsWriter.writeShort((short) (srcPrecision));
+ break;
+
+ case java.sql.Types.LONGVARBINARY:
+ case java.sql.Types.VARBINARY: // 0xA5
+ // BIGVARBINARY
+ tdsWriter.writeByte(TDSType.BIGVARBINARY.byteValue());
+ if (isStreaming) {
+ tdsWriter.writeShort((short) 0xFFFF);
+ }
+ else {
+ tdsWriter.writeShort((short) (srcPrecision));
+ }
+ break;
+
+ case microsoft.sql.Types.DATETIME:
+ case microsoft.sql.Types.SMALLDATETIME:
+ case java.sql.Types.TIMESTAMP:
+ if ((!isBaseType) && (null != sourceBulkRecord)) {
+ tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
+ tdsWriter.writeShort((short) (srcPrecision));
+ collation.writeCollation(tdsWriter);
+ }
+ else {
+ switch (destSSType) {
+ case SMALLDATETIME:
+ if (!srcNullable)
+ tdsWriter.writeByte(TDSType.DATETIME4.byteValue());
+ else {
+ tdsWriter.writeByte(TDSType.DATETIMEN.byteValue());
+ tdsWriter.writeByte((byte) 4);
+ }
+ break;
+ case DATETIME:
+ if (!srcNullable)
+ tdsWriter.writeByte(TDSType.DATETIME8.byteValue());
+ else {
+ tdsWriter.writeByte(TDSType.DATETIMEN.byteValue());
+ tdsWriter.writeByte((byte) 8);
+ }
+ break;
+ default:
+ // DATETIME2 0x2A
+ tdsWriter.writeByte(TDSType.DATETIME2N.byteValue());
+ tdsWriter.writeByte((byte) srcScale);
+ break;
+ }
+ }
+ break;
+
+ case java.sql.Types.DATE: // 0x28
+ /*
+ * SQL Server supports numerous string literal formats for temporal types, hence sending them as varchar with approximate
+ * precision(length) needed to send supported string literals if destination is unencrypted. string literal formats supported by
+ * temporal types are available in MSDN page on data types.
+ */
+ if ((!isBaseType) && (null != sourceBulkRecord)) {
+ tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
+ tdsWriter.writeShort((short) (srcPrecision));
+ collation.writeCollation(tdsWriter);
+ }
+ else {
+ tdsWriter.writeByte(TDSType.DATEN.byteValue());
+ }
+ break;
+
+ case java.sql.Types.TIME: // 0x29
+ if ((!isBaseType) && (null != sourceBulkRecord)) {
+ tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
+ tdsWriter.writeShort((short) (srcPrecision));
+ collation.writeCollation(tdsWriter);
+ }
+ else {
+ tdsWriter.writeByte(TDSType.TIMEN.byteValue());
+ tdsWriter.writeByte((byte) srcScale);
+ }
+ break;
+
+ // Send as DATETIMEOFFSET for TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE
+ case 2013: // java.sql.Types.TIME_WITH_TIMEZONE
+ case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ tdsWriter.writeByte(TDSType.DATETIMEOFFSETN.byteValue());
+ tdsWriter.writeByte((byte) srcScale);
+ break;
+
+ case microsoft.sql.Types.DATETIMEOFFSET: // 0x2B
+ if ((!isBaseType) && (null != sourceBulkRecord)) {
+ tdsWriter.writeByte(TDSType.BIGVARCHAR.byteValue());
+ tdsWriter.writeShort((short) (srcPrecision));
+ collation.writeCollation(tdsWriter);
+ }
+ else {
+ tdsWriter.writeByte(TDSType.DATETIMEOFFSETN.byteValue());
+ tdsWriter.writeByte((byte) srcScale);
+ }
+ break;
+
+ default:
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
+ String unsupportedDataType = JDBCType.of(srcJdbcType).toString().toLowerCase(Locale.ENGLISH);
+ throw new SQLServerException(form.format(new Object[] {unsupportedDataType}), null, 0, null);
}
}
-
+
/*
- * Writes the CEK table needed for AE.
- * Cek table (with 0 entries) will be present
- * if AE was enabled and server supports it!
- * OR if encryption was disabled in connection options
+ * Writes the CEK table needed for AE. Cek table (with 0 entries) will be present if AE was enabled and server supports it! OR if encryption was
+ * disabled in connection options
*/
- private void writeCekTable(TDSWriter tdsWriter) throws SQLServerException
- {
- if(connection.getServerSupportsColumnEncryption())
- {
- if ((null != destCekTable) && (0 < destCekTable.getSize()))
- {
- tdsWriter.writeShort((short) destCekTable.getSize());
- for (int cekIndx = 0; cekIndx < destCekTable.getSize(); cekIndx++)
- {
- tdsWriter.writeInt(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).databaseId);
- tdsWriter.writeInt(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).cekId);
- tdsWriter.writeInt(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).cekVersion);
- tdsWriter.writeBytes(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).cekMdVersion);
-
- // We don't need to send the keys. So count for EncryptionKeyValue is 0 in EK_INFO TDS rule.
- tdsWriter.writeByte((byte) 0x00);
- }
- }
- else
- {
- // If no encrypted columns, but the connection setting is true write 0 as the size of the CekTable.
- tdsWriter.writeShort((short) 0);
- }
- }
+ private void writeCekTable(TDSWriter tdsWriter) throws SQLServerException {
+ if (connection.getServerSupportsColumnEncryption()) {
+ if ((null != destCekTable) && (0 < destCekTable.getSize())) {
+ tdsWriter.writeShort((short) destCekTable.getSize());
+ for (int cekIndx = 0; cekIndx < destCekTable.getSize(); cekIndx++) {
+ tdsWriter.writeInt(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).databaseId);
+ tdsWriter.writeInt(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).cekId);
+ tdsWriter.writeInt(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).cekVersion);
+ tdsWriter.writeBytes(destCekTable.getCekTableEntry(cekIndx).getColumnEncryptionKeyValues().get(0).cekMdVersion);
+
+ // We don't need to send the keys. So count for EncryptionKeyValue is 0 in EK_INFO TDS rule.
+ tdsWriter.writeByte((byte) 0x00);
+ }
+ }
+ else {
+ // If no encrypted columns, but the connection setting is true write 0 as the size of the CekTable.
+ tdsWriter.writeShort((short) 0);
+ }
+ }
}
-
-
- /*
- *
- * ...
- *
- */
- private void writeColumnMetaData(TDSWriter tdsWriter) throws SQLServerException
- {
-
- /* TDS rules for Always Encrypted metadata
- * COLMETADATA = TokenType
- Count
- CekTable
- NoMetaData / (1 *ColumnData)
- CekTable = EkValueCount
- *EK_INFO
- EK_INFO = DatabaseId
- CekId
- CekVersion
- CekMDVersion
- Count
- *EncryptionKeyValue
- */
-
- /*
- * TokenType: The token value is 0x81
- */
- tdsWriter.writeByte((byte)TDS.TDS_COLMETADATA);
-
-
- /*
- * Count token: The count of columns.
- * Should be USHORT. Not Supported in Java.
- *
- * Remedy:
- * tdsWriter.writeShort((short)columnMappings.size());
- */
- byte[] count = new byte[2];
- count[0] = (byte) (columnMappings.size() & 0xFF);
- count[1] = (byte) ((columnMappings.size() >> 8) & 0xFF);
- tdsWriter.writeBytes(count);
-
- writeCekTable(tdsWriter);
-
- /*
- * Writing ColumnData section
- * Columndata tokens is written for each destination column in columnMappings
- */
- for (int i=0; i ...
+ */
+ private void writeColumnMetaData(TDSWriter tdsWriter) throws SQLServerException {
+
+ /*
+ * TDS rules for Always Encrypted metadata COLMETADATA = TokenType Count CekTable NoMetaData / (1 *ColumnData) CekTable = EkValueCount EK_INFO
+ * EK_INFO = DatabaseId CekId CekVersion CekMDVersion Count EncryptionKeyValue
+ */
+
+ /*
+ * TokenType: The token value is 0x81
+ */
+ tdsWriter.writeByte((byte) TDS.TDS_COLMETADATA);
+
+ /*
+ * Count token: The count of columns. Should be USHORT. Not Supported in Java.
+ *
+ * Remedy: tdsWriter.writeShort((short)columnMappings.size());
+ */
+ byte[] count = new byte[2];
+ count[0] = (byte) (columnMappings.size() & 0xFF);
+ count[1] = (byte) ((columnMappings.size() >> 8) & 0xFF);
+ tdsWriter.writeBytes(count);
+
+ writeCekTable(tdsWriter);
+
+ /*
+ * Writing ColumnData section Columndata tokens is written for each destination column in columnMappings
+ */
+ for (int i = 0; i < columnMappings.size(); i++) {
+ writeColumnMetaDataColumnData(tdsWriter, i);
+ }
}
/*
* Helper method that throws a timeout exception if the cause of the exception was that the query was cancelled
*/
- private void checkForTimeoutException(SQLException e, BulkTimeoutTimer timeoutTimer) throws SQLServerException
- {
- if ((null != e.getSQLState()) &&
- (e.getSQLState().equals(SQLState.STATEMENT_CANCELED.getSQLStateCode())) &&
- timeoutTimer.expired())
- {
+ private void checkForTimeoutException(SQLException e,
+ BulkTimeoutTimer timeoutTimer) throws SQLServerException {
+ if ((null != e.getSQLState()) && (e.getSQLState().equals(SQLState.STATEMENT_CANCELED.getSQLStateCode())) && timeoutTimer.expired()) {
// If SQLServerBulkCopy is managing the transaction, a rollback is needed.
- if (copyOptions.isUseInternalTransaction())
- {
+ if (copyOptions.isUseInternalTransaction()) {
connection.rollback();
}
-
- throw new SQLServerException(
- SQLServerException.getErrString("R_queryTimedOut"),
- SQLState.STATEMENT_CANCELED,
- DriverError.NOT_SET,
- null);
- }
- }
-
-
+
+ throw new SQLServerException(SQLServerException.getErrString("R_queryTimedOut"), SQLState.STATEMENT_CANCELED, DriverError.NOT_SET, null);
+ }
+ }
+
/*
- * Validates whether the source JDBC types are compatible with the destination table data types.
- * We need to do this only once for the whole bulk copy session.
+ * Validates whether the source JDBC types are compatible with the destination table data types. We need to do this only once for the whole bulk
+ * copy session.
*/
- private void validateDataTypeConversions(int srcColOrdinal, int destColOrdinal) throws SQLServerException
- {
- // Reducing unnecessary assignment to optimize for performance. This reduces code readability, but
- // may be worth it for the bulk copy.
-
- CryptoMetadata sourceCryptoMeta = srcColumnMetadata.get(srcColOrdinal).cryptoMeta;
- CryptoMetadata destCryptoMeta = destColumnMetadata.get(destColOrdinal).cryptoMeta;
-
- JDBCType srcJdbcType = (null != sourceCryptoMeta) ?
- sourceCryptoMeta.baseTypeInfo.getSSType().getJDBCType() :
- JDBCType.of(srcColumnMetadata.get(srcColOrdinal).jdbcType);
-
- SSType destSSType = (null != destCryptoMeta) ?
- destCryptoMeta.baseTypeInfo.getSSType() :
- destColumnMetadata.get(destColOrdinal).ssType;
+ private void validateDataTypeConversions(int srcColOrdinal,
+ int destColOrdinal) throws SQLServerException {
+ // Reducing unnecessary assignment to optimize for performance. This reduces code readability, but
+ // may be worth it for the bulk copy.
+
+ CryptoMetadata sourceCryptoMeta = srcColumnMetadata.get(srcColOrdinal).cryptoMeta;
+ CryptoMetadata destCryptoMeta = destColumnMetadata.get(destColOrdinal).cryptoMeta;
+
+ JDBCType srcJdbcType = (null != sourceCryptoMeta) ? sourceCryptoMeta.baseTypeInfo.getSSType().getJDBCType()
+ : JDBCType.of(srcColumnMetadata.get(srcColOrdinal).jdbcType);
+
+ SSType destSSType = (null != destCryptoMeta) ? destCryptoMeta.baseTypeInfo.getSSType() : destColumnMetadata.get(destColOrdinal).ssType;
// Throw if the source type cannot be converted to the destination type.
- if (!srcJdbcType.convertsTo(destSSType))
- {
+ if (!srcJdbcType.convertsTo(destSSType)) {
DataTypes.throwConversionError(srcJdbcType.toString(), destSSType.toString());
}
- }
-
-
- private String getDestTypeFromSrcType(int srcColIndx, int destColIndx, TDSWriter tdsWriter) throws SQLServerException
- {
- boolean isStreaming;
- SSType destSSType = (null != destColumnMetadata.get(destColIndx).cryptoMeta) ?
- destColumnMetadata.get(destColIndx).cryptoMeta.baseTypeInfo.getSSType() :
- destColumnMetadata.get(destColIndx).ssType ;
-
- int bulkJdbcType = 0, bulkPrecision = 0 , bulkScale = 0;
- int srcPrecision = 0;
-
- bulkJdbcType = srcColumnMetadata.get(srcColIndx).jdbcType;
- // For char/varchar precision is the size.
- bulkPrecision = srcPrecision = srcColumnMetadata.get(srcColIndx).precision;
- int destPrecision = destColumnMetadata.get(destColIndx).precision;
- bulkScale = srcColumnMetadata.get(srcColIndx).scale;
-
- CryptoMetadata destCryptoMeta = destColumnMetadata.get(destColIndx).cryptoMeta;
- if(null != destCryptoMeta || (null == destCryptoMeta && copyOptions.isAllowEncryptedValueModifications()))
- {
- // Encrypted columns are sent as binary data.
- tdsWriter.setCryptoMetaData(destColumnMetadata.get(destColIndx).cryptoMeta);
-
- // if destination is encrypted send metadata from destination and not from source
- if (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision)
- {
- return "varbinary(max)";
- }
- else
- {
- return "varbinary("+destColumnMetadata.get(destColIndx).precision+")";
- }
- }
-
- // isAllowEncryptedValueModifications is used only with source result set.
- if((null != sourceResultSet)
- && (null != destColumnMetadata.get(destColIndx).encryptionType)
- && copyOptions.isAllowEncryptedValueModifications())
- {
- return "varbinary("+bulkPrecision+")";
- }
- bulkPrecision = validateSourcePrecision(srcPrecision, bulkJdbcType, destPrecision);
-
- // if encrypted source and unencrypted destination combination
- if((sourceResultSet instanceof SQLServerResultSet) &&
- (connection.isColumnEncryptionSettingEnabled()))
- {
- bulkJdbcType = destColumnMetadata.get(destColIndx).jdbcType;
- bulkPrecision = destPrecision;
- bulkScale = destColumnMetadata.get(destColIndx).scale;
- }
-
- if ((java.sql.Types.NCHAR == bulkJdbcType) ||
- (java.sql.Types.NVARCHAR == bulkJdbcType) ||
- (java.sql.Types.LONGNVARCHAR == bulkJdbcType))
- {
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < srcPrecision) ||
- (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
- }
- else
- {
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < srcPrecision) ||
- (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision);
- }
-
- // SQL Server does not convert string to binary, we will have to explicitly convert before sending.
- if (Util.isCharType(bulkJdbcType) && Util.isBinaryType(destSSType))
- {
- if(isStreaming)
- return "varbinary(max)";
- else
- // Return binary(n) or varbinary(n) or varbinary(max) depending on destination type/precision.
- return destSSType.toString() +
- "(" +
- ((DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision) ? "max" : destPrecision) +
- ")";
- }
-
- switch(bulkJdbcType)
- {
- case java.sql.Types.INTEGER:
- return "int";
-
- case java.sql.Types.SMALLINT:
- return "smallint";
-
- case java.sql.Types.BIGINT:
- return "bigint";
-
- case java.sql.Types.BIT:
- return "bit";
-
- case java.sql.Types.TINYINT:
- return "tinyint";
-
- case java.sql.Types.DOUBLE:
- return "float";
-
- case java.sql.Types.REAL:
- return "real";
-
- case microsoft.sql.Types.MONEY:
- case microsoft.sql.Types.SMALLMONEY:
- case java.sql.Types.DECIMAL:
- return "decimal(" + bulkPrecision + ", " + bulkScale + ")";
-
- case java.sql.Types.NUMERIC:
- return "numeric(" + bulkPrecision + ", " + bulkScale + ")";
-
- case microsoft.sql.Types.GUID:
- case java.sql.Types.CHAR:
- // For char the value has to be between 0 to 8000.
- return "char(" + bulkPrecision + ")";
-
- case java.sql.Types.NCHAR:
- return "NCHAR(" + bulkPrecision + ")";
-
- case java.sql.Types.LONGVARCHAR:
- case java.sql.Types.VARCHAR:
- // Here the actual size of the varchar is used from the source table.
- // Doesn't need to match with the exact size of data or with the destination column size.
- if (isStreaming)
- {
- return "varchar(max)";
- }
- else
- {
- return "varchar(" + bulkPrecision + ")";
- }
-
- // For INSERT BULK operations, XMLTYPE is to be sent as NVARCHAR(N) or NVARCHAR(MAX) data type.
- // An error is produced if XMLTYPE is specified.
- case java.sql.Types.LONGNVARCHAR:
- case java.sql.Types.NVARCHAR:
- if (isStreaming)
- {
- return "NVARCHAR(MAX)";
- }
- else
- {
- return "NVARCHAR(" + bulkPrecision + ")";
- }
-
- case java.sql.Types.BINARY:
- // For binary the value has to be between 0 to 8000.
- return "binary(" + bulkPrecision + ")";
-
- case java.sql.Types.LONGVARBINARY:
- case java.sql.Types.VARBINARY:
- if (isStreaming)
- return "varbinary(max)";
- else
- return "varbinary(" + bulkPrecision + ")";
-
- case microsoft.sql.Types.DATETIME:
- case microsoft.sql.Types.SMALLDATETIME:
- case java.sql.Types.TIMESTAMP:
- switch (destSSType)
- {
- case SMALLDATETIME:
- if(null != sourceBulkRecord)
- {
- return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
- }
- else
- {
- return "smalldatetime";
- }
- case DATETIME:
- if(null != sourceBulkRecord)
- {
- return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
- }
- else
- {
- return "datetime";
- }
- default:
- // datetime2
- /*
- * If encrypted, varbinary will be returned before. The code will come here only if unencrypted.
- * For unencrypted bulk copy
- * if the source is CSV, we send the data as varchar and SQL Server will do the conversion.
- * if the source is ResultSet, we send the data as the corresponding temporal type.
- */
- if(null != sourceBulkRecord)
- {
- return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
- }
- else
- {
- return "datetime2(" + bulkScale + ")";
- }
- }
-
- case java.sql.Types.DATE:
- /*
- * If encrypted, varbinary will be returned before. The code will come here only if unencrypted.
- * For unencrypted bulk copy
- * if the source is CSV, we send the data as varchar and SQL Server will do the conversion.
- * if the source is ResultSet, we send the data as the corresponding temporal type.
- */
- if (null != sourceBulkRecord)
- {
- return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
- }
- else
- {
- return "date";
- }
-
- case java.sql.Types.TIME:
- /*
- * If encrypted, varbinary will be returned before. The code will come here only if unencrypted.
- * For unencrypted bulk copy
- * if the source is CSV, we send the data as varchar and SQL Server will do the conversion.
- * if the source is ResultSet, we send the data as the corresponding temporal type.
- */
- if(null != sourceBulkRecord)
- {
- return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
- }
- else
- {
- return "time(" + bulkScale + ")";
- }
-
- // Return DATETIMEOFFSET for TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE
- case 2013: //java.sql.Types.TIME_WITH_TIMEZONE
- case 2014: //java.sql.Types.TIMESTAMP_WITH_TIMEZONE
- return "datetimeoffset(" + bulkScale + ")";
-
- case microsoft.sql.Types.DATETIMEOFFSET:
- /*
- * If encrypted, varbinary will be returned before. The code will come here only if unencrypted.
- * For unencrypted bulk copy
- * if the source is CSV, we send the data as varchar and SQL Server will do the conversion.
- * if the source is ResultSet, we send the data as the corresponding temporal type.
- */
- if (null != sourceBulkRecord)
- {
- return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
- }
- else
- {
- return "datetimeoffset(" + bulkScale + ")";
- }
-
- default:
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
- Object[] msgArgs = {JDBCType.of(bulkJdbcType).toString().toLowerCase(Locale.ENGLISH)};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
- }
- }
- return null;
}
-
- private String createInsertBulkCommand(TDSWriter tdsWriter) throws SQLServerException
- {
- StringBuilder bulkCmd = new StringBuilder();
- List bulkOptions = new Vector();
- String endColumn=" , ";
- bulkCmd.append( "INSERT BULK " + destinationTableName + " (" );
-
- for (int i = 0; i < (columnMappings.size()); ++i)
- {
- if (i == columnMappings.size() - 1)
- {
- endColumn = " ) ";
+ private String getDestTypeFromSrcType(int srcColIndx,
+ int destColIndx,
+ TDSWriter tdsWriter) throws SQLServerException {
+ boolean isStreaming;
+ SSType destSSType = (null != destColumnMetadata.get(destColIndx).cryptoMeta)
+ ? destColumnMetadata.get(destColIndx).cryptoMeta.baseTypeInfo.getSSType() : destColumnMetadata.get(destColIndx).ssType;
+
+ int bulkJdbcType = 0, bulkPrecision = 0, bulkScale = 0;
+ int srcPrecision = 0;
+
+ bulkJdbcType = srcColumnMetadata.get(srcColIndx).jdbcType;
+ // For char/varchar precision is the size.
+ bulkPrecision = srcPrecision = srcColumnMetadata.get(srcColIndx).precision;
+ int destPrecision = destColumnMetadata.get(destColIndx).precision;
+ bulkScale = srcColumnMetadata.get(srcColIndx).scale;
+
+ CryptoMetadata destCryptoMeta = destColumnMetadata.get(destColIndx).cryptoMeta;
+ if (null != destCryptoMeta || (null == destCryptoMeta && copyOptions.isAllowEncryptedValueModifications())) {
+ // Encrypted columns are sent as binary data.
+ tdsWriter.setCryptoMetaData(destColumnMetadata.get(destColIndx).cryptoMeta);
+
+ // if destination is encrypted send metadata from destination and not from source
+ if (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision) {
+ return "varbinary(max)";
}
- ColumnMapping colMapping = columnMappings.get(i);
- String columnCollation = destColumnMetadata
- .get(columnMappings.get(i).destinationColumnOrdinal).collationName;
- String addCollate = "";
-
-
- String destType=getDestTypeFromSrcType(
- colMapping.sourceColumnOrdinal,
- colMapping.destinationColumnOrdinal,
- tdsWriter).toUpperCase(Locale.ENGLISH);
- if (null != columnCollation && columnCollation.trim().length() > 0)
- {
- // we are adding collate in command only for char and varchar
- if (null != destType &&
- (destType.toLowerCase().trim().startsWith("char") || destType.toLowerCase()
- .trim().startsWith("varchar")))
- addCollate = " COLLATE " + columnCollation;
+ else {
+ return "varbinary(" + destColumnMetadata.get(destColIndx).precision + ")";
}
- bulkCmd.append("[" +
- colMapping.destinationColumnName +
- "] "
- + destType
- + addCollate
- + endColumn);
- }
-
- if( true == copyOptions.isCheckConstraints() )
- {
- bulkOptions.add("CHECK_CONSTRAINTS");
- }
-
- if( true == copyOptions.isFireTriggers() )
- {
- bulkOptions.add("FIRE_TRIGGERS");
- }
-
- if( true == copyOptions.isKeepNulls() )
- {
- bulkOptions.add("KEEP_NULLS");
- }
-
- if( copyOptions.getBatchSize() > 0 )
- {
- bulkOptions.add("ROWS_PER_BATCH = " + copyOptions.getBatchSize());
- }
-
- if( true == copyOptions.isTableLock() )
- {
- bulkOptions.add("TABLOCK");
- }
-
- if( true == copyOptions.isAllowEncryptedValueModifications() )
- {
- bulkOptions.add("ALLOW_ENCRYPTED_VALUE_MODIFICATIONS");
- }
-
- Iterator it = bulkOptions.iterator();
- if(it.hasNext())
- {
- bulkCmd.append( " with (" );
- while(it.hasNext())
- {
- bulkCmd.append( it.next().toString());
- if(it.hasNext())
- {
- bulkCmd.append( ", ");
- }
- }
- bulkCmd.append( ")" );
- }
-
- if (loggerExternal.isLoggable(Level.FINER))
- loggerExternal.finer(this.toString() + " TDSCommand: " + bulkCmd);
+ }
- return bulkCmd.toString();
- }
-
+ // isAllowEncryptedValueModifications is used only with source result set.
+ if ((null != sourceResultSet) && (null != destColumnMetadata.get(destColIndx).encryptionType)
+ && copyOptions.isAllowEncryptedValueModifications()) {
+ return "varbinary(" + bulkPrecision + ")";
+ }
+ bulkPrecision = validateSourcePrecision(srcPrecision, bulkJdbcType, destPrecision);
- private boolean doInsertBulk(TDSCommand command) throws SQLServerException
- {
- if (copyOptions.isUseInternalTransaction())
- {
- // Begin a manual transaction for this batch.
- connection.setAutoCommit(false);
+ // if encrypted source and unencrypted destination combination
+ if ((sourceResultSet instanceof SQLServerResultSet) && (connection.isColumnEncryptionSettingEnabled())) {
+ bulkJdbcType = destColumnMetadata.get(destColIndx).jdbcType;
+ bulkPrecision = destPrecision;
+ bulkScale = destColumnMetadata.get(destColIndx).scale;
}
-
- // Create and send the initial command for bulk copy ("INSERT BULK ...").
- TDSWriter tdsWriter = command.startRequest(TDS.PKT_QUERY);
- String bulkCmd = createInsertBulkCommand(tdsWriter);
- tdsWriter.writeString(bulkCmd);
- TDSParser.parse(command.startResponse(), command.getLogContext());
-
- // Send the bulk data. This is the BulkLoadBCP TDS stream.
- tdsWriter = command.startRequest(TDS.PKT_BULK);
- boolean moreDataAvailable = false;
- try
- {
- // Write the COLUMNMETADATA token in the stream.
- writeColumnMetaData(tdsWriter);
-
- // Write all ROW tokens in the stream.
- moreDataAvailable = writeBatchData(tdsWriter);
- }
- catch (SQLServerException ex)
- {
- // Close the TDS packet before handling the exception
- writePacketDataDone(tdsWriter);
-
- // Send Attention packet to interrupt a complete request that was already sent to the server
- command.startRequest(TDS.PKT_CANCEL_REQ);
-
- TDSParser.parse(command.startResponse(), command.getLogContext());
- command.interrupt(ex.getMessage());
- command.onRequestComplete();
-
- throw ex;
- }
- finally
- {
- // reset the cryptoMeta in IOBuffer
- tdsWriter.setCryptoMetaData(null);
- }
- // Write the DONE token in the stream. We may have to append the DONE token with every packet that is sent.
- // For the current packets the driver does not generate a DONE token, but the BulkLoadBCP stream needs a DONE token
- // after every packet. For now add it manually here for one packet.
- // Note: This may break if more than one packet is sent.
- // This is an example from https://msdn.microsoft.com/en-us/library/dd340549.aspx
- writePacketDataDone(tdsWriter);
-
- // Send to the server and read response.
+ if ((java.sql.Types.NCHAR == bulkJdbcType) || (java.sql.Types.NVARCHAR == bulkJdbcType) || (java.sql.Types.LONGNVARCHAR == bulkJdbcType)) {
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < srcPrecision) || (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
+ }
+ else {
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < srcPrecision) || (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision);
+ }
+
+ // SQL Server does not convert string to binary, we will have to explicitly convert before sending.
+ if (Util.isCharType(bulkJdbcType) && Util.isBinaryType(destSSType)) {
+ if (isStreaming)
+ return "varbinary(max)";
+ else
+ // Return binary(n) or varbinary(n) or varbinary(max) depending on destination type/precision.
+ return destSSType.toString() + "(" + ((DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision) ? "max" : destPrecision) + ")";
+ }
+
+ switch (bulkJdbcType) {
+ case java.sql.Types.INTEGER:
+ return "int";
+
+ case java.sql.Types.SMALLINT:
+ return "smallint";
+
+ case java.sql.Types.BIGINT:
+ return "bigint";
+
+ case java.sql.Types.BIT:
+ return "bit";
+
+ case java.sql.Types.TINYINT:
+ return "tinyint";
+
+ case java.sql.Types.DOUBLE:
+ return "float";
+
+ case java.sql.Types.REAL:
+ return "real";
+
+ case microsoft.sql.Types.MONEY:
+ case microsoft.sql.Types.SMALLMONEY:
+ case java.sql.Types.DECIMAL:
+ return "decimal(" + bulkPrecision + ", " + bulkScale + ")";
+
+ case java.sql.Types.NUMERIC:
+ return "numeric(" + bulkPrecision + ", " + bulkScale + ")";
+
+ case microsoft.sql.Types.GUID:
+ case java.sql.Types.CHAR:
+ // For char the value has to be between 0 to 8000.
+ return "char(" + bulkPrecision + ")";
+
+ case java.sql.Types.NCHAR:
+ return "NCHAR(" + bulkPrecision + ")";
+
+ case java.sql.Types.LONGVARCHAR:
+ case java.sql.Types.VARCHAR:
+ // Here the actual size of the varchar is used from the source table.
+ // Doesn't need to match with the exact size of data or with the destination column size.
+ if (isStreaming) {
+ return "varchar(max)";
+ }
+ else {
+ return "varchar(" + bulkPrecision + ")";
+ }
+
+ // For INSERT BULK operations, XMLTYPE is to be sent as NVARCHAR(N) or NVARCHAR(MAX) data type.
+ // An error is produced if XMLTYPE is specified.
+ case java.sql.Types.LONGNVARCHAR:
+ case java.sql.Types.NVARCHAR:
+ if (isStreaming) {
+ return "NVARCHAR(MAX)";
+ }
+ else {
+ return "NVARCHAR(" + bulkPrecision + ")";
+ }
+
+ case java.sql.Types.BINARY:
+ // For binary the value has to be between 0 to 8000.
+ return "binary(" + bulkPrecision + ")";
+
+ case java.sql.Types.LONGVARBINARY:
+ case java.sql.Types.VARBINARY:
+ if (isStreaming)
+ return "varbinary(max)";
+ else
+ return "varbinary(" + bulkPrecision + ")";
+
+ case microsoft.sql.Types.DATETIME:
+ case microsoft.sql.Types.SMALLDATETIME:
+ case java.sql.Types.TIMESTAMP:
+ switch (destSSType) {
+ case SMALLDATETIME:
+ if (null != sourceBulkRecord) {
+ return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
+ }
+ else {
+ return "smalldatetime";
+ }
+ case DATETIME:
+ if (null != sourceBulkRecord) {
+ return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
+ }
+ else {
+ return "datetime";
+ }
+ default:
+ // datetime2
+ /*
+ * If encrypted, varbinary will be returned before. The code will come here only if unencrypted. For unencrypted bulk copy if
+ * the source is CSV, we send the data as varchar and SQL Server will do the conversion. if the source is ResultSet, we send
+ * the data as the corresponding temporal type.
+ */
+ if (null != sourceBulkRecord) {
+ return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
+ }
+ else {
+ return "datetime2(" + bulkScale + ")";
+ }
+ }
+
+ case java.sql.Types.DATE:
+ /*
+ * If encrypted, varbinary will be returned before. The code will come here only if unencrypted. For unencrypted bulk copy if the
+ * source is CSV, we send the data as varchar and SQL Server will do the conversion. if the source is ResultSet, we send the data as
+ * the corresponding temporal type.
+ */
+ if (null != sourceBulkRecord) {
+ return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
+ }
+ else {
+ return "date";
+ }
+
+ case java.sql.Types.TIME:
+ /*
+ * If encrypted, varbinary will be returned before. The code will come here only if unencrypted. For unencrypted bulk copy if the
+ * source is CSV, we send the data as varchar and SQL Server will do the conversion. if the source is ResultSet, we send the data as
+ * the corresponding temporal type.
+ */
+ if (null != sourceBulkRecord) {
+ return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
+ }
+ else {
+ return "time(" + bulkScale + ")";
+ }
+
+ // Return DATETIMEOFFSET for TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE
+ case 2013: // java.sql.Types.TIME_WITH_TIMEZONE
+ case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ return "datetimeoffset(" + bulkScale + ")";
+
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ /*
+ * If encrypted, varbinary will be returned before. The code will come here only if unencrypted. For unencrypted bulk copy if the
+ * source is CSV, we send the data as varchar and SQL Server will do the conversion. if the source is ResultSet, we send the data as
+ * the corresponding temporal type.
+ */
+ if (null != sourceBulkRecord) {
+ return "varchar(" + ((0 == bulkPrecision) ? destPrecision : bulkPrecision) + ")";
+ }
+ else {
+ return "datetimeoffset(" + bulkScale + ")";
+ }
+
+ default: {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
+ Object[] msgArgs = {JDBCType.of(bulkJdbcType).toString().toLowerCase(Locale.ENGLISH)};
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
+ }
+ }
+ return null;
+ }
+
+ private String createInsertBulkCommand(TDSWriter tdsWriter) throws SQLServerException {
+ StringBuilder bulkCmd = new StringBuilder();
+ List bulkOptions = new Vector();
+ String endColumn = " , ";
+ bulkCmd.append("INSERT BULK " + destinationTableName + " (");
+
+ for (int i = 0; i < (columnMappings.size()); ++i) {
+ if (i == columnMappings.size() - 1) {
+ endColumn = " ) ";
+ }
+ ColumnMapping colMapping = columnMappings.get(i);
+ String columnCollation = destColumnMetadata.get(columnMappings.get(i).destinationColumnOrdinal).collationName;
+ String addCollate = "";
+
+ String destType = getDestTypeFromSrcType(colMapping.sourceColumnOrdinal, colMapping.destinationColumnOrdinal, tdsWriter)
+ .toUpperCase(Locale.ENGLISH);
+ if (null != columnCollation && columnCollation.trim().length() > 0) {
+ // we are adding collate in command only for char and varchar
+ if (null != destType && (destType.toLowerCase().trim().startsWith("char") || destType.toLowerCase().trim().startsWith("varchar")))
+ addCollate = " COLLATE " + columnCollation;
+ }
+ bulkCmd.append("[" + colMapping.destinationColumnName + "] " + destType + addCollate + endColumn);
+ }
+
+ if (true == copyOptions.isCheckConstraints()) {
+ bulkOptions.add("CHECK_CONSTRAINTS");
+ }
+
+ if (true == copyOptions.isFireTriggers()) {
+ bulkOptions.add("FIRE_TRIGGERS");
+ }
+
+ if (true == copyOptions.isKeepNulls()) {
+ bulkOptions.add("KEEP_NULLS");
+ }
+
+ if (copyOptions.getBatchSize() > 0) {
+ bulkOptions.add("ROWS_PER_BATCH = " + copyOptions.getBatchSize());
+ }
+
+ if (true == copyOptions.isTableLock()) {
+ bulkOptions.add("TABLOCK");
+ }
+
+ if (true == copyOptions.isAllowEncryptedValueModifications()) {
+ bulkOptions.add("ALLOW_ENCRYPTED_VALUE_MODIFICATIONS");
+ }
+
+ Iterator it = bulkOptions.iterator();
+ if (it.hasNext()) {
+ bulkCmd.append(" with (");
+ while (it.hasNext()) {
+ bulkCmd.append(it.next().toString());
+ if (it.hasNext()) {
+ bulkCmd.append(", ");
+ }
+ }
+ bulkCmd.append(")");
+ }
+
+ if (loggerExternal.isLoggable(Level.FINER))
+ loggerExternal.finer(this.toString() + " TDSCommand: " + bulkCmd);
+
+ return bulkCmd.toString();
+ }
+
+ private boolean doInsertBulk(TDSCommand command) throws SQLServerException {
+ if (copyOptions.isUseInternalTransaction()) {
+ // Begin a manual transaction for this batch.
+ connection.setAutoCommit(false);
+ }
+
+ // Create and send the initial command for bulk copy ("INSERT BULK ...").
+ TDSWriter tdsWriter = command.startRequest(TDS.PKT_QUERY);
+ String bulkCmd = createInsertBulkCommand(tdsWriter);
+ tdsWriter.writeString(bulkCmd);
+ TDSParser.parse(command.startResponse(), command.getLogContext());
+
+ // Send the bulk data. This is the BulkLoadBCP TDS stream.
+ tdsWriter = command.startRequest(TDS.PKT_BULK);
+
+ boolean moreDataAvailable = false;
+ try {
+ // Write the COLUMNMETADATA token in the stream.
+ writeColumnMetaData(tdsWriter);
+
+ // Write all ROW tokens in the stream.
+ moreDataAvailable = writeBatchData(tdsWriter);
+ }
+ catch (SQLServerException ex) {
+ // Close the TDS packet before handling the exception
+ writePacketDataDone(tdsWriter);
+
+ // Send Attention packet to interrupt a complete request that was already sent to the server
+ command.startRequest(TDS.PKT_CANCEL_REQ);
+
+ TDSParser.parse(command.startResponse(), command.getLogContext());
+ command.interrupt(ex.getMessage());
+ command.onRequestComplete();
+
+ throw ex;
+ }
+ finally {
+ // reset the cryptoMeta in IOBuffer
+ tdsWriter.setCryptoMetaData(null);
+ }
+ // Write the DONE token in the stream. We may have to append the DONE token with every packet that is sent.
+ // For the current packets the driver does not generate a DONE token, but the BulkLoadBCP stream needs a DONE token
+ // after every packet. For now add it manually here for one packet.
+ // Note: This may break if more than one packet is sent.
+ // This is an example from https://msdn.microsoft.com/en-us/library/dd340549.aspx
+ writePacketDataDone(tdsWriter);
+
+ // Send to the server and read response.
TDSParser.parse(command.startResponse(), command.getLogContext());
-
- if (copyOptions.isUseInternalTransaction())
- {
- // Commit the transaction for this batch.
- connection.commit();
+
+ if (copyOptions.isUseInternalTransaction()) {
+ // Commit the transaction for this batch.
+ connection.commit();
}
-
+
return moreDataAvailable;
}
-
- private void writePacketDataDone(TDSWriter tdsWriter) throws SQLServerException
- {
+
+ private void writePacketDataDone(TDSWriter tdsWriter) throws SQLServerException {
// This is an example from https://msdn.microsoft.com/en-us/library/dd340549.aspx
tdsWriter.writeByte((byte) 0xFD);
tdsWriter.writeLong(0);
tdsWriter.writeInt(0);
}
-
+
/*
* Helper method to throw a SQLServerExeption with the invalidArgument message and given argument.
*/
- private void throwInvalidArgument(String argument) throws SQLServerException
- {
+ private void throwInvalidArgument(String argument) throws SQLServerException {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidArgument"));
Object[] msgArgs = {argument};
SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, false);
}
-
+
/*
* Helper method to throw a SQLServerExeption with the errorConvertingValue message and given arguments.
*/
- private void throwInvalidJavaToJDBC(String javaClassName, int jdbcType) throws SQLServerException
- {
+ private void throwInvalidJavaToJDBC(String javaClassName,
+ int jdbcType) throws SQLServerException {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue"));
throw new SQLServerException(form.format(new Object[] {javaClassName, jdbcType}), null, 0, null);
}
-
+
/*
* The bulk copy operation
*/
- private void writeToServer() throws SQLServerException
- {
- if (connection.isClosed())
- {
- SQLServerException.makeFromDriverError(
- null,
- null,
- SQLServerException.getErrString("R_connectionIsClosed"),
- SQLServerException.EXCEPTION_XOPEN_CONNECTION_DOES_NOT_EXIST,
- false);
- }
-
+ private void writeToServer() throws SQLServerException {
+ if (connection.isClosed()) {
+ SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_connectionIsClosed"),
+ SQLServerException.EXCEPTION_XOPEN_CONNECTION_DOES_NOT_EXIST, false);
+ }
+
long start = System.currentTimeMillis();
if (loggerExternal.isLoggable(Level.FINER))
loggerExternal.finer(this.toString() + " Start writeToServer: " + start);
-
+
getDestinationMetadata();
- // Get source metadata in the BulkColumnMetaData object so that we can access metadata
+ // Get source metadata in the BulkColumnMetaData object so that we can access metadata
// from the same object for both ResultSet and File.
getSourceMetadata();
-
+
validateColumnMappings();
-
+
sendBulkLoadBCP();
long end = System.currentTimeMillis();
- if (loggerExternal.isLoggable(Level.FINER))
- {
+ if (loggerExternal.isLoggable(Level.FINER)) {
loggerExternal.finer(this.toString() + " End writeToServer: " + end);
int seconds = (int) ((end - start) / 1000L);
loggerExternal.finer(this.toString() + "Time elapsed: " + seconds + " seconds");
}
}
-
-
-
- private void validateStringBinaryLengths(Object colValue, int srcCol, int destCol) throws SQLServerException
- {
- int sourcePrecision = 0;
- int destPrecision = destColumnMetadata.get(destCol).precision;
- int srcJdbcType = srcColumnMetadata.get(srcCol).jdbcType;
- SSType destSSType = destColumnMetadata.get(destCol).ssType;
-
- if ((Util.isCharType(srcJdbcType) && Util.isCharType(destSSType)) ||
- (Util.isBinaryType(srcJdbcType) && Util.isBinaryType(destSSType)))
- {
- if (colValue instanceof String)
- {
- sourcePrecision = ((String) colValue).length();
- }
- else if (colValue instanceof byte[])
- {
- sourcePrecision = ((byte[]) colValue).length;
- }
- else
- {
- return;
- }
-
- if (sourcePrecision > destPrecision)
- {
- String srcType = JDBCType.of(srcJdbcType) + "(" + sourcePrecision +")";
- String destType = destSSType.toString() + "(" + destPrecision +")";
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcType, destType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- }
- }
-
- /*
+
+ private void validateStringBinaryLengths(Object colValue,
+ int srcCol,
+ int destCol) throws SQLServerException {
+ int sourcePrecision = 0;
+ int destPrecision = destColumnMetadata.get(destCol).precision;
+ int srcJdbcType = srcColumnMetadata.get(srcCol).jdbcType;
+ SSType destSSType = destColumnMetadata.get(destCol).ssType;
+
+ if ((Util.isCharType(srcJdbcType) && Util.isCharType(destSSType)) || (Util.isBinaryType(srcJdbcType) && Util.isBinaryType(destSSType))) {
+ if (colValue instanceof String) {
+ sourcePrecision = ((String) colValue).length();
+ }
+ else if (colValue instanceof byte[]) {
+ sourcePrecision = ((byte[]) colValue).length;
+ }
+ else {
+ return;
+ }
+
+ if (sourcePrecision > destPrecision) {
+ String srcType = JDBCType.of(srcJdbcType) + "(" + sourcePrecision + ")";
+ String destType = destSSType.toString() + "(" + destPrecision + ")";
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcType, destType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ }
+ }
+
+ /*
* Retrieves the column metadata for the destination table (and saves it for later)
*/
- private void getDestinationMetadata() throws SQLServerException
- {
- if (null == destinationTableName)
- {
+ private void getDestinationMetadata() throws SQLServerException {
+ if (null == destinationTableName) {
SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDestinationTable"), null, false);
}
-
+
SQLServerResultSet rs = null;
- SQLServerResultSet rsMoreMetaData = null;
-
+ SQLServerResultSet rsMoreMetaData = null;
+
try {
// Get destination metadata
- rs =
- ((SQLServerStatement) connection.createStatement()).executeQueryInternal(
- "SET FMTONLY ON SELECT * FROM " +
- destinationTableName +
- " SET FMTONLY OFF ");
-
- destColumnCount = rs.getMetaData().getColumnCount();
- destColumnMetadata = new HashMap();
- destCekTable = rs.getCekTable();
-
- if(!connection.getServerSupportsColumnEncryption()){
- //SQL server prior to 2016 does not support encryption_type
- rsMoreMetaData =
- ((SQLServerStatement) connection.createStatement()).executeQueryInternal(
- "select collation_name from sys.columns where "
- + "object_id=OBJECT_ID('"+destinationTableName+"') "
- + "order by column_id ASC");
- }
- else{
- rsMoreMetaData =
- ((SQLServerStatement) connection.createStatement()).executeQueryInternal(
- "select collation_name, encryption_type from sys.columns where "
- + "object_id=OBJECT_ID('"+destinationTableName+"') "
- + "order by column_id ASC");
- }
- for (int i = 1; i <= destColumnCount; ++i){
- if (rsMoreMetaData.next())
- {
- if(!connection.getServerSupportsColumnEncryption()){
- destColumnMetadata.put(
- i,
- new BulkColumnMetaData(rs.getColumn(i), rsMoreMetaData.getString("collation_name"), null));
- }
- else{
- destColumnMetadata.put(
- i,
- new BulkColumnMetaData(rs.getColumn(i),
- rsMoreMetaData.getString("collation_name"),
- rsMoreMetaData.getString("encryption_type")));
- }
- }
- else
- {
- destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i)));
- }
- }
- }
+ rs = ((SQLServerStatement) connection.createStatement())
+ .executeQueryInternal("SET FMTONLY ON SELECT * FROM " + destinationTableName + " SET FMTONLY OFF ");
+
+ destColumnCount = rs.getMetaData().getColumnCount();
+ destColumnMetadata = new HashMap();
+ destCekTable = rs.getCekTable();
+
+ if (!connection.getServerSupportsColumnEncryption()) {
+ // SQL server prior to 2016 does not support encryption_type
+ rsMoreMetaData = ((SQLServerStatement) connection.createStatement())
+ .executeQueryInternal("select collation_name from sys.columns where " + "object_id=OBJECT_ID('" + destinationTableName + "') "
+ + "order by column_id ASC");
+ }
+ else {
+ rsMoreMetaData = ((SQLServerStatement) connection.createStatement())
+ .executeQueryInternal("select collation_name, encryption_type from sys.columns where " + "object_id=OBJECT_ID('"
+ + destinationTableName + "') " + "order by column_id ASC");
+ }
+ for (int i = 1; i <= destColumnCount; ++i) {
+ if (rsMoreMetaData.next()) {
+ if (!connection.getServerSupportsColumnEncryption()) {
+ destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i), rsMoreMetaData.getString("collation_name"), null));
+ }
+ else {
+ destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i), rsMoreMetaData.getString("collation_name"),
+ rsMoreMetaData.getString("encryption_type")));
+ }
+ }
+ else {
+ destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i)));
+ }
+ }
+ }
catch (SQLException e) {
// Unable to retrieve metadata for destination
throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
- }
- finally
- {
- if (null != rs) rs.close();
- if (null != rsMoreMetaData) rsMoreMetaData.close();
- }
+ }
+ finally {
+ if (null != rs)
+ rs.close();
+ if (null != rsMoreMetaData)
+ rsMoreMetaData.close();
+ }
}
/*
- * Retrieves the column metadata for the source (and saves it for later).
- * Retrieving source metadata in BulkColumnMetaData object helps to access source metadata
- * from the same place for both ResultSet and File.
+ * Retrieves the column metadata for the source (and saves it for later). Retrieving source metadata in BulkColumnMetaData object helps to access
+ * source metadata from the same place for both ResultSet and File.
*/
- private void getSourceMetadata() throws SQLServerException
- {
- srcColumnMetadata = new HashMap();
- int currentColumn;
- if (null != sourceResultSet)
- {
- try {
- srcColumnCount = sourceResultSetMetaData.getColumnCount();
- for (int i = 1; i <= srcColumnCount; ++i)
- {
- srcColumnMetadata.put(i, new BulkColumnMetaData(
- sourceResultSetMetaData.getColumnName(i),
- ((ResultSetMetaData.columnNoNulls == sourceResultSetMetaData.isNullable(i)) ? false : true),
- sourceResultSetMetaData.getPrecision(i),
- sourceResultSetMetaData.getScale(i),
- sourceResultSetMetaData.getColumnType(i),
- null
- ));
- }
+ private void getSourceMetadata() throws SQLServerException {
+ srcColumnMetadata = new HashMap();
+ int currentColumn;
+ if (null != sourceResultSet) {
+ try {
+ srcColumnCount = sourceResultSetMetaData.getColumnCount();
+ for (int i = 1; i <= srcColumnCount; ++i) {
+ srcColumnMetadata.put(i,
+ new BulkColumnMetaData(sourceResultSetMetaData.getColumnName(i),
+ ((ResultSetMetaData.columnNoNulls == sourceResultSetMetaData.isNullable(i)) ? false : true),
+ sourceResultSetMetaData.getPrecision(i), sourceResultSetMetaData.getScale(i),
+ sourceResultSetMetaData.getColumnType(i), null));
+ }
}
catch (SQLException e) {
// Unable to retrieve meta data for destination
throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
- }
+ }
}
- else if (null != sourceBulkRecord)
- {
+ else if (null != sourceBulkRecord) {
Set columnOrdinals = sourceBulkRecord.getColumnOrdinals();
- srcColumnCount = columnOrdinals.size();
- if (0 == srcColumnCount)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
- }
- else
- {
- Iterator columnsIterator = columnOrdinals.iterator();
- while(columnsIterator.hasNext())
- {
- currentColumn = columnsIterator.next();
- srcColumnMetadata.put(currentColumn, new BulkColumnMetaData(
- sourceBulkRecord.getColumnName(currentColumn),
- true,
- sourceBulkRecord.getPrecision(currentColumn),
- sourceBulkRecord.getScale(currentColumn),
- sourceBulkRecord.getColumnType(currentColumn),
- ((sourceBulkRecord instanceof SQLServerBulkCSVFileRecord)
- ? ((SQLServerBulkCSVFileRecord) sourceBulkRecord).getColumnDateTimeFormatter(currentColumn)
- : null
- )
- ));
- }
- }
- }
- else
- {
+ srcColumnCount = columnOrdinals.size();
+ if (0 == srcColumnCount) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
+ }
+ else {
+ Iterator columnsIterator = columnOrdinals.iterator();
+ while (columnsIterator.hasNext()) {
+ currentColumn = columnsIterator.next();
+ srcColumnMetadata.put(currentColumn,
+ new BulkColumnMetaData(sourceBulkRecord.getColumnName(currentColumn), true, sourceBulkRecord.getPrecision(currentColumn),
+ sourceBulkRecord.getScale(currentColumn), sourceBulkRecord.getColumnType(currentColumn),
+ ((sourceBulkRecord instanceof SQLServerBulkCSVFileRecord)
+ ? ((SQLServerBulkCSVFileRecord) sourceBulkRecord).getColumnDateTimeFormatter(currentColumn) : null)));
+ }
+ }
+ }
+ else {
// Unable to retrieve meta data for source
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
}
}
-
- /*
- * Oracle 12c database returns precision = 0 for char/varchar data types.
+
+ /*
+ * Oracle 12c database returns precision = 0 for char/varchar data types.
*/
- private int validateSourcePrecision(int srcPrecision, int srcJdbcType, int destPrecision)
- {
- if ( (1 > srcPrecision) && Util.isCharType(srcJdbcType) )
- {
- srcPrecision = destPrecision;
- }
- return srcPrecision;
+ private int validateSourcePrecision(int srcPrecision,
+ int srcJdbcType,
+ int destPrecision) {
+ if ((1 > srcPrecision) && Util.isCharType(srcJdbcType)) {
+ srcPrecision = destPrecision;
+ }
+ return srcPrecision;
}
/*
* Validates the column mappings
*/
- private void validateColumnMappings() throws SQLServerException
- {
- try
- {
- if (columnMappings.isEmpty())
- {
- // Check that the source schema matches the destination schema
+ private void validateColumnMappings() throws SQLServerException {
+ try {
+ if (columnMappings.isEmpty()) {
+ // Check that the source schema matches the destination schema
// If the number of columns are different, there is an obvious mismatch.
- if (destColumnCount != srcColumnCount)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_schemaMismatch"), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
+ if (destColumnCount != srcColumnCount) {
+ throw new SQLServerException(SQLServerException.getErrString("R_schemaMismatch"), SQLState.COL_NOT_FOUND, DriverError.NOT_SET,
+ null);
}
-
+
// Could validate that the data types can be converted, but easier to leave that check for later
-
+
// Generate default column mappings
ColumnMapping cm;
- for (int i = 1; i <= srcColumnCount; ++i)
- {
- // Only skip identity column mapping if KEEP IDENTITY OPTION is FALSE
- if ( !(destColumnMetadata.get(i).isIdentity && !copyOptions.isKeepIdentity()) )
- {
- cm = new ColumnMapping(i,i);
+ for (int i = 1; i <= srcColumnCount; ++i) {
+ // Only skip identity column mapping if KEEP IDENTITY OPTION is FALSE
+ if (!(destColumnMetadata.get(i).isIdentity && !copyOptions.isKeepIdentity())) {
+ cm = new ColumnMapping(i, i);
// The vector destColumnMetadata is indexed from 1 to be consistent with column indexing.
cm.destinationColumnName = destColumnMetadata.get(i).columnName;
- columnMappings.add( cm );
- }
+ columnMappings.add(cm);
+ }
}
// if no mapping is provided for csv file and metadata is missing for some columns throw error
- if(null != sourceBulkRecord)
- {
+ if (null != sourceBulkRecord) {
Set columnOrdinals = sourceBulkRecord.getColumnOrdinals();
Iterator columnsIterator = columnOrdinals.iterator();
int j = 1;
- while(columnsIterator.hasNext())
- {
+ while (columnsIterator.hasNext()) {
int currentOrdinal = columnsIterator.next();
- if(j != currentOrdinal)
- {
+ if (j != currentOrdinal) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
Object[] msgArgs = {currentOrdinal};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
@@ -2091,117 +1816,95 @@ private void validateColumnMappings() throws SQLServerException
}
}
}
- else
- {
+ else {
int numMappings = columnMappings.size();
ColumnMapping cm;
-
- // Check that the destination names or ordinals exist
- for (int i = 0; i < numMappings; ++i)
- {
+
+ // Check that the destination names or ordinals exist
+ for (int i = 0; i < numMappings; ++i) {
cm = columnMappings.get(i);
// Validate that the destination column name exists if the ordinal is not provided.
- if (-1 == cm.destinationColumnOrdinal)
- {
+ if (-1 == cm.destinationColumnOrdinal) {
boolean foundColumn = false;
-
- for (int j = 1; j <= destColumnCount; ++j)
- {
- if (destColumnMetadata.get(j).columnName.equals(cm.destinationColumnName))
- {
+
+ for (int j = 1; j <= destColumnCount; ++j) {
+ if (destColumnMetadata.get(j).columnName.equals(cm.destinationColumnName)) {
foundColumn = true;
cm.destinationColumnOrdinal = j;
break;
}
}
-
- if (!foundColumn)
- {
+
+ if (!foundColumn) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
Object[] msgArgs = {cm.destinationColumnName};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
- }
+ }
}
- else if (0 > cm.destinationColumnOrdinal || destColumnCount < cm.destinationColumnOrdinal)
- {
+ else if (0 > cm.destinationColumnOrdinal || destColumnCount < cm.destinationColumnOrdinal) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
Object[] msgArgs = {cm.destinationColumnOrdinal};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
}
- else
- {
+ else {
cm.destinationColumnName = destColumnMetadata.get(cm.destinationColumnOrdinal).columnName;
}
}
// Check that the required source ordinals are present and within range
- for (int i = 0; i < numMappings; ++i)
- {
+ for (int i = 0; i < numMappings; ++i) {
cm = columnMappings.get(i);
// Validate that the source column name exists if the ordinal is not provided.
- if (-1 == cm.sourceColumnOrdinal)
- {
+ if (-1 == cm.sourceColumnOrdinal) {
boolean foundColumn = false;
- if (null != sourceResultSet)
- {
+ if (null != sourceResultSet) {
int columns = sourceResultSetMetaData.getColumnCount();
- for (int j = 1; j <= columns; ++j)
- {
- if (sourceResultSetMetaData.getColumnName(j).equals(cm.sourceColumnName))
- {
+ for (int j = 1; j <= columns; ++j) {
+ if (sourceResultSetMetaData.getColumnName(j).equals(cm.sourceColumnName)) {
foundColumn = true;
cm.sourceColumnOrdinal = j;
break;
}
}
- }
- else
- {
+ }
+ else {
Set columnOrdinals = sourceBulkRecord.getColumnOrdinals();
Iterator columnsIterator = columnOrdinals.iterator();
- while(columnsIterator.hasNext())
- {
+ while (columnsIterator.hasNext()) {
int currentColumn = columnsIterator.next();
- if (sourceBulkRecord.getColumnName(currentColumn).equals(cm.sourceColumnName))
- {
+ if (sourceBulkRecord.getColumnName(currentColumn).equals(cm.sourceColumnName)) {
foundColumn = true;
cm.sourceColumnOrdinal = currentColumn;
break;
}
}
}
-
- if (!foundColumn)
- {
+
+ if (!foundColumn) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
Object[] msgArgs = {cm.sourceColumnName};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
- }
+ }
}
else // Validate that the source column is in range
{
boolean columnOutOfRange = true;
- if (null != sourceResultSet)
- {
+ if (null != sourceResultSet) {
int columns = sourceResultSetMetaData.getColumnCount();
- if (0 < cm.sourceColumnOrdinal && columns >= cm.sourceColumnOrdinal)
- {
+ if (0 < cm.sourceColumnOrdinal && columns >= cm.sourceColumnOrdinal) {
columnOutOfRange = false;
}
- }
- else
- {
+ }
+ else {
// check if the ordinal is in SQLServerBulkCSVFileRecord.addColumnMetadata()
- if (srcColumnMetadata.containsKey(cm.sourceColumnOrdinal))
- {
- columnOutOfRange = false;
- }
+ if (srcColumnMetadata.containsKey(cm.sourceColumnOrdinal)) {
+ columnOutOfRange = false;
+ }
}
-
- if (columnOutOfRange)
- {
+
+ if (columnOutOfRange) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
Object[] msgArgs = {cm.sourceColumnOrdinal};
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET, null);
@@ -2209,8 +1912,7 @@ else if (0 > cm.destinationColumnOrdinal || destColumnCount < cm.destinationColu
}
// Remove mappings for identity column if KEEP IDENTITY OPTION is FALSE
- if ( destColumnMetadata.get(cm.destinationColumnOrdinal).isIdentity && !copyOptions.isKeepIdentity() )
- {
+ if (destColumnMetadata.get(cm.destinationColumnOrdinal).isIdentity && !copyOptions.isKeepIdentity()) {
columnMappings.remove(i);
numMappings--;
i--;
@@ -2218,1475 +1920,1254 @@ else if (0 > cm.destinationColumnOrdinal || destColumnCount < cm.destinationColu
}
}
}
- catch(SQLException e)
- {
+ catch (SQLException e) {
// Let the column mapping validations go straight through as a single exception
- if ((e instanceof SQLServerException) && (null != e.getSQLState()) && e.getSQLState().equals(SQLState.COL_NOT_FOUND.getSQLStateCode()))
- {
+ if ((e instanceof SQLServerException) && (null != e.getSQLState()) && e.getSQLState().equals(SQLState.COL_NOT_FOUND.getSQLStateCode())) {
throw (SQLServerException) e;
}
-
+
// Difficulty retrieving column names from source or destination
throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
}
-
+
// Throw an exception is Column mapping is empty.
- // If keep identity option is set to be false and not other mapping is explicitly provided.
- if(columnMappings.isEmpty())
- {
+ // If keep identity option is set to be false and not other mapping is explicitly provided.
+ if (columnMappings.isEmpty()) {
throw new SQLServerException(null, SQLServerException.getErrString("R_BulkColumnMappingsIsEmpty"), null, 0, false);
}
}
-
- private void writeNullToTdsWriter(TDSWriter tdsWriter, int srcJdbcType, boolean isStreaming) throws SQLServerException
- {
-
- switch (srcJdbcType)
- {
- case java.sql.Types.CHAR:
- case java.sql.Types.NCHAR:
- case java.sql.Types.VARCHAR:
- case java.sql.Types.NVARCHAR:
- case java.sql.Types.BINARY:
- case java.sql.Types.VARBINARY:
- case java.sql.Types.LONGVARCHAR:
- case java.sql.Types.LONGNVARCHAR:
- case java.sql.Types.LONGVARBINARY:
- if (isStreaming)
- {
- tdsWriter.writeLong(PLPInputStream.PLP_NULL);
- }
- else
- {
- tdsWriter.writeByte((byte) 0xFF);
- tdsWriter.writeByte((byte) 0xFF);
- }
- return;
- case java.sql.Types.BIT:
- case java.sql.Types.TINYINT:
- case java.sql.Types.SMALLINT:
+
+ private void writeNullToTdsWriter(TDSWriter tdsWriter,
+ int srcJdbcType,
+ boolean isStreaming) throws SQLServerException {
+
+ switch (srcJdbcType) {
+ case java.sql.Types.CHAR:
+ case java.sql.Types.NCHAR:
+ case java.sql.Types.VARCHAR:
+ case java.sql.Types.NVARCHAR:
+ case java.sql.Types.BINARY:
+ case java.sql.Types.VARBINARY:
+ case java.sql.Types.LONGVARCHAR:
+ case java.sql.Types.LONGNVARCHAR:
+ case java.sql.Types.LONGVARBINARY:
+ if (isStreaming) {
+ tdsWriter.writeLong(PLPInputStream.PLP_NULL);
+ }
+ else {
+ tdsWriter.writeByte((byte) 0xFF);
+ tdsWriter.writeByte((byte) 0xFF);
+ }
+ return;
+ case java.sql.Types.BIT:
+ case java.sql.Types.TINYINT:
+ case java.sql.Types.SMALLINT:
+ case java.sql.Types.INTEGER:
+ case java.sql.Types.BIGINT:
+ case java.sql.Types.REAL:
+ case java.sql.Types.DOUBLE:
+ case java.sql.Types.DECIMAL:
+ case java.sql.Types.NUMERIC:
+ case java.sql.Types.TIMESTAMP:
+ case java.sql.Types.DATE:
+ case java.sql.Types.TIME:
+ case 2013: // java.sql.Types.TIME_WITH_TIMEZONE
+ case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ tdsWriter.writeByte((byte) 0x00);
+ return;
+ default:
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
+ Object[] msgArgs = {JDBCType.of(srcJdbcType).toString().toLowerCase(Locale.ENGLISH)};
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
+ }
+ }
+
+ private void writeColumnToTdsWriter(TDSWriter tdsWriter,
+ int bulkPrecision,
+ int bulkScale,
+ int bulkJdbcType,
+ boolean bulkNullable, // should it be destNullable instead?
+ int srcColOrdinal,
+ int destColOrdinal,
+ boolean isStreaming,
+ Object colValue) throws SQLServerException {
+ SSType destSSType = destColumnMetadata.get(destColOrdinal).ssType;
+
+ bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType, destColumnMetadata.get(destColOrdinal).precision);
+
+ CryptoMetadata sourceCryptoMeta = srcColumnMetadata.get(srcColOrdinal).cryptoMeta;
+ if (((null != destColumnMetadata.get(destColOrdinal).encryptionType) && copyOptions.isAllowEncryptedValueModifications())
+ // if destination is encrypted send varbinary explicitly(needed for unencrypted source)
+ || (null != destColumnMetadata.get(destColOrdinal).cryptoMeta)) {
+ bulkJdbcType = java.sql.Types.VARBINARY;
+ }
+ /*
+ * if source is encrypted and destination is unenecrypted, use destination sql type to send since there is no way of finding if source is
+ * encrypted without accessing the resultset, send destination type if source resultset set is of type SQLServer and encryption is enabled
+ */
+ else if (null != sourceCryptoMeta) {
+ bulkJdbcType = destColumnMetadata.get(destColOrdinal).jdbcType;
+ bulkScale = destColumnMetadata.get(destColOrdinal).scale;
+ }
+ else if (null != sourceBulkRecord) {
+ // Bulk copy from CSV and destination is not encrypted. In this case, we send the temporal types as varchar and
+ // SQL Server does the conversion. If destination is encrypted, then temporal types can not be sent as varchar.
+ switch (bulkJdbcType) {
+ case java.sql.Types.DATE:
+ case java.sql.Types.TIME:
+ case java.sql.Types.TIMESTAMP:
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ bulkJdbcType = java.sql.Types.VARCHAR;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // We are sending the data using JDBCType and not using SSType as SQL Server will automatically do the conversion.
+ switch (bulkJdbcType) {
+ case java.sql.Types.INTEGER:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (bulkNullable) {
+ tdsWriter.writeByte((byte) 0x04);
+ }
+ tdsWriter.writeInt((int) colValue);
+ }
+ break;
+
+ case java.sql.Types.SMALLINT:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (bulkNullable) {
+ tdsWriter.writeByte((byte) 0x02);
+ }
+ tdsWriter.writeShort(((Number) colValue).shortValue());
+ }
+ break;
+
+ case java.sql.Types.BIGINT:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (bulkNullable) {
+ tdsWriter.writeByte((byte) 0x08);
+ }
+ tdsWriter.writeLong((long) colValue);
+ }
+ break;
+
+ case java.sql.Types.BIT:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (bulkNullable) {
+ tdsWriter.writeByte((byte) 0x01);
+ }
+ tdsWriter.writeByte((byte) (((Boolean) colValue).booleanValue() ? 1 : 0));
+ }
+ break;
+
+ case java.sql.Types.TINYINT:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (bulkNullable) {
+ tdsWriter.writeByte((byte) 0x01);
+ }
+ // TINYINT JDBC type is returned as a short in getObject.
+ // MYSQL returns TINYINT as an Integer. Convert it to a Number to get the short value.
+ tdsWriter.writeByte((byte) ((((Number) colValue).shortValue()) & 0xFF));
+
+ }
+ break;
+
+ case java.sql.Types.DOUBLE:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (bulkNullable) {
+ tdsWriter.writeByte((byte) 0x08);
+ }
+ tdsWriter.writeDouble((double) colValue);
+ }
+ break;
+
+ case java.sql.Types.REAL:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (bulkNullable) {
+ tdsWriter.writeByte((byte) 0x04);
+ }
+ tdsWriter.writeReal((float) colValue);
+ }
+ break;
+
+ case microsoft.sql.Types.MONEY:
+ case microsoft.sql.Types.SMALLMONEY:
+ case java.sql.Types.DECIMAL:
+ case java.sql.Types.NUMERIC:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ tdsWriter.writeBigDecimal((BigDecimal) colValue, bulkJdbcType, bulkPrecision);
+ }
+ break;
+
+ case microsoft.sql.Types.GUID:
+ case java.sql.Types.LONGVARCHAR:
+ case java.sql.Types.CHAR: // Fixed-length, non-Unicode string data.
+ case java.sql.Types.VARCHAR: // Variable-length, non-Unicode string data.
+ if (isStreaming) // PLP
+ {
+ // PLP_BODY rule in TDS
+ // Use ResultSet.getString for non-streaming data and ResultSet.getCharacterStream() for streaming data,
+ // so that if the source data source does not have streaming enabled, the smaller size data will still work.
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ // Send length as unknown.
+ tdsWriter.writeLong(PLPInputStream.UNKNOWN_PLP_LEN);
+ try {
+ // Read and Send the data as chunks
+ // VARBINARYMAX --- only when streaming.
+ Reader reader = null;
+ if (colValue instanceof Reader) {
+ reader = (Reader) colValue;
+ }
+ else {
+ reader = new StringReader(colValue.toString());
+ }
+
+ if ((SSType.BINARY == destSSType) || (SSType.VARBINARY == destSSType) || (SSType.VARBINARYMAX == destSSType)
+ || (SSType.IMAGE == destSSType)) {
+ tdsWriter.writeNonUnicodeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, true, null);
+ }
+ else {
+ SQLCollation destCollation = destColumnMetadata.get(destColOrdinal).collation;
+ if (null != destCollation) {
+ tdsWriter.writeNonUnicodeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, false, destCollation.getCharset());
+ }
+ else {
+ tdsWriter.writeNonUnicodeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, false, null);
+ }
+ }
+ reader.close();
+ }
+ catch (IOException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
+ }
+ }
+ }
+ else // Non-PLP
+ {
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ String colValueStr = colValue.toString();
+ if ((SSType.BINARY == destSSType) || (SSType.VARBINARY == destSSType)) {
+ byte[] bytes = null;
+ try {
+ bytes = ParameterUtils.HexToBin(colValueStr);
+ }
+ catch (SQLServerException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
+ }
+ tdsWriter.writeShort((short) bytes.length);
+ tdsWriter.writeBytes(bytes);
+ }
+ else {
+ tdsWriter.writeShort((short) (colValueStr.length()));
+ // converting string into destination collation using Charset
+
+ SQLCollation destCollation = destColumnMetadata.get(destColOrdinal).collation;
+ if (null != destCollation) {
+ tdsWriter.writeBytes(colValueStr.getBytes(destColumnMetadata.get(destColOrdinal).collation.getCharset()));
+
+ }
+ else {
+ tdsWriter.writeBytes(colValueStr.getBytes());
+ }
+ }
+ }
+ }
+ break;
+
+ /*
+ * The length value associated with these data types is specified within a USHORT. see MS-TDS.pdf page 38. However, nchar(n) nvarchar(n)
+ * supports n = 1 .. 4000 (see MSDN SQL 2014, SQL 2016 Transact-SQL) NVARCHAR/NCHAR/LONGNVARCHAR is not compatible with BINARY/VARBINARY
+ * as specified in enum UpdaterConversion of DataTypes.java
+ */
+ case java.sql.Types.LONGNVARCHAR:
+ case java.sql.Types.NCHAR:
+ case java.sql.Types.NVARCHAR:
+ if (isStreaming) {
+ // PLP_BODY rule in TDS
+ // Use ResultSet.getString for non-streaming data and ResultSet.getNCharacterStream() for streaming data,
+ // so that if the source data source does not have streaming enabled, the smaller size data will still work.
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ // Send length as unknown.
+ tdsWriter.writeLong(PLPInputStream.UNKNOWN_PLP_LEN);
+ try {
+ // Read and Send the data as chunks.
+ Reader reader = null;
+ if (colValue instanceof Reader) {
+ reader = (Reader) colValue;
+ }
+ else {
+ reader = new StringReader(colValue.toString());
+ }
+
+ // writeReader is unicode.
+ tdsWriter.writeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, true);
+ reader.close();
+ }
+ catch (IOException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
+ }
+ }
+ }
+ else {
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ int stringLength = colValue.toString().length();
+ byte[] typevarlen = new byte[2];
+ typevarlen[0] = (byte) (2 * stringLength & 0xFF);
+ typevarlen[1] = (byte) ((2 * stringLength >> 8) & 0xFF);
+ tdsWriter.writeBytes(typevarlen);
+ tdsWriter.writeString(colValue.toString());
+ }
+ }
+ break;
+
+ case java.sql.Types.LONGVARBINARY:
+ case java.sql.Types.BINARY:
+ case java.sql.Types.VARBINARY:
+ if (isStreaming) // PLP
+ {
+ // Check for null separately for streaming and non-streaming data types, there could be source data sources who
+ // does not support streaming data.
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ // Send length as unknown.
+ tdsWriter.writeLong(PLPInputStream.UNKNOWN_PLP_LEN);
+ try {
+ // Read and Send the data as chunks
+ InputStream iStream = null;
+ if (colValue instanceof InputStream) {
+ iStream = (InputStream) colValue;
+ }
+ else {
+ if (colValue instanceof byte[]) {
+ iStream = new ByteArrayInputStream((byte[]) colValue);
+ }
+ else
+ iStream = new ByteArrayInputStream(ParameterUtils.HexToBin(colValue.toString()));
+ }
+ // We do not need to check for null values here as it is already checked above.
+ tdsWriter.writeStream(iStream, DataTypes.UNKNOWN_STREAM_LENGTH, true);
+ iStream.close();
+ }
+ catch (IOException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
+ }
+ }
+ }
+ else // Non-PLP
+ {
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ byte[] srcBytes;
+ if (colValue instanceof byte[]) {
+ srcBytes = (byte[]) colValue;
+ }
+ else {
+ try {
+ srcBytes = ParameterUtils.HexToBin(colValue.toString());
+ }
+ catch (SQLServerException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
+ }
+ }
+ tdsWriter.writeShort((short) srcBytes.length);
+ tdsWriter.writeBytes(srcBytes);
+ }
+ }
+ break;
+
+ case microsoft.sql.Types.DATETIME:
+ case microsoft.sql.Types.SMALLDATETIME:
+ case java.sql.Types.TIMESTAMP:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ switch (destSSType) {
+ case SMALLDATETIME:
+ if (bulkNullable)
+ tdsWriter.writeByte((byte) 0x04);
+ tdsWriter.writeSmalldatetime(colValue.toString());
+ break;
+ case DATETIME:
+ if (bulkNullable)
+ tdsWriter.writeByte((byte) 0x08);
+ tdsWriter.writeDatetime(colValue.toString());
+ break;
+ default: // DATETIME2
+ if (bulkNullable) {
+ if (2 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x06);
+ else if (4 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x07);
+ else
+ tdsWriter.writeByte((byte) 0x08);
+ }
+ String timeStampValue = colValue.toString();
+ tdsWriter.writeTime(java.sql.Timestamp.valueOf(timeStampValue), bulkScale);
+ // Send only the date part
+ tdsWriter.writeDate(timeStampValue.substring(0, timeStampValue.lastIndexOf(' ')));
+ }
+ }
+ break;
+
+ case java.sql.Types.DATE:
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ tdsWriter.writeByte((byte) 0x03);
+ tdsWriter.writeDate(colValue.toString());
+ }
+ break;
+
+ case java.sql.Types.TIME:
+ // java.sql.Types.TIME allows maximum of 3 fractional second precision
+ // SQL Server time(n) allows maximum of 7 fractional second precision, to avoid truncation
+ // values are read as java.sql.Types.TIMESTAMP if srcJdbcType is java.sql.Types.TIME
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (2 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x03);
+ else if (4 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x04);
+ else
+ tdsWriter.writeByte((byte) 0x05);
+
+ tdsWriter.writeTime((java.sql.Timestamp) colValue, bulkScale);
+ }
+ break;
+
+ case 2013: // java.sql.Types.TIME_WITH_TIMEZONE
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (2 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x08);
+ else if (4 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x09);
+ else
+ tdsWriter.writeByte((byte) 0x0A);
+
+ tdsWriter.writeOffsetTimeWithTimezone((OffsetTime) colValue, bulkScale);
+ }
+ break;
+
+ case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (2 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x08);
+ else if (4 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x09);
+ else
+ tdsWriter.writeByte((byte) 0x0A);
+
+ tdsWriter.writeOffsetDateTimeWithTimezone((OffsetDateTime) colValue, bulkScale);
+ }
+ break;
+
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ // We can safely cast the result set to a SQLServerResultSet as the DatetimeOffset type is only available in the JDBC driver.
+ if (null == colValue) {
+ writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
+ }
+ else {
+ if (2 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x08);
+ else if (4 >= bulkScale)
+ tdsWriter.writeByte((byte) 0x09);
+ else
+ tdsWriter.writeByte((byte) 0x0A);
+
+ tdsWriter.writeDateTimeOffset(colValue, bulkScale, destSSType);
+ }
+ break;
+
+ default:
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
+ Object[] msgArgs = {JDBCType.of(bulkJdbcType).toString().toLowerCase(Locale.ENGLISH)};
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
+ } // End of switch
+ }
+
+ private Object readColumnFromResultSet(int srcColOrdinal,
+ int srcJdbcType,
+ boolean isStreaming,
+ boolean isDestEncrypted) throws SQLServerException {
+ CryptoMetadata srcCryptoMeta = null;
+
+ // if source is encrypted read its baseTypeInfo
+ if ((sourceResultSet instanceof SQLServerResultSet)
+ && (null != (srcCryptoMeta = ((SQLServerResultSet) sourceResultSet).getterGetColumn(srcColOrdinal).getCryptoMetadata()))) {
+ srcJdbcType = srcCryptoMeta.baseTypeInfo.getSSType().getJDBCType().asJavaSqlType();
+ BulkColumnMetaData temp = srcColumnMetadata.get(srcColOrdinal);
+ srcColumnMetadata.put(srcColOrdinal, new BulkColumnMetaData(temp, srcCryptoMeta));
+ }
+
+ try {
+ // We are sending the data using JDBCType and not using SSType as SQL Server will automatically do the conversion.
+ switch (srcJdbcType) {
+ // For numeric types (like, int, smallint, bit, ...) use getObject() instead of get* methods as get* methods
+ // return 0 if the value is null.
+ // Change getObject to get* as other data sources may not have similar implementation.
case java.sql.Types.INTEGER:
+ case java.sql.Types.SMALLINT:
case java.sql.Types.BIGINT:
- case java.sql.Types.REAL:
+ case java.sql.Types.BIT:
+ case java.sql.Types.TINYINT:
case java.sql.Types.DOUBLE:
+ case java.sql.Types.REAL:
+ return sourceResultSet.getObject(srcColOrdinal);
+
+ case microsoft.sql.Types.MONEY:
+ case microsoft.sql.Types.SMALLMONEY:
case java.sql.Types.DECIMAL:
case java.sql.Types.NUMERIC:
- case java.sql.Types.TIMESTAMP:
- case java.sql.Types.DATE:
- case java.sql.Types.TIME:
- case 2013: //java.sql.Types.TIME_WITH_TIMEZONE
- case 2014: //java.sql.Types.TIMESTAMP_WITH_TIMEZONE
+ return sourceResultSet.getBigDecimal(srcColOrdinal);
+
+ case microsoft.sql.Types.GUID:
+ case java.sql.Types.LONGVARCHAR:
+ case java.sql.Types.CHAR: // Fixed-length, non-Unicode string data.
+ case java.sql.Types.VARCHAR: // Variable-length, non-Unicode string data.
+
+ // PLP if stream type and both the source and destination are not encrypted
+ // This is because AE does not support streaming types.
+ // Therefore an encrypted source or destination means the data must not actually be streaming data
+ if (isStreaming && !isDestEncrypted && (null == srcCryptoMeta)) // PLP
+ {
+ // Use ResultSet.getString for non-streaming data and ResultSet.getCharacterStream() for streaming data,
+ // so that if the source data source does not have streaming enabled, the smaller size data will still work.
+ return sourceResultSet.getCharacterStream(srcColOrdinal);
+ }
+ else // Non-PLP
+ {
+ return sourceResultSet.getString(srcColOrdinal);
+ }
+
+ case java.sql.Types.LONGNVARCHAR:
+ case java.sql.Types.NCHAR:
+ case java.sql.Types.NVARCHAR:
+ // PLP if stream type and both the source and destination are not encrypted
+ // This is because AE does not support streaming types.
+ // Therefore an encrypted source or destination means the data must not actually be streaming data
+ if (isStreaming && !isDestEncrypted && (null == srcCryptoMeta)) // PLP
+ {
+ // Use ResultSet.getString for non-streaming data and ResultSet.getNCharacterStream() for streaming data,
+ // so that if the source data source does not have streaming enabled, the smaller size data will still work.
+ return sourceResultSet.getNCharacterStream(srcColOrdinal);
+ }
+ else {
+ return sourceResultSet.getObject(srcColOrdinal);
+ }
+
+ case java.sql.Types.LONGVARBINARY:
+ case java.sql.Types.BINARY:
+ case java.sql.Types.VARBINARY:
+ // PLP if stream type and both the source and destination are not encrypted
+ // This is because AE does not support streaming types.
+ // Therefore an encrypted source or destination means the data must not actually be streaming data
+ if (isStreaming && !isDestEncrypted && (null == srcCryptoMeta)) // PLP
+ {
+ return sourceResultSet.getBinaryStream(srcColOrdinal);
+ }
+ else // Non-PLP
+ {
+ return sourceResultSet.getBytes(srcColOrdinal);
+ }
+
+ case microsoft.sql.Types.DATETIME:
+ case microsoft.sql.Types.SMALLDATETIME:
+ case java.sql.Types.TIMESTAMP:
+ return sourceResultSet.getTimestamp(srcColOrdinal);
+
+ case java.sql.Types.DATE:
+ return sourceResultSet.getDate(srcColOrdinal);
+
+ case java.sql.Types.TIME:
+ // java.sql.Types.TIME allows maximum of 3 fractional second precision
+ // SQL Server time(n) allows maximum of 7 fractional second precision, to avoid truncation
+ // values are read as java.sql.Types.TIMESTAMP if srcJdbcType is java.sql.Types.TIME
+ return sourceResultSet.getTimestamp(srcColOrdinal);
+
case microsoft.sql.Types.DATETIMEOFFSET:
- tdsWriter.writeByte((byte) 0x00);
- return;
+ // We can safely cast the result set to a SQLServerResultSet as the DatetimeOffset type is only available in the JDBC driver.
+ return ((SQLServerResultSet) sourceResultSet).getDateTimeOffset(srcColOrdinal);
+
default:
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
Object[] msgArgs = {JDBCType.of(srcJdbcType).toString().toLowerCase(Locale.ENGLISH)};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
+ SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
+ // This return will never be executed, but it is needed as Eclipse complains otherwise.
+ return null;
+ } // End of switch
+ }// End of Try
+ catch (SQLException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
+ }
+ }
+
+ /*
+ * Reads the given column from the result set current row and writes the data to tdsWriter.
+ */
+ private void writeColumn(TDSWriter tdsWriter,
+ int srcColOrdinal,
+ int destColOrdinal,
+ Object colValue) throws SQLServerException {
+ int srcPrecision = 0, srcScale = 0, destPrecision = 0, srcJdbcType = 0;
+ SSType destSSType = null;
+ boolean isStreaming = false, srcNullable;
+ srcPrecision = srcColumnMetadata.get(srcColOrdinal).precision;
+ srcScale = srcColumnMetadata.get(srcColOrdinal).scale;
+ srcJdbcType = srcColumnMetadata.get(srcColOrdinal).jdbcType;
+ srcNullable = srcColumnMetadata.get(srcColOrdinal).isNullable;
+
+ destPrecision = destColumnMetadata.get(destColOrdinal).precision;
+
+ if ((java.sql.Types.NCHAR == srcJdbcType) || (java.sql.Types.NVARCHAR == srcJdbcType) || (java.sql.Types.LONGNVARCHAR == srcJdbcType)) {
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < srcPrecision) || (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
+ }
+ else {
+ isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < srcPrecision) || (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision);
+ }
+
+ CryptoMetadata destCryptoMeta = destColumnMetadata.get(destColOrdinal).cryptoMeta;
+ if (null != destCryptoMeta) {
+ destSSType = destCryptoMeta.baseTypeInfo.getSSType();
+ }
+
+ // Get the cell from the source result set if we are copying from result set.
+ // If we are copying from a bulk reader colValue will be passed as the argument.
+ if (null != sourceResultSet) {
+ colValue = readColumnFromResultSet(srcColOrdinal, srcJdbcType, isStreaming, (null != destCryptoMeta));
+ validateStringBinaryLengths(colValue, srcColOrdinal, destColOrdinal);
+
+ // if AllowEncryptedValueModifications is set send varbinary read from source without checking type conversion
+ if (!((copyOptions.isAllowEncryptedValueModifications())
+ // normalizationCheck() will be called for encrypted columns so skip this validation
+ || ((null != destCryptoMeta) && (null != colValue)))) {
+ validateDataTypeConversions(srcColOrdinal, destColOrdinal);
}
- }
-
- private void writeColumnToTdsWriter(
- TDSWriter tdsWriter,
- int bulkPrecision,
- int bulkScale,
- int bulkJdbcType,
- boolean bulkNullable, // should it be destNullable instead?
- int srcColOrdinal,
- int destColOrdinal,
- boolean isStreaming,
- Object colValue
- ) throws SQLServerException
- {
- SSType destSSType = destColumnMetadata.get(destColOrdinal).ssType;
-
- bulkPrecision = validateSourcePrecision(bulkPrecision, bulkJdbcType, destColumnMetadata.get(destColOrdinal).precision);
-
- CryptoMetadata sourceCryptoMeta = srcColumnMetadata.get(srcColOrdinal).cryptoMeta;
- if(((null != destColumnMetadata.get(destColOrdinal).encryptionType)
- && copyOptions.isAllowEncryptedValueModifications())
- // if destination is encrypted send varbinary explicitly(needed for unencrypted source)
- || (null != destColumnMetadata.get(destColOrdinal).cryptoMeta))
- {
- bulkJdbcType = java.sql.Types.VARBINARY;
- }
- /*
- * if source is encrypted and destination is unenecrypted, use destination sql type to send
- * since there is no way of finding if source is encrypted without accessing the resultset, send destination type if
- * source resultset set is of type SQLServer and encryption is enabled
- */
- else if(null != sourceCryptoMeta)
- {
- bulkJdbcType = destColumnMetadata.get(destColOrdinal).jdbcType;
- bulkScale = destColumnMetadata.get(destColOrdinal).scale;
- }
- else if (null != sourceBulkRecord)
- {
- // Bulk copy from CSV and destination is not encrypted. In this case, we send the temporal types as varchar and
- // SQL Server does the conversion. If destination is encrypted, then temporal types can not be sent as varchar.
- switch(bulkJdbcType)
- {
- case java.sql.Types.DATE:
- case java.sql.Types.TIME:
- case java.sql.Types.TIMESTAMP:
- case microsoft.sql.Types.DATETIMEOFFSET:
- bulkJdbcType = java.sql.Types.VARCHAR;
- break;
- default:
- break;
- }
- }
-
- // We are sending the data using JDBCType and not using SSType as SQL Server will automatically do the conversion.
- switch(bulkJdbcType)
- {
- case java.sql.Types.INTEGER:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if (bulkNullable)
- {
- tdsWriter.writeByte((byte) 0x04);
- }
- tdsWriter.writeInt((int) colValue);
- }
- break;
-
- case java.sql.Types.SMALLINT:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if (bulkNullable)
- {
- tdsWriter.writeByte((byte) 0x02);
- }
- tdsWriter.writeShort(((Number) colValue).shortValue());
- }
- break;
-
- case java.sql.Types.BIGINT:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if (bulkNullable)
- {
- tdsWriter.writeByte((byte) 0x08);
- }
- tdsWriter.writeLong((long) colValue);
- }
- break;
-
- case java.sql.Types.BIT:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if (bulkNullable)
- {
- tdsWriter.writeByte((byte) 0x01);
- }
- tdsWriter.writeByte((byte) (((Boolean) colValue).booleanValue() ? 1 : 0));
- }
- break;
-
- case java.sql.Types.TINYINT:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if (bulkNullable)
- {
- tdsWriter.writeByte((byte) 0x01);
- }
- // TINYINT JDBC type is returned as a short in getObject.
- // MYSQL returns TINYINT as an Integer. Convert it to a Number to get the short value.
- tdsWriter.writeByte((byte) ((((Number) colValue).shortValue()) & 0xFF));
+ }
+ else if ((null != sourceBulkRecord) && (null != destCryptoMeta)) {
+ // From CSV to encrypted column. Convert to respective object.
+ if ((java.sql.Types.DATE == srcJdbcType) || (java.sql.Types.TIME == srcJdbcType) || (java.sql.Types.TIMESTAMP == srcJdbcType)
+ || (microsoft.sql.Types.DATETIMEOFFSET == srcJdbcType) || (2013 == srcJdbcType) || (2014 == srcJdbcType)) {
+ colValue = getTemporalObjectFromCSV(colValue, srcJdbcType, srcColOrdinal);
+ }
+ else if ((java.sql.Types.NUMERIC == srcJdbcType) || (java.sql.Types.DECIMAL == srcJdbcType)) {
+ int baseDestPrecision = destCryptoMeta.baseTypeInfo.getPrecision();
+ int baseDestScale = destCryptoMeta.baseTypeInfo.getScale();
+ if ((srcScale != baseDestScale) || (srcPrecision != baseDestPrecision)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ String src = JDBCType.of(srcJdbcType) + "(" + srcPrecision + "," + srcScale + ")";
+ String dest = destSSType + "(" + baseDestPrecision + "," + baseDestScale + ")";
+ Object[] msgArgs = {src, dest};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ }
+ }
- }
- break;
-
- case java.sql.Types.DOUBLE:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if (bulkNullable)
- {
- tdsWriter.writeByte((byte) 0x08);
- }
- tdsWriter.writeDouble((double) colValue);
- }
- break;
-
- case java.sql.Types.REAL:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if (bulkNullable)
- {
- tdsWriter.writeByte((byte) 0x04);
- }
- tdsWriter.writeReal((float) colValue);
- }
- break;
-
- case microsoft.sql.Types.MONEY:
- case microsoft.sql.Types.SMALLMONEY:
- case java.sql.Types.DECIMAL:
- case java.sql.Types.NUMERIC:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- tdsWriter.writeBigDecimal((BigDecimal) colValue, bulkJdbcType, bulkPrecision);
- }
- break;
-
- case microsoft.sql.Types.GUID:
- case java.sql.Types.LONGVARCHAR:
- case java.sql.Types.CHAR: // Fixed-length, non-Unicode string data.
- case java.sql.Types.VARCHAR: // Variable-length, non-Unicode string data.
- if (isStreaming) //PLP
- {
- // PLP_BODY rule in TDS
- // Use ResultSet.getString for non-streaming data and ResultSet.getCharacterStream() for streaming data,
- // so that if the source data source does not have streaming enabled, the smaller size data will still work.
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- // Send length as unknown.
- tdsWriter.writeLong(PLPInputStream.UNKNOWN_PLP_LEN);
- try
- {
- // Read and Send the data as chunks
- // VARBINARYMAX --- only when streaming.
- Reader reader = null;
- if (colValue instanceof Reader)
- {
- reader = (Reader) colValue;
- }
- else
- {
- reader = new StringReader(colValue.toString());
- }
-
- if ((SSType.BINARY == destSSType) || (SSType.VARBINARY == destSSType) || (SSType.VARBINARYMAX == destSSType) || (SSType.IMAGE == destSSType))
- {
- tdsWriter.writeNonUnicodeReader(
- reader,
- DataTypes.UNKNOWN_STREAM_LENGTH,
- true,
- null);
- }
- else
- {
- SQLCollation destCollation = destColumnMetadata
- .get(destColOrdinal).collation;
- if (null != destCollation)
- {
- tdsWriter.writeNonUnicodeReader(
- reader,
- DataTypes.UNKNOWN_STREAM_LENGTH,
- false,
- destCollation.getCharset());
- }
- else
- {
- tdsWriter.writeNonUnicodeReader(
- reader,
- DataTypes.UNKNOWN_STREAM_LENGTH,
- false,
- null);
- }
- }
- reader.close();
- }
- catch(IOException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
- }
- }
- }
- else //Non-PLP
- {
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- String colValueStr = colValue.toString();
- if ((SSType.BINARY == destSSType) || (SSType.VARBINARY == destSSType))
- {
- byte[] bytes = null;
- try
- {
- bytes = ParameterUtils.HexToBin(colValueStr);
- }
- catch(SQLServerException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
- }
- tdsWriter.writeShort((short) bytes.length);
- tdsWriter.writeBytes(bytes);
- }
- else
- {
- tdsWriter.writeShort((short) (colValueStr.length()));
- // converting string into destination collation using Charset
-
- SQLCollation destCollation = destColumnMetadata
- .get(destColOrdinal).collation;
- if (null != destCollation)
- {
- tdsWriter.writeBytes(colValueStr.getBytes(
- destColumnMetadata.get(destColOrdinal).collation
- .getCharset()));
+ CryptoMetadata srcCryptoMeta = srcColumnMetadata.get(srcColOrdinal).cryptoMeta;
+ // If destination is encrypted column, transparently encrypt the data
+ if ((null != destCryptoMeta) && (null != colValue)) {
+ JDBCType baseSrcJdbcType = (null != srcCryptoMeta)
+ ? srcColumnMetadata.get(srcColOrdinal).cryptoMeta.baseTypeInfo.getSSType().getJDBCType() : JDBCType.of(srcJdbcType);
- }
- else
- {
- tdsWriter.writeBytes(colValueStr.getBytes());
- }
- }
- }
- }
- break;
-
- /*
- * The length value associated with these data types is specified within a USHORT.
- * see MS-TDS.pdf page 38.
- * However, nchar(n) nvarchar(n) supports n = 1 .. 4000
- * (see MSDN SQL 2014, SQL 2016 Transact-SQL)
- * NVARCHAR/NCHAR/LONGNVARCHAR is not compatible with BINARY/VARBINARY as specified in enum UpdaterConversion of DataTypes.java
- */
- case java.sql.Types.LONGNVARCHAR:
- case java.sql.Types.NCHAR:
- case java.sql.Types.NVARCHAR:
- if (isStreaming)
- {
- // PLP_BODY rule in TDS
- // Use ResultSet.getString for non-streaming data and ResultSet.getNCharacterStream() for streaming data,
- // so that if the source data source does not have streaming enabled, the smaller size data will still work.
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- // Send length as unknown.
- tdsWriter.writeLong(PLPInputStream.UNKNOWN_PLP_LEN);
- try
- {
- // Read and Send the data as chunks.
- Reader reader = null;
- if (colValue instanceof Reader)
- {
- reader = (Reader) colValue;
- }
- else
- {
- reader = new StringReader(colValue.toString());
- }
-
- // writeReader is unicode.
- tdsWriter.writeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, true);
- reader.close();
- }
- catch(IOException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
- }
- }
- }
- else
- {
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- int stringLength = colValue.toString().length();
- byte[] typevarlen = new byte[2];
- typevarlen[0] = (byte) (2*stringLength & 0xFF);
- typevarlen[1] = (byte) ((2*stringLength >> 8) & 0xFF);
- tdsWriter.writeBytes(typevarlen);
- tdsWriter.writeString(colValue.toString());
- }
- }
- break;
-
- case java.sql.Types.LONGVARBINARY:
- case java.sql.Types.BINARY:
- case java.sql.Types.VARBINARY:
- if (isStreaming) // PLP
- {
- // Check for null separately for streaming and non-streaming data types, there could be source data sources who
- // does not support streaming data.
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- // Send length as unknown.
- tdsWriter.writeLong(PLPInputStream.UNKNOWN_PLP_LEN);
- try
- {
- // Read and Send the data as chunks
- InputStream iStream = null;
- if (colValue instanceof InputStream)
- {
- iStream = (InputStream) colValue;
- }
- else
- {
- if (colValue instanceof byte[])
- {
- iStream = new ByteArrayInputStream((byte[]) colValue);
- }
- else
- iStream = new ByteArrayInputStream(ParameterUtils.HexToBin(colValue.toString()));
- }
- // We do not need to check for null values here as it is already checked above.
- tdsWriter.writeStream(iStream, DataTypes.UNKNOWN_STREAM_LENGTH, true);
- iStream.close();
- }
- catch(IOException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
- }
- }
- }
- else // Non-PLP
- {
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- byte[] srcBytes;
- if (colValue instanceof byte[])
- {
- srcBytes = (byte[]) colValue;
- }
- else
- {
- try
- {
- srcBytes = ParameterUtils.HexToBin(colValue.toString());
- }
- catch(SQLServerException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
- }
- }
- tdsWriter.writeShort((short) srcBytes.length);
- tdsWriter.writeBytes(srcBytes);
- }
- }
- break;
-
- case microsoft.sql.Types.DATETIME:
- case microsoft.sql.Types.SMALLDATETIME:
- case java.sql.Types.TIMESTAMP:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- switch (destSSType)
- {
- case SMALLDATETIME:
- if (bulkNullable)
- tdsWriter.writeByte((byte) 0x04);
- tdsWriter.writeSmalldatetime(colValue.toString());
- break;
- case DATETIME:
- if (bulkNullable)
- tdsWriter.writeByte((byte) 0x08);
- tdsWriter.writeDatetime(colValue.toString());
- break;
- default: // DATETIME2
- if (bulkNullable)
- {
- if(2 >= bulkScale)
- tdsWriter.writeByte((byte) 0x06);
- else if(4 >= bulkScale)
- tdsWriter.writeByte((byte) 0x07);
- else
- tdsWriter.writeByte((byte) 0x08);
- }
- String timeStampValue = colValue.toString();
- tdsWriter.writeTime(java.sql.Timestamp.valueOf(timeStampValue),bulkScale);
- // Send only the date part
- tdsWriter.writeDate(timeStampValue.substring(0, timeStampValue.lastIndexOf(' ')));
- }
- }
- break;
-
- case java.sql.Types.DATE:
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- tdsWriter.writeByte((byte) 0x03);
- tdsWriter.writeDate(colValue.toString());
- }
- break;
-
- case java.sql.Types.TIME:
- // java.sql.Types.TIME allows maximum of 3 fractional second precision
- // SQL Server time(n) allows maximum of 7 fractional second precision, to avoid truncation
- // values are read as java.sql.Types.TIMESTAMP if srcJdbcType is java.sql.Types.TIME
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if(2 >= bulkScale)
- tdsWriter.writeByte((byte) 0x03);
- else if(4 >= bulkScale)
- tdsWriter.writeByte((byte) 0x04);
- else
- tdsWriter.writeByte((byte) 0x05);
-
- tdsWriter.writeTime((java.sql.Timestamp) colValue,bulkScale);
- }
- break;
-
- case 2013: //java.sql.Types.TIME_WITH_TIMEZONE
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if(2 >= bulkScale)
- tdsWriter.writeByte((byte) 0x08);
- else if(4 >= bulkScale)
- tdsWriter.writeByte((byte) 0x09);
- else
- tdsWriter.writeByte((byte) 0x0A);
-
- tdsWriter.writeOffsetTimeWithTimezone((OffsetTime)colValue, bulkScale);
- }
- break;
-
- case 2014: //java.sql.Types.TIMESTAMP_WITH_TIMEZONE
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if(2 >= bulkScale)
- tdsWriter.writeByte((byte) 0x08);
- else if(4 >= bulkScale)
- tdsWriter.writeByte((byte) 0x09);
- else
- tdsWriter.writeByte((byte) 0x0A);
-
- tdsWriter.writeOffsetDateTimeWithTimezone((OffsetDateTime)colValue, bulkScale);
- }
- break;
-
- case microsoft.sql.Types.DATETIMEOFFSET:
- // We can safely cast the result set to a SQLServerResultSet as the DatetimeOffset type is only available in the JDBC driver.
- if (null == colValue)
- {
- writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming);
- }
- else
- {
- if(2 >= bulkScale)
- tdsWriter.writeByte((byte) 0x08);
- else if(4 >= bulkScale)
- tdsWriter.writeByte((byte) 0x09);
- else
- tdsWriter.writeByte((byte) 0x0A);
-
- tdsWriter.writeDateTimeOffset(colValue, bulkScale, destSSType);
- }
- break;
-
- default:
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
- Object[] msgArgs = {JDBCType.of(bulkJdbcType).toString().toLowerCase(Locale.ENGLISH)};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
- } // End of switch
- }
-
- private Object readColumnFromResultSet(
- int srcColOrdinal,
- int srcJdbcType,
- boolean isStreaming,
- boolean isDestEncrypted
- ) throws SQLServerException
- {
- CryptoMetadata srcCryptoMeta = null;
-
- // if source is encrypted read its baseTypeInfo
- if((sourceResultSet instanceof SQLServerResultSet) &&
- (null != ( srcCryptoMeta = ((SQLServerResultSet) sourceResultSet).getterGetColumn(srcColOrdinal).getCryptoMetadata())))
- {
- srcJdbcType = srcCryptoMeta.baseTypeInfo.getSSType().getJDBCType().asJavaSqlType();
- BulkColumnMetaData temp = srcColumnMetadata.get(srcColOrdinal);
- srcColumnMetadata.put(srcColOrdinal,new BulkColumnMetaData(temp,srcCryptoMeta));
- }
-
- try{
- // We are sending the data using JDBCType and not using SSType as SQL Server will automatically do the conversion.
- switch(srcJdbcType)
- {
- // For numeric types (like, int, smallint, bit, ...) use getObject() instead of get* methods as get* methods
- // return 0 if the value is null.
- // Change getObject to get* as other data sources may not have similar implementation.
- case java.sql.Types.INTEGER:
- case java.sql.Types.SMALLINT:
- case java.sql.Types.BIGINT:
- case java.sql.Types.BIT:
- case java.sql.Types.TINYINT:
- case java.sql.Types.DOUBLE:
- case java.sql.Types.REAL:
- return sourceResultSet.getObject(srcColOrdinal);
-
- case microsoft.sql.Types.MONEY:
- case microsoft.sql.Types.SMALLMONEY:
- case java.sql.Types.DECIMAL:
- case java.sql.Types.NUMERIC:
- return sourceResultSet.getBigDecimal(srcColOrdinal);
-
- case microsoft.sql.Types.GUID:
- case java.sql.Types.LONGVARCHAR:
- case java.sql.Types.CHAR: // Fixed-length, non-Unicode string data.
- case java.sql.Types.VARCHAR: // Variable-length, non-Unicode string data.
-
- // PLP if stream type and both the source and destination are not encrypted
- // This is because AE does not support streaming types.
- // Therefore an encrypted source or destination means the data must not actually be streaming data
- if (isStreaming && !isDestEncrypted && (null == srcCryptoMeta)) //PLP
- {
- // Use ResultSet.getString for non-streaming data and ResultSet.getCharacterStream() for streaming data,
- // so that if the source data source does not have streaming enabled, the smaller size data will still work.
- return sourceResultSet.getCharacterStream(srcColOrdinal);
- }
- else //Non-PLP
- {
- return sourceResultSet.getString(srcColOrdinal);
- }
-
- case java.sql.Types.LONGNVARCHAR:
- case java.sql.Types.NCHAR:
- case java.sql.Types.NVARCHAR:
- // PLP if stream type and both the source and destination are not encrypted
- // This is because AE does not support streaming types.
- // Therefore an encrypted source or destination means the data must not actually be streaming data
- if (isStreaming && !isDestEncrypted && (null == srcCryptoMeta)) //PLP
- {
- // Use ResultSet.getString for non-streaming data and ResultSet.getNCharacterStream() for streaming data,
- // so that if the source data source does not have streaming enabled, the smaller size data will still work.
- return sourceResultSet.getNCharacterStream(srcColOrdinal);
- }
- else
- {
- return sourceResultSet.getObject(srcColOrdinal);
- }
-
- case java.sql.Types.LONGVARBINARY:
- case java.sql.Types.BINARY:
- case java.sql.Types.VARBINARY:
- // PLP if stream type and both the source and destination are not encrypted
- // This is because AE does not support streaming types.
- // Therefore an encrypted source or destination means the data must not actually be streaming data
- if (isStreaming && !isDestEncrypted && (null == srcCryptoMeta)) // PLP
- {
- return sourceResultSet.getBinaryStream(srcColOrdinal);
- }
- else // Non-PLP
- {
- return sourceResultSet.getBytes(srcColOrdinal);
- }
-
- case microsoft.sql.Types.DATETIME:
- case microsoft.sql.Types.SMALLDATETIME:
- case java.sql.Types.TIMESTAMP:
- return sourceResultSet.getTimestamp(srcColOrdinal);
-
- case java.sql.Types.DATE:
- return sourceResultSet.getDate(srcColOrdinal);
-
- case java.sql.Types.TIME:
- // java.sql.Types.TIME allows maximum of 3 fractional second precision
- // SQL Server time(n) allows maximum of 7 fractional second precision, to avoid truncation
- // values are read as java.sql.Types.TIMESTAMP if srcJdbcType is java.sql.Types.TIME
- return sourceResultSet.getTimestamp(srcColOrdinal);
-
- case microsoft.sql.Types.DATETIMEOFFSET:
- // We can safely cast the result set to a SQLServerResultSet as the DatetimeOffset type is only available in the JDBC driver.
- return ((SQLServerResultSet) sourceResultSet).getDateTimeOffset(srcColOrdinal);
-
- default:
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_BulkTypeNotSupported"));
- Object[] msgArgs = {JDBCType.of(srcJdbcType).toString().toLowerCase(Locale.ENGLISH)};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
- // This return will never be executed, but it is needed as Eclipse complains otherwise.
- return null;
- } // End of switch
- }// End of Try
- catch(SQLException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
- }
- }
-
- /*
- * Reads the given column from the result set current row and writes the data to tdsWriter.
- */
- private void writeColumn(
- TDSWriter tdsWriter,
- int srcColOrdinal,
- int destColOrdinal,
- Object colValue
- ) throws SQLServerException
- {
- int srcPrecision = 0, srcScale = 0, destPrecision = 0, srcJdbcType = 0;
- SSType destSSType = null;
- boolean isStreaming = false, srcNullable;
- srcPrecision = srcColumnMetadata.get(srcColOrdinal).precision;
- srcScale = srcColumnMetadata.get(srcColOrdinal).scale;
- srcJdbcType = srcColumnMetadata.get(srcColOrdinal).jdbcType;
- srcNullable = srcColumnMetadata.get(srcColOrdinal).isNullable;
-
- destPrecision = destColumnMetadata.get(destColOrdinal).precision;
-
- if ((java.sql.Types.NCHAR == srcJdbcType) ||
- (java.sql.Types.NVARCHAR == srcJdbcType) ||
- (java.sql.Types.LONGNVARCHAR == srcJdbcType))
- {
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_CHARS < srcPrecision) ||
- (DataTypes.SHORT_VARTYPE_MAX_CHARS < destPrecision);
- }
- else
- {
- isStreaming = (DataTypes.SHORT_VARTYPE_MAX_BYTES < srcPrecision) ||
- (DataTypes.SHORT_VARTYPE_MAX_BYTES < destPrecision);
- }
-
- CryptoMetadata destCryptoMeta = destColumnMetadata.get(destColOrdinal).cryptoMeta;
- if (null != destCryptoMeta)
- {
- destSSType = destCryptoMeta.baseTypeInfo.getSSType();
- }
-
- // Get the cell from the source result set if we are copying from result set.
- // If we are copying from a bulk reader colValue will be passed as the argument.
- if (null != sourceResultSet)
- {
- colValue = readColumnFromResultSet(srcColOrdinal, srcJdbcType, isStreaming, (null != destCryptoMeta));
- validateStringBinaryLengths(colValue, srcColOrdinal, destColOrdinal);
-
- // if AllowEncryptedValueModifications is set send varbinary read from source without checking type conversion
- if(!((copyOptions.isAllowEncryptedValueModifications())
- // normalizationCheck() will be called for encrypted columns so skip this validation
- || ((null != destCryptoMeta) && (null != colValue))))
- {
- validateDataTypeConversions(srcColOrdinal,destColOrdinal);
- }
- }
- else if((null != sourceBulkRecord) && (null != destCryptoMeta))
- {
- // From CSV to encrypted column. Convert to respective object.
- if ((java.sql.Types.DATE == srcJdbcType) ||
- (java.sql.Types.TIME == srcJdbcType) ||
- (java.sql.Types.TIMESTAMP == srcJdbcType) ||
- (microsoft.sql.Types.DATETIMEOFFSET == srcJdbcType) ||
- (2013 == srcJdbcType) ||
- (2014 == srcJdbcType))
- {
- colValue = getTemporalObjectFromCSV(colValue, srcJdbcType, srcColOrdinal);
- }
- else if ((java.sql.Types.NUMERIC == srcJdbcType) || (java.sql.Types.DECIMAL == srcJdbcType))
- {
- int baseDestPrecision = destCryptoMeta.baseTypeInfo.getPrecision();
- int baseDestScale = destCryptoMeta.baseTypeInfo.getScale();
- if ((srcScale != baseDestScale) || (srcPrecision != baseDestPrecision))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- String src = JDBCType.of(srcJdbcType) + "(" + srcPrecision + "," + srcScale + ")";
- String dest = destSSType + "(" + baseDestPrecision + "," + baseDestScale + ")";
- Object[] msgArgs = { src, dest };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- }
- }
-
- CryptoMetadata srcCryptoMeta = srcColumnMetadata.get(srcColOrdinal).cryptoMeta;
- // If destination is encrypted column, transparently encrypt the data
- if((null != destCryptoMeta) && (null != colValue))
- {
- JDBCType baseSrcJdbcType = (null != srcCryptoMeta) ?
- srcColumnMetadata.get(srcColOrdinal).cryptoMeta.baseTypeInfo.getSSType().getJDBCType() : JDBCType.of(srcJdbcType) ;
-
- if(JDBCType.TIMESTAMP == baseSrcJdbcType){
- if(SSType.DATETIME == destSSType)
- {
- baseSrcJdbcType = JDBCType.DATETIME;
- }
- else if(SSType.SMALLDATETIME == destSSType)
- {
- baseSrcJdbcType = JDBCType.SMALLDATETIME;
- }
- }
-
- if(!((SSType.MONEY == destSSType && JDBCType.DECIMAL == baseSrcJdbcType)
- || (SSType.SMALLMONEY == destSSType && JDBCType.DECIMAL == baseSrcJdbcType)
- || (SSType.GUID == destSSType && JDBCType.CHAR == baseSrcJdbcType))){
- //check for bulkcopy from other than SQLServer, for instance for MYSQL, if anykind of chartype pass
- if (!(Util.isCharType(destSSType) && Util.isCharType(srcJdbcType)) && !(sourceResultSet instanceof SQLServerResultSet))
- // check for normalization of AE data types
- if(!baseSrcJdbcType.normalizationCheck(destSSType))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionAE"));
- Object[] msgArgs = {baseSrcJdbcType,destSSType};
- throw new SQLServerException(this , form.format(msgArgs), null, 0 , false);
- }
- }
- // if source is encrypted and temporal, call IOBuffer functions to encrypt
- if((baseSrcJdbcType == JDBCType.DATE)
- ||(baseSrcJdbcType == JDBCType.TIMESTAMP)
- ||(baseSrcJdbcType == JDBCType.TIME)
- ||(baseSrcJdbcType == JDBCType.DATETIMEOFFSET)
- ||(baseSrcJdbcType == JDBCType.DATETIME)
- ||(baseSrcJdbcType == JDBCType.SMALLDATETIME))
- {
- colValue = getEncryptedTemporalBytes(tdsWriter, baseSrcJdbcType, colValue, srcColOrdinal, destCryptoMeta.baseTypeInfo.getScale());
- }
- else
- {
- TypeInfo destTypeInfo = destCryptoMeta.getBaseTypeInfo();
- JDBCType destJdbcType = destTypeInfo.getSSType().getJDBCType();
-
- /*
- * the following if checks that no casting exception would be thrown in the normalizedValue() method below
- * a SQLServerException is then thrown before the ClassCastException could occur
- * an example of how this situation could arise would be if the application creates encrypted source and destination tables
- * the result set used to read the source would have AE disabled (causing colValue to be varbinary)
- * AE would be enabled on the connection used to complete the bulkCopy operation
- */
- if((!Util.isBinaryType(destJdbcType.getIntValue())) && (colValue instanceof byte[])){
-
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { baseSrcJdbcType, destJdbcType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- // normalize the values before encrypting them
- colValue = SQLServerSecurityUtility.encryptWithKey(
- normalizedValue(destJdbcType, colValue, baseSrcJdbcType, destTypeInfo.getPrecision(), destTypeInfo.getScale()),
- destCryptoMeta, connection);
- }
- }
- writeColumnToTdsWriter(
- tdsWriter,
- srcPrecision,
- srcScale,
- srcJdbcType,
- srcNullable,
- srcColOrdinal,
- destColOrdinal,
- isStreaming,
- colValue
- );
- }
-
- //this method is called against jdbc41, but it require jdbc42 to work
- //therefore, we will throw exception.
- protected Object getTemporalObjectFromCSVWithFormatter(String valueStrUntrimmed, int srcJdbcType, int srcColOrdinal, DateTimeFormatter dateTimeFormatter) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
-
- SQLServerBulkCopy42Helper.getTemporalObjectFromCSVWithFormatter(valueStrUntrimmed, srcJdbcType, srcColOrdinal, dateTimeFormatter, connection, this);
-
- return null;
- }
-
- private Object getTemporalObjectFromCSV(Object value, int srcJdbcType, int srcColOrdinal) throws SQLServerException
- {
- // TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE are not supported with encrypted columns.
- if (2013 == srcJdbcType)
- {
- MessageFormat form1 = new MessageFormat(SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
- Object[] msgArgs1 = { "TIME_WITH_TIMEZONE" };
- throw new SQLServerException(this, form1.format(msgArgs1), null, 0, false);
- }
- else if (2014 == srcJdbcType)
- {
- MessageFormat form2 = new MessageFormat(SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
- Object[] msgArgs2 = { "TIMESTAMP_WITH_TIMEZONE" };
- throw new SQLServerException(this, form2.format(msgArgs2), null, 0, false);
- }
-
- String valueStr = null;
- String valueStrUntrimmed = null;
-
- if (null != value && value instanceof String)
- {
- valueStrUntrimmed = (String) value;
- valueStr = valueStrUntrimmed.trim();
- }
-
- // Handle null case
- if (null == valueStr)
- {
- switch(srcJdbcType)
- {
- case java.sql.Types.TIMESTAMP:
- case java.sql.Types.TIME:
- return null;
- case java.sql.Types.DATE:
- return null;
- case microsoft.sql.Types.DATETIMEOFFSET:
- return null;
- }
- }
-
- // If we are here value is non-null.
- Calendar cal = null;
-
- // Get the temporal values from the formatter
- DateTimeFormatter dateTimeFormatter = srcColumnMetadata.get(srcColOrdinal).dateTimeFormatter;
- if (null != dateTimeFormatter)
- {
- return getTemporalObjectFromCSVWithFormatter(valueStrUntrimmed, srcJdbcType, srcColOrdinal, dateTimeFormatter);
- }
-
- // If we are here that means datetimeformatter is not present. Only default format is supported in this case.
- try
- {
- switch(srcJdbcType)
- {
- case java.sql.Types.TIMESTAMP:
- // For CSV, value will be of String type.
- return Timestamp.valueOf(valueStr);
-
- case java.sql.Types.TIME:
- {
- String time = connection.baseYear() + "-01-01 " + valueStr;
- Timestamp ts = java.sql.Timestamp.valueOf(time);
- return ts;
- }
- case java.sql.Types.DATE:
- return java.sql.Date.valueOf(valueStr);
-
- case microsoft.sql.Types.DATETIMEOFFSET:
- int endIndx = valueStr.indexOf('-', 0);
- int year = Integer.parseInt(valueStr.substring(0, endIndx));
-
- int startIndx = ++endIndx; // skip the -
- endIndx = valueStr.indexOf('-', startIndx);
- int month = Integer.parseInt(valueStr.substring(startIndx, endIndx));
-
- startIndx = ++endIndx; // skip the -
- endIndx = valueStr.indexOf(' ', startIndx);
- int day = Integer.parseInt(valueStr.substring(startIndx, endIndx));
-
- startIndx = ++endIndx; // skip the space
- endIndx = valueStr.indexOf(':', startIndx);
- int hour = Integer.parseInt(valueStr.substring(startIndx, endIndx));
-
- startIndx = ++endIndx; // skip the :
- endIndx = valueStr.indexOf(':', startIndx);
- int minute = Integer.parseInt(valueStr.substring(startIndx, endIndx));
-
- startIndx = ++endIndx; // skip the :
- endIndx = valueStr.indexOf('.', startIndx);
- int seconds = 0, offsethour, offsetMinute, totalOffset = 0, fractionalSeconds = 0;
- boolean isNegativeOffset = false;
- boolean hasTimeZone = false;
- int fractionalSecondsLength = 0;
- if (-1 != endIndx) //has fractional seconds, has a '.'
- {
- seconds = Integer.parseInt(valueStr.substring(startIndx, endIndx));
-
- startIndx = ++endIndx; // skip the .
- endIndx = valueStr.indexOf(' ', startIndx);
- if (-1 != endIndx) // has time zone
- {
- fractionalSeconds = Integer.parseInt(valueStr.substring(startIndx, endIndx));
- fractionalSecondsLength = endIndx - startIndx;
- hasTimeZone = true;
- }
- else // no timezone
- {
- fractionalSeconds = Integer.parseInt(valueStr.substring(startIndx));
- fractionalSecondsLength = valueStr.length() - startIndx;
- }
- }
- else
- {
- endIndx = valueStr.indexOf(' ', startIndx);
- if (-1 != endIndx){
- hasTimeZone= true;
- seconds = Integer.parseInt(valueStr.substring(startIndx, endIndx));
- }
- else
- {
- seconds = Integer.parseInt(valueStr.substring(startIndx));
- startIndx = ++endIndx; // skip the space
- }
- }
- if (hasTimeZone)
- {
- startIndx = ++endIndx; // skip the space
- if ('+' == valueStr.charAt(startIndx))
- startIndx++; // skip +
- else if ('-' == valueStr.charAt(startIndx))
- {
- isNegativeOffset = true;
- startIndx++; // skip -
- }
- endIndx = valueStr.indexOf(':', startIndx);
-
- offsethour = Integer.parseInt(valueStr.substring(startIndx, endIndx));
- startIndx = ++endIndx; // skip :
- offsetMinute = Integer.parseInt(valueStr.substring(startIndx));
- totalOffset = offsethour * 60 + offsetMinute;
- if (isNegativeOffset)
- totalOffset = -totalOffset;
- }
- cal = new GregorianCalendar(new SimpleTimeZone(totalOffset * 60 * 1000, ""), Locale.US);
- cal.clear();
- cal.set(Calendar.HOUR_OF_DAY, hour);
- cal.set(Calendar.MINUTE, minute);
- cal.set(Calendar.SECOND, seconds);
- cal.set(Calendar.DATE, day);
- cal.set(Calendar.MONTH, month - 1);
- cal.set(Calendar.YEAR, year);
- for (int i = 0; i < (9 - fractionalSecondsLength); i++)
- fractionalSeconds *= 10;
-
- Timestamp ts = new Timestamp(cal.getTimeInMillis());
- ts.setNanos(fractionalSeconds);
- return microsoft.sql.DateTimeOffset.valueOf(ts, totalOffset);
- }
- }
- catch(IndexOutOfBoundsException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
- Object[] msgArgs = { JDBCType.of(srcJdbcType) };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- catch(NumberFormatException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
- Object[] msgArgs = { JDBCType.of(srcJdbcType) };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- catch(IllegalArgumentException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
- Object[] msgArgs = { JDBCType.of(srcJdbcType) };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- // unreachable code. Need to do to compile from Eclipse.
- return value;
- }
-
-
- private byte[] getEncryptedTemporalBytes(TDSWriter tdsWriter,
- JDBCType srcTemporalJdbcType,
- Object colValue,
- int srcColOrdinal,
- int scale) throws SQLServerException
- {
- long utcMillis = 0;
- GregorianCalendar calendar = null;
-
- switch(srcTemporalJdbcType)
- {
- case DATE:
- calendar = new GregorianCalendar(java.util.TimeZone.getDefault(),java.util.Locale.US);
- calendar.setLenient(true);
- calendar.clear();
- calendar.setTimeInMillis(((Date) colValue).getTime());
- return tdsWriter.writeEncryptedScaledTemporal(
- calendar,
- 0, // subsecond nanos (none for a date value)
- 0, // scale (dates are not scaled)
- SSType.DATE,
- (short) 0);
-
- case TIME:
- calendar = new GregorianCalendar(java.util.TimeZone.getDefault(),java.util.Locale.US);
- calendar.setLenient(true);
- calendar.clear();
- utcMillis=((java.sql.Timestamp) colValue).getTime();
- calendar.setTimeInMillis(utcMillis);
- int subSecondNanos = 0;
- if (colValue instanceof java.sql.Timestamp)
- {
- subSecondNanos = ((java.sql.Timestamp) colValue).getNanos();
- }
- else
- {
- subSecondNanos = Nanos.PER_MILLISECOND * (int)(utcMillis % 1000);
- if (subSecondNanos < 0)
- subSecondNanos += Nanos.PER_SECOND;
- }
- return tdsWriter.writeEncryptedScaledTemporal(
- calendar,
- subSecondNanos,
- scale,
- SSType.TIME,
- (short) 0);
-
- case TIMESTAMP:
- calendar = new GregorianCalendar(java.util.TimeZone.getDefault(),java.util.Locale.US);
- calendar.setLenient(true);
- calendar.clear();
- utcMillis=((java.sql.Timestamp) colValue).getTime();
- calendar.setTimeInMillis(utcMillis);
- subSecondNanos = ((java.sql.Timestamp) colValue).getNanos();
- return tdsWriter.writeEncryptedScaledTemporal(
- calendar,
- subSecondNanos,
- scale,
- SSType.DATETIME2,
- (short) 0);
-
- case DATETIME:
- case SMALLDATETIME:
- calendar = new GregorianCalendar(java.util.TimeZone.getDefault(),java.util.Locale.US);
- calendar.setLenient(true);
- calendar.clear();
- utcMillis=((java.sql.Timestamp) colValue).getTime();
- calendar.setTimeInMillis(utcMillis);
- subSecondNanos = ((java.sql.Timestamp) colValue).getNanos();
- return tdsWriter.getEncryptedDateTimeAsBytes(
- calendar,
- subSecondNanos,
- srcTemporalJdbcType);
-
- case DATETIMEOFFSET:
- microsoft.sql.DateTimeOffset dtoValue = (microsoft.sql.DateTimeOffset) colValue;
- utcMillis = dtoValue.getTimestamp().getTime();
- subSecondNanos = dtoValue.getTimestamp().getNanos();
- int minutesOffset = dtoValue.getMinutesOffset();
- calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
- calendar.setLenient(true);
- calendar.clear();
- calendar.setTimeInMillis(utcMillis);
- return tdsWriter.writeEncryptedScaledTemporal(
- calendar,
- subSecondNanos,
- scale,
- SSType.DATETIMEOFFSET,
- (short) minutesOffset);
-
- default:
-
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
- Object[] msgArgs = { srcTemporalJdbcType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- }
-
- private byte[] normalizedValue(JDBCType destJdbcType, Object value, JDBCType srcJdbcType, int destPrecision, int destScale) throws SQLServerException
- {
- Long longValue = null;
+ if (JDBCType.TIMESTAMP == baseSrcJdbcType) {
+ if (SSType.DATETIME == destSSType) {
+ baseSrcJdbcType = JDBCType.DATETIME;
+ }
+ else if (SSType.SMALLDATETIME == destSSType) {
+ baseSrcJdbcType = JDBCType.SMALLDATETIME;
+ }
+ }
+
+ if (!((SSType.MONEY == destSSType && JDBCType.DECIMAL == baseSrcJdbcType)
+ || (SSType.SMALLMONEY == destSSType && JDBCType.DECIMAL == baseSrcJdbcType)
+ || (SSType.GUID == destSSType && JDBCType.CHAR == baseSrcJdbcType))) {
+ // check for bulkcopy from other than SQLServer, for instance for MYSQL, if anykind of chartype pass
+ if (!(Util.isCharType(destSSType) && Util.isCharType(srcJdbcType)) && !(sourceResultSet instanceof SQLServerResultSet))
+ // check for normalization of AE data types
+ if (!baseSrcJdbcType.normalizationCheck(destSSType)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionAE"));
+ Object[] msgArgs = {baseSrcJdbcType, destSSType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ }
+ // if source is encrypted and temporal, call IOBuffer functions to encrypt
+ if ((baseSrcJdbcType == JDBCType.DATE) || (baseSrcJdbcType == JDBCType.TIMESTAMP) || (baseSrcJdbcType == JDBCType.TIME)
+ || (baseSrcJdbcType == JDBCType.DATETIMEOFFSET) || (baseSrcJdbcType == JDBCType.DATETIME)
+ || (baseSrcJdbcType == JDBCType.SMALLDATETIME)) {
+ colValue = getEncryptedTemporalBytes(tdsWriter, baseSrcJdbcType, colValue, srcColOrdinal, destCryptoMeta.baseTypeInfo.getScale());
+ }
+ else {
+ TypeInfo destTypeInfo = destCryptoMeta.getBaseTypeInfo();
+ JDBCType destJdbcType = destTypeInfo.getSSType().getJDBCType();
+
+ /*
+ * the following if checks that no casting exception would be thrown in the normalizedValue() method below a SQLServerException is
+ * then thrown before the ClassCastException could occur an example of how this situation could arise would be if the application
+ * creates encrypted source and destination tables the result set used to read the source would have AE disabled (causing colValue to
+ * be varbinary) AE would be enabled on the connection used to complete the bulkCopy operation
+ */
+ if ((!Util.isBinaryType(destJdbcType.getIntValue())) && (colValue instanceof byte[])) {
+
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {baseSrcJdbcType, destJdbcType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ // normalize the values before encrypting them
+ colValue = SQLServerSecurityUtility.encryptWithKey(
+ normalizedValue(destJdbcType, colValue, baseSrcJdbcType, destTypeInfo.getPrecision(), destTypeInfo.getScale()),
+ destCryptoMeta, connection);
+ }
+ }
+ writeColumnToTdsWriter(tdsWriter, srcPrecision, srcScale, srcJdbcType, srcNullable, srcColOrdinal, destColOrdinal, isStreaming, colValue);
+ }
+
+ // this method is called against jdbc41, but it require jdbc42 to work
+ // therefore, we will throw exception.
+ protected Object getTemporalObjectFromCSVWithFormatter(String valueStrUntrimmed,
+ int srcJdbcType,
+ int srcColOrdinal,
+ DateTimeFormatter dateTimeFormatter) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
+
+ SQLServerBulkCopy42Helper.getTemporalObjectFromCSVWithFormatter(valueStrUntrimmed, srcJdbcType, srcColOrdinal, dateTimeFormatter, connection,
+ this);
+
+ return null;
+ }
+
+ private Object getTemporalObjectFromCSV(Object value,
+ int srcJdbcType,
+ int srcColOrdinal) throws SQLServerException {
+ // TIME_WITH_TIMEZONE and TIMESTAMP_WITH_TIMEZONE are not supported with encrypted columns.
+ if (2013 == srcJdbcType) {
+ MessageFormat form1 = new MessageFormat(SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
+ Object[] msgArgs1 = {"TIME_WITH_TIMEZONE"};
+ throw new SQLServerException(this, form1.format(msgArgs1), null, 0, false);
+ }
+ else if (2014 == srcJdbcType) {
+ MessageFormat form2 = new MessageFormat(SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
+ Object[] msgArgs2 = {"TIMESTAMP_WITH_TIMEZONE"};
+ throw new SQLServerException(this, form2.format(msgArgs2), null, 0, false);
+ }
+
+ String valueStr = null;
+ String valueStrUntrimmed = null;
+
+ if (null != value && value instanceof String) {
+ valueStrUntrimmed = (String) value;
+ valueStr = valueStrUntrimmed.trim();
+ }
+
+ // Handle null case
+ if (null == valueStr) {
+ switch (srcJdbcType) {
+ case java.sql.Types.TIMESTAMP:
+ case java.sql.Types.TIME:
+ return null;
+ case java.sql.Types.DATE:
+ return null;
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ return null;
+ }
+ }
+
+ // If we are here value is non-null.
+ Calendar cal = null;
+
+ // Get the temporal values from the formatter
+ DateTimeFormatter dateTimeFormatter = srcColumnMetadata.get(srcColOrdinal).dateTimeFormatter;
+ if (null != dateTimeFormatter) {
+ return getTemporalObjectFromCSVWithFormatter(valueStrUntrimmed, srcJdbcType, srcColOrdinal, dateTimeFormatter);
+ }
+
+ // If we are here that means datetimeformatter is not present. Only default format is supported in this case.
+ try {
+ switch (srcJdbcType) {
+ case java.sql.Types.TIMESTAMP:
+ // For CSV, value will be of String type.
+ return Timestamp.valueOf(valueStr);
+
+ case java.sql.Types.TIME: {
+ String time = connection.baseYear() + "-01-01 " + valueStr;
+ Timestamp ts = java.sql.Timestamp.valueOf(time);
+ return ts;
+ }
+ case java.sql.Types.DATE:
+ return java.sql.Date.valueOf(valueStr);
+
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ int endIndx = valueStr.indexOf('-', 0);
+ int year = Integer.parseInt(valueStr.substring(0, endIndx));
+
+ int startIndx = ++endIndx; // skip the -
+ endIndx = valueStr.indexOf('-', startIndx);
+ int month = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+
+ startIndx = ++endIndx; // skip the -
+ endIndx = valueStr.indexOf(' ', startIndx);
+ int day = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+
+ startIndx = ++endIndx; // skip the space
+ endIndx = valueStr.indexOf(':', startIndx);
+ int hour = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+
+ startIndx = ++endIndx; // skip the :
+ endIndx = valueStr.indexOf(':', startIndx);
+ int minute = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+
+ startIndx = ++endIndx; // skip the :
+ endIndx = valueStr.indexOf('.', startIndx);
+ int seconds = 0, offsethour, offsetMinute, totalOffset = 0, fractionalSeconds = 0;
+ boolean isNegativeOffset = false;
+ boolean hasTimeZone = false;
+ int fractionalSecondsLength = 0;
+ if (-1 != endIndx) // has fractional seconds, has a '.'
+ {
+ seconds = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+
+ startIndx = ++endIndx; // skip the .
+ endIndx = valueStr.indexOf(' ', startIndx);
+ if (-1 != endIndx) // has time zone
+ {
+ fractionalSeconds = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+ fractionalSecondsLength = endIndx - startIndx;
+ hasTimeZone = true;
+ }
+ else // no timezone
+ {
+ fractionalSeconds = Integer.parseInt(valueStr.substring(startIndx));
+ fractionalSecondsLength = valueStr.length() - startIndx;
+ }
+ }
+ else {
+ endIndx = valueStr.indexOf(' ', startIndx);
+ if (-1 != endIndx) {
+ hasTimeZone = true;
+ seconds = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+ }
+ else {
+ seconds = Integer.parseInt(valueStr.substring(startIndx));
+ startIndx = ++endIndx; // skip the space
+ }
+ }
+ if (hasTimeZone) {
+ startIndx = ++endIndx; // skip the space
+ if ('+' == valueStr.charAt(startIndx))
+ startIndx++; // skip +
+ else if ('-' == valueStr.charAt(startIndx)) {
+ isNegativeOffset = true;
+ startIndx++; // skip -
+ }
+ endIndx = valueStr.indexOf(':', startIndx);
+
+ offsethour = Integer.parseInt(valueStr.substring(startIndx, endIndx));
+ startIndx = ++endIndx; // skip :
+ offsetMinute = Integer.parseInt(valueStr.substring(startIndx));
+ totalOffset = offsethour * 60 + offsetMinute;
+ if (isNegativeOffset)
+ totalOffset = -totalOffset;
+ }
+ cal = new GregorianCalendar(new SimpleTimeZone(totalOffset * 60 * 1000, ""), Locale.US);
+ cal.clear();
+ cal.set(Calendar.HOUR_OF_DAY, hour);
+ cal.set(Calendar.MINUTE, minute);
+ cal.set(Calendar.SECOND, seconds);
+ cal.set(Calendar.DATE, day);
+ cal.set(Calendar.MONTH, month - 1);
+ cal.set(Calendar.YEAR, year);
+ for (int i = 0; i < (9 - fractionalSecondsLength); i++)
+ fractionalSeconds *= 10;
+
+ Timestamp ts = new Timestamp(cal.getTimeInMillis());
+ ts.setNanos(fractionalSeconds);
+ return microsoft.sql.DateTimeOffset.valueOf(ts, totalOffset);
+ }
+ }
+ catch (IndexOutOfBoundsException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
+ Object[] msgArgs = {JDBCType.of(srcJdbcType)};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ catch (NumberFormatException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
+ Object[] msgArgs = {JDBCType.of(srcJdbcType)};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ catch (IllegalArgumentException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
+ Object[] msgArgs = {JDBCType.of(srcJdbcType)};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ // unreachable code. Need to do to compile from Eclipse.
+ return value;
+ }
+
+ private byte[] getEncryptedTemporalBytes(TDSWriter tdsWriter,
+ JDBCType srcTemporalJdbcType,
+ Object colValue,
+ int srcColOrdinal,
+ int scale) throws SQLServerException {
+ long utcMillis = 0;
+ GregorianCalendar calendar = null;
+
+ switch (srcTemporalJdbcType) {
+ case DATE:
+ calendar = new GregorianCalendar(java.util.TimeZone.getDefault(), java.util.Locale.US);
+ calendar.setLenient(true);
+ calendar.clear();
+ calendar.setTimeInMillis(((Date) colValue).getTime());
+ return tdsWriter.writeEncryptedScaledTemporal(calendar, 0, // subsecond nanos (none for a date value)
+ 0, // scale (dates are not scaled)
+ SSType.DATE, (short) 0);
+
+ case TIME:
+ calendar = new GregorianCalendar(java.util.TimeZone.getDefault(), java.util.Locale.US);
+ calendar.setLenient(true);
+ calendar.clear();
+ utcMillis = ((java.sql.Timestamp) colValue).getTime();
+ calendar.setTimeInMillis(utcMillis);
+ int subSecondNanos = 0;
+ if (colValue instanceof java.sql.Timestamp) {
+ subSecondNanos = ((java.sql.Timestamp) colValue).getNanos();
+ }
+ else {
+ subSecondNanos = Nanos.PER_MILLISECOND * (int) (utcMillis % 1000);
+ if (subSecondNanos < 0)
+ subSecondNanos += Nanos.PER_SECOND;
+ }
+ return tdsWriter.writeEncryptedScaledTemporal(calendar, subSecondNanos, scale, SSType.TIME, (short) 0);
+
+ case TIMESTAMP:
+ calendar = new GregorianCalendar(java.util.TimeZone.getDefault(), java.util.Locale.US);
+ calendar.setLenient(true);
+ calendar.clear();
+ utcMillis = ((java.sql.Timestamp) colValue).getTime();
+ calendar.setTimeInMillis(utcMillis);
+ subSecondNanos = ((java.sql.Timestamp) colValue).getNanos();
+ return tdsWriter.writeEncryptedScaledTemporal(calendar, subSecondNanos, scale, SSType.DATETIME2, (short) 0);
+
+ case DATETIME:
+ case SMALLDATETIME:
+ calendar = new GregorianCalendar(java.util.TimeZone.getDefault(), java.util.Locale.US);
+ calendar.setLenient(true);
+ calendar.clear();
+ utcMillis = ((java.sql.Timestamp) colValue).getTime();
+ calendar.setTimeInMillis(utcMillis);
+ subSecondNanos = ((java.sql.Timestamp) colValue).getNanos();
+ return tdsWriter.getEncryptedDateTimeAsBytes(calendar, subSecondNanos, srcTemporalJdbcType);
+
+ case DATETIMEOFFSET:
+ microsoft.sql.DateTimeOffset dtoValue = (microsoft.sql.DateTimeOffset) colValue;
+ utcMillis = dtoValue.getTimestamp().getTime();
+ subSecondNanos = dtoValue.getTimestamp().getNanos();
+ int minutesOffset = dtoValue.getMinutesOffset();
+ calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ calendar.setLenient(true);
+ calendar.clear();
+ calendar.setTimeInMillis(utcMillis);
+ return tdsWriter.writeEncryptedScaledTemporal(calendar, subSecondNanos, scale, SSType.DATETIMEOFFSET, (short) minutesOffset);
+
+ default:
+
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
+ Object[] msgArgs = {srcTemporalJdbcType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ }
+
+ private byte[] normalizedValue(JDBCType destJdbcType,
+ Object value,
+ JDBCType srcJdbcType,
+ int destPrecision,
+ int destScale) throws SQLServerException {
+ Long longValue = null;
byte[] byteValue = null;
int srcDataPrecision, srcDataScale;
- try
- {
- switch (destJdbcType)
- {
- case BIT:
- longValue = Long.valueOf((Boolean) value ? 1: 0);
- return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
-
- case TINYINT:
- case SMALLINT:
- switch (srcJdbcType)
- {
- case BIT:
- longValue = new Long((Boolean) value ? 1: 0);
- break;
- default:
- if(value instanceof Integer)
- {
- int intValue = (int) value;
- short shortValue = (short) intValue;
- longValue = new Long(shortValue);
- }
- else longValue = new Long((short) value);
-
- }
- return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
-
- case INTEGER:
- switch (srcJdbcType)
- {
- case BIT:
- longValue = new Long((Boolean) value ? 1: 0);
- break;
- case TINYINT:
- case SMALLINT:
- longValue = new Long((short) value);
- break;
- default:
- longValue = new Long((Integer) value);
- }
- return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
-
- case BIGINT:
- switch (srcJdbcType)
- {
- case BIT:
- longValue = new Long((Boolean) value ? 1: 0);
- break;
- case TINYINT:
- case SMALLINT:
- longValue = new Long((short) value);
- break;
- case INTEGER:
- longValue = new Long((Integer) value);
- break;
- default:
- longValue = new Long((long) value);
- }
- return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
-
- case BINARY:
- case VARBINARY:
- case LONGVARBINARY:
- byte[] byteArrayValue = null;
- if (value instanceof String)
- {
- byteArrayValue = ParameterUtils.HexToBin((String) value);
- }
- else
- {
- byteArrayValue = (byte[]) value;
- }
- if(byteArrayValue.length > destPrecision)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcJdbcType, destJdbcType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- return byteArrayValue;
- case GUID:
- return Util.asGuidByteArray(UUID.fromString((String) value));
-
- case CHAR:
- case VARCHAR:
- case LONGVARCHAR:
-
- // Throw exception if length sent in column metadata is smaller than actual data
- if( ((String)value).length() > destPrecision)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcJdbcType, destJdbcType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- return ((String)value).getBytes(UTF_8);
-
- case NCHAR:
- case NVARCHAR:
- case LONGNVARCHAR:
- // Throw exception if length sent in column metadata is smaller than actual data
- if( ((String)value).length() > destPrecision)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcJdbcType, destJdbcType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- return ((String)value).getBytes(UTF_16LE);
-
- case REAL:
- case FLOAT:
-
- Float floatValue = (value instanceof String) ? Float.parseFloat((String) value) : (Float) value;
- return ByteBuffer.allocate((Float.SIZE/ Byte.SIZE)).order(ByteOrder.LITTLE_ENDIAN).putFloat(floatValue).array();
-
- case DOUBLE:
- Double doubleValue = (value instanceof String) ? Double.parseDouble((String) value) : (Double) value;
- return ByteBuffer.allocate((Double.SIZE/ Byte.SIZE)).order(ByteOrder.LITTLE_ENDIAN).putDouble(doubleValue).array();
-
- case NUMERIC:
- case DECIMAL:
- srcDataScale = ((BigDecimal)value).scale();
- srcDataPrecision = ((BigDecimal)value).precision();
- BigDecimal bigDataValue = (BigDecimal)value;
- if( (srcDataPrecision > destPrecision) || (srcDataScale > destScale))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcJdbcType, destJdbcType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- else if(srcDataScale < destScale )
- // update the scale of source data based on the metadata for scale sent early
- bigDataValue= bigDataValue.setScale(destScale);
-
- byteValue = DDC.convertBigDecimalToBytes(bigDataValue, bigDataValue.scale()) ;
- byte[] decimalbyteValue = new byte[16];
- // removing the precision and scale information from the decimalToByte array
- System.arraycopy(byteValue, 2, decimalbyteValue, 0, byteValue.length - 2);
- return decimalbyteValue;
-
- case SMALLMONEY:
- case MONEY:
- // For TDS we need to send the money value multiplied by 10^4 - this gives us the
- // money value as integer. 4 is the default and only scale available with money.
- // smallmoney is noralized to money.
- BigDecimal bdValue = (BigDecimal) value;
- // Need to validate range in the client side as we are converting BigDecimal to integers.
- Util.validateMoneyRange(bdValue, destJdbcType);
-
- // Get the total number of digits after the multiplication. Scale is hardcoded to 4. This is needed to get the proper rounding.
- int digitCount = (bdValue.precision() - bdValue.scale()) + 4;
-
- long moneyVal = ((BigDecimal) value).multiply(new BigDecimal(10000), new java.math.MathContext(digitCount, java.math.RoundingMode.HALF_UP)).longValue();
- ByteBuffer bbuf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
- bbuf.putInt((int) (moneyVal >> 32)).array();
- bbuf.putInt((int) moneyVal).array();
- return bbuf.array();
-
- default:
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
- Object[] msgArgs = { destJdbcType };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
+ try {
+ switch (destJdbcType) {
+ case BIT:
+ longValue = Long.valueOf((Boolean) value ? 1 : 0);
+ return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
+
+ case TINYINT:
+ case SMALLINT:
+ switch (srcJdbcType) {
+ case BIT:
+ longValue = new Long((Boolean) value ? 1 : 0);
+ break;
+ default:
+ if (value instanceof Integer) {
+ int intValue = (int) value;
+ short shortValue = (short) intValue;
+ longValue = new Long(shortValue);
+ }
+ else
+ longValue = new Long((short) value);
+
+ }
+ return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
+
+ case INTEGER:
+ switch (srcJdbcType) {
+ case BIT:
+ longValue = new Long((Boolean) value ? 1 : 0);
+ break;
+ case TINYINT:
+ case SMALLINT:
+ longValue = new Long((short) value);
+ break;
+ default:
+ longValue = new Long((Integer) value);
+ }
+ return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
+
+ case BIGINT:
+ switch (srcJdbcType) {
+ case BIT:
+ longValue = new Long((Boolean) value ? 1 : 0);
+ break;
+ case TINYINT:
+ case SMALLINT:
+ longValue = new Long((short) value);
+ break;
+ case INTEGER:
+ longValue = new Long((Integer) value);
+ break;
+ default:
+ longValue = new Long((long) value);
+ }
+ return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).order(ByteOrder.LITTLE_ENDIAN).putLong(longValue.longValue()).array();
+
+ case BINARY:
+ case VARBINARY:
+ case LONGVARBINARY:
+ byte[] byteArrayValue = null;
+ if (value instanceof String) {
+ byteArrayValue = ParameterUtils.HexToBin((String) value);
+ }
+ else {
+ byteArrayValue = (byte[]) value;
+ }
+ if (byteArrayValue.length > destPrecision) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcJdbcType, destJdbcType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ return byteArrayValue;
+ case GUID:
+ return Util.asGuidByteArray(UUID.fromString((String) value));
+
+ case CHAR:
+ case VARCHAR:
+ case LONGVARCHAR:
+
+ // Throw exception if length sent in column metadata is smaller than actual data
+ if (((String) value).length() > destPrecision) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcJdbcType, destJdbcType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ return ((String) value).getBytes(UTF_8);
+
+ case NCHAR:
+ case NVARCHAR:
+ case LONGNVARCHAR:
+ // Throw exception if length sent in column metadata is smaller than actual data
+ if (((String) value).length() > destPrecision) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcJdbcType, destJdbcType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ return ((String) value).getBytes(UTF_16LE);
+
+ case REAL:
+ case FLOAT:
+
+ Float floatValue = (value instanceof String) ? Float.parseFloat((String) value) : (Float) value;
+ return ByteBuffer.allocate((Float.SIZE / Byte.SIZE)).order(ByteOrder.LITTLE_ENDIAN).putFloat(floatValue).array();
+
+ case DOUBLE:
+ Double doubleValue = (value instanceof String) ? Double.parseDouble((String) value) : (Double) value;
+ return ByteBuffer.allocate((Double.SIZE / Byte.SIZE)).order(ByteOrder.LITTLE_ENDIAN).putDouble(doubleValue).array();
+
+ case NUMERIC:
+ case DECIMAL:
+ srcDataScale = ((BigDecimal) value).scale();
+ srcDataPrecision = ((BigDecimal) value).precision();
+ BigDecimal bigDataValue = (BigDecimal) value;
+ if ((srcDataPrecision > destPrecision) || (srcDataScale > destScale)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcJdbcType, destJdbcType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ else if (srcDataScale < destScale)
+ // update the scale of source data based on the metadata for scale sent early
+ bigDataValue = bigDataValue.setScale(destScale);
+
+ byteValue = DDC.convertBigDecimalToBytes(bigDataValue, bigDataValue.scale());
+ byte[] decimalbyteValue = new byte[16];
+ // removing the precision and scale information from the decimalToByte array
+ System.arraycopy(byteValue, 2, decimalbyteValue, 0, byteValue.length - 2);
+ return decimalbyteValue;
+
+ case SMALLMONEY:
+ case MONEY:
+ // For TDS we need to send the money value multiplied by 10^4 - this gives us the
+ // money value as integer. 4 is the default and only scale available with money.
+ // smallmoney is noralized to money.
+ BigDecimal bdValue = (BigDecimal) value;
+ // Need to validate range in the client side as we are converting BigDecimal to integers.
+ Util.validateMoneyRange(bdValue, destJdbcType);
+
+ // Get the total number of digits after the multiplication. Scale is hardcoded to 4. This is needed to get the proper rounding.
+ int digitCount = (bdValue.precision() - bdValue.scale()) + 4;
+
+ long moneyVal = ((BigDecimal) value)
+ .multiply(new BigDecimal(10000), new java.math.MathContext(digitCount, java.math.RoundingMode.HALF_UP)).longValue();
+ ByteBuffer bbuf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
+ bbuf.putInt((int) (moneyVal >> 32)).array();
+ bbuf.putInt((int) moneyVal).array();
+ return bbuf.array();
+
+ default:
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnsupportedDataTypeAE"));
+ Object[] msgArgs = {destJdbcType};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
}
// we don't want to throw R_errorConvertingValue error as it might expose decrypted data if source was encrypted
- catch (NumberFormatException ex)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcJdbcType, destJdbcType };
+ catch (NumberFormatException ex) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcJdbcType, destJdbcType};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- catch (IllegalArgumentException ex)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcJdbcType, destJdbcType };
+ }
+ catch (IllegalArgumentException ex) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcJdbcType, destJdbcType};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- catch (ClassCastException ex)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
- Object[] msgArgs = { srcJdbcType, destJdbcType };
+ }
+ catch (ClassCastException ex) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidDataForAE"));
+ Object[] msgArgs = {srcJdbcType, destJdbcType};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- }
-
- private boolean goToNextRow() throws SQLServerException
- {
- try{
- if (null != sourceResultSet)
- {
- return sourceResultSet.next();
- }
- else
- {
- return sourceBulkRecord.next();
- }
- }
- catch(SQLException e)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
- }
- }
-
- /*
- * Writes data for a batch of rows to the TDSWriter object.
- * Writes the following part in the BulkLoadBCP stream (https://msdn.microsoft.com/en-us/library/dd340549.aspx)
- *
- * ...
- *
- */
- private boolean writeBatchData(TDSWriter tdsWriter) throws SQLServerException
- {
- int batchsize = copyOptions.getBatchSize();
- int row = 0;
- while(true)
- {
- // Default batchsize is 0 - means all rows are sent in one batch. In this case we will return
- // when all rows in the resultset are processed. If batchsize is not zero, we will return when one batch of rows are processed.
- if (0 != batchsize && row >= batchsize)
- return true;
-
- // No more data available, return false so we do not execute any more batches.
- if(!goToNextRow())
- return false;
-
- // Write row header for each row.
- tdsWriter.writeByte((byte) TDS.TDS_ROW);
- int mappingColumnCount = columnMappings.size();
-
- // Copying from a resultset.
- if (null != sourceResultSet)
- {
- // Loop for each destination column. The mappings is a many to one mapping
- // where multiple source columns can be mapped to one destination column.
- for (int i = 0; i < mappingColumnCount; ++i)
- {
- writeColumn(
- tdsWriter,
- columnMappings.get(i).sourceColumnOrdinal,
- columnMappings.get(i).destinationColumnOrdinal,
- null // cell value is retrieved inside writeRowData() method.
- );
- }
- }
- // Copy from a file.
- else
- {
- // Get all the column values of the current row.
- Object[] rowObjects = sourceBulkRecord.getRowData();
-
- for (int i = 0; i < mappingColumnCount; ++i)
- {
- // If the SQLServerBulkCSVRecord does not have metadata for columns, it returns strings in the object array.
- // COnvert the strings using destination table types.
- writeColumn(
- tdsWriter,
- columnMappings.get(i).sourceColumnOrdinal,
- columnMappings.get(i).destinationColumnOrdinal,
- rowObjects[columnMappings.get(i).sourceColumnOrdinal - 1]);
- }
- }
- row++;
+ }
+ }
+
+ private boolean goToNextRow() throws SQLServerException {
+ try {
+ if (null != sourceResultSet) {
+ return sourceResultSet.next();
}
+ else {
+ return sourceBulkRecord.next();
+ }
+ }
+ catch (SQLException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);
}
+ }
+
+ /*
+ * Writes data for a batch of rows to the TDSWriter object. Writes the following part in the BulkLoadBCP stream
+ * (https://msdn.microsoft.com/en-us/library/dd340549.aspx) ...
+ */
+ private boolean writeBatchData(TDSWriter tdsWriter) throws SQLServerException {
+ int batchsize = copyOptions.getBatchSize();
+ int row = 0;
+ while (true) {
+ // Default batchsize is 0 - means all rows are sent in one batch. In this case we will return
+ // when all rows in the resultset are processed. If batchsize is not zero, we will return when one batch of rows are processed.
+ if (0 != batchsize && row >= batchsize)
+ return true;
+
+ // No more data available, return false so we do not execute any more batches.
+ if (!goToNextRow())
+ return false;
+
+ // Write row header for each row.
+ tdsWriter.writeByte((byte) TDS.TDS_ROW);
+ int mappingColumnCount = columnMappings.size();
+
+ // Copying from a resultset.
+ if (null != sourceResultSet) {
+ // Loop for each destination column. The mappings is a many to one mapping
+ // where multiple source columns can be mapped to one destination column.
+ for (int i = 0; i < mappingColumnCount; ++i) {
+ writeColumn(tdsWriter, columnMappings.get(i).sourceColumnOrdinal, columnMappings.get(i).destinationColumnOrdinal, null // cell
+ // value is
+ // retrieved
+ // inside
+ // writeRowData()
+ // method.
+ );
+ }
+ }
+ // Copy from a file.
+ else {
+ // Get all the column values of the current row.
+ Object[] rowObjects = sourceBulkRecord.getRowData();
+
+ for (int i = 0; i < mappingColumnCount; ++i) {
+ // If the SQLServerBulkCSVRecord does not have metadata for columns, it returns strings in the object array.
+ // COnvert the strings using destination table types.
+ writeColumn(tdsWriter, columnMappings.get(i).sourceColumnOrdinal, columnMappings.get(i).destinationColumnOrdinal,
+ rowObjects[columnMappings.get(i).sourceColumnOrdinal - 1]);
+ }
+ }
+ row++;
+ }
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy42Helper.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy42Helper.java
index aa961ec8d..5b5533bc3 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy42Helper.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy42Helper.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerBulkCopy42Helper.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.sql.Timestamp;
@@ -33,76 +22,76 @@
/**
*
- * This class is separated from SQLServerBulkCopy class to resolve run-time error of missing Java 8 types
- * when running with Java 7
+ * This class is separated from SQLServerBulkCopy class to resolve run-time error of missing Java 8 types when running with Java 7
*
*/
-class SQLServerBulkCopy42Helper
-{
- static Object getTemporalObjectFromCSVWithFormatter(String valueStrUntrimmed, int srcJdbcType, int srcColOrdinal, DateTimeFormatter dateTimeFormatter, SQLServerConnection connection, SQLServerBulkCopy sqlServerBC) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+class SQLServerBulkCopy42Helper {
+ static Object getTemporalObjectFromCSVWithFormatter(String valueStrUntrimmed,
+ int srcJdbcType,
+ int srcColOrdinal,
+ DateTimeFormatter dateTimeFormatter,
+ SQLServerConnection connection,
+ SQLServerBulkCopy sqlServerBC) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- try{
- TemporalAccessor ta = dateTimeFormatter.parse(valueStrUntrimmed);
+ try {
+ TemporalAccessor ta = dateTimeFormatter.parse(valueStrUntrimmed);
- int taHour, taMin, taSec, taYear, taMonth, taDay, taNano , taOffsetSec;
- taHour = taMin = taSec = taYear = taMonth = taDay = taNano = taOffsetSec = 0;
- if (ta.isSupported(ChronoField.NANO_OF_SECOND))
- taNano = ta.get(ChronoField.NANO_OF_SECOND);
- if (ta.isSupported(ChronoField.OFFSET_SECONDS))
- taOffsetSec = ta.get(ChronoField.OFFSET_SECONDS);
- if (ta.isSupported(ChronoField.HOUR_OF_DAY))
- taHour = ta.get(ChronoField.HOUR_OF_DAY);
- if (ta.isSupported(ChronoField.MINUTE_OF_HOUR))
- taMin = ta.get(ChronoField.MINUTE_OF_HOUR);
- if (ta.isSupported(ChronoField.SECOND_OF_MINUTE))
- taSec = ta.get(ChronoField.SECOND_OF_MINUTE);
- if (ta.isSupported(ChronoField.DAY_OF_MONTH))
- taDay = ta.get(ChronoField.DAY_OF_MONTH);
- if (ta.isSupported(ChronoField.MONTH_OF_YEAR))
- taMonth = ta.get(ChronoField.MONTH_OF_YEAR);
- if (ta.isSupported(ChronoField.YEAR))
- taYear = ta.get(ChronoField.YEAR);
+ int taHour, taMin, taSec, taYear, taMonth, taDay, taNano, taOffsetSec;
+ taHour = taMin = taSec = taYear = taMonth = taDay = taNano = taOffsetSec = 0;
+ if (ta.isSupported(ChronoField.NANO_OF_SECOND))
+ taNano = ta.get(ChronoField.NANO_OF_SECOND);
+ if (ta.isSupported(ChronoField.OFFSET_SECONDS))
+ taOffsetSec = ta.get(ChronoField.OFFSET_SECONDS);
+ if (ta.isSupported(ChronoField.HOUR_OF_DAY))
+ taHour = ta.get(ChronoField.HOUR_OF_DAY);
+ if (ta.isSupported(ChronoField.MINUTE_OF_HOUR))
+ taMin = ta.get(ChronoField.MINUTE_OF_HOUR);
+ if (ta.isSupported(ChronoField.SECOND_OF_MINUTE))
+ taSec = ta.get(ChronoField.SECOND_OF_MINUTE);
+ if (ta.isSupported(ChronoField.DAY_OF_MONTH))
+ taDay = ta.get(ChronoField.DAY_OF_MONTH);
+ if (ta.isSupported(ChronoField.MONTH_OF_YEAR))
+ taMonth = ta.get(ChronoField.MONTH_OF_YEAR);
+ if (ta.isSupported(ChronoField.YEAR))
+ taYear = ta.get(ChronoField.YEAR);
- Calendar cal = null;
- cal = new GregorianCalendar(new SimpleTimeZone(taOffsetSec * 1000, ""));
- cal.clear();
- cal.set(Calendar.HOUR_OF_DAY, taHour);
- cal.set(Calendar.MINUTE, taMin);
- cal.set(Calendar.SECOND, taSec);
- cal.set(Calendar.DATE, taDay);
- cal.set(Calendar.MONTH, taMonth - 1);
- cal.set(Calendar.YEAR, taYear);
- int fractionalSecondsLength = Integer.toString(taNano).length();
- for (int i = 0; i < (9 - fractionalSecondsLength); i++)
- taNano *= 10;
- Timestamp ts = new Timestamp(cal.getTimeInMillis());
- ts.setNanos(taNano);
+ Calendar cal = null;
+ cal = new GregorianCalendar(new SimpleTimeZone(taOffsetSec * 1000, ""));
+ cal.clear();
+ cal.set(Calendar.HOUR_OF_DAY, taHour);
+ cal.set(Calendar.MINUTE, taMin);
+ cal.set(Calendar.SECOND, taSec);
+ cal.set(Calendar.DATE, taDay);
+ cal.set(Calendar.MONTH, taMonth - 1);
+ cal.set(Calendar.YEAR, taYear);
+ int fractionalSecondsLength = Integer.toString(taNano).length();
+ for (int i = 0; i < (9 - fractionalSecondsLength); i++)
+ taNano *= 10;
+ Timestamp ts = new Timestamp(cal.getTimeInMillis());
+ ts.setNanos(taNano);
- switch(srcJdbcType)
- {
- case java.sql.Types.TIMESTAMP:
- return ts;
- case java.sql.Types.TIME:
- // Time is returned as Timestamp to preserve nano seconds.
- cal.set(connection.baseYear(), 00, 01);
- ts = new java.sql.Timestamp(cal.getTimeInMillis());
- ts.setNanos(taNano);
- return new java.sql.Timestamp(ts.getTime());
- case java.sql.Types.DATE:
- return new java.sql.Date(ts.getTime());
- case microsoft.sql.Types.DATETIMEOFFSET:
- return DateTimeOffset.valueOf(ts, taOffsetSec / 60);
- }
- }
- catch(DateTimeException | ArithmeticException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
- Object[] msgArgs = { JDBCType.of(srcJdbcType) };
- throw new SQLServerException(sqlServerBC, form.format(msgArgs), null, 0, false);
- }
- // unreachable code. Need to do to compile from Eclipse.
- return valueStrUntrimmed;
- }
+ switch (srcJdbcType) {
+ case java.sql.Types.TIMESTAMP:
+ return ts;
+ case java.sql.Types.TIME:
+ // Time is returned as Timestamp to preserve nano seconds.
+ cal.set(connection.baseYear(), 00, 01);
+ ts = new java.sql.Timestamp(cal.getTimeInMillis());
+ ts.setNanos(taNano);
+ return new java.sql.Timestamp(ts.getTime());
+ case java.sql.Types.DATE:
+ return new java.sql.Date(ts.getTime());
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ return DateTimeOffset.valueOf(ts, taOffsetSec / 60);
+ }
+ }
+ catch (DateTimeException | ArithmeticException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ParsingError"));
+ Object[] msgArgs = {JDBCType.of(srcJdbcType)};
+ throw new SQLServerException(sqlServerBC, form.format(msgArgs), null, 0, false);
+ }
+ // unreachable code. Need to do to compile from Eclipse.
+ return valueStrUntrimmed;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java
index 3bce3908b..774cd39cb 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopyOptions.java
@@ -1,56 +1,42 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerBulkCopyOptions.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.text.MessageFormat;
/**
- * A collection of settings that control how an instance of SQLServerBulkCopy behaves.
- * Used when constructing a SQLServerBulkCopy instance to change how the writeToServer methods for that instance behave.
+ * A collection of settings that control how an instance of SQLServerBulkCopy behaves. Used when constructing a SQLServerBulkCopy instance to change
+ * how the writeToServer methods for that instance behave.
*/
-public class SQLServerBulkCopyOptions
-{
+public class SQLServerBulkCopyOptions {
/**
* Number of rows in each batch.
*
- * A batch is complete when BatchSize rows have been processed or there are no more rows to send
- * to the destination data source. Zero (the default) indicates that each WriteToServer operation
- * is a single batch. If the SqlBulkCopy instance has been declared without the UseInternalTransaction
- * option in effect, rows are sent to the server BatchSize rows at a time, but no transaction-related
- * action is taken. If UseInternalTransaction is in effect, each batch of rows is inserted as a separate
- * transaction.
+ * A batch is complete when BatchSize rows have been processed or there are no more rows to send to the destination data source. Zero (the
+ * default) indicates that each WriteToServer operation is a single batch. If the SqlBulkCopy instance has been declared without the
+ * UseInternalTransaction option in effect, rows are sent to the server BatchSize rows at a time, but no transaction-related action is taken. If
+ * UseInternalTransaction is in effect, each batch of rows is inserted as a separate transaction.
*
* Default: 0
*/
private int batchSize;
-
+
/**
* Number of seconds for the operation to complete before it times out.
*
- * A value of 0 indicates no limit; the bulk copy will wait indefinitely. If the operation does time out,
- * the transaction is not committed and all copied rows are removed from the destination table.
+ * A value of 0 indicates no limit; the bulk copy will wait indefinitely. If the operation does time out, the transaction is not committed and all
+ * copied rows are removed from the destination table.
*
* Default: 60
*/
private int bulkCopyTimeout;
-
+
/**
* Check constraints while data is being inserted.
*
@@ -64,42 +50,41 @@ public class SQLServerBulkCopyOptions
* Default: false - no triggers are fired.
*/
private boolean fireTriggers;
-
+
/**
* Preserve source identity values.
*
* Default: false - identity values are assigned by the destination.
*/
private boolean keepIdentity;
-
+
/**
- * Preserve null values in the destination table regardless of the settings for default values.
+ * Preserve null values in the destination table regardless of the settings for default values.
*
* Default: false - null values are replaced by default values where applicable.
*/
private boolean keepNulls;
-
+
/**
* Obtain a bulk update lock for the duration of the bulk copy operation.
*
* Default: false - row locks are used.
*/
private boolean tableLock;
-
+
/**
- * When specified, each batch of the bulk-copy operation will occur within a transaction.
+ * When specified, each batch of the bulk-copy operation will occur within a transaction.
*
* Default: false - no transaction
*/
private boolean useInternalTransaction;
-
+
private boolean allowEncryptedValueModifications;
-
+
/**
* Initializes an instance of the SQLServerBulkCopySettings class using defaults for all of the settings.
*/
- public SQLServerBulkCopyOptions()
- {
+ public SQLServerBulkCopyOptions() {
batchSize = 0;
bulkCopyTimeout = 60;
checkConstraints = false;
@@ -110,61 +95,57 @@ public SQLServerBulkCopyOptions()
useInternalTransaction = false;
allowEncryptedValueModifications = false;
}
-
+
/**
- * Gets the number of rows in each batch. At the end of each batch, the rows in the batch are sent to the server.
+ * Gets the number of rows in each batch. At the end of each batch, the rows in the batch are sent to the server.
*
* @return Number of rows in each batch.
*/
- public int getBatchSize()
- {
+ public int getBatchSize() {
return batchSize;
}
-
+
/**
- * Sets the number of rows in each batch. At the end of each batch, the rows in the batch are sent to the server.
+ * Sets the number of rows in each batch. At the end of each batch, the rows in the batch are sent to the server.
*
- * @param batchSize Number of rows in each batch.
- * @throws SQLServerException If the batchSize being set is invalid.
+ * @param batchSize
+ * Number of rows in each batch.
+ * @throws SQLServerException
+ * If the batchSize being set is invalid.
*/
- public void setBatchSize(int batchSize) throws SQLServerException
- {
- if( batchSize >= 0 )
- {
- this.batchSize = batchSize;
- }
- else
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidNegativeArg"));
+ public void setBatchSize(int batchSize) throws SQLServerException {
+ if (batchSize >= 0) {
+ this.batchSize = batchSize;
+ }
+ else {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidNegativeArg"));
Object[] msgArgs = {"batchSize"};
SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, false);
- }
+ }
}
-
+
/**
* Gets the number of seconds for the operation to complete before it times out.
*
* @return Number of seconds before operation times out.
*/
- public int getBulkCopyTimeout()
- {
+ public int getBulkCopyTimeout() {
return bulkCopyTimeout;
}
-
+
/**
* Sets the number of seconds for the operation to complete before it times out.
*
- * @param timeout Number of seconds before operation times out.
- * @throws SQLServerException If the batchSize being set is invalid.
+ * @param timeout
+ * Number of seconds before operation times out.
+ * @throws SQLServerException
+ * If the batchSize being set is invalid.
*/
- public void setBulkCopyTimeout(int timeout) throws SQLServerException
- {
- if( timeout >= 0 )
- {
+ public void setBulkCopyTimeout(int timeout) throws SQLServerException {
+ if (timeout >= 0) {
this.bulkCopyTimeout = timeout;
}
- else
- {
+ else {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidNegativeArg"));
Object[] msgArgs = {"timeout"};
SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, false);
@@ -176,40 +157,38 @@ public void setBulkCopyTimeout(int timeout) throws SQLServerException
*
* @return True if source identity values are to be preserved; false if they are to be assigned by the destination.
*/
- public boolean isKeepIdentity()
- {
+ public boolean isKeepIdentity() {
return keepIdentity;
}
/**
* Sets whether or not to preserve any source identity values.
*
- * @param keepIdentity True if source identity values are to be preserved; false if they are to be assigned by the destination
+ * @param keepIdentity
+ * True if source identity values are to be preserved; false if they are to be assigned by the destination
*/
- public void setKeepIdentity(boolean keepIdentity)
- {
+ public void setKeepIdentity(boolean keepIdentity) {
this.keepIdentity = keepIdentity;
}
/**
- * Indicates whether to preserve null values in the destination table regardless of the settings for default values,
- * or if they should be replaced by default values (where applicable).
+ * Indicates whether to preserve null values in the destination table regardless of the settings for default values, or if they should be replaced
+ * by default values (where applicable).
*
* @return True if null values should be preserved; false if null values should be replaced by default values where applicable.
*/
- public boolean isKeepNulls()
- {
+ public boolean isKeepNulls() {
return keepNulls;
}
/**
- * Sets whether to preserve null values in the destination table regardless of the settings for default values,
- * or if they should be replaced by default values (where applicable).
+ * Sets whether to preserve null values in the destination table regardless of the settings for default values, or if they should be replaced by
+ * default values (where applicable).
*
- * @param keepNulls True if null values should be preserved; false if null values should be replaced by default values where applicable.
+ * @param keepNulls
+ * True if null values should be preserved; false if null values should be replaced by default values where applicable.
*/
- public void setKeepNulls(boolean keepNulls)
- {
+ public void setKeepNulls(boolean keepNulls) {
this.keepNulls = keepNulls;
}
@@ -218,18 +197,17 @@ public void setKeepNulls(boolean keepNulls)
*
* @return True to obtain row locks; false otherwise.
*/
- public boolean isTableLock()
- {
+ public boolean isTableLock() {
return tableLock;
}
/**
* Sets whether SQLServerBulkCopy should obtain a bulk update lock for the duration of the bulk copy operation.
*
- * @param tableLock True to obtain row locks; false otherwise.
+ * @param tableLock
+ * True to obtain row locks; false otherwise.
*/
- public void setTableLock(boolean tableLock)
- {
+ public void setTableLock(boolean tableLock) {
this.tableLock = tableLock;
}
@@ -238,18 +216,17 @@ public void setTableLock(boolean tableLock)
*
* @return True if the batch will occur within a transaction; false otherwise.
*/
- public boolean isUseInternalTransaction()
- {
+ public boolean isUseInternalTransaction() {
return useInternalTransaction;
}
/**
* Sets whether each batch of the bulk-copy operation will occur within a transaction or not.
*
- * @param useInternalTransaction True if the batch will occur within a transaction; false otherwise.
+ * @param useInternalTransaction
+ * True if the batch will occur within a transaction; false otherwise.
*/
- public void setUseInternalTransaction(boolean useInternalTransaction)
- {
+ public void setUseInternalTransaction(boolean useInternalTransaction) {
this.useInternalTransaction = useInternalTransaction;
}
@@ -258,18 +235,17 @@ public void setUseInternalTransaction(boolean useInternalTransaction)
*
* @return True if constraints are to be checked; false otherwise.
*/
- public boolean isCheckConstraints()
- {
+ public boolean isCheckConstraints() {
return checkConstraints;
}
/**
* Sets whether constraints are to be checked while data is being inserted or not.
*
- * @param checkConstraints True if constraints are to be checked; false otherwise.
+ * @param checkConstraints
+ * True if constraints are to be checked; false otherwise.
*/
- public void setCheckConstraints(boolean checkConstraints)
- {
+ public void setCheckConstraints(boolean checkConstraints) {
this.checkConstraints = checkConstraints;
}
@@ -278,44 +254,41 @@ public void setCheckConstraints(boolean checkConstraints)
*
* @return True triggers are enabled; false otherwise.
*/
- public boolean isFireTriggers()
- {
+ public boolean isFireTriggers() {
return fireTriggers;
}
/**
* Sets whether the server should be set to fire insert triggers for rows being inserted into the database.
*
- * @param fireTriggers True triggers are to be enabled; false otherwise.
+ * @param fireTriggers
+ * True triggers are to be enabled; false otherwise.
*/
- public void setFireTriggers(boolean fireTriggers)
- {
+ public void setFireTriggers(boolean fireTriggers) {
this.fireTriggers = fireTriggers;
- }
-
- /**
+ }
+
+ /**
* Indicates if allowEncryptedValueModifications option is enabled or not
+ *
* @return True if allowEncryptedValueModification is set to true; false otherwise.
*/
- public boolean isAllowEncryptedValueModifications()
- {
+ public boolean isAllowEncryptedValueModifications() {
return allowEncryptedValueModifications;
}
- /**
+ /**
* Sets whether the driver would send data as is or would decrypt the data and encrypt it again before sending to SQL Server
*
- * Use caution when specifying allowEncryptedValueModifications as this may lead to corrupting the database
- * because the driver does not check if the data is indeed encrypted, or if it is correctly encrypted using the
- * same encryption type, algorithm and key as the target column.
+ * Use caution when specifying allowEncryptedValueModifications as this may lead to corrupting the database because the driver does not check if
+ * the data is indeed encrypted, or if it is correctly encrypted using the same encryption type, algorithm and key as the target column.
*
- * @param allowEncryptedValueModifications enables bulk copying of encrypted data between tables or databases, without
- * decrypting the data. Typically, an application would select data from encrypted columns from one table without
- * decrypting the data (the app would connect to the database with the column encryption setting keyword set to disabled)
- * and then would use this option to bulk insert the data, which is still encrypted.
+ * @param allowEncryptedValueModifications
+ * enables bulk copying of encrypted data between tables or databases, without decrypting the data. Typically, an application would
+ * select data from encrypted columns from one table without decrypting the data (the app would connect to the database with the column
+ * encryption setting keyword set to disabled) and then would use this option to bulk insert the data, which is still encrypted.
*/
- public void setAllowEncryptedValueModifications(boolean allowEncryptedValueModifications)
- {
+ public void setAllowEncryptedValueModifications(boolean allowEncryptedValueModifications) {
this.allowEncryptedValueModifications = allowEncryptedValueModifications;
}
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java
index 32435a756..c1a3c3770 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerCallableStatement.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.io.Closeable;
@@ -44,29 +33,25 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
/**
-* CallableStatement implements JDBC callable statements.
-* CallableStatement allows the caller to specify the procedure name to call along with input parameter
-* value and output parameter types. Callable statement also allows the return of a return status with
-* the ? = call( ?, ..) JDBC syntax
-*
-* The API javadoc for JDBC API methods that this class implements are not repeated here. Please
-* see Sun's JDBC API interfaces javadoc for those details.
-*/
+ * CallableStatement implements JDBC callable statements. CallableStatement allows the caller to specify the procedure name to call along with input
+ * parameter value and output parameter types. Callable statement also allows the return of a return status with the ? = call( ?, ..) JDBC syntax
+ *
+ * The API javadoc for JDBC API methods that this class implements are not repeated here. Please see Sun's JDBC API interfaces javadoc for those
+ * details.
+ */
public class SQLServerCallableStatement extends SQLServerPreparedStatement implements ISQLServerCallableStatement {
- /** the call param names */
- private ArrayList paramNames;
-
+ /** the call param names */
+ private ArrayList paramNames;
/** Number of registered OUT parameters */
int nOutParams = 0;
- /** number of out params assigned already*/
+ /** number of out params assigned already */
int nOutParamsAssigned = 0;
- /** The index of the out params indexed - internal index*/
+ /** The index of the out params indexed - internal index */
private int outParamIndex = -1;
// The last out param accessed.
@@ -76,49 +61,53 @@ public class SQLServerCallableStatement extends SQLServerPreparedStatement imple
private Closeable activeStream;
// Internal function used in tracing
- String getClassNameInternal()
- {
- return "SQLServerCallableStatement";
- }
-
-
- /**
- * Create a new callable statement.
- * @param connection the connection
- * @param sql the users call syntax
- * @param nRSType the result set type
- * @param nRSConcur the result set concurrency
- * @param stmtColEncSetting the statement column encryption setting
- * @throws SQLServerException
- */
- SQLServerCallableStatement(SQLServerConnection connection, String sql, int nRSType, int nRSConcur, SQLServerStatementColumnEncryptionSetting stmtColEncSetting)
- throws SQLServerException
- {
+ String getClassNameInternal() {
+ return "SQLServerCallableStatement";
+ }
+
+ /**
+ * Create a new callable statement.
+ *
+ * @param connection
+ * the connection
+ * @param sql
+ * the users call syntax
+ * @param nRSType
+ * the result set type
+ * @param nRSConcur
+ * the result set concurrency
+ * @param stmtColEncSetting
+ * the statement column encryption setting
+ * @throws SQLServerException
+ */
+ SQLServerCallableStatement(SQLServerConnection connection,
+ String sql,
+ int nRSType,
+ int nRSConcur,
+ SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
super(connection, sql, nRSType, nRSConcur, stmtColEncSetting);
}
- public void registerOutParameter(int index, int sqlType) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), new Integer(sqlType)});
+ public void registerOutParameter(int index,
+ int sqlType) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {new Integer(index), new Integer(sqlType)});
checkClosed();
- if (index < 1 || index > inOutParam.length)
- {
+ if (index < 1 || index > inOutParam.length) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_indexOutOfRange"));
Object[] msgArgs = {new Integer(index)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "7009", false);
}
// REF_CURSOR 2012 is a special type - should throw SQLFeatureNotSupportedException as per spec
- // but this will require changing API to throw SQLException.
- // This should be reviewed in 4199060
- if (2012 == sqlType)
- {
+ // but this will require changing API to throw SQLException.
+ // This should be reviewed in 4199060
+ if (2012 == sqlType) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_featureNotSupported"));
Object[] msgArgs = {"REF_CURSOR"};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false);
}
-
+
JDBCType jdbcType = JDBCType.of(sqlType);
// Registering an OUT parameter is an indication that the app is done
@@ -130,7 +119,7 @@ public void registerOutParameter(int index, int sqlType) throws SQLServerExcepti
if (jdbcType.isUnsupported())
jdbcType = JDBCType.BINARY;
- Parameter param = inOutParam[index-1];
+ Parameter param = inOutParam[index - 1];
assert null != param;
// If the parameter was not previously registered for OUTPUT then
@@ -141,48 +130,45 @@ public void registerOutParameter(int index, int sqlType) throws SQLServerExcepti
// (Re)register the parameter for OUTPUT with the specified SQL type
// overriding any previous registration with another SQL type.
param.registerForOutput(jdbcType, connection);
- switch(sqlType)
- {
- case microsoft.sql.Types.DATETIME:
- param.setOutScale(3);
- break;
- case java.sql.Types.TIME:
- case java.sql.Types.TIMESTAMP:
- case microsoft.sql.Types.DATETIMEOFFSET:
- param.setOutScale(7);
- break;
- default:
- break;
+ switch (sqlType) {
+ case microsoft.sql.Types.DATETIME:
+ param.setOutScale(3);
+ break;
+ case java.sql.Types.TIME:
+ case java.sql.Types.TIMESTAMP:
+ case microsoft.sql.Types.DATETIMEOFFSET:
+ param.setOutScale(7);
+ break;
+ default:
+ break;
}
-
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}
/**
* Locate any output parameter values returned from the procedure call
*/
- private Parameter getOutParameter(int i) throws SQLServerException
- {
- // Process any remaining result sets and update counts. This positions
- // us for retrieving the OUT parameters. Note that after retrieving
+ private Parameter getOutParameter(int i) throws SQLServerException {
+ // Process any remaining result sets and update counts. This positions
+ // us for retrieving the OUT parameters. Note that after retrieving
// an OUT parameter, an SQLException is thrown if the application tries
// to go back and process the results.
processResults();
// if this item has been indexed already leave!
- if(inOutParam[i-1]== lastParamAccessed || inOutParam[i-1].isValueGotten())
- return inOutParam[i-1];
+ if (inOutParam[i - 1] == lastParamAccessed || inOutParam[i - 1].isValueGotten())
+ return inOutParam[i - 1];
// Skip OUT parameters (buffering them as we go) until we
// reach the one we're looking for.
- while (outParamIndex != i-1)
+ while (outParamIndex != i - 1)
skipOutParameters(1, false);
- return inOutParam[i-1];
+ return inOutParam[i - 1];
}
- void startResults()
- {
+ void startResults() {
super.startResults();
outParamIndex = -1;
nOutParamsAssigned = 0;
@@ -190,24 +176,21 @@ void startResults()
assert null == activeStream;
}
- void processBatch() throws SQLServerException
- {
+ void processBatch() throws SQLServerException {
processResults();
// If there were any OUT parameters, then process them
- // and the rest of the batch that follows them. If there were
+ // and the rest of the batch that follows them. If there were
// no OUT parameters, than the entire batch was already processed
// in the processResults call above.
assert nOutParams >= 0;
- if (nOutParams > 0)
- {
+ if (nOutParams > 0) {
processOutParameters();
processBatchRemainder();
}
}
- final void processOutParameters() throws SQLServerException
- {
+ final void processOutParameters() throws SQLServerException {
assert nOutParams > 0;
assert null != inOutParam;
@@ -216,16 +199,13 @@ final void processOutParameters() throws SQLServerException
// First, discard all of the previously indexed OUT parameters up to,
// but not including, the last-indexed parameter.
- if (outParamIndex >= 0)
- {
+ if (outParamIndex >= 0) {
// Note: It doesn't matter that they're not cleared in the order they
- // appear in the response stream. What counts is that at the end
+ // appear in the response stream. What counts is that at the end
// none of them has any TDSReaderMarks holding onto any portion of
// the response stream.
- for (int index = 0; index < inOutParam.length; ++index)
- {
- if (index != outParamIndex && inOutParam[index].isValueGotten())
- {
+ for (int index = 0; index < inOutParam.length; ++index) {
+ if (index != outParamIndex && inOutParam[index].isValueGotten()) {
assert inOutParam[index].isOutput();
inOutParam[index].resetOutputValue();
}
@@ -237,12 +217,11 @@ final void processOutParameters() throws SQLServerException
if (nOutParamsAssigned < nOutParams)
skipOutParameters(nOutParams - nOutParamsAssigned, true);
- // Finally, skip the last-indexed parameter. If there were no unindexed parameters
+ // Finally, skip the last-indexed parameter. If there were no unindexed parameters
// in the previous step, then this is the last-indexed parameter left from the first
- // step. If we skipped unindexed parameters in the previous step, then this is the
+ // step. If we skipped unindexed parameters in the previous step, then this is the
// last-indexed parameter left at the end of that step.
- if (outParamIndex >= 0)
- {
+ if (outParamIndex >= 0) {
inOutParam[outParamIndex].skipValue(resultsReader(), true);
inOutParam[outParamIndex].resetOutputValue();
outParamIndex = -1;
@@ -250,20 +229,16 @@ final void processOutParameters() throws SQLServerException
}
/**
- * Processes the remainder of the batch up to the final or batch-terminating DONE token
- * that marks the end of a sp_[cursor][prep]exec stored procedure call.
+ * Processes the remainder of the batch up to the final or batch-terminating DONE token that marks the end of a sp_[cursor][prep]exec stored
+ * procedure call.
*/
- private void processBatchRemainder() throws SQLServerException
- {
- final class ExecDoneHandler extends TDSTokenHandler
- {
- ExecDoneHandler()
- {
+ private void processBatchRemainder() throws SQLServerException {
+ final class ExecDoneHandler extends TDSTokenHandler {
+ ExecDoneHandler() {
super("ExecDoneHandler");
}
- boolean onDone(TDSReader tdsReader) throws SQLServerException
- {
+ boolean onDone(TDSReader tdsReader) throws SQLServerException {
// Consume the done token and decide what to do with it...
StreamDone doneToken = new StreamDone();
doneToken.setFromTDS(tdsReader);
@@ -271,8 +246,7 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException
// If this is a non-final batch-terminating DONE token,
// then stop parsing the response now and set up for
// the next batch.
- if (doneToken.wasRPCInBatch())
- {
+ if (doneToken.wasRPCInBatch()) {
startResults();
return false;
}
@@ -287,28 +261,27 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException
TDSParser.parse(resultsReader(), execDoneHandler);
}
- private void skipOutParameters(int numParamsToSkip, boolean discardValues) throws SQLServerException
- {
+ private void skipOutParameters(int numParamsToSkip,
+ boolean discardValues) throws SQLServerException {
/** TDS token handler for locating OUT parameters (RETURN_VALUE tokens) in the response token stream */
- final class OutParamHandler extends TDSTokenHandler
- {
+ final class OutParamHandler extends TDSTokenHandler {
final StreamRetValue srv = new StreamRetValue();
private boolean foundParam;
- final boolean foundParam() { return foundParam; }
- OutParamHandler()
- {
+ final boolean foundParam() {
+ return foundParam;
+ }
+
+ OutParamHandler() {
super("OutParamHandler");
}
- final void reset()
- {
+ final void reset() {
foundParam = false;
}
- boolean onRetValue(TDSReader tdsReader) throws SQLServerException
- {
+ boolean onRetValue(TDSReader tdsReader) throws SQLServerException {
srv.setFromTDS(tdsReader);
foundParam = true;
return false;
@@ -319,12 +292,10 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException
// Index the application OUT parameters
assert numParamsToSkip <= nOutParams - nOutParamsAssigned;
- for (int paramsSkipped = 0; paramsSkipped < numParamsToSkip; ++paramsSkipped)
- {
+ for (int paramsSkipped = 0; paramsSkipped < numParamsToSkip; ++paramsSkipped) {
// Discard the last-indexed parameter by skipping over it and
// discarding the value if it is no longer needed.
- if (-1 != outParamIndex)
- {
+ if (-1 != outParamIndex) {
inOutParam[outParamIndex].skipValue(resultsReader(), discardValues);
if (discardValues)
inOutParam[outParamIndex].resetOutputValue();
@@ -337,10 +308,9 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException
// If we don't find it, then most likely the server encountered some error that
// was bad enough to halt statement execution before returning OUT params, but
// not necessarily bad enough to close the connection.
- if (!outParamHandler.foundParam())
- {
+ if (!outParamHandler.foundParam()) {
// If we were just going to discard the OUT parameters we found anyway,
- // then it's no problem that we didn't find any of them. For exmaple,
+ // then it's no problem that we didn't find any of them. For exmaple,
// when we are closing or reexecuting this CallableStatement (that is,
// calling in through processResponse), we don't care that execution
// failed to return the OUT parameters.
@@ -350,89 +320,89 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException
// If we were asked to retain the OUT parameters as we skip past them,
// then report an error if we did not find any.
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueNotSetForParameter"));
- Object[] msgArgs = {new Integer(outParamIndex+1)};
+ Object[] msgArgs = {new Integer(outParamIndex + 1)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), null, false);
}
-
- // In Yukon and later, large Object output parameters are reordered to appear at
- // the end of the stream. First group of small parameters is sent, followed by
- // group of large output parameters. There is no reordering within the groups.
-
- // Note that parameter ordinals are 0-indexed and that the return status is not
- // considered to be an output parameter.
- outParamIndex = outParamHandler.srv.getOrdinalOrLength();
-
- // Statements need to have their out param indices adjusted by the number
- // of sp_[cursor][prep]exec params.
- outParamIndex -= outParamIndexAdjustment;
- if((outParamIndex < 0 || outParamIndex >= inOutParam.length) || (!inOutParam[outParamIndex].isOutput()))
- {
- getStatementLogger().info(toString() +" Unexpected outParamIndex: " + outParamIndex + "; adjustment: " + outParamIndexAdjustment);
- connection.throwInvalidTDS();
- }
-
+ // In Yukon and later, large Object output parameters are reordered to appear at
+ // the end of the stream. First group of small parameters is sent, followed by
+ // group of large output parameters. There is no reordering within the groups.
+
+ // Note that parameter ordinals are 0-indexed and that the return status is not
+ // considered to be an output parameter.
+ outParamIndex = outParamHandler.srv.getOrdinalOrLength();
+
+ // Statements need to have their out param indices adjusted by the number
+ // of sp_[cursor][prep]exec params.
+ outParamIndex -= outParamIndexAdjustment;
+ if ((outParamIndex < 0 || outParamIndex >= inOutParam.length) || (!inOutParam[outParamIndex].isOutput())) {
+ getStatementLogger().info(toString() + " Unexpected outParamIndex: " + outParamIndex + "; adjustment: " + outParamIndexAdjustment);
+ connection.throwInvalidTDS();
+ }
++nOutParamsAssigned;
}
}
- /*L0*/ public void registerOutParameter (int index, int sqlType, String typeName) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), new Integer(sqlType), typeName});
-
+ /* L0 */ public void registerOutParameter(int index,
+ int sqlType,
+ String typeName) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {new Integer(index), new Integer(sqlType), typeName});
+
checkClosed();
-
+
registerOutParameter(index, sqlType);
-
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}
- /*L0*/ public void registerOutParameter(int index, int sqlType, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), new Integer(sqlType), new Integer(scale)});
-
+ /* L0 */ public void registerOutParameter(int index,
+ int sqlType,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
+ new Object[] {new Integer(index), new Integer(sqlType), new Integer(scale)});
+
checkClosed();
-
+
registerOutParameter(index, sqlType);
- inOutParam[index-1].setOutScale(scale);
-
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ inOutParam[index - 1].setOutScale(scale);
+
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}
- public void registerOutParameter(int index, int sqlType, int precision, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), new Integer(sqlType), new Integer(scale), new Integer(precision)});
-
- checkClosed();
-
- registerOutParameter(index, sqlType);
- inOutParam[index-1].setValueLength(precision);
- inOutParam[index-1].setOutScale(scale);
-
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ public void registerOutParameter(int index,
+ int sqlType,
+ int precision,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
+ new Object[] {new Integer(index), new Integer(sqlType), new Integer(scale), new Integer(precision)});
- /* ---------------------- JDBC API: Get Output Params -------------------------- */
+ checkClosed();
+
+ registerOutParameter(index, sqlType);
+ inOutParam[index - 1].setValueLength(precision);
+ inOutParam[index - 1].setOutScale(scale);
+
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- private Parameter getterGetParam(int index) throws SQLServerException
- {
+ /* ---------------------- JDBC API: Get Output Params -------------------------- */
+
+ private Parameter getterGetParam(int index) throws SQLServerException {
checkClosed();
// Check for valid index
- if (index < 1 || index > inOutParam.length)
- {
+ if (index < 1 || index > inOutParam.length) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidOutputParameter"));
Object[] msgArgs = {new Integer(index)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "07009", false);
}
// Check index refers to a registered OUT parameter
- if (!inOutParam[index-1].isOutput())
- {
+ if (!inOutParam[index - 1].isOutput()) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_outputParameterNotRegisteredForOutput"));
Object[] msgArgs = {new Integer(index)};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "07009", true);
@@ -445,58 +415,46 @@ private Parameter getterGetParam(int index) throws SQLServerException
resultsReader().getCommand().checkForInterrupt();
closeActiveStream();
- if(getStatementLogger().isLoggable(java.util.logging.Level.FINER))
- getStatementLogger().finer(toString() +" Getting Param:"+index);
+ if (getStatementLogger().isLoggable(java.util.logging.Level.FINER))
+ getStatementLogger().finer(toString() + " Getting Param:" + index);
// Dynamically load OUT params from TDS response buffer
lastParamAccessed = getOutParameter(index);
return lastParamAccessed;
}
- private Object getValue(int parameterIndex, JDBCType jdbcType) throws SQLServerException
- {
+ private Object getValue(int parameterIndex,
+ JDBCType jdbcType) throws SQLServerException {
return getterGetParam(parameterIndex).getValue(jdbcType, null, null, resultsReader());
}
- private Object getValue(int parameterIndex, JDBCType jdbcType, Calendar cal) throws SQLServerException
- {
+ private Object getValue(int parameterIndex,
+ JDBCType jdbcType,
+ Calendar cal) throws SQLServerException {
return getterGetParam(parameterIndex).getValue(jdbcType, null, cal, resultsReader());
}
- private Object getStream(int parameterIndex, StreamType streamType) throws SQLServerException
- {
- Object value = getterGetParam(parameterIndex).getValue(
- streamType.getJDBCType(),
- new InputStreamGetterArgs(
- streamType,
- getIsResponseBufferingAdaptive(),
- getIsResponseBufferingAdaptive(),
- toString()),
- null, // calendar
- resultsReader());
+ private Object getStream(int parameterIndex,
+ StreamType streamType) throws SQLServerException {
+ Object value = getterGetParam(parameterIndex).getValue(streamType.getJDBCType(),
+ new InputStreamGetterArgs(streamType, getIsResponseBufferingAdaptive(), getIsResponseBufferingAdaptive(), toString()), null, // calendar
+ resultsReader());
- activeStream = (Closeable) value;
+ activeStream = (Closeable) value;
return value;
}
- private Object getSQLXMLInternal(int parameterIndex) throws SQLServerException
- {
- SQLServerSQLXML value = (SQLServerSQLXML)getterGetParam(parameterIndex).getValue(
- JDBCType.SQLXML,
- new InputStreamGetterArgs(
- StreamType.SQLXML,
- getIsResponseBufferingAdaptive(),
- getIsResponseBufferingAdaptive(),
- toString()),
- null, // calendar
- resultsReader());
-
- if(null != value)
+
+ private Object getSQLXMLInternal(int parameterIndex) throws SQLServerException {
+ SQLServerSQLXML value = (SQLServerSQLXML) getterGetParam(parameterIndex).getValue(JDBCType.SQLXML,
+ new InputStreamGetterArgs(StreamType.SQLXML, getIsResponseBufferingAdaptive(), getIsResponseBufferingAdaptive(), toString()), null, // calendar
+ resultsReader());
+
+ if (null != value)
activeStream = value.getStream();
return value;
}
- public int getInt(int index) throws SQLServerException
- {
+ public int getInt(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getInt", index);
checkClosed();
Integer value = (Integer) getValue(index, JDBCType.INTEGER);
@@ -504,8 +462,7 @@ public int getInt(int index) throws SQLServerException
return null != value ? value.intValue() : 0;
}
- public int getInt(String sCol) throws SQLServerException
- {
+ public int getInt(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getInt", sCol);
checkClosed();
Integer value = (Integer) getValue(findColumn(sCol), JDBCType.INTEGER);
@@ -513,8 +470,7 @@ public int getInt(String sCol) throws SQLServerException
return null != value ? value.intValue() : 0;
}
- public String getString(int index) throws SQLServerException
- {
+ public String getString(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getString", index);
checkClosed();
String value = (String) getValue(index, JDBCType.CHAR);
@@ -522,8 +478,7 @@ public String getString(int index) throws SQLServerException
return value;
}
- public String getString(String sCol) throws SQLServerException
- {
+ public String getString(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getString", sCol);
checkClosed();
String value = (String) getValue(findColumn(sCol), JDBCType.CHAR);
@@ -531,8 +486,7 @@ public String getString(String sCol) throws SQLServerException
return value;
}
- public final String getNString(int parameterIndex) throws SQLException
- {
+ public final String getNString(int parameterIndex) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getNString", parameterIndex);
@@ -541,9 +495,8 @@ public final String getNString(int parameterIndex) throws SQLException
loggerExternal.exiting(getClassNameLogging(), "getNString", value);
return value;
}
-
- public final String getNString(String parameterName) throws SQLException
- {
+
+ public final String getNString(String parameterName) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getNString", parameterName);
@@ -552,11 +505,12 @@ public final String getNString(String parameterName) throws SQLException
loggerExternal.exiting(getClassNameLogging(), "getNString", value);
return value;
}
-
- @Deprecated public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getBigDecimal", new Object[]{Integer.valueOf(parameterIndex), Integer.valueOf(scale)});
+
+ @Deprecated
+ public BigDecimal getBigDecimal(int parameterIndex,
+ int scale) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getBigDecimal", new Object[] {Integer.valueOf(parameterIndex), Integer.valueOf(scale)});
checkClosed();
BigDecimal value = (BigDecimal) getValue(parameterIndex, JDBCType.DECIMAL);
if (null != value)
@@ -565,10 +519,11 @@ public final String getNString(String parameterName) throws SQLException
return value;
}
- @Deprecated public BigDecimal getBigDecimal(String parameterName, int scale) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getBigDecimal", new Object[]{parameterName, Integer.valueOf(scale)});
+ @Deprecated
+ public BigDecimal getBigDecimal(String parameterName,
+ int scale) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getBigDecimal", new Object[] {parameterName, Integer.valueOf(scale)});
checkClosed();
BigDecimal value = (BigDecimal) getValue(findColumn(parameterName), JDBCType.DECIMAL);
if (null != value)
@@ -577,17 +532,15 @@ public final String getNString(String parameterName) throws SQLException
return value;
}
- public boolean getBoolean(int index) throws SQLServerException
- {
+ public boolean getBoolean(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBoolean", index);
checkClosed();
Boolean value = (Boolean) getValue(index, JDBCType.BIT);
loggerExternal.exiting(getClassNameLogging(), "getBoolean", value);
return null != value ? value.booleanValue() : false;
}
-
- public boolean getBoolean(String sCol) throws SQLServerException
- {
+
+ public boolean getBoolean(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBoolean", sCol);
checkClosed();
Boolean value = (Boolean) getValue(findColumn(sCol), JDBCType.BIT);
@@ -595,8 +548,7 @@ public boolean getBoolean(String sCol) throws SQLServerException
return null != value ? value.booleanValue() : false;
}
- public byte getByte(int index) throws SQLServerException
- {
+ public byte getByte(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getByte", index);
checkClosed();
Short shortValue = (Short) getValue(index, JDBCType.TINYINT);
@@ -605,8 +557,7 @@ public byte getByte(int index) throws SQLServerException
return byteValue;
}
- public byte getByte(String sCol) throws SQLServerException
- {
+ public byte getByte(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getByte", sCol);
checkClosed();
Short shortValue = (Short) getValue(findColumn(sCol), JDBCType.TINYINT);
@@ -615,8 +566,7 @@ public byte getByte(String sCol) throws SQLServerException
return byteValue;
}
- public byte[] getBytes(int index) throws SQLServerException
- {
+ public byte[] getBytes(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBytes", index);
checkClosed();
byte[] value = (byte[]) getValue(index, JDBCType.BINARY);
@@ -624,8 +574,7 @@ public byte[] getBytes(int index) throws SQLServerException
return value;
}
- public byte[] getBytes(String sCol) throws SQLServerException
- {
+ public byte[] getBytes(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBytes", sCol);
checkClosed();
byte[] value = (byte[]) getValue(findColumn(sCol), JDBCType.BINARY);
@@ -633,8 +582,7 @@ public byte[] getBytes(String sCol) throws SQLServerException
return value;
}
- public Date getDate(int index) throws SQLServerException
- {
+ public Date getDate(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getDate", index);
checkClosed();
java.sql.Date value = (java.sql.Date) getValue(index, JDBCType.DATE);
@@ -642,8 +590,7 @@ public Date getDate(int index) throws SQLServerException
return value;
}
- public Date getDate(String sCol) throws SQLServerException
- {
+ public Date getDate(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getDate", sCol);
checkClosed();
java.sql.Date value = (java.sql.Date) getValue(findColumn(sCol), JDBCType.DATE);
@@ -651,9 +598,9 @@ public Date getDate(String sCol) throws SQLServerException
return value;
}
- public Date getDate(int index, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ public Date getDate(int index,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "getDate", new Object[] {index, cal});
checkClosed();
java.sql.Date value = (java.sql.Date) getValue(index, JDBCType.DATE, cal);
@@ -661,9 +608,9 @@ public Date getDate(int index, Calendar cal) throws SQLServerException
return value;
}
- public Date getDate(String sCol, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ public Date getDate(String sCol,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "getDate", new Object[] {sCol, cal});
checkClosed();
java.sql.Date value = (java.sql.Date) getValue(findColumn(sCol), JDBCType.DATE, cal);
@@ -671,8 +618,7 @@ public Date getDate(String sCol, Calendar cal) throws SQLServerException
return value;
}
- public double getDouble(int index) throws SQLServerException
- {
+ public double getDouble(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getDouble", index);
checkClosed();
Double value = (Double) getValue(index, JDBCType.DOUBLE);
@@ -680,8 +626,7 @@ public double getDouble(int index) throws SQLServerException
return null != value ? value.doubleValue() : 0;
}
- public double getDouble(String sCol) throws SQLServerException
- {
+ public double getDouble(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getDouble", sCol);
checkClosed();
Double value = (Double) getValue(findColumn(sCol), JDBCType.DOUBLE);
@@ -689,8 +634,7 @@ public double getDouble(String sCol) throws SQLServerException
return null != value ? value.doubleValue() : 0;
}
- public float getFloat(int index) throws SQLServerException
- {
+ public float getFloat(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getFloat", index);
checkClosed();
Float value = (Float) getValue(index, JDBCType.REAL);
@@ -698,28 +642,25 @@ public float getFloat(int index) throws SQLServerException
return null != value ? value.floatValue() : 0;
}
- public float getFloat(String sCol) throws SQLServerException
- {
-
- loggerExternal.entering(getClassNameLogging(), "getFloat", sCol);
+ public float getFloat(String sCol) throws SQLServerException {
+
+ loggerExternal.entering(getClassNameLogging(), "getFloat", sCol);
checkClosed();
Float value = (Float) getValue(findColumn(sCol), JDBCType.REAL);
loggerExternal.exiting(getClassNameLogging(), "getFloat", value);
return null != value ? value.floatValue() : 0;
}
- public long getLong(int index) throws SQLServerException
- {
-
- loggerExternal.entering(getClassNameLogging(), "getLong", index);
+ public long getLong(int index) throws SQLServerException {
+
+ loggerExternal.entering(getClassNameLogging(), "getLong", index);
checkClosed();
Long value = (Long) getValue(index, JDBCType.BIGINT);
loggerExternal.exiting(getClassNameLogging(), "getLong", value);
return null != value ? value.longValue() : 0;
}
-
- public long getLong(String sCol) throws SQLServerException
- {
+
+ public long getLong(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getLong", sCol);
checkClosed();
Long value = (Long) getValue(findColumn(sCol), JDBCType.BIGINT);
@@ -727,60 +668,52 @@ public long getLong(String sCol) throws SQLServerException
return null != value ? value.longValue() : 0;
}
- public Object getObject(int index) throws SQLServerException
- {
+ public Object getObject(int index) throws SQLServerException {
- loggerExternal.entering(getClassNameLogging(), "getObject", index);
- checkClosed();
- Object value = getValue(index,
- getterGetParam(index).getJdbcTypeSetByUser() != null ?
- getterGetParam(index).getJdbcTypeSetByUser() :
- getterGetParam(index).getJdbcType());
- loggerExternal.exiting(getClassNameLogging(), "getObject", value);
- return value;
+ loggerExternal.entering(getClassNameLogging(), "getObject", index);
+ checkClosed();
+ Object value = getValue(index, getterGetParam(index).getJdbcTypeSetByUser() != null ? getterGetParam(index).getJdbcTypeSetByUser()
+ : getterGetParam(index).getJdbcType());
+ loggerExternal.exiting(getClassNameLogging(), "getObject", value);
+ return value;
}
- public T getObject(int index, Class type) throws SQLException
- {
- DriverJDBCVersion.checkSupportsJDBC41();
+ public T getObject(int index,
+ Class type) throws SQLException {
+ DriverJDBCVersion.checkSupportsJDBC41();
- // The driver currently does not implement the optional JDBC APIs
- throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
+ // The driver currently does not implement the optional JDBC APIs
+ throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
}
- public Object getObject(String sCol) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "getObject", sCol);
- checkClosed();
- int parameterIndex = findColumn(sCol);
- Object value = getValue(parameterIndex,
- getterGetParam(parameterIndex).getJdbcTypeSetByUser() != null ?
- getterGetParam(parameterIndex).getJdbcTypeSetByUser() :
- getterGetParam(parameterIndex).getJdbcType());
- loggerExternal.exiting(getClassNameLogging(), "getObject", value);
- return value;
+ public Object getObject(String sCol) throws SQLServerException {
+ loggerExternal.entering(getClassNameLogging(), "getObject", sCol);
+ checkClosed();
+ int parameterIndex = findColumn(sCol);
+ Object value = getValue(parameterIndex, getterGetParam(parameterIndex).getJdbcTypeSetByUser() != null
+ ? getterGetParam(parameterIndex).getJdbcTypeSetByUser() : getterGetParam(parameterIndex).getJdbcType());
+ loggerExternal.exiting(getClassNameLogging(), "getObject", value);
+ return value;
}
- public T getObject(String sCol, Class type) throws SQLException
- {
+ public T getObject(String sCol,
+ Class type) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC41();
- // The driver currently does not implement the optional JDBC APIs
- throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
+ // The driver currently does not implement the optional JDBC APIs
+ throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
}
- public short getShort(int index) throws SQLServerException
- {
-
- loggerExternal.entering(getClassNameLogging(), "getShort", index);
+ public short getShort(int index) throws SQLServerException {
+
+ loggerExternal.entering(getClassNameLogging(), "getShort", index);
checkClosed();
Short value = (Short) getValue(index, JDBCType.SMALLINT);
loggerExternal.exiting(getClassNameLogging(), "getShort", value);
return null != value ? value.shortValue() : 0;
}
- public short getShort(String sCol) throws SQLServerException
- {
+ public short getShort(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getShort", sCol);
checkClosed();
Short value = (Short) getValue(findColumn(sCol), JDBCType.SMALLINT);
@@ -788,18 +721,16 @@ public short getShort(String sCol) throws SQLServerException
return null != value ? value.shortValue() : 0;
}
- public Time getTime(int index) throws SQLServerException
- {
-
- loggerExternal.entering(getClassNameLogging(), "getTime", index);
+ public Time getTime(int index) throws SQLServerException {
+
+ loggerExternal.entering(getClassNameLogging(), "getTime", index);
checkClosed();
java.sql.Time value = (java.sql.Time) getValue(index, JDBCType.TIME);
loggerExternal.exiting(getClassNameLogging(), "getTime", value);
return value;
}
- public Time getTime(String sCol) throws SQLServerException
- {
+ public Time getTime(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getTime", sCol);
checkClosed();
java.sql.Time value = (java.sql.Time) getValue(findColumn(sCol), JDBCType.TIME);
@@ -807,29 +738,28 @@ public Time getTime(String sCol) throws SQLServerException
return value;
}
- public Time getTime(int index, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getTime", new Object[]{index, cal});
+ public Time getTime(int index,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getTime", new Object[] {index, cal});
checkClosed();
java.sql.Time value = (java.sql.Time) getValue(index, JDBCType.TIME, cal);
loggerExternal.exiting(getClassNameLogging(), "getTime", value);
return value;
}
- public Time getTime(String sCol, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getTime", new Object[]{sCol, cal});
+ public Time getTime(String sCol,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getTime", new Object[] {sCol, cal});
checkClosed();
java.sql.Time value = (java.sql.Time) getValue(findColumn(sCol), JDBCType.TIME, cal);
loggerExternal.exiting(getClassNameLogging(), "getTime", value);
return value;
}
- public Timestamp getTimestamp(int index) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ public Timestamp getTimestamp(int index) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "getTimestamp", index);
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(index, JDBCType.TIMESTAMP);
@@ -837,8 +767,7 @@ public Timestamp getTimestamp(int index) throws SQLServerException
return value;
}
- public Timestamp getTimestamp(String sCol) throws SQLServerException
- {
+ public Timestamp getTimestamp(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getTimestamp", sCol);
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(sCol), JDBCType.TIMESTAMP);
@@ -846,35 +775,38 @@ public Timestamp getTimestamp(String sCol) throws SQLServerException
return value;
}
- public Timestamp getTimestamp(int index, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getTimestamp", new Object[]{index, cal});
+ public Timestamp getTimestamp(int index,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getTimestamp", new Object[] {index, cal});
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(index, JDBCType.TIMESTAMP, cal);
loggerExternal.exiting(getClassNameLogging(), "getTimestamp", value);
return value;
}
- public Timestamp getTimestamp(String name, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getTimestamp", new Object[]{name, cal});
+ public Timestamp getTimestamp(String name,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getTimestamp", new Object[] {name, cal});
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(name), JDBCType.TIMESTAMP, cal);
loggerExternal.exiting(getClassNameLogging(), "getTimestamp", value);
return value;
}
- /**
- * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming language.
- * @param index the first column is 1, the second is 2, ...
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming
+ * language.
+ *
+ * @param index
+ * the first column is 1, the second is 2, ...
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getDateTime(int index) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ public Timestamp getDateTime(int index) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "getDateTime", index);
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(index, JDBCType.DATETIME);
@@ -882,14 +814,18 @@ public Timestamp getDateTime(int index) throws SQLServerException
return value;
}
- /**
- * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming language.
- * @param sCol the label for the column specified with the SQL AS clause. If the SQL AS clause was not specified, then the label is the name of the column
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming
+ * language.
+ *
+ * @param sCol
+ * the label for the column specified with the SQL AS clause. If the SQL AS clause was not specified, then the label is the name of the
+ * column
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getDateTime(String sCol) throws SQLServerException
- {
+ public Timestamp getDateTime(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getDateTime", sCol);
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(sCol), JDBCType.DATETIME);
@@ -897,66 +833,82 @@ public Timestamp getDateTime(String sCol) throws SQLServerException
return value;
}
- /**
- * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming language.
- * This method uses the given calendar to construct an appropriate millisecond value for the timestamp if the underlying database does not store timezone information.
- * @param index the first column is 1, the second is 2, ...
- * @param cal the java.util.Calendar object to use in constructing the dateTime
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming
+ * language. This method uses the given calendar to construct an appropriate millisecond value for the timestamp if the underlying database does
+ * not store timezone information.
+ *
+ * @param index
+ * the first column is 1, the second is 2, ...
+ * @param cal
+ * the java.util.Calendar object to use in constructing the dateTime
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getDateTime(int index, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getDateTime", new Object[]{index, cal});
+ public Timestamp getDateTime(int index,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getDateTime", new Object[] {index, cal});
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(index, JDBCType.DATETIME, cal);
loggerExternal.exiting(getClassNameLogging(), "getDateTime", value);
return value;
}
- /**
- * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming language.
- * This method uses the given calendar to construct an appropriate millisecond value for the timestamp if the underlying database does not store timezone information.
- * @param name the name of the column
- * @param cal the java.util.Calendar object to use in constructing the dateTime
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming
+ * language. This method uses the given calendar to construct an appropriate millisecond value for the timestamp if the underlying database does
+ * not store timezone information.
+ *
+ * @param name
+ * the name of the column
+ * @param cal
+ * the java.util.Calendar object to use in constructing the dateTime
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getDateTime(String name, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getDateTime", new Object[]{name, cal});
+ public Timestamp getDateTime(String name,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getDateTime", new Object[] {name, cal});
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(name), JDBCType.DATETIME, cal);
loggerExternal.exiting(getClassNameLogging(), "getDateTime", value);
return value;
}
-
- /**
- * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming language.
- * @param index the first column is 1, the second is 2, ...
+
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming
+ * language.
+ *
+ * @param index
+ * the first column is 1, the second is 2, ...
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getSmallDateTime(int index) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ public Timestamp getSmallDateTime(int index) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", index);
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(index, JDBCType.SMALLDATETIME);
loggerExternal.exiting(getClassNameLogging(), "getSmallDateTime", value);
return value;
}
-
- /**
- * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming language.
- * @param sCol The name of a column.
+
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming
+ * language.
+ *
+ * @param sCol
+ * The name of a column.
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getSmallDateTime(String sCol) throws SQLServerException
- {
+ public Timestamp getSmallDateTime(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", sCol);
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(sCol), JDBCType.SMALLDATETIME);
@@ -964,117 +916,106 @@ public Timestamp getSmallDateTime(String sCol) throws SQLServerException
return value;
}
- /**
- * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming language.
- * @param index the first column is 1, the second is 2, ...
- * @param cal the java.util.Calendar object to use in constructing the smalldateTime
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet object as a java.sql.Timestamp object in the Java programming
+ * language.
+ *
+ * @param index
+ * the first column is 1, the second is 2, ...
+ * @param cal
+ * the java.util.Calendar object to use in constructing the smalldateTime
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getSmallDateTime(int index, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", new Object[]{index, cal});
+ public Timestamp getSmallDateTime(int index,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", new Object[] {index, cal});
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(index, JDBCType.SMALLDATETIME, cal);
loggerExternal.exiting(getClassNameLogging(), "getSmallDateTime", value);
return value;
}
- /**
+ /**
*
- * @param name The name of a column
- * @param cal the java.util.Calendar object to use in constructing the smalldateTime
+ * @param name
+ * The name of a column
+ * @param cal
+ * the java.util.Calendar object to use in constructing the smalldateTime
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public Timestamp getSmallDateTime(String name, Calendar cal) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", new Object[]{name, cal});
+ public Timestamp getSmallDateTime(String name,
+ Calendar cal) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "getSmallDateTime", new Object[] {name, cal});
checkClosed();
java.sql.Timestamp value = (java.sql.Timestamp) getValue(findColumn(name), JDBCType.SMALLDATETIME, cal);
loggerExternal.exiting(getClassNameLogging(), "getSmallDateTime", value);
return value;
}
- public microsoft.sql.DateTimeOffset getDateTimeOffset(int index) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ public microsoft.sql.DateTimeOffset getDateTimeOffset(int index) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
loggerExternal.entering(getClassNameLogging(), "getDateTimeOffset", index);
checkClosed();
// DateTimeOffset is not supported with SQL Server versions earlier than Katmai
if (!connection.isKatmaiOrLater())
- throw new SQLServerException(
- SQLServerException.getErrString("R_notSupported"),
- SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
- DriverError.NOT_SET,
- null);
+ throw new SQLServerException(SQLServerException.getErrString("R_notSupported"), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET,
+ null);
microsoft.sql.DateTimeOffset value = (microsoft.sql.DateTimeOffset) getValue(index, JDBCType.DATETIMEOFFSET);
loggerExternal.exiting(getClassNameLogging(), "getDateTimeOffset", value);
return value;
}
- public microsoft.sql.DateTimeOffset getDateTimeOffset(String sCol) throws SQLException
- {
+ public microsoft.sql.DateTimeOffset getDateTimeOffset(String sCol) throws SQLException {
loggerExternal.entering(getClassNameLogging(), "getDateTimeOffset", sCol);
checkClosed();
// DateTimeOffset is not supported with SQL Server versions earlier than Katmai
if (!connection.isKatmaiOrLater())
- throw new SQLServerException(
- SQLServerException.getErrString("R_notSupported"),
- SQLState.DATA_EXCEPTION_NOT_SPECIFIC,
- DriverError.NOT_SET,
- null);
+ throw new SQLServerException(SQLServerException.getErrString("R_notSupported"), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET,
+ null);
microsoft.sql.DateTimeOffset value = (microsoft.sql.DateTimeOffset) getValue(findColumn(sCol), JDBCType.DATETIMEOFFSET);
loggerExternal.exiting(getClassNameLogging(), "getDateTimeOffset", value);
return value;
}
- /*L0*/ public boolean wasNull() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "wasNull");
+ /* L0 */ public boolean wasNull() throws SQLServerException {
+ loggerExternal.entering(getClassNameLogging(), "wasNull");
checkClosed();
boolean bWasNull = false;
- if(null != lastParamAccessed)
- {
- bWasNull = lastParamAccessed.isNull();
+ if (null != lastParamAccessed) {
+ bWasNull = lastParamAccessed.isNull();
}
loggerExternal.exiting(getClassNameLogging(), "wasNull", bWasNull);
return bWasNull;
}
- /**
- * Retrieves the value of the designated column in the current row
- * of this ResultSet
object as
- * a stream of ASCII characters. The value can then be read in chunks from the
- * stream. This method is particularly
- * suitable for retrieving large LONGVARCHAR
values.
- * The JDBC driver will
- * do any necessary conversion from the database format into ASCII.
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet
object as a stream of ASCII characters. The
+ * value can then be read in chunks from the stream. This method is particularly suitable for retrieving large LONGVARCHAR
values.
+ * The JDBC driver will do any necessary conversion from the database format into ASCII.
*
- * Note: All the data in the returned stream must be
- * read prior to getting the value of any other column. The next
- * call to a getter method implicitly closes the stream. Also, a
- * stream may return 0
when the method
- * InputStream.available
- * is called whether there is data available or not.
+ *
+ * Note: All the data in the returned stream must be read prior to getting the value of any other column. The next call to a getter method
+ * implicitly closes the stream. Also, a stream may return 0
when the method InputStream.available
is called whether
+ * there is data available or not.
*
- * @param paramIndex the first column is 1, the second is 2, ...
- * @return a Java input stream that delivers the database column value
- * as a stream of one-byte ASCII characters;
- * if the value is SQL NULL
, the
- * value returned is null
- * @throws SQLServerException if the columnIndex is not valid;
- * if a database access error occurs or this method is
- * called on a closed result set
+ * @param paramIndex
+ * the first column is 1, the second is 2, ...
+ * @return a Java input stream that delivers the database column value as a stream of one-byte ASCII characters; if the value is SQL
+ * NULL
, the value returned is null
+ * @throws SQLServerException
+ * if the columnIndex is not valid; if a database access error occurs or this method is called on a closed result set
*/
- public final java.io.InputStream getAsciiStream(int paramIndex) throws SQLServerException
- {
+ public final java.io.InputStream getAsciiStream(int paramIndex) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getAsciiStream", paramIndex);
checkClosed();
InputStream value = (InputStream) getStream(paramIndex, StreamType.ASCII);
@@ -1082,32 +1023,24 @@ public final java.io.InputStream getAsciiStream(int paramIndex) throws SQLServer
return value;
}
- /**
- * Retrieves the value of the designated column in the current row
- * of this ResultSet
object as a stream of
- * ASCII characters. The value can then be read in chunks from the
- * stream. This method is particularly
- * suitable for retrieving large LONGVARCHAR
values.
- * The JDBC driver will
- * do any necessary conversion from the database format into ASCII.
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet
object as a stream of ASCII characters. The
+ * value can then be read in chunks from the stream. This method is particularly suitable for retrieving large LONGVARCHAR
values.
+ * The JDBC driver will do any necessary conversion from the database format into ASCII.
*
- *
Note: All the data in the returned stream must be
- * read prior to getting the value of any other column. The next
- * call to a getter method implicitly closes the stream. Also, a
- * stream may return 0
when the method available
- * is called whether there is data available or not.
+ *
+ * Note: All the data in the returned stream must be read prior to getting the value of any other column. The next call to a getter method
+ * implicitly closes the stream. Also, a stream may return 0
when the method available
is called whether there is data
+ * available or not.
*
- * @param paramName the name of the parameter
- * @return a Java input stream that delivers the database column value
- * as a stream of one-byte ASCII characters.
- * If the value is SQL NULL
,
- * the value returned is null
.
- * @throws SQLServerException if the columnLabel is not valid;
- * if a database access error occurs or this method is
- * called on a closed result set
+ * @param paramName
+ * the name of the parameter
+ * @return a Java input stream that delivers the database column value as a stream of one-byte ASCII characters. If the value is SQL
+ * NULL
, the value returned is null
.
+ * @throws SQLServerException
+ * if the columnLabel is not valid; if a database access error occurs or this method is called on a closed result set
*/
- public final java.io.InputStream getAsciiStream(String paramName) throws SQLServerException
- {
+ public final java.io.InputStream getAsciiStream(String paramName) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getAsciiStream", paramName);
checkClosed();
InputStream value = (InputStream) getStream(findColumn(paramName), StreamType.ASCII);
@@ -1115,8 +1048,7 @@ public final java.io.InputStream getAsciiStream(String paramName) throws SQLServ
return value;
}
- public BigDecimal getBigDecimal(int index) throws SQLServerException
- {
+ public BigDecimal getBigDecimal(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBigDecimal", index);
checkClosed();
BigDecimal value = (BigDecimal) getValue(index, JDBCType.DECIMAL);
@@ -1124,8 +1056,7 @@ public BigDecimal getBigDecimal(int index) throws SQLServerException
return value;
}
- public BigDecimal getBigDecimal(String sCol) throws SQLServerException
- {
+ public BigDecimal getBigDecimal(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBigDecimal", sCol);
checkClosed();
BigDecimal value = (BigDecimal) getValue(findColumn(sCol), JDBCType.DECIMAL);
@@ -1133,14 +1064,16 @@ public BigDecimal getBigDecimal(String sCol) throws SQLServerException
return value;
}
- /**
+ /**
* Retrieves the value of the column specified as a java.math.BigDecimal object.
- * @param index The zero-based ordinal of a column.
+ *
+ * @param index
+ * The zero-based ordinal of a column.
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public BigDecimal getMoney(int index) throws SQLServerException
- {
+ public BigDecimal getMoney(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getMoney", index);
checkClosed();
BigDecimal value = (BigDecimal) getValue(index, JDBCType.MONEY);
@@ -1148,29 +1081,33 @@ public BigDecimal getMoney(int index) throws SQLServerException
return value;
}
- /**
+ /**
* Retrieves the value of the column specified as a java.math.BigDecimal object.
- * @param sCol The name of a column.
+ *
+ * @param sCol
+ * The name of a column.
* @return the column value; if the value is SQL NULL, the value returned is null.
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public BigDecimal getMoney(String sCol) throws SQLServerException
- {
+ public BigDecimal getMoney(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getMoney", sCol);
checkClosed();
BigDecimal value = (BigDecimal) getValue(findColumn(sCol), JDBCType.MONEY);
loggerExternal.exiting(getClassNameLogging(), "getMoney", value);
return value;
}
-
- /**
+
+ /**
* Retrieves the value of the column specified as a java.math.BigDecimal object.
- * @param index The zero-based ordinal of a column.
+ *
+ * @param index
+ * The zero-based ordinal of a column.
* @return the column value; if the value is SQL NULL, the value returned is null
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public BigDecimal getSmallMoney(int index) throws SQLServerException
- {
+ public BigDecimal getSmallMoney(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getSmallMoney", index);
checkClosed();
BigDecimal value = (BigDecimal) getValue(index, JDBCType.SMALLMONEY);
@@ -1178,14 +1115,16 @@ public BigDecimal getSmallMoney(int index) throws SQLServerException
return value;
}
- /**
+ /**
* Retrieves the value of the column specified as a java.math.BigDecimal object.
- * @param sCol The name of a column.
+ *
+ * @param sCol
+ * The name of a column.
* @return the column value; if the value is SQL NULL, the value returned is null.
- * @throws SQLServerException when an error occurs
+ * @throws SQLServerException
+ * when an error occurs
*/
- public BigDecimal getSmallMoney(String sCol) throws SQLServerException
- {
+ public BigDecimal getSmallMoney(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getSmallMoney", sCol);
checkClosed();
BigDecimal value = (BigDecimal) getValue(findColumn(sCol), JDBCType.SMALLMONEY);
@@ -1193,31 +1132,23 @@ public BigDecimal getSmallMoney(String sCol) throws SQLServerException
return value;
}
- /**
- * Retrieves the value of the designated column in the current row
- * of this ResultSet
object as a stream of
- * uninterpreted bytes. The value can then be read in chunks from the
- * stream. This method is particularly
- * suitable for retrieving large LONGVARBINARY
values.
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet
object as a stream of uninterpreted bytes. The
+ * value can then be read in chunks from the stream. This method is particularly suitable for retrieving large LONGVARBINARY
values.
*
- *
Note: All the data in the returned stream must be
- * read prior to getting the value of any other column. The next
- * call to a getter method implicitly closes the stream. Also, a
- * stream may return 0
when the method
- * InputStream.available
- * is called whether there is data available or not.
+ *
+ * Note: All the data in the returned stream must be read prior to getting the value of any other column. The next call to a getter method
+ * implicitly closes the stream. Also, a stream may return 0
when the method InputStream.available
is called whether
+ * there is data available or not.
*
- * @param paramIndex the first column is 1, the second is 2, ...
- * @return a Java input stream that delivers the database column value
- * as a stream of uninterpreted bytes;
- * if the value is SQL NULL
, the value returned is
- * null
- * @throws SQLServerException if the columnIndex is not valid;
- * if a database access error occurs or this method is
- * called on a closed result set
+ * @param paramIndex
+ * the first column is 1, the second is 2, ...
+ * @return a Java input stream that delivers the database column value as a stream of uninterpreted bytes; if the value is SQL NULL
,
+ * the value returned is null
+ * @throws SQLServerException
+ * if the columnIndex is not valid; if a database access error occurs or this method is called on a closed result set
*/
- public final java.io.InputStream getBinaryStream(int paramIndex) throws SQLServerException
- {
+ public final java.io.InputStream getBinaryStream(int paramIndex) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBinaryStream", paramIndex);
checkClosed();
InputStream value = (InputStream) getStream(paramIndex, StreamType.BINARY);
@@ -1225,31 +1156,24 @@ public final java.io.InputStream getBinaryStream(int paramIndex) throws SQLServe
return value;
}
- /**
- * Retrieves the value of the designated column in the current row
- * of this ResultSet
object as a stream of uninterpreted
- * byte
s.
- * The value can then be read in chunks from the
- * stream. This method is particularly
- * suitable for retrieving large LONGVARBINARY
- * values.
+ /**
+ * Retrieves the value of the designated column in the current row of this ResultSet
object as a stream of uninterpreted
+ * byte
s. The value can then be read in chunks from the stream. This method is particularly suitable for retrieving large
+ * LONGVARBINARY
values.
*
- *
Note: All the data in the returned stream must be
- * read prior to getting the value of any other column. The next
- * call to a getter method implicitly closes the stream. Also, a
- * stream may return 0
when the method available
- * is called whether there is data available or not.
+ *
+ * Note: All the data in the returned stream must be read prior to getting the value of any other column. The next call to a getter method
+ * implicitly closes the stream. Also, a stream may return 0
when the method available
is called whether there is data
+ * available or not.
*
- * @param paramName the name of the parameter
- * @return a Java input stream that delivers the database column value
- * as a stream of uninterpreted bytes;
- * if the value is SQL NULL
, the result is null
- * @throws SQLServerException if the columnLabel is not valid;
- * if a database access error occurs or this method is
- * called on a closed result set
+ * @param paramName
+ * the name of the parameter
+ * @return a Java input stream that delivers the database column value as a stream of uninterpreted bytes; if the value is SQL NULL
,
+ * the result is null
+ * @throws SQLServerException
+ * if the columnLabel is not valid; if a database access error occurs or this method is called on a closed result set
*/
- public final java.io.InputStream getBinaryStream(String paramName) throws SQLServerException
- {
+ public final java.io.InputStream getBinaryStream(String paramName) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBinaryStream", paramName);
checkClosed();
InputStream value = (InputStream) getStream(findColumn(paramName), StreamType.BINARY);
@@ -1257,8 +1181,7 @@ public final java.io.InputStream getBinaryStream(String paramName) throws SQLSe
return value;
}
- public Blob getBlob (int index) throws SQLServerException
- {
+ public Blob getBlob(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBlob", index);
checkClosed();
Blob value = (Blob) getValue(index, JDBCType.BLOB);
@@ -1266,8 +1189,7 @@ public Blob getBlob (int index) throws SQLServerException
return value;
}
- public Blob getBlob(String sCol) throws SQLServerException
- {
+ public Blob getBlob(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getBlob", sCol);
checkClosed();
Blob value = (Blob) getValue(findColumn(sCol), JDBCType.BLOB);
@@ -1275,8 +1197,7 @@ public Blob getBlob(String sCol) throws SQLServerException
return value;
}
- public final java.io.Reader getCharacterStream(int paramIndex) throws SQLServerException
- {
+ public final java.io.Reader getCharacterStream(int paramIndex) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getCharacterStream", paramIndex);
checkClosed();
Reader reader = (Reader) getStream(paramIndex, StreamType.CHARACTER);
@@ -1284,8 +1205,7 @@ public final java.io.Reader getCharacterStream(int paramIndex) throws SQLServerE
return reader;
}
- public final java.io.Reader getCharacterStream(String parameterName) throws SQLException
- {
+ public final java.io.Reader getCharacterStream(String parameterName) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getCharacterStream", parameterName);
@@ -1295,8 +1215,7 @@ public final java.io.Reader getCharacterStream(String parameterName) throws SQLE
return reader;
}
- public final java.io.Reader getNCharacterStream(int parameterIndex) throws SQLException
- {
+ public final java.io.Reader getNCharacterStream(int parameterIndex) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getNCharacterStream", parameterIndex);
checkClosed();
@@ -1305,8 +1224,7 @@ public final java.io.Reader getNCharacterStream(int parameterIndex) throws SQLEx
return reader;
}
- public final java.io.Reader getNCharacterStream(String parameterName) throws SQLException
- {
+ public final java.io.Reader getNCharacterStream(String parameterName) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getNCharacterStream", parameterName);
@@ -1316,28 +1234,21 @@ public final java.io.Reader getNCharacterStream(String parameterName) throws SQL
return reader;
}
- void closeActiveStream() throws SQLServerException
- {
- if (null != activeStream )
- {
- try
- {
+ void closeActiveStream() throws SQLServerException {
+ if (null != activeStream) {
+ try {
activeStream.close();
}
- catch (IOException e)
- {
+ catch (IOException e) {
SQLServerException.makeFromDriverError(null, null, e.getMessage(), null, true);
}
- finally
- {
+ finally {
activeStream = null;
}
}
}
-
- public Clob getClob (int index) throws SQLServerException
- {
+ public Clob getClob(int index) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getClob", index);
checkClosed();
Clob clob = (Clob) getValue(index, JDBCType.CLOB);
@@ -1345,8 +1256,7 @@ public Clob getClob (int index) throws SQLServerException
return clob;
}
- public Clob getClob(String sCol) throws SQLServerException
- {
+ public Clob getClob(String sCol) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "getClob", sCol);
checkClosed();
Clob clob = (Clob) getValue(findColumn(sCol), JDBCType.CLOB);
@@ -1354,8 +1264,7 @@ public Clob getClob(String sCol) throws SQLServerException
return clob;
}
- public NClob getNClob(int parameterIndex) throws SQLException
- {
+ public NClob getNClob(int parameterIndex) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getNClob", parameterIndex);
checkClosed();
@@ -1364,8 +1273,7 @@ public NClob getNClob(int parameterIndex) throws SQLException
return nClob;
}
- public NClob getNClob(String parameterName) throws SQLException
- {
+ public NClob getNClob(String parameterName) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getNClob", parameterName);
checkClosed();
@@ -1374,619 +1282,609 @@ public NClob getNClob(String parameterName) throws SQLException
return nClob;
}
- /*L0*/ public Object getObject (int index, java.util.Map> map) throws SQLServerException {
- NotImplemented();
- return null;
- }
-
- /*L3*/ public Object getObject(String sCol, java.util.Map> m) throws SQLServerException {
- checkClosed();
- return getObject(findColumn(sCol), m);
- }
-
- /*L0*/ public Ref getRef (int i) throws SQLServerException {
- NotImplemented();
- return null;
- }
- /*L3*/ public Ref getRef(String sCol) throws SQLServerException {
- checkClosed();
- return getRef(findColumn(sCol));
- }
-
- /*L0*/ public java.sql.Array getArray (int i) throws SQLServerException {
- NotImplemented();
- return null;
- }
- /*L3*/ public java.sql.Array getArray(String sCol) throws SQLServerException {
- checkClosed();
- return getArray(findColumn(sCol));
- }
-
- /* JDBC 3.0 */
-
- /**
- * Find a column's index given its name.
- * @param columnName the name
- * @throws SQLServerException when an error occurs
- * @return the index
- */
- /*L3*/ private int findColumn(String columnName) throws SQLServerException
-{
-
- final class ThreePartNamesParser
- {
-
- private String procedurePart =null;
- private String ownerPart =null;
- private String databasePart =null;
-
- String getProcedurePart() { return procedurePart;}
- String getOwnerPart() { return ownerPart;}
- String getDatabasePart() { return databasePart;}
-
- /*
- * Three part names parsing
- * For metdata calls we parse the procedure name into parts so we can
- * use it in sp_sproc_columns
- * sp_sproc_columns [[@procedure_name =] 'name']
- * [,[@procedure_owner =] 'owner']
- * [,[@procedure_qualifier =] 'qualifier']
- *
- */
- private final Pattern threePartName =
- Pattern.compile(JDBCSyntaxTranslator.getSQLIdentifierWithGroups());
-
- final void parseProcedureNameIntoParts(String theProcName)
- {
- Matcher matcher;
- if(null != theProcName)
- {
- matcher = threePartName.matcher(theProcName);
- if (matcher.matches())
- {
- if(matcher.group(2) != null)
- {
- databasePart = matcher.group(1);
-
- // if we have two parts look to see if the last part can be broken even more
- matcher = threePartName.matcher(matcher.group(2));
- if(matcher.matches())
- {
- if(null != matcher.group(2))
- {
- ownerPart = matcher.group(1);
- procedurePart =matcher.group(2);
- }
- else
- {
- ownerPart = databasePart;
- databasePart =null;
- procedurePart = matcher.group(1);
- }
- }
-
- }
- else
- procedurePart = matcher.group(1);
-
- }
- else
- {
- procedurePart = theProcName;
- }
- }
-
- }
-
- }
-
-
- if (paramNames==null)
- {
- try
- {
- // Note we are concatenating the information from the passed in sql, not any arguments provided by the user
- // if the user can execute the sql, any fragments of it is potentially executed via the meta data call through injection
- // is not a security issue.
- SQLServerStatement s = (SQLServerStatement) connection.createStatement();
- ThreePartNamesParser translator = new ThreePartNamesParser();
- translator.parseProcedureNameIntoParts(procedureName);
- StringBuilder metaQuery = new StringBuilder("exec sp_sproc_columns ");
- if(null !=translator.getDatabasePart())
- {
- metaQuery.append("@procedure_qualifier=");
- metaQuery.append(translator.getDatabasePart());
- metaQuery.append(", ");
- }
- if(null !=translator.getOwnerPart())
- {
- metaQuery.append("@procedure_owner=");
- metaQuery.append(translator.getOwnerPart());
- metaQuery.append(", ");
- }
- if(null != translator.getProcedurePart())
- {
- // we should always have a procedure name part
- metaQuery.append("@procedure_name=");
- metaQuery.append(translator.getProcedurePart());
- metaQuery.append(" , @ODBCVer=3");
- }
- else
- {
- // This should rarely happen, this will only happen if we cant find the stored procedure name
- // invalidly formatted call syntax.
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_parameterNotDefinedForProcedure"));
- Object[] msgArgs = {columnName, ""};
- SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "07009", false);
- }
-
- ResultSet rs = s.executeQueryInternal(metaQuery.toString());
- paramNames = new ArrayList();
- while (rs.next())
- {
- String sCol = rs.getString(4);
- paramNames.add(sCol.trim());
- }
- }
- catch (SQLException e)
- {
- SQLServerException.makeFromDriverError(connection, this, e.toString(), null, false);
- }
- }
-
- int l=0;
- if (paramNames!=null)
- l = paramNames.size();
-
- // In order to be as accurate as possible when locating parameter name
- // indexes, as well as be deterministic when running on various client
- // locales, we search for parameter names using the following scheme:
-
- // 1. Search using case-sensitive non-locale specific (binary) compare first.
- // 2. Search using case-insensitive, non-locale specific (binary) compare last.
-
- int i=0;
- int matchPos = -1;
- // Search using case-sensitive, non-locale specific (binary) compare.
- // If the user supplies a true match for the parameter name, we will find it here.
- for (i = 0; i> map) throws SQLServerException {
+ NotImplemented();
+ return null;
+ }
+
+ /* L3 */ public Object getObject(String sCol,
+ java.util.Map> m) throws SQLServerException {
+ checkClosed();
+ return getObject(findColumn(sCol), m);
+ }
+
+ /* L0 */ public Ref getRef(int i) throws SQLServerException {
+ NotImplemented();
+ return null;
+ }
+
+ /* L3 */ public Ref getRef(String sCol) throws SQLServerException {
+ checkClosed();
+ return getRef(findColumn(sCol));
+ }
+
+ /* L0 */ public java.sql.Array getArray(int i) throws SQLServerException {
+ NotImplemented();
+ return null;
+ }
+
+ /* L3 */ public java.sql.Array getArray(String sCol) throws SQLServerException {
+ checkClosed();
+ return getArray(findColumn(sCol));
+ }
+
+ /* JDBC 3.0 */
+
+ /**
+ * Find a column's index given its name.
+ *
+ * @param columnName
+ * the name
+ * @throws SQLServerException
+ * when an error occurs
+ * @return the index
+ */
+ /* L3 */ private int findColumn(String columnName) throws SQLServerException {
+
+ final class ThreePartNamesParser {
+
+ private String procedurePart = null;
+ private String ownerPart = null;
+ private String databasePart = null;
+
+ String getProcedurePart() {
+ return procedurePart;
+ }
+
+ String getOwnerPart() {
+ return ownerPart;
+ }
+
+ String getDatabasePart() {
+ return databasePart;
+ }
+
+ /*
+ * Three part names parsing For metdata calls we parse the procedure name into parts so we can use it in sp_sproc_columns sp_sproc_columns
+ * [[@procedure_name =] 'name'] [,[@procedure_owner =] 'owner'] [,[@procedure_qualifier =] 'qualifier']
+ *
+ */
+ private final Pattern threePartName = Pattern.compile(JDBCSyntaxTranslator.getSQLIdentifierWithGroups());
+
+ final void parseProcedureNameIntoParts(String theProcName) {
+ Matcher matcher;
+ if (null != theProcName) {
+ matcher = threePartName.matcher(theProcName);
+ if (matcher.matches()) {
+ if (matcher.group(2) != null) {
+ databasePart = matcher.group(1);
+
+ // if we have two parts look to see if the last part can be broken even more
+ matcher = threePartName.matcher(matcher.group(2));
+ if (matcher.matches()) {
+ if (null != matcher.group(2)) {
+ ownerPart = matcher.group(1);
+ procedurePart = matcher.group(2);
+ }
+ else {
+ ownerPart = databasePart;
+ databasePart = null;
+ procedurePart = matcher.group(1);
+ }
+ }
+
+ }
+ else
+ procedurePart = matcher.group(1);
+
+ }
+ else {
+ procedurePart = theProcName;
+ }
+ }
+
+ }
+
+ }
+
+ if (paramNames == null) {
+ try {
+ // Note we are concatenating the information from the passed in sql, not any arguments provided by the user
+ // if the user can execute the sql, any fragments of it is potentially executed via the meta data call through injection
+ // is not a security issue.
+ SQLServerStatement s = (SQLServerStatement) connection.createStatement();
+ ThreePartNamesParser translator = new ThreePartNamesParser();
+ translator.parseProcedureNameIntoParts(procedureName);
+ StringBuilder metaQuery = new StringBuilder("exec sp_sproc_columns ");
+ if (null != translator.getDatabasePart()) {
+ metaQuery.append("@procedure_qualifier=");
+ metaQuery.append(translator.getDatabasePart());
+ metaQuery.append(", ");
+ }
+ if (null != translator.getOwnerPart()) {
+ metaQuery.append("@procedure_owner=");
+ metaQuery.append(translator.getOwnerPart());
+ metaQuery.append(", ");
+ }
+ if (null != translator.getProcedurePart()) {
+ // we should always have a procedure name part
+ metaQuery.append("@procedure_name=");
+ metaQuery.append(translator.getProcedurePart());
+ metaQuery.append(" , @ODBCVer=3");
+ }
+ else {
+ // This should rarely happen, this will only happen if we cant find the stored procedure name
+ // invalidly formatted call syntax.
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_parameterNotDefinedForProcedure"));
+ Object[] msgArgs = {columnName, ""};
+ SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "07009", false);
+ }
+
+ ResultSet rs = s.executeQueryInternal(metaQuery.toString());
+ paramNames = new ArrayList();
+ while (rs.next()) {
+ String sCol = rs.getString(4);
+ paramNames.add(sCol.trim());
+ }
+ }
+ catch (SQLException e) {
+ SQLServerException.makeFromDriverError(connection, this, e.toString(), null, false);
+ }
+ }
+
+ int l = 0;
+ if (paramNames != null)
+ l = paramNames.size();
+
+ // In order to be as accurate as possible when locating parameter name
+ // indexes, as well as be deterministic when running on various client
+ // locales, we search for parameter names using the following scheme:
+
+ // 1. Search using case-sensitive non-locale specific (binary) compare first.
+ // 2. Search using case-insensitive, non-locale specific (binary) compare last.
+
+ int i = 0;
+ int matchPos = -1;
+ // Search using case-sensitive, non-locale specific (binary) compare.
+ // If the user supplies a true match for the parameter name, we will find it here.
+ for (i = 0; i < l; i++) {
+ String sParam = paramNames.get(i);
+ sParam = sParam.substring(1, sParam.length());
+ if (sParam.equals(columnName)) {
+ matchPos = i;
+ break;
+ }
+ }
+
+ if (-1 == matchPos) {
+ // Check for case-insensitive match using a non-locale aware method.
+ // Use VM supplied String.equalsIgnoreCase to do the "case-insensitive search".
+ for (i = 0; i < l; i++) {
+ String sParam = paramNames.get(i);
+ sParam = sParam.substring(1, sParam.length());
+ if (sParam.equalsIgnoreCase(columnName)) {
+ matchPos = i;
+ break;
+ }
+ }
+ }
+
+ if (-1 == matchPos) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_parameterNotDefinedForProcedure"));
+ Object[] msgArgs = {columnName, procedureName};
+ SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), "07009", false);
+ }
+ // @RETURN_VALUE is always in the list. If the user uses return value ?=call(@p1) syntax then
+ // @p1 is index 2 otherwise its index 1.
+ if (bReturnValueSyntax) // 3.2717
+ return matchPos + 1;
+ else
+ return matchPos;
+ }
- public void setTimestamp(String sCol, java.sql.Timestamp x, Calendar c) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTimeStamp", new Object[]{sCol, x, c});
+ public void setTimestamp(String sCol,
+ java.sql.Timestamp x,
+ Calendar c) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTimeStamp", new Object[] {sCol, x, c});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIMESTAMP, x, JavaType.TIMESTAMP, c, false);
- loggerExternal.exiting(getClassNameLogging(), "setTimeStamp");
- }
-
- /**
- * Sets the designated parameter to the given java.sql.Timestamp
value.
- * The driver
- * converts this to an SQL TIMESTAMP
value when it sends it to the
- * database.
+ loggerExternal.exiting(getClassNameLogging(), "setTimeStamp");
+ }
+
+ /**
+ * Sets the designated parameter to the given java.sql.Timestamp
value. The driver converts this to an SQL TIMESTAMP
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param x the parameter value
- * @param c a java.util.Calendar
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the parameter value
+ * @param c
+ * a java.util.Calendar
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see #getTimestamp
*/
- public void setTimestamp(String sCol, java.sql.Timestamp x, Calendar c, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTimeStamp", new Object[]{sCol, x, c, forceEncrypt});
+ public void setTimestamp(String sCol,
+ java.sql.Timestamp x,
+ Calendar c,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTimeStamp", new Object[] {sCol, x, c, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIMESTAMP, x, JavaType.TIMESTAMP, c, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setTimeStamp");
+ loggerExternal.exiting(getClassNameLogging(), "setTimeStamp");
}
- public void setTime(String sCol, java.sql.Time x, Calendar c) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTime", new Object[]{sCol, x, c});
+ public void setTime(String sCol,
+ java.sql.Time x,
+ Calendar c) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {sCol, x, c});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIME, x, JavaType.TIME, c, false);
- loggerExternal.exiting(getClassNameLogging(), "setTime");
- }
-
- /**
- * Sets the designated parameter to the given java.sql.Time
value,
- * using the given Calendar
object. The driver uses
- * the Calendar
object to construct an SQL TIME
value,
- * which the driver then sends to the database. With a
- * a Calendar
object, the driver can calculate the time
- * taking into account a custom timezone. If no
- * Calendar
object is specified, the driver uses the default
- * timezone, which is that of the virtual machine running the application.
+ loggerExternal.exiting(getClassNameLogging(), "setTime");
+ }
+
+ /**
+ * Sets the designated parameter to the given java.sql.Time
value, using the given Calendar
object. The driver uses the
+ * Calendar
object to construct an SQL TIME
value, which the driver then sends to the database. With a a
+ * Calendar
object, the driver can calculate the time taking into account a custom timezone. If no Calendar
object is
+ * specified, the driver uses the default timezone, which is that of the virtual machine running the application.
*
- * @param sCol the name of the parameter
- * @param x the parameter value
- * @param c the Calendar
object the driver will use
- * to construct the time
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the parameter value
+ * @param c
+ * the Calendar
object the driver will use to construct the time
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see #getTime
*/
- public void setTime(String sCol, java.sql.Time x, Calendar c, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTime", new Object[]{sCol, x, c, forceEncrypt});
+ public void setTime(String sCol,
+ java.sql.Time x,
+ Calendar c,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {sCol, x, c, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIME, x, JavaType.TIME, c, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setTime");
+ loggerExternal.exiting(getClassNameLogging(), "setTime");
}
- public void setDate(String sCol, java.sql.Date x, Calendar c) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDate", new Object[]{sCol, x, c});
+ public void setDate(String sCol,
+ java.sql.Date x,
+ Calendar c) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {sCol, x, c});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATE, x, JavaType.DATE, c, false);
- loggerExternal.exiting(getClassNameLogging(), "setDate");
- }
-
- /**
- * Sets the designated parameter to the given java.sql.Date
value,
- * using the given Calendar
object. The driver uses
- * the Calendar
object to construct an SQL DATE
value,
- * which the driver then sends to the database. With a
- * a Calendar
object, the driver can calculate the date
- * taking into account a custom timezone. If no
- * Calendar
object is specified, the driver uses the default
- * timezone, which is that of the virtual machine running the application.
+ loggerExternal.exiting(getClassNameLogging(), "setDate");
+ }
+
+ /**
+ * Sets the designated parameter to the given java.sql.Date
value, using the given Calendar
object. The driver uses the
+ * Calendar
object to construct an SQL DATE
value, which the driver then sends to the database. With a a
+ * Calendar
object, the driver can calculate the date taking into account a custom timezone. If no Calendar
object is
+ * specified, the driver uses the default timezone, which is that of the virtual machine running the application.
*
- * @param sCol the name of the parameter
- * @param x the parameter value
- * @param c the Calendar
object the driver will use
- * to construct the date
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the parameter value
+ * @param c
+ * the Calendar
object the driver will use to construct the date
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see #getDate
*/
- public void setDate(String sCol, java.sql.Date x, Calendar c, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDate", new Object[]{sCol, x, c, forceEncrypt});
+ public void setDate(String sCol,
+ java.sql.Date x,
+ Calendar c,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {sCol, x, c, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATE, x, JavaType.DATE, c, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setDate");
+ loggerExternal.exiting(getClassNameLogging(), "setDate");
}
- public final void setCharacterStream(String parameterName, Reader reader) throws SQLException
- {
+ public final void setCharacterStream(String parameterName,
+ Reader reader) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[]{parameterName, reader});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterName, reader});
checkClosed();
setStream(findColumn(parameterName), StreamType.CHARACTER, reader, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH);
- loggerExternal.exiting(getClassNameLogging(), "setCharacterStream");
+ loggerExternal.exiting(getClassNameLogging(), "setCharacterStream");
}
- public final void setCharacterStream(String parameterName, Reader value, int length) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[]{parameterName, value, length});
+ public final void setCharacterStream(String parameterName,
+ Reader value,
+ int length) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterName, value, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.CHARACTER, value, JavaType.READER, length);
- loggerExternal.exiting(getClassNameLogging(), "setCharacterStream");
+ loggerExternal.exiting(getClassNameLogging(), "setCharacterStream");
}
- public final void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException
- {
-
+ public final void setCharacterStream(String parameterName,
+ Reader reader,
+ long length) throws SQLException {
+
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[]{parameterName, reader, length});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setCharacterStream", new Object[] {parameterName, reader, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.CHARACTER, reader, JavaType.READER, length);
- loggerExternal.exiting(getClassNameLogging(), "setCharacterStream");
+ loggerExternal.exiting(getClassNameLogging(), "setCharacterStream");
}
- public final void setNCharacterStream(String parameterName, Reader value) throws SQLException
- {
+ public final void setNCharacterStream(String parameterName,
+ Reader value) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[]{parameterName, value});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterName, value});
checkClosed();
setStream(findColumn(parameterName), StreamType.NCHARACTER, value, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH);
- loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream");
+ loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream");
}
- public final void setNCharacterStream(String parameterName, Reader value, long length) throws SQLException
- {
+ public final void setNCharacterStream(String parameterName,
+ Reader value,
+ long length) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[]{parameterName, value, length});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNCharacterStream", new Object[] {parameterName, value, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.NCHARACTER, value, JavaType.READER, length);
- loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream");
+ loggerExternal.exiting(getClassNameLogging(), "setNCharacterStream");
}
- public final void setClob(String parameterName, Clob x) throws SQLException
- {
+ public final void setClob(String parameterName,
+ Clob x) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setClob", new Object[]{parameterName, x });
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterName, x});
checkClosed();
setValue(findColumn(parameterName), JDBCType.CLOB, x, JavaType.CLOB, false);
- loggerExternal.exiting(getClassNameLogging(), "setClob");
+ loggerExternal.exiting(getClassNameLogging(), "setClob");
}
- public final void setClob(String parameterName, Reader reader) throws SQLException
- {
+ public final void setClob(String parameterName,
+ Reader reader) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setClob", new Object[]{parameterName, reader });
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterName, reader});
checkClosed();
setStream(findColumn(parameterName), StreamType.CHARACTER, reader, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH);
- loggerExternal.exiting(getClassNameLogging(), "setClob");
+ loggerExternal.exiting(getClassNameLogging(), "setClob");
}
- public final void setClob(String parameterName, Reader value, long length) throws SQLException
- {
+ public final void setClob(String parameterName,
+ Reader value,
+ long length) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setClob", new Object[]{parameterName, value, length});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setClob", new Object[] {parameterName, value, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.CHARACTER, value, JavaType.READER, length);
- loggerExternal.exiting(getClassNameLogging(), "setClob");
+ loggerExternal.exiting(getClassNameLogging(), "setClob");
}
- public final void setNClob(String parameterName, NClob value) throws SQLException
- {
+ public final void setNClob(String parameterName,
+ NClob value) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[]{parameterName, value });
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, value});
checkClosed();
setValue(findColumn(parameterName), JDBCType.NCLOB, value, JavaType.NCLOB, false);
- loggerExternal.exiting(getClassNameLogging(), "setNClob");
+ loggerExternal.exiting(getClassNameLogging(), "setNClob");
}
- public final void setNClob(String parameterName, Reader reader) throws SQLException
- {
+ public final void setNClob(String parameterName,
+ Reader reader) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[]{parameterName, reader });
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, reader});
checkClosed();
setStream(findColumn(parameterName), StreamType.NCHARACTER, reader, JavaType.READER, DataTypes.UNKNOWN_STREAM_LENGTH);
- loggerExternal.exiting(getClassNameLogging(), "setNClob");
+ loggerExternal.exiting(getClassNameLogging(), "setNClob");
}
- public final void setNClob(String parameterName, Reader reader, long length) throws SQLException
- {
+ public final void setNClob(String parameterName,
+ Reader reader,
+ long length) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[]{parameterName, reader,length });
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNClob", new Object[] {parameterName, reader, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.NCHARACTER, reader, JavaType.READER, length);
- loggerExternal.exiting(getClassNameLogging(), "setNClob");
+ loggerExternal.exiting(getClassNameLogging(), "setNClob");
}
- public final void setNString(String parameterName, String value) throws SQLException
- {
+ public final void setNString(String parameterName,
+ String value) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNString", new Object[]{parameterName, value });
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNString", new Object[] {parameterName, value});
checkClosed();
setValue(findColumn(parameterName), JDBCType.NVARCHAR, value, JavaType.STRING, false);
- loggerExternal.exiting(getClassNameLogging(), "setNString");
+ loggerExternal.exiting(getClassNameLogging(), "setNString");
}
-
- /**
- * Sets the designated parameter to the given String
object.
- * The driver converts this to a SQL NCHAR
or
+
+ /**
+ * Sets the designated parameter to the given String
object. The driver converts this to a SQL NCHAR
or
* NVARCHAR
or LONGNVARCHAR
- * @param parameterName the name of the parameter to be set
- * @param value the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLException if parameterName does not correspond to a named
- * parameter; if the driver does not support national
- * character sets; if the driver can detect that a data conversion
- * error could occur; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ *
+ * @param parameterName
+ * the name of the parameter to be set
+ * @param value
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLException
+ * if parameterName does not correspond to a named parameter; if the driver does not support national character sets; if the driver
+ * can detect that a data conversion error could occur; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public final void setNString(String parameterName, String value, boolean forceEncrypt) throws SQLException
- {
+ public final void setNString(String parameterName,
+ String value,
+ boolean forceEncrypt) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNString", new Object[]{parameterName, value, forceEncrypt});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNString", new Object[] {parameterName, value, forceEncrypt});
checkClosed();
setValue(findColumn(parameterName), JDBCType.NVARCHAR, value, JavaType.STRING, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setNString");
+ loggerExternal.exiting(getClassNameLogging(), "setNString");
}
- public void setObject(String sCol, Object o) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, o });
+ public void setObject(String sCol,
+ Object o) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, o});
checkClosed();
setObjectNoType(findColumn(sCol), o, false);
- loggerExternal.exiting(getClassNameLogging(), "setObject");
- }
-
- public void setObject(String sCol, Object o, int n) throws SQLServerException
- {
- String tvpName = null;
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, o, n });
- checkClosed();
- if (microsoft.sql.Types.STRUCTURED == n)
- {
- tvpName = getTVPNameIfNull(findColumn(sCol), null);
- setObject(setterGetParam(findColumn(sCol)), o, JavaType.TVP, JDBCType.TVP, null, null, false, findColumn(sCol), tvpName);
- }
- else
- setObject(setterGetParam(findColumn(sCol)), o, JavaType.of(o), JDBCType.of(n), null, null, false, findColumn(sCol), tvpName);
- loggerExternal.exiting(getClassNameLogging(), "setObject");
- }
-
- public void setObject(String sCol, Object o, int n, int m) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, o, n , m});
- checkClosed();
-
- setObject(
- setterGetParam(findColumn(sCol)),
- o,
- JavaType.of(o),
- JDBCType.of(n),
- m,
- null,
- false,
- findColumn(sCol),
- null
- );
-
- loggerExternal.exiting(getClassNameLogging(), "setObject");
- }
-
- /**
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
+ }
+
+ public void setObject(String sCol,
+ Object o,
+ int n) throws SQLServerException {
+ String tvpName = null;
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, o, n});
+ checkClosed();
+ if (microsoft.sql.Types.STRUCTURED == n) {
+ tvpName = getTVPNameIfNull(findColumn(sCol), null);
+ setObject(setterGetParam(findColumn(sCol)), o, JavaType.TVP, JDBCType.TVP, null, null, false, findColumn(sCol), tvpName);
+ }
+ else
+ setObject(setterGetParam(findColumn(sCol)), o, JavaType.of(o), JDBCType.of(n), null, null, false, findColumn(sCol), tvpName);
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
+ }
+
+ public void setObject(String sCol,
+ Object o,
+ int n,
+ int m) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, o, n, m});
+ checkClosed();
+
+ setObject(setterGetParam(findColumn(sCol)), o, JavaType.of(o), JDBCType.of(n), m, null, false, findColumn(sCol), null);
+
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
+ }
+
+ /**
* Sets the value of the designated parameter with the given object.
*
- * The given Java object will be converted to the given targetSqlType
- * before being sent to the database.
+ *
+ * The given Java object will be converted to the given targetSqlType before being sent to the database.
*
- * If the object has a custom mapping (is of a class implementing the
- * interface SQLData
),
- * the JDBC driver should call the method SQLData.writeSQL
to write it
- * to the SQL data stream.
- * If, on the other hand, the object is of a class implementing
- * Ref
, Blob
, Clob
, NClob
,
- * Struct
, java.net.URL
,
- * or Array
, the driver should pass it to the database as a
- * value of the corresponding SQL type.
+ * If the object has a custom mapping (is of a class implementing the interface SQLData
), the JDBC driver should call the method
+ * SQLData.writeSQL
to write it to the SQL data stream. If, on the other hand, the object is of a class implementing
+ * Ref
, Blob
, Clob
, NClob
, Struct
, java.net.URL
, or
+ * Array
, the driver should pass it to the database as a value of the corresponding SQL type.
*
- * Note that this method may be used to pass datatabase-
- * specific abstract data types.
+ * Note that this method may be used to pass datatabase- specific abstract data types.
*
- * @param sCol the name of the parameter
- * @param o the object containing the input parameter value
- * @param n the SQL type (as defined in java.sql.Types) to be
- * sent to the database. The scale argument may further qualify this type.
- * @param m for java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types,
- * this is the number of digits after the decimal point. For all other
- * types, this value will be ignored.
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param o
+ * the object containing the input parameter value
+ * @param n
+ * the SQL type (as defined in java.sql.Types) to be sent to the database. The scale argument may further qualify this type.
+ * @param m
+ * for java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types, this is the number of digits after the decimal point. For all other
+ * types, this value will be ignored.
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see Types
* @see #getObject
*/
- public void setObject(String sCol, Object o, int n, int m, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, o, n , m, forceEncrypt});
+ public void setObject(String sCol,
+ Object o,
+ int n,
+ int m,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, o, n, m, forceEncrypt});
checkClosed();
// scale - for java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types,
// this is the number of digits after the decimal point.
- // For all other types, this value will be ignored.
+ // For all other types, this value will be ignored.
- setObject(
- setterGetParam(findColumn(sCol)),
- o,
- JavaType.of(o),
- JDBCType.of(n),
- (java.sql.Types.NUMERIC == n || java.sql.Types.DECIMAL == n) ?
- Integer.valueOf(m) : null,
- null,
- forceEncrypt,
- findColumn(sCol),
- null
- );
+ setObject(setterGetParam(findColumn(sCol)), o, JavaType.of(o), JDBCType.of(n),
+ (java.sql.Types.NUMERIC == n || java.sql.Types.DECIMAL == n) ? Integer.valueOf(m) : null, null, forceEncrypt, findColumn(sCol), null);
- loggerExternal.exiting(getClassNameLogging(), "setObject");
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
}
- /**
+ /**
* Sets the value of the designated parameter with the given object.
*
- *
The given Java object will be converted to the given targetSqlType
- * before being sent to the database.
+ *
+ * The given Java object will be converted to the given targetSqlType before being sent to the database.
*
- * If the object has a custom mapping (is of a class implementing the
- * interface SQLData
),
- * the JDBC driver should call the method SQLData.writeSQL
to write it
- * to the SQL data stream.
- * If, on the other hand, the object is of a class implementing
- * Ref
, Blob
, Clob
, NClob
,
- * Struct
, java.net.URL
,
- * or Array
, the driver should pass it to the database as a
- * value of the corresponding SQL type.
+ * If the object has a custom mapping (is of a class implementing the interface SQLData
), the JDBC driver should call the method
+ * SQLData.writeSQL
to write it to the SQL data stream. If, on the other hand, the object is of a class implementing
+ * Ref
, Blob
, Clob
, NClob
, Struct
, java.net.URL
, or
+ * Array
, the driver should pass it to the database as a value of the corresponding SQL type.
*
- * Note that this method may be used to pass datatabase-
- * specific abstract data types.
+ * Note that this method may be used to pass datatabase- specific abstract data types.
*
- * @param sCol the name of the parameter
- * @param x the object containing the input parameter value
- * @param targetSqlType the SQL type (as defined in java.sql.Types) to be
- * sent to the database. The scale argument may further qualify this type.
- * @param precision the precision of the column.
- * @param scale the scale of the column.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the object containing the input parameter value
+ * @param targetSqlType
+ * the SQL type (as defined in java.sql.Types) to be sent to the database. The scale argument may further qualify this type.
+ * @param precision
+ * the precision of the column.
+ * @param scale
+ * the scale of the column.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see Types
* @see #getObject
*/
- public final void setObject(String sCol, Object x, int targetSqlType, Integer precision, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, x, targetSqlType, precision, scale});
+ public final void setObject(String sCol,
+ Object x,
+ int targetSqlType,
+ Integer precision,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, x, targetSqlType, precision, scale});
checkClosed();
// scale - for java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types,
@@ -1994,928 +1892,1042 @@ public final void setObject(String sCol, Object x, int targetSqlType, Integer pr
// InputStream and Reader, this is the length of the data in the stream or reader.
// For all other types, this value will be ignored.
- setObject(
- setterGetParam(findColumn(sCol)),
- x,
- JavaType.of(x),
- JDBCType.of(targetSqlType),
- (java.sql.Types.NUMERIC == targetSqlType ||
- java.sql.Types.DECIMAL == targetSqlType ||
- InputStream.class.isInstance(x) ||
- Reader.class.isInstance(x)) ?
- Integer.valueOf(scale) : null,
- precision,
- false,
- findColumn(sCol),
- null
- );
-
- loggerExternal.exiting(getClassNameLogging(), "setObject");
- }
-
- public final void setAsciiStream(String parameterName, InputStream x) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[]{parameterName, x});
+ setObject(setterGetParam(findColumn(sCol)), x, JavaType.of(x),
+ JDBCType.of(targetSqlType), (java.sql.Types.NUMERIC == targetSqlType || java.sql.Types.DECIMAL == targetSqlType
+ || InputStream.class.isInstance(x) || Reader.class.isInstance(x)) ? Integer.valueOf(scale) : null,
+ precision, false, findColumn(sCol), null);
+
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
+ }
+
+ public final void setAsciiStream(String parameterName,
+ InputStream x) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterName, x});
DriverJDBCVersion.checkSupportsJDBC4();
checkClosed();
setStream(findColumn(parameterName), StreamType.ASCII, x, JavaType.INPUTSTREAM, DataTypes.UNKNOWN_STREAM_LENGTH);
- loggerExternal.exiting(getClassNameLogging(), "setAsciiStream");
+ loggerExternal.exiting(getClassNameLogging(), "setAsciiStream");
}
- public final void setAsciiStream(String parameterName, InputStream value, int length) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[]{parameterName, value, length});
+ public final void setAsciiStream(String parameterName,
+ InputStream value,
+ int length) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterName, value, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.ASCII, value, JavaType.INPUTSTREAM, length);
- loggerExternal.exiting(getClassNameLogging(), "setAsciiStream");
+ loggerExternal.exiting(getClassNameLogging(), "setAsciiStream");
}
- public final void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[]{parameterName, x, length});
+ public final void setAsciiStream(String parameterName,
+ InputStream x,
+ long length) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setAsciiStream", new Object[] {parameterName, x, length});
DriverJDBCVersion.checkSupportsJDBC4();
checkClosed();
setStream(findColumn(parameterName), StreamType.ASCII, x, JavaType.INPUTSTREAM, length);
- loggerExternal.exiting(getClassNameLogging(), "setAsciiStream");
+ loggerExternal.exiting(getClassNameLogging(), "setAsciiStream");
}
- public final void setBinaryStream(String parameterName, InputStream x) throws SQLException
- {
-
+ public final void setBinaryStream(String parameterName,
+ InputStream x) throws SQLException {
+
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[]{parameterName, x});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {parameterName, x});
checkClosed();
setStream(findColumn(parameterName), StreamType.BINARY, x, JavaType.INPUTSTREAM, DataTypes.UNKNOWN_STREAM_LENGTH);
- loggerExternal.exiting(getClassNameLogging(), "setBinaryStream");
+ loggerExternal.exiting(getClassNameLogging(), "setBinaryStream");
}
- public final void setBinaryStream(String parameterName, InputStream value, int length) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[]{parameterName, value, length});
+ public final void setBinaryStream(String parameterName,
+ InputStream value,
+ int length) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {parameterName, value, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.BINARY, value, JavaType.INPUTSTREAM, length);
- loggerExternal.exiting(getClassNameLogging(), "setBinaryStream");
+ loggerExternal.exiting(getClassNameLogging(), "setBinaryStream");
}
- public final void setBinaryStream(String parameterName, InputStream x, long length) throws SQLException
- {
+ public final void setBinaryStream(String parameterName,
+ InputStream x,
+ long length) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[]{parameterName, x, length});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBinaryStream", new Object[] {parameterName, x, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.BINARY, x, JavaType.INPUTSTREAM, length);
- loggerExternal.exiting(getClassNameLogging(), "setBinaryStream");
+ loggerExternal.exiting(getClassNameLogging(), "setBinaryStream");
}
- public final void setBlob(String parameterName, Blob inputStream) throws SQLException
- {
+ public final void setBlob(String parameterName,
+ Blob inputStream) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[]{parameterName, inputStream});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterName, inputStream});
checkClosed();
setValue(findColumn(parameterName), JDBCType.BLOB, inputStream, JavaType.BLOB, false);
- loggerExternal.exiting(getClassNameLogging(), "setBlob");
+ loggerExternal.exiting(getClassNameLogging(), "setBlob");
}
- public final void setBlob(String parameterName, InputStream value) throws SQLException
- {
+ public final void setBlob(String parameterName,
+ InputStream value) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
-
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[]{parameterName, value});
+
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterName, value});
checkClosed();
setStream(findColumn(parameterName), StreamType.BINARY, value, JavaType.INPUTSTREAM, DataTypes.UNKNOWN_STREAM_LENGTH);
- loggerExternal.exiting(getClassNameLogging(), "setBlob");
+ loggerExternal.exiting(getClassNameLogging(), "setBlob");
}
- public final void setBlob(String parameterName, InputStream inputStream, long length) throws SQLException
- {
+ public final void setBlob(String parameterName,
+ InputStream inputStream,
+ long length) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[]{parameterName, inputStream, length});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBlob", new Object[] {parameterName, inputStream, length});
checkClosed();
setStream(findColumn(parameterName), StreamType.BINARY, inputStream, JavaType.INPUTSTREAM, length);
- loggerExternal.exiting(getClassNameLogging(), "setBlob");
+ loggerExternal.exiting(getClassNameLogging(), "setBlob");
}
- public void setTimestamp(String sCol, java.sql.Timestamp t) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[]{sCol, t});
+ public void setTimestamp(String sCol,
+ java.sql.Timestamp t) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {sCol, t});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIMESTAMP, t, JavaType.TIMESTAMP, false);
- loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
- }
-
- /**
- * Sets the designated parameter to the given java.sql.Timestamp
value.
- * The driver
- * converts this to an SQL TIMESTAMP
value when it sends it to the
- * database.
+ loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
+ }
+
+ /**
+ * Sets the designated parameter to the given java.sql.Timestamp
value. The driver converts this to an SQL TIMESTAMP
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param t the parameter value
- * @param scale the scale of the parameter
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param t
+ * the parameter value
+ * @param scale
+ * the scale of the parameter
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see #getTimestamp
*/
- public void setTimestamp(String sCol, java.sql.Timestamp t, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[]{sCol, t});
+ public void setTimestamp(String sCol,
+ java.sql.Timestamp t,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {sCol, t});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIMESTAMP, t, JavaType.TIMESTAMP, null, scale, false);
- loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
- }
-
- /**
- * Sets the designated parameter to the given java.sql.Timestamp
value.
- * The driver
- * converts this to an SQL TIMESTAMP
value when it sends it to the
- * database.
+ loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
+ }
+
+ /**
+ * Sets the designated parameter to the given java.sql.Timestamp
value. The driver converts this to an SQL TIMESTAMP
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param t the parameter value
- * @param scale the scale of the parameter
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param t
+ * the parameter value
+ * @param scale
+ * the scale of the parameter
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see #getTimestamp
*/
- public void setTimestamp(String sCol, java.sql.Timestamp t, int scale, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[]{sCol, t, forceEncrypt});
+ public void setTimestamp(String sCol,
+ java.sql.Timestamp t,
+ int scale,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTimestamp", new Object[] {sCol, t, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIMESTAMP, t, JavaType.TIMESTAMP, null, scale, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
+ loggerExternal.exiting(getClassNameLogging(), "setTimestamp");
}
- public void setDateTimeOffset(String sCol, microsoft.sql.DateTimeOffset t) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[]{sCol, t});
+ public void setDateTimeOffset(String sCol,
+ microsoft.sql.DateTimeOffset t) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {sCol, t});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATETIMEOFFSET, t, JavaType.DATETIMEOFFSET, false);
- loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset");
- }
-
- /**
- * Sets parameter parameterName to DateTimeOffset x
- * @param sCol the name of the parameter
- * @param t DateTimeOffset value
- * @param scale the scale of the parameter
- * @throws SQLException if an error occurs
- */
- public void setDateTimeOffset(String sCol, microsoft.sql.DateTimeOffset t, int scale) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[]{sCol, t});
+ loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset");
+ }
+
+ /**
+ * Sets parameter parameterName to DateTimeOffset x
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param t
+ * DateTimeOffset value
+ * @param scale
+ * the scale of the parameter
+ * @throws SQLException
+ * if an error occurs
+ */
+ public void setDateTimeOffset(String sCol,
+ microsoft.sql.DateTimeOffset t,
+ int scale) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {sCol, t});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATETIMEOFFSET, t, JavaType.DATETIMEOFFSET, null, scale, false);
- loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset");
- }
-
- /**
- * Sets parameter parameterName to DateTimeOffset x
- * @param sCol the name of the parameter
- * @param t DateTimeOffset value
- * @param scale the scale of the parameter
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLException if an error occurs
- */
- public void setDateTimeOffset(String sCol, microsoft.sql.DateTimeOffset t, int scale, boolean forceEncrypt) throws SQLException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[]{sCol, t, forceEncrypt});
+ loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset");
+ }
+
+ /**
+ * Sets parameter parameterName to DateTimeOffset x
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param t
+ * DateTimeOffset value
+ * @param scale
+ * the scale of the parameter
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLException
+ * if an error occurs
+ */
+ public void setDateTimeOffset(String sCol,
+ microsoft.sql.DateTimeOffset t,
+ int scale,
+ boolean forceEncrypt) throws SQLException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDateTimeOffset", new Object[] {sCol, t, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATETIMEOFFSET, t, JavaType.DATETIMEOFFSET, null, scale, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset");
+ loggerExternal.exiting(getClassNameLogging(), "setDateTimeOffset");
}
- public void setDate(String sCol, java.sql.Date d) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDate", new Object[]{sCol, d});
+ public void setDate(String sCol,
+ java.sql.Date d) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDate", new Object[] {sCol, d});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATE, d, JavaType.DATE, false);
- loggerExternal.exiting(getClassNameLogging(), "setDate");
+ loggerExternal.exiting(getClassNameLogging(), "setDate");
}
- public void setTime(String sCol, java.sql.Time t) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTime", new Object[]{sCol, t});
+ public void setTime(String sCol,
+ java.sql.Time t) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {sCol, t});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIME, t, JavaType.TIME, false);
- loggerExternal.exiting(getClassNameLogging(), "setTime");
+ loggerExternal.exiting(getClassNameLogging(), "setTime");
}
-
- /**
- * Sets the designated parameter to the given java.sql.Time
value.
- * The driver converts this
- * to an SQL TIME
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.sql.Time
value. The driver converts this to an SQL TIME
value when it
+ * sends it to the database.
*
- * @param sCol the name of the parameter
- * @param t the parameter value
- * @param scale the scale of the column
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param t
+ * the parameter value
+ * @param scale
+ * the scale of the column
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see #getTime
*/
- public void setTime(String sCol, java.sql.Time t, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTime", new Object[]{sCol, t});
+ public void setTime(String sCol,
+ java.sql.Time t,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {sCol, t});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIME, t, JavaType.TIME, null, scale, false);
- loggerExternal.exiting(getClassNameLogging(), "setTime");
+ loggerExternal.exiting(getClassNameLogging(), "setTime");
}
-
- /**
- * Sets the designated parameter to the given java.sql.Time
value.
- * The driver converts this
- * to an SQL TIME
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.sql.Time
value. The driver converts this to an SQL TIME
value when it
+ * sends it to the database.
*
- * @param sCol the name of the parameter
- * @param t the parameter value
- * @param scale the scale of the column
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param t
+ * the parameter value
+ * @param scale
+ * the scale of the column
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
* @see #getTime
*/
- public void setTime(String sCol, java.sql.Time t, int scale, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setTime", new Object[]{sCol, t, forceEncrypt});
+ public void setTime(String sCol,
+ java.sql.Time t,
+ int scale,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setTime", new Object[] {sCol, t, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.TIME, t, JavaType.TIME, null, scale, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setTime");
+ loggerExternal.exiting(getClassNameLogging(), "setTime");
}
-
- /**
- * Sets the designated parameter to the given java.sql.Timestamp
value.
- * The driver converts this
- * to an SQL DATETIME
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.sql.Timestamp
value. The driver converts this to an SQL DATETIME
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param x the parameter value
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the parameter value
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setDateTime(String sCol, java.sql.Timestamp x) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[]{sCol, x});
+ public void setDateTime(String sCol,
+ java.sql.Timestamp x) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[] {sCol, x});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATETIME, x, JavaType.TIMESTAMP, false);
- loggerExternal.exiting(getClassNameLogging(), "setDateTime");
+ loggerExternal.exiting(getClassNameLogging(), "setDateTime");
}
-
- /**
- * Sets the designated parameter to the given java.sql.Timestamp
value.
- * The driver converts this
- * to an SQL DATETIME
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.sql.Timestamp
value. The driver converts this to an SQL DATETIME
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param x the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setDateTime(String sCol, java.sql.Timestamp x, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[]{sCol, x, forceEncrypt});
+ public void setDateTime(String sCol,
+ java.sql.Timestamp x,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDateTime", new Object[] {sCol, x, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.DATETIME, x, JavaType.TIMESTAMP, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setDateTime");
+ loggerExternal.exiting(getClassNameLogging(), "setDateTime");
}
-
- /**
- * Sets the designated parameter to the given java.sql.Timestamp
value.
- * The driver converts this
- * to an SQL SMALLDATETIME
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.sql.Timestamp
value. The driver converts this to an SQL SMALLDATETIME
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param x the parameter value
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the parameter value
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setSmallDateTime(String sCol, java.sql.Timestamp x) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[]{sCol, x});
+ public void setSmallDateTime(String sCol,
+ java.sql.Timestamp x) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {sCol, x});
checkClosed();
setValue(findColumn(sCol), JDBCType.SMALLDATETIME, x, JavaType.TIMESTAMP, false);
- loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime");
+ loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime");
}
-
- /**
- * Sets the designated parameter to the given java.sql.Timestamp
value.
- * The driver converts this
- * to an SQL SMALLDATETIME
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.sql.Timestamp
value. The driver converts this to an SQL SMALLDATETIME
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param x the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param x
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setSmallDateTime(String sCol, java.sql.Timestamp x, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[]{sCol, x, forceEncrypt});
+ public void setSmallDateTime(String sCol,
+ java.sql.Timestamp x,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setSmallDateTime", new Object[] {sCol, x, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.SMALLDATETIME, x, JavaType.TIMESTAMP, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime");
+ loggerExternal.exiting(getClassNameLogging(), "setSmallDateTime");
}
- /**
- * Sets the designated parameter to the given String
value.
- * The driver converts this
- * to an SQL uniqueIdentifier
value when it sends it to the database.
+ /**
+ * Sets the designated parameter to the given String
value. The driver converts this to an SQL uniqueIdentifier
value
+ * when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param guid the parameter value
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param guid
+ * the parameter value
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setUniqueIdentifier(String sCol, String guid) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[]{sCol, guid});
+ public void setUniqueIdentifier(String sCol,
+ String guid) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[] {sCol, guid});
checkClosed();
setValue(findColumn(sCol), JDBCType.GUID, guid, JavaType.STRING, false);
- loggerExternal.exiting(getClassNameLogging(), "setUniqueIdentifier");
+ loggerExternal.exiting(getClassNameLogging(), "setUniqueIdentifier");
}
-
- /**
- * Sets the designated parameter to the given String
value.
- * The driver converts this
- * to an SQL uniqueIdentifier
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given String
value. The driver converts this to an SQL uniqueIdentifier
value
+ * when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param guid the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param guid
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setUniqueIdentifier(String sCol, String guid, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[]{sCol, guid, forceEncrypt});
+ public void setUniqueIdentifier(String sCol,
+ String guid,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setUniqueIdentifier", new Object[] {sCol, guid, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.GUID, guid, JavaType.STRING, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setUniqueIdentifier");
+ loggerExternal.exiting(getClassNameLogging(), "setUniqueIdentifier");
}
-
- public void setBytes(String sCol, byte[] b) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[]{sCol, b});
+
+ public void setBytes(String sCol,
+ byte[] b) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {sCol, b});
checkClosed();
setValue(findColumn(sCol), JDBCType.BINARY, b, JavaType.BYTEARRAY, false);
- loggerExternal.exiting(getClassNameLogging(), "setBytes");
- }
-
- /**
- * Sets the designated parameter to the given Java array of bytes.
- * The driver converts this to an SQL VARBINARY
or
- * LONGVARBINARY
(depending on the argument's size relative
- * to the driver's limits on VARBINARY
values) when it sends
- * it to the database.
+ loggerExternal.exiting(getClassNameLogging(), "setBytes");
+ }
+
+ /**
+ * Sets the designated parameter to the given Java array of bytes. The driver converts this to an SQL VARBINARY
or
+ * LONGVARBINARY
(depending on the argument's size relative to the driver's limits on VARBINARY
values) when it sends it
+ * to the database.
*
- * @param sCol the name of the parameter
- * @param b the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param b
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setBytes(String sCol, byte[] b, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[]{sCol, b, forceEncrypt});
+ public void setBytes(String sCol,
+ byte[] b,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBytes", new Object[] {sCol, b, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.BINARY, b, JavaType.BYTEARRAY, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setBytes");
+ loggerExternal.exiting(getClassNameLogging(), "setBytes");
}
- public void setByte(String sCol, byte b) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setByte", new Object[]{sCol, b});
+ public void setByte(String sCol,
+ byte b) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {sCol, b});
checkClosed();
setValue(findColumn(sCol), JDBCType.TINYINT, Byte.valueOf(b), JavaType.BYTE, false);
- loggerExternal.exiting(getClassNameLogging(), "setByte");
+ loggerExternal.exiting(getClassNameLogging(), "setByte");
}
-
- /**
- * Sets the designated parameter to the given Java byte
value.
- * The driver converts this
- * to an SQL TINYINT
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given Java byte
value. The driver converts this to an SQL TINYINT
value when it
+ * sends it to the database.
*
- * @param sCol the name of the parameter
- * @param b the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param b
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setByte(String sCol, byte b, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setByte", new Object[]{sCol, b, forceEncrypt});
+ public void setByte(String sCol,
+ byte b,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setByte", new Object[] {sCol, b, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.TINYINT, Byte.valueOf(b), JavaType.BYTE, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setByte");
+ loggerExternal.exiting(getClassNameLogging(), "setByte");
}
- public void setString(String sCol, String s) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setString", new Object[]{sCol, s});
+ public void setString(String sCol,
+ String s) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {sCol, s});
checkClosed();
setValue(findColumn(sCol), JDBCType.VARCHAR, s, JavaType.STRING, false);
- loggerExternal.exiting(getClassNameLogging(), "setString");
- }
-
- /**
- * Sets the designated parameter to the given Java String
value.
- * The driver converts this
- * to an SQL VARCHAR
or LONGVARCHAR
value
- * (depending on the argument's
- * size relative to the driver's limits on VARCHAR
values)
- * when it sends it to the database.
+ loggerExternal.exiting(getClassNameLogging(), "setString");
+ }
+
+ /**
+ * Sets the designated parameter to the given Java String
value. The driver converts this to an SQL VARCHAR
or
+ * LONGVARCHAR
value (depending on the argument's size relative to the driver's limits on VARCHAR
values) when it sends
+ * it to the database.
*
- * @param sCol the name of the parameter
- * @param s the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param s
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setString(String sCol, String s, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setString", new Object[]{sCol, s, forceEncrypt});
+ public void setString(String sCol,
+ String s,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setString", new Object[] {sCol, s, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.VARCHAR, s, JavaType.STRING, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setString");
- }
-
- /**
- * Sets the designated parameter to the given Java java.math.BigDecimal
value.
- * The driver converts this
- * to an SQL Money
value.
- * @param sCol the name of the parameter
- * @param bd the parameter value
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ loggerExternal.exiting(getClassNameLogging(), "setString");
+ }
+
+ /**
+ * Sets the designated parameter to the given Java java.math.BigDecimal
value. The driver converts this to an SQL Money
+ * value.
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param bd
+ * the parameter value
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setMoney(String sCol, BigDecimal bd) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[]{sCol, bd});
- checkClosed();
+ public void setMoney(String sCol,
+ BigDecimal bd) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[] {sCol, bd});
+ checkClosed();
setValue(findColumn(sCol), JDBCType.MONEY, bd, JavaType.BIGDECIMAL, false);
- loggerExternal.exiting(getClassNameLogging(), "setMoney");
- }
-
- /**
- * Sets the designated parameter to the given Java java.math.BigDecimal
value.
- * The driver converts this
- * to an SQL Money
value.
- * @param sCol the name of the parameter
- * @param bd the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ loggerExternal.exiting(getClassNameLogging(), "setMoney");
+ }
+
+ /**
+ * Sets the designated parameter to the given Java java.math.BigDecimal
value. The driver converts this to an SQL Money
+ * value.
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param bd
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setMoney(String sCol, BigDecimal bd, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[]{sCol, bd, forceEncrypt});
- checkClosed();
+ public void setMoney(String sCol,
+ BigDecimal bd,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setMoney", new Object[] {sCol, bd, forceEncrypt});
+ checkClosed();
setValue(findColumn(sCol), JDBCType.MONEY, bd, JavaType.BIGDECIMAL, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setMoney");
- }
-
- /**
- * Sets the designated parameter to the given Java java.math.BigDecimal
value.
- * The driver converts this
- * to an SQL smallMoney
value.
- * @param sCol the name of the parameter
- * @param bd the parameter value
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ loggerExternal.exiting(getClassNameLogging(), "setMoney");
+ }
+
+ /**
+ * Sets the designated parameter to the given Java java.math.BigDecimal
value. The driver converts this to an SQL
+ * smallMoney
value.
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param bd
+ * the parameter value
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setSmallMoney(String sCol, BigDecimal bd) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[]{sCol, bd});
+ public void setSmallMoney(String sCol,
+ BigDecimal bd) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[] {sCol, bd});
checkClosed();
setValue(findColumn(sCol), JDBCType.SMALLMONEY, bd, JavaType.BIGDECIMAL, false);
- loggerExternal.exiting(getClassNameLogging(), "setSmallMoney");
- }
-
- /**
- * Sets the designated parameter to the given Java java.math.BigDecimal
value.
- * The driver converts this
- * to an SQL smallMoney
value.
- * @param sCol the name of the parameter
- * @param bd the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ loggerExternal.exiting(getClassNameLogging(), "setSmallMoney");
+ }
+
+ /**
+ * Sets the designated parameter to the given Java java.math.BigDecimal
value. The driver converts this to an SQL
+ * smallMoney
value.
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param bd
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setSmallMoney(String sCol, BigDecimal bd, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[]{sCol, bd, forceEncrypt});
+ public void setSmallMoney(String sCol,
+ BigDecimal bd,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setSmallMoney", new Object[] {sCol, bd, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.SMALLMONEY, bd, JavaType.BIGDECIMAL, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setSmallMoney");
+ loggerExternal.exiting(getClassNameLogging(), "setSmallMoney");
}
-
- public void setBigDecimal(String sCol, BigDecimal bd) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[]{sCol, bd});
+
+ public void setBigDecimal(String sCol,
+ BigDecimal bd) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {sCol, bd});
checkClosed();
setValue(findColumn(sCol), JDBCType.DECIMAL, bd, JavaType.BIGDECIMAL, false);
- loggerExternal.exiting(getClassNameLogging(), "setBigDecimal");
+ loggerExternal.exiting(getClassNameLogging(), "setBigDecimal");
}
-
- /**
- * Sets the designated parameter to the given
- * java.math.BigDecimal
value.
- * The driver converts this to an SQL NUMERIC
value when
- * it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.math.BigDecimal
value. The driver converts this to an SQL NUMERIC
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param bd the parameter value
- * @param precision the precision of the column
- * @param scale the scale of the column
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param bd
+ * the parameter value
+ * @param precision
+ * the precision of the column
+ * @param scale
+ * the scale of the column
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setBigDecimal(String sCol, BigDecimal bd, int precision, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[]{sCol, bd, precision, scale});
+ public void setBigDecimal(String sCol,
+ BigDecimal bd,
+ int precision,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {sCol, bd, precision, scale});
checkClosed();
setValue(findColumn(sCol), JDBCType.DECIMAL, bd, JavaType.BIGDECIMAL, precision, scale, false);
- loggerExternal.exiting(getClassNameLogging(), "setBigDecimal");
+ loggerExternal.exiting(getClassNameLogging(), "setBigDecimal");
}
-
- /**
- * Sets the designated parameter to the given
- * java.math.BigDecimal
value.
- * The driver converts this to an SQL NUMERIC
value when
- * it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given java.math.BigDecimal
value. The driver converts this to an SQL NUMERIC
+ * value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param bd the parameter value
- * @param precision the precision of the column
- * @param scale the scale of the column
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param bd
+ * the parameter value
+ * @param precision
+ * the precision of the column
+ * @param scale
+ * the scale of the column
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setBigDecimal(String sCol, BigDecimal bd, int precision, int scale, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[]{sCol, bd, precision, scale, forceEncrypt});
+ public void setBigDecimal(String sCol,
+ BigDecimal bd,
+ int precision,
+ int scale,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBigDecimal", new Object[] {sCol, bd, precision, scale, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.DECIMAL, bd, JavaType.BIGDECIMAL, precision, scale, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setBigDecimal");
+ loggerExternal.exiting(getClassNameLogging(), "setBigDecimal");
}
- public void setDouble(String sCol, double d) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[]{sCol, d});
+ public void setDouble(String sCol,
+ double d) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {sCol, d});
checkClosed();
setValue(findColumn(sCol), JDBCType.DOUBLE, Double.valueOf(d), JavaType.DOUBLE, false);
- loggerExternal.exiting(getClassNameLogging(), "setDouble");
+ loggerExternal.exiting(getClassNameLogging(), "setDouble");
}
-
- /**
- * Sets the designated parameter to the given Java double
value.
- * The driver converts this
- * to an SQL DOUBLE
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given Java double
value. The driver converts this to an SQL DOUBLE
value when it
+ * sends it to the database.
*
- * @param sCol the name of the parameter
- * @param d the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param d
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setDouble(String sCol, double d, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[]{sCol, d, forceEncrypt});
+ public void setDouble(String sCol,
+ double d,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setDouble", new Object[] {sCol, d, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.DOUBLE, Double.valueOf(d), JavaType.DOUBLE, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setDouble");
+ loggerExternal.exiting(getClassNameLogging(), "setDouble");
}
- public void setFloat(String sCol, float f) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[]{sCol, f});
+ public void setFloat(String sCol,
+ float f) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[] {sCol, f});
checkClosed();
setValue(findColumn(sCol), JDBCType.REAL, Float.valueOf(f), JavaType.FLOAT, false);
- loggerExternal.exiting(getClassNameLogging(), "setFloat");
+ loggerExternal.exiting(getClassNameLogging(), "setFloat");
}
-
- /**
- * Sets the designated parameter to the given Java float
value.
- * The driver converts this
- * to an SQL FLOAT
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given Java float
value. The driver converts this to an SQL FLOAT
value when it
+ * sends it to the database.
*
- * @param sCol the name of the parameter
- * @param f the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param f
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setFloat(String sCol, float f, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[]{sCol, f, forceEncrypt});
+ public void setFloat(String sCol,
+ float f,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setFloat", new Object[] {sCol, f, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.REAL, Float.valueOf(f), JavaType.FLOAT, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setFloat");
+ loggerExternal.exiting(getClassNameLogging(), "setFloat");
}
- public void setInt(String sCol, int i) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setInt", new Object[]{sCol, i});
+ public void setInt(String sCol,
+ int i) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {sCol, i});
checkClosed();
setValue(findColumn(sCol), JDBCType.INTEGER, Integer.valueOf(i), JavaType.INTEGER, false);
- loggerExternal.exiting(getClassNameLogging(), "setInt");
+ loggerExternal.exiting(getClassNameLogging(), "setInt");
}
-
- /**
- * Sets the designated parameter to the given Java int
value.
- * The driver converts this
- * to an SQL INTEGER
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given Java int
value. The driver converts this to an SQL INTEGER
value when it
+ * sends it to the database.
*
- * @param sCol the name of the parameter
- * @param i the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param i
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setInt(String sCol, int i, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setInt", new Object[]{sCol, i, forceEncrypt});
+ public void setInt(String sCol,
+ int i,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setInt", new Object[] {sCol, i, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.INTEGER, Integer.valueOf(i), JavaType.INTEGER, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setInt");
+ loggerExternal.exiting(getClassNameLogging(), "setInt");
}
- public void setLong(String sCol, long l) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setLong", new Object[]{sCol, l});
+ public void setLong(String sCol,
+ long l) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {sCol, l});
checkClosed();
setValue(findColumn(sCol), JDBCType.BIGINT, Long.valueOf(l), JavaType.LONG, false);
- loggerExternal.exiting(getClassNameLogging(), "setLong");
+ loggerExternal.exiting(getClassNameLogging(), "setLong");
}
-
- /**
- * Sets the designated parameter to the given Java long
value.
- * The driver converts this
- * to an SQL BIGINT
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given Java long
value. The driver converts this to an SQL BIGINT
value when it
+ * sends it to the database.
*
- * @param sCol the name of the parameter
- * @param l the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param l
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setLong(String sCol, long l, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setLong", new Object[]{sCol, l, forceEncrypt});
+ public void setLong(String sCol,
+ long l,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setLong", new Object[] {sCol, l, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.BIGINT, Long.valueOf(l), JavaType.LONG, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setLong");
+ loggerExternal.exiting(getClassNameLogging(), "setLong");
}
- public void setShort(String sCol, short s) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setShort", new Object[]{sCol, s});
+ public void setShort(String sCol,
+ short s) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setShort", new Object[] {sCol, s});
checkClosed();
setValue(findColumn(sCol), JDBCType.SMALLINT, Short.valueOf(s), JavaType.SHORT, false);
- loggerExternal.exiting(getClassNameLogging(), "setShort");
+ loggerExternal.exiting(getClassNameLogging(), "setShort");
}
-
- /**
- * Sets the designated parameter to the given Java short
value.
- * The driver converts this
- * to an SQL SMALLINT
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given Java short
value. The driver converts this to an SQL SMALLINT
value when
+ * it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param s the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param s
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setShort(String sCol, short s, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setShort", new Object[]{sCol, s, forceEncrypt});
+ public void setShort(String sCol,
+ short s,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setShort", new Object[] {sCol, s, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.SMALLINT, Short.valueOf(s), JavaType.SHORT, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setShort");
+ loggerExternal.exiting(getClassNameLogging(), "setShort");
}
- public void setBoolean(String sCol, boolean b) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[]{sCol, b});
+ public void setBoolean(String sCol,
+ boolean b) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[] {sCol, b});
checkClosed();
setValue(findColumn(sCol), JDBCType.BIT, Boolean.valueOf(b), JavaType.BOOLEAN, false);
- loggerExternal.exiting(getClassNameLogging(), "setBoolean");
+ loggerExternal.exiting(getClassNameLogging(), "setBoolean");
}
-
- /**
- * Sets the designated parameter to the given Java boolean
value.
- * The driver converts this
- * to an SQL BIT
or BOOLEAN
value when it sends it to the database.
+
+ /**
+ * Sets the designated parameter to the given Java boolean
value. The driver converts this to an SQL BIT
or
+ * BOOLEAN
value when it sends it to the database.
*
- * @param sCol the name of the parameter
- * @param b the parameter value
- * @param forceEncrypt If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted
- * and Always Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force encryption on parameters.
- * @throws SQLServerException if parameterName does not correspond to a named
- * parameter; if a database access error occurs or
- * this method is called on a closed CallableStatement
+ * @param sCol
+ * the name of the parameter
+ * @param b
+ * the parameter value
+ * @param forceEncrypt
+ * If the boolean forceEncrypt is set to true, the query parameter will only be set if the designation column is encrypted and Always
+ * Encrypted is enabled on the connection or on the statement. If the boolean forceEncrypt is set to false, the driver will not force
+ * encryption on parameters.
+ * @throws SQLServerException
+ * if parameterName does not correspond to a named parameter; if a database access error occurs or this method is called on a closed
+ * CallableStatement
*/
- public void setBoolean(String sCol, boolean b, boolean forceEncrypt) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[]{sCol, b, forceEncrypt});
+ public void setBoolean(String sCol,
+ boolean b,
+ boolean forceEncrypt) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setBoolean", new Object[] {sCol, b, forceEncrypt});
checkClosed();
setValue(findColumn(sCol), JDBCType.BIT, Boolean.valueOf(b), JavaType.BOOLEAN, forceEncrypt);
- loggerExternal.exiting(getClassNameLogging(), "setBoolean");
+ loggerExternal.exiting(getClassNameLogging(), "setBoolean");
}
- public void setNull(String sCol, int nType) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNull", new Object[]{sCol, nType});
+ public void setNull(String sCol,
+ int nType) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNull", new Object[] {sCol, nType});
checkClosed();
setObject(setterGetParam(findColumn(sCol)), null, JavaType.OBJECT, JDBCType.of(nType), null, null, false, findColumn(sCol), null);
- loggerExternal.exiting(getClassNameLogging(), "setNull");
+ loggerExternal.exiting(getClassNameLogging(), "setNull");
}
- public void setNull(String sCol, int nType, String sTypeName) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setNull", new Object[]{sCol, nType, sTypeName});
+ public void setNull(String sCol,
+ int nType,
+ String sTypeName) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setNull", new Object[] {sCol, nType, sTypeName});
checkClosed();
setObject(setterGetParam(findColumn(sCol)), null, JavaType.OBJECT, JDBCType.of(nType), null, null, false, findColumn(sCol), sTypeName);
- loggerExternal.exiting(getClassNameLogging(), "setNull");
+ loggerExternal.exiting(getClassNameLogging(), "setNull");
}
- public void setURL(String sCol, URL u) throws SQLServerException
- {
+ public void setURL(String sCol,
+ URL u) throws SQLServerException {
loggerExternal.entering(getClassNameLogging(), "setURL", sCol);
checkClosed();
setURL(findColumn(sCol), u);
- loggerExternal.exiting(getClassNameLogging(), "setURL");
+ loggerExternal.exiting(getClassNameLogging(), "setURL");
}
- /**
- * Populates a table valued parameter passed to a stored procedure with a data table.
- * @param sCol the name of the parameter
- * @param tvpName the name of the type TVP
- * @param tvpDataTable the data table object
- * @throws SQLServerException when an error occurs
+ /**
+ * Populates a table valued parameter passed to a stored procedure with a data table.
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param tvpName
+ * the name of the type TVP
+ * @param tvpDataTable
+ * the data table object
+ * @throws SQLServerException
+ * when an error occurs
*/
- public final void setStructured(String sCol, String tvpName, SQLServerDataTable tvpDataTable) throws SQLServerException
- {
- tvpName = getTVPNameIfNull(findColumn(sCol), tvpName);
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[]{sCol, tvpName, tvpDataTable});
- checkClosed();
- setValue(findColumn(sCol), JDBCType.TVP, tvpDataTable, JavaType.TVP, tvpName);
- loggerExternal.exiting(getClassNameLogging(), "setStructured");
- }
-
- /**
- * Populates a table valued parameter passed to a stored procedure with a ResultSet retrieved from another table
- * @param sCol the name of the parameter
- * @param tvpName the name of the type TVP
- * @param tvpResultSet the source result set object
- * @throws SQLServerException when an error occurs
+ public final void setStructured(String sCol,
+ String tvpName,
+ SQLServerDataTable tvpDataTable) throws SQLServerException {
+ tvpName = getTVPNameIfNull(findColumn(sCol), tvpName);
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {sCol, tvpName, tvpDataTable});
+ checkClosed();
+ setValue(findColumn(sCol), JDBCType.TVP, tvpDataTable, JavaType.TVP, tvpName);
+ loggerExternal.exiting(getClassNameLogging(), "setStructured");
+ }
+
+ /**
+ * Populates a table valued parameter passed to a stored procedure with a ResultSet retrieved from another table
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param tvpName
+ * the name of the type TVP
+ * @param tvpResultSet
+ * the source result set object
+ * @throws SQLServerException
+ * when an error occurs
*/
- public final void setStructured(String sCol, String tvpName, ResultSet tvpResultSet) throws SQLServerException
- {
- tvpName = getTVPNameIfNull(findColumn(sCol), tvpName);
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[]{sCol, tvpName, tvpResultSet});
- checkClosed();
- setValue(findColumn(sCol), JDBCType.TVP, tvpResultSet, JavaType.TVP, tvpName);
- loggerExternal.exiting(getClassNameLogging(), "setStructured");
- }
-
- /**
- * Populates a table valued parameter passed to a stored procedure with an ISQLServerDataRecord object.
- * @param sCol the name of the parameter
- * @param tvpName the name of the type TVP
- * @param tvpDataRecord ISQLServerDataRecord is used for streaming data and the user decides how to use it.
- * tvpDataRecord is an ISQLServerDataRecord object.the source result set object
- * @throws SQLServerException when an error occurs
+ public final void setStructured(String sCol,
+ String tvpName,
+ ResultSet tvpResultSet) throws SQLServerException {
+ tvpName = getTVPNameIfNull(findColumn(sCol), tvpName);
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {sCol, tvpName, tvpResultSet});
+ checkClosed();
+ setValue(findColumn(sCol), JDBCType.TVP, tvpResultSet, JavaType.TVP, tvpName);
+ loggerExternal.exiting(getClassNameLogging(), "setStructured");
+ }
+
+ /**
+ * Populates a table valued parameter passed to a stored procedure with an ISQLServerDataRecord object.
+ *
+ * @param sCol
+ * the name of the parameter
+ * @param tvpName
+ * the name of the type TVP
+ * @param tvpDataRecord
+ * ISQLServerDataRecord is used for streaming data and the user decides how to use it. tvpDataRecord is an ISQLServerDataRecord
+ * object.the source result set object
+ * @throws SQLServerException
+ * when an error occurs
*/
- public final void setStructured(String sCol, String tvpName, ISQLServerDataRecord tvpDataRecord) throws SQLServerException
- {
- tvpName = getTVPNameIfNull(findColumn(sCol), tvpName);
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[]{sCol, tvpName, tvpDataRecord});
- checkClosed();
- setValue(findColumn(sCol), JDBCType.TVP, tvpDataRecord, JavaType.TVP, tvpName);
- loggerExternal.exiting(getClassNameLogging(), "setStructured");
- }
-
- public URL getURL(int n) throws SQLServerException
- {
- NotImplemented();
- return null;
- }
-
- public URL getURL(String s) throws SQLServerException
- {
- NotImplemented();
- return null;
- }
-
- public final void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException
- {
+ public final void setStructured(String sCol,
+ String tvpName,
+ ISQLServerDataRecord tvpDataRecord) throws SQLServerException {
+ tvpName = getTVPNameIfNull(findColumn(sCol), tvpName);
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setStructured", new Object[] {sCol, tvpName, tvpDataRecord});
+ checkClosed();
+ setValue(findColumn(sCol), JDBCType.TVP, tvpDataRecord, JavaType.TVP, tvpName);
+ loggerExternal.exiting(getClassNameLogging(), "setStructured");
+ }
+
+ public URL getURL(int n) throws SQLServerException {
+ NotImplemented();
+ return null;
+ }
+
+ public URL getURL(String s) throws SQLServerException {
+ NotImplemented();
+ return null;
+ }
+
+ public final void setSQLXML(String parameterName,
+ SQLXML xmlObject) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setSQLXML", new Object[]{parameterName, xmlObject});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setSQLXML", new Object[] {parameterName, xmlObject});
checkClosed();
setSQLXMLInternal(findColumn(parameterName), xmlObject);
- loggerExternal.exiting(getClassNameLogging(), "setSQLXML");
+ loggerExternal.exiting(getClassNameLogging(), "setSQLXML");
}
- public final SQLXML getSQLXML(int parameterIndex) throws SQLException
- {
+ public final SQLXML getSQLXML(int parameterIndex) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getSQLXML", parameterIndex);
checkClosed();
@@ -2924,8 +2936,7 @@ public final SQLXML getSQLXML(int parameterIndex) throws SQLException
return value;
}
- public final SQLXML getSQLXML(String parameterName) throws SQLException
- {
+ public final SQLXML getSQLXML(String parameterName) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
loggerExternal.entering(getClassNameLogging(), "getSQLXML", parameterName);
checkClosed();
@@ -2934,66 +2945,68 @@ public final SQLXML getSQLXML(String parameterName) throws SQLException
return value;
}
- public final void setRowId(String parameterName, RowId x) throws SQLException
- {
+ public final void setRowId(String parameterName,
+ RowId x) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
// Not implemented
throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
}
- public final RowId getRowId(int parameterIndex) throws SQLException
- {
+ public final RowId getRowId(int parameterIndex) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
// Not implemented
throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
}
- public final RowId getRowId(String parameterName) throws SQLException
- {
+ public final RowId getRowId(String parameterName) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
// Not implemented
throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported"));
}
- public void registerOutParameter(String s, int n, String s1) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{s, new Integer(n), s1});
+ public void registerOutParameter(String s,
+ int n,
+ String s1) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {s, new Integer(n), s1});
checkClosed();
registerOutParameter(findColumn(s), n, s1);
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}
- public void registerOutParameter(String parameterName, int sqlType, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
- new Object[]{parameterName, new Integer(sqlType), new Integer(scale)});
+ public void registerOutParameter(String parameterName,
+ int sqlType,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
+ new Object[] {parameterName, new Integer(sqlType), new Integer(scale)});
checkClosed();
registerOutParameter(findColumn(parameterName), sqlType, scale);
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}
-
- public void registerOutParameter(String parameterName, int sqlType, int precision, int scale) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
- new Object[]{parameterName, new Integer(sqlType), new Integer(scale)});
+
+ public void registerOutParameter(String parameterName,
+ int sqlType,
+ int precision,
+ int scale) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
+ new Object[] {parameterName, new Integer(sqlType), new Integer(scale)});
checkClosed();
registerOutParameter(findColumn(parameterName), sqlType, precision, scale);
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}
- public void registerOutParameter(String s, int n) throws SQLServerException
- {
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{s, new Integer(n)});
+ public void registerOutParameter(String s,
+ int n) throws SQLServerException {
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {s, new Integer(n)});
checkClosed();
registerOutParameter(findColumn(s), n);
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}
-
+
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement42.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement42.java
index a5892d6d2..ee73cc176 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement42.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement42.java
@@ -1,205 +1,218 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerCallableStatement42.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.sql.SQLType;
/**
*
- * This class is separated from SQLServerCallableStatement class in order to resolve compiling error of
- * missing Java 8 Types when running with Java 7.
+ * This class is separated from SQLServerCallableStatement class in order to resolve compiling error of missing Java 8 Types when running with Java 7.
*
* This class will be initialized instead of SQLServerCallableStatement when Java 8 and JDBC 4.2 are used.
*
* It shares the same PreparedStatement implementation with SQLServerPreparedStatement42.
*
*/
-public class SQLServerCallableStatement42 extends SQLServerCallableStatement implements ISQLServerCallableStatement42{
-
- SQLServerCallableStatement42(SQLServerConnection connection, String sql, int nRSType, int nRSConcur,
- SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
- super(connection, sql, nRSType, nRSConcur, stmtColEncSetting);
- }
-
- public void registerOutParameter(int index, SQLType sqlType) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
-
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), sqlType});
-
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(index, sqlType.getVendorTypeNumber().intValue());
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
-
- public void registerOutParameter (int index, SQLType sqlType, String typeName) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
-
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), sqlType, typeName});
-
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(index, sqlType.getVendorTypeNumber().intValue(), typeName);
+public class SQLServerCallableStatement42 extends SQLServerCallableStatement implements ISQLServerCallableStatement42 {
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ SQLServerCallableStatement42(SQLServerConnection connection,
+ String sql,
+ int nRSType,
+ int nRSConcur,
+ SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
+ super(connection, sql, nRSType, nRSConcur, stmtColEncSetting);
+ }
- public void registerOutParameter(int index, SQLType sqlType, int scale) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ public void registerOutParameter(int index,
+ SQLType sqlType) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), sqlType, new Integer(scale)});
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {new Integer(index), sqlType});
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(index, sqlType.getVendorTypeNumber().intValue(), scale);
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(index, sqlType.getVendorTypeNumber().intValue());
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ public void registerOutParameter(int index,
+ SQLType sqlType,
+ String typeName) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- public void registerOutParameter(int index, SQLType sqlType, int precision, int scale) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {new Integer(index), sqlType, typeName});
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{new Integer(index), sqlType, new Integer(scale)});
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(index, sqlType.getVendorTypeNumber().intValue(), typeName);
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(index, sqlType.getVendorTypeNumber().intValue(), precision, scale);
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ public void registerOutParameter(int index,
+ SQLType sqlType,
+ int scale) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- public void setObject(String sCol, Object obj, SQLType jdbcType) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {new Integer(index), sqlType, new Integer(scale)});
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, obj, jdbcType });
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(index, sqlType.getVendorTypeNumber().intValue(), scale);
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- setObject(sCol, obj, jdbcType.getVendorTypeNumber().intValue());
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- loggerExternal.exiting(getClassNameLogging(), "setObject");
- }
+ public void registerOutParameter(int index,
+ SQLType sqlType,
+ int precision,
+ int scale) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- public void setObject(String sCol, Object obj, SQLType jdbcType, int scale) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {new Integer(index), sqlType, new Integer(scale)});
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, obj, jdbcType , scale});
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(index, sqlType.getVendorTypeNumber().intValue(), precision, scale);
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- setObject(sCol, obj, jdbcType.getVendorTypeNumber().intValue(), scale);
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- loggerExternal.exiting(getClassNameLogging(), "setObject");
- }
+ public void setObject(String sCol,
+ Object obj,
+ SQLType jdbcType) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- public void setObject(String sCol, Object obj, SQLType jdbcType, int scale, boolean forceEncrypt) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, obj, jdbcType});
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setObject", new Object[]{sCol, obj, jdbcType , scale, forceEncrypt});
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ setObject(sCol, obj, jdbcType.getVendorTypeNumber().intValue());
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- setObject(sCol, obj, jdbcType.getVendorTypeNumber().intValue(), scale, forceEncrypt);
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
+ }
- loggerExternal.exiting(getClassNameLogging(), "setObject");
- }
+ public void setObject(String sCol,
+ Object obj,
+ SQLType jdbcType,
+ int scale) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- public void registerOutParameter(String parameterName, SQLType sqlType, String typeName) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, obj, jdbcType, scale});
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{parameterName, sqlType, typeName});
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ setObject(sCol, obj, jdbcType.getVendorTypeNumber().intValue(), scale);
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue(), typeName);
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
+ }
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ public void setObject(String sCol,
+ Object obj,
+ SQLType jdbcType,
+ int scale,
+ boolean forceEncrypt) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- public void registerOutParameter(String parameterName, SQLType sqlType, int scale) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "setObject", new Object[] {sCol, obj, jdbcType, scale, forceEncrypt});
+
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ setObject(sCol, obj, jdbcType.getVendorTypeNumber().intValue(), scale, forceEncrypt);
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
- new Object[]{parameterName, sqlType, new Integer(scale)});
+ loggerExternal.exiting(getClassNameLogging(), "setObject");
+ }
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue(), scale);
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType,
+ String typeName) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType, typeName});
- public void registerOutParameter(String parameterName, SQLType sqlType, int precision, int scale) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue(), typeName);
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter",
- new Object[]{parameterName, sqlType, new Integer(scale)});
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue(), precision, scale);
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType,
+ int scale) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType, new Integer(scale)});
- public void registerOutParameter(String parameterName, SQLType sqlType) throws SQLServerException
- {
- DriverJDBCVersion.checkSupportsJDBC42();
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue(), scale);
- if(loggerExternal.isLoggable(java.util.logging.Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[]{parameterName, sqlType});
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
- registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue());
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType,
+ int precision,
+ int scale) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
- }
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType, new Integer(scale)});
- public final void setObject(int index, Object obj, SQLType jdbcType) throws SQLServerException
- {
- SQLServerPreparedStatement42Helper.setObject(this, index, obj, jdbcType);
- }
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue(), precision, scale);
- public final void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLServerException
- {
- SQLServerPreparedStatement42Helper.setObject(this, parameterIndex, x, targetSqlType, scaleOrLength);
- }
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
- public final void setObject(int parameterIndex, Object x, SQLType targetSqlType, Integer precision, Integer scale) throws SQLServerException
- {
- SQLServerPreparedStatement42Helper.setObject(this, parameterIndex, x, targetSqlType, precision, scale);
- }
+ public void registerOutParameter(String parameterName,
+ SQLType sqlType) throws SQLServerException {
+ DriverJDBCVersion.checkSupportsJDBC42();
- public final void setObject(int parameterIndex, Object x, SQLType targetSqlType, Integer precision, Integer scale, boolean forceEncrypt) throws SQLServerException
- {
- SQLServerPreparedStatement42Helper.setObject(this, parameterIndex, x, targetSqlType, precision, scale, forceEncrypt);
- }
+ if (loggerExternal.isLoggable(java.util.logging.Level.FINER))
+ loggerExternal.entering(getClassNameLogging(), "registerOutParameter", new Object[] {parameterName, sqlType});
+
+ // getVendorTypeNumber() returns the same constant integer values as in java.sql.Types
+ registerOutParameter(parameterName, sqlType.getVendorTypeNumber().intValue());
+
+ loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
+ }
+
+ public final void setObject(int index,
+ Object obj,
+ SQLType jdbcType) throws SQLServerException {
+ SQLServerPreparedStatement42Helper.setObject(this, index, obj, jdbcType);
+ }
+
+ public final void setObject(int parameterIndex,
+ Object x,
+ SQLType targetSqlType,
+ int scaleOrLength) throws SQLServerException {
+ SQLServerPreparedStatement42Helper.setObject(this, parameterIndex, x, targetSqlType, scaleOrLength);
+ }
+
+ public final void setObject(int parameterIndex,
+ Object x,
+ SQLType targetSqlType,
+ Integer precision,
+ Integer scale) throws SQLServerException {
+ SQLServerPreparedStatement42Helper.setObject(this, parameterIndex, x, targetSqlType, precision, scale);
+ }
+
+ public final void setObject(int parameterIndex,
+ Object x,
+ SQLType targetSqlType,
+ Integer precision,
+ Integer scale,
+ boolean forceEncrypt) throws SQLServerException {
+ SQLServerPreparedStatement42Helper.setObject(this, parameterIndex, x, targetSqlType, precision, scale, forceEncrypt);
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerClob.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerClob.java
index c0a1377f7..f26c2bb79 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerClob.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerClob.java
@@ -1,23 +1,13 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerClob.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
+
import static java.nio.charset.StandardCharsets.US_ASCII;
import java.io.BufferedInputStream;
@@ -38,48 +28,50 @@
import java.util.logging.Logger;
/**
-* SQLServerClob represents a character LOB object and implements java.sql.Clob.
-*/
-
-public class SQLServerClob extends SQLServerClobBase implements Clob
-{
- private static final long serialVersionUID = 2872035282200133865L;
-
- // Loggers should be class static to avoid lock contention with multiple threads
+ * SQLServerClob represents a character LOB object and implements java.sql.Clob.
+ */
+
+public class SQLServerClob extends SQLServerClobBase implements Clob {
+ private static final long serialVersionUID = 2872035282200133865L;
+
+ // Loggers should be class static to avoid lock contention with multiple threads
private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerClob");
/**
* Create a new CLOB
*
- * @param connection the database connection this blob is implemented on
- * @param data the BLOB's data
+ * @param connection
+ * the database connection this blob is implemented on
+ * @param data
+ * the BLOB's data
*/
- @Deprecated public SQLServerClob(SQLServerConnection connection, String data)
- {
+ @Deprecated
+ public SQLServerClob(SQLServerConnection connection,
+ String data) {
super(connection, data, (null == connection) ? null : connection.getDatabaseCollation(), logger);
if (null == data)
throw new NullPointerException(SQLServerException.getErrString("R_cantSetNull"));
}
- SQLServerClob(SQLServerConnection connection)
- {
+ SQLServerClob(SQLServerConnection connection) {
super(connection, "", connection.getDatabaseCollation(), logger);
}
- SQLServerClob(BaseInputStream stream, TypeInfo typeInfo) throws SQLServerException, UnsupportedEncodingException
- {
+ SQLServerClob(BaseInputStream stream,
+ TypeInfo typeInfo) throws SQLServerException, UnsupportedEncodingException {
super(null, new String(stream.getBytes(), typeInfo.getCharset()), typeInfo.getSQLCollation(), logger);
}
- final JDBCType getJdbcType() { return JDBCType.CLOB; }
+ final JDBCType getJdbcType() {
+ return JDBCType.CLOB;
+ }
}
-abstract class SQLServerClobBase implements Serializable
-{
- private static final long serialVersionUID = 8691072211054430124L;
+abstract class SQLServerClobBase implements Serializable {
+ private static final long serialVersionUID = 8691072211054430124L;
- // The value of the CLOB that this Clob object represents.
+ // The value of the CLOB that this Clob object represents.
// This value is never null unless/until the free() method is called.
private String value;
@@ -90,79 +82,78 @@ abstract class SQLServerClobBase implements Serializable
// Active streams which must be closed when the Clob/NClob is closed
//
// Initial size of the array is based on an assumption that a Clob/NClob object is
- // typically used either for input or output, and then only once. The array size
+ // typically used either for input or output, and then only once. The array size
// grows automatically if multiple streams are used.
private ArrayList activeStreams = new ArrayList(1);
transient SQLServerConnection con;
private static Logger logger;
final private String traceID = getClass().getName().substring(1 + getClass().getName().lastIndexOf('.')) + ":" + nextInstanceID();
- final public String toString()
- {
+
+ final public String toString() {
return traceID;
}
- static private final AtomicInteger baseID = new AtomicInteger(0); // Unique id generator for each instance (used for logging).
+
+ static private final AtomicInteger baseID = new AtomicInteger(0); // Unique id generator for each instance (used for logging).
// Returns unique id for each instance.
- private static int nextInstanceID()
- {
+
+ private static int nextInstanceID() {
return baseID.incrementAndGet();
}
abstract JDBCType getJdbcType();
- private String getDisplayClassName()
- {
+ private String getDisplayClassName() {
String fullClassName = getJdbcType().className();
return fullClassName.substring(1 + fullClassName.lastIndexOf('.'));
}
/**
* Create a new CLOB from a String
- * @param connection SQLServerConnection
- * @param data the CLOB data
- * @param collation the data collation
- * @param logger
+ *
+ * @param connection
+ * SQLServerConnection
+ * @param data
+ * the CLOB data
+ * @param collation
+ * the data collation
+ * @param logger
*/
- SQLServerClobBase(SQLServerConnection connection, String data, SQLCollation collation, Logger logger)
- {
+ SQLServerClobBase(SQLServerConnection connection,
+ String data,
+ SQLCollation collation,
+ Logger logger) {
this.con = connection;
this.value = data;
this.sqlCollation = collation;
SQLServerClobBase.logger = logger;
- if(logger.isLoggable(Level.FINE))
- {
- String loggingInfo = (null !=connection)? connection.toString(): "null connection";
- logger.fine(toString() + " created by (" + loggingInfo+ ")");
+ if (logger.isLoggable(Level.FINE)) {
+ String loggingInfo = (null != connection) ? connection.toString() : "null connection";
+ logger.fine(toString() + " created by (" + loggingInfo + ")");
}
}
/**
* Frees this Clob/NClob object and releases the resources that it holds.
*
- * After free() has been called, any attempt to invoke a method other than free() will
- * result in a SQLException being thrown. If free() is called multiple times, the subsequent
- * calls to free are treated as a no-op.
- *
- * @throws SQLException when an error occurs
+ * After free() has been called, any attempt to invoke a method other than free() will result in a SQLException being thrown. If free() is called
+ * multiple times, the subsequent calls to free are treated as a no-op.
+ *
+ * @throws SQLException
+ * when an error occurs
*/
- public void free() throws SQLException
- {
+ public void free() throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
- if (!isClosed)
- {
+ if (!isClosed) {
// Close active streams, ignoring any errors, since nothing can be done with them after that point anyway.
- if (null != activeStreams)
- {
- for (Closeable stream : activeStreams)
- {
- try
- {
+ if (null != activeStreams) {
+ for (Closeable stream : activeStreams) {
+ try {
stream.close();
}
- catch (IOException ioException)
- {
+ catch (IOException ioException) {
logger.fine(toString() + " ignored IOException closing stream " + stream + ": " + ioException.getMessage());
}
}
@@ -179,10 +170,8 @@ public void free() throws SQLException
/**
* Throws a SQLException if the LOB has been freed.
*/
- private void checkClosed() throws SQLServerException
- {
- if (isClosed)
- {
+ private void checkClosed() throws SQLServerException {
+ if (isClosed) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_isFreed"));
SQLServerException.makeFromDriverError(con, null, form.format(new Object[] {getDisplayClassName()}), null, true);
}
@@ -190,11 +179,12 @@ private void checkClosed() throws SQLServerException
/**
* Materialize the CLOB as an ASCII stream.
- * @throws SQLException when an error occurs
+ *
+ * @throws SQLException
+ * when an error occurs
* @return the data as an input stream
*/
- public InputStream getAsciiStream() throws SQLException
- {
+ public InputStream getAsciiStream() throws SQLException {
checkClosed();
if (null != sqlCollation && !sqlCollation.supportsAsciiConversion())
@@ -208,13 +198,13 @@ public InputStream getAsciiStream() throws SQLException
}
/**
- * Retrieves the CLOB value designated by this Clob object as a java.io.Reader object
- * (or as a stream of characters).
- * @throws SQLException if there is an error accessing the CLOB value
+ * Retrieves the CLOB value designated by this Clob object as a java.io.Reader object (or as a stream of characters).
+ *
+ * @throws SQLException
+ * if there is an error accessing the CLOB value
* @return a java.io.Reader object containing the CLOB data
*/
- public Reader getCharacterStream() throws SQLException
- {
+ public Reader getCharacterStream() throws SQLException {
checkClosed();
Reader getterStream = new StringReader(value);
@@ -222,15 +212,19 @@ public Reader getCharacterStream() throws SQLException
return getterStream;
}
- /**
+ /**
* Returns the Clob data as a java.io.Reader object or as a stream of characters with the specified position and length.
- * @param pos A long that indicates the offset to the first character of the partial value to be retrieved.
- * @param length A long that indicates the length in characters of the partial value to be retrieved.
+ *
+ * @param pos
+ * A long that indicates the offset to the first character of the partial value to be retrieved.
+ * @param length
+ * A long that indicates the length in characters of the partial value to be retrieved.
* @return A Reader object that contains the Clob data.
- * @throws SQLException when an error occurs.
+ * @throws SQLException
+ * when an error occurs.
*/
- public Reader getCharacterStream(long pos, long length) throws SQLException
- {
+ public Reader getCharacterStream(long pos,
+ long length) throws SQLException {
DriverJDBCVersion.checkSupportsJDBC4();
// Not implemented
@@ -238,27 +232,28 @@ public Reader getCharacterStream(long pos, long length) throws SQLException
}
/**
- * Retrieves a copy of the specified substring in the CLOB value designated by this Clob object.
- * The substring begins at position pos and has up to length consecutive characters.
+ * Retrieves a copy of the specified substring in the CLOB value designated by this Clob object. The substring begins at position pos and has up
+ * to length consecutive characters.
*
- * @param pos - the first character of the substring to be extracted. The first character is at position 1.
- * @param length - the number of consecutive characters to be copied; the value for length must be 0 or greater
+ * @param pos
+ * - the first character of the substring to be extracted. The first character is at position 1.
+ * @param length
+ * - the number of consecutive characters to be copied; the value for length must be 0 or greater
* @return a String that is the specified substring in the CLOB value designated by this Clob object
- * @throws SQLException - if there is an error accessing the CLOB value; if pos is less than 1 or length is less than 0
+ * @throws SQLException
+ * - if there is an error accessing the CLOB value; if pos is less than 1 or length is less than 0
*/
- public String getSubString(long pos, int length) throws SQLException
- {
+ public String getSubString(long pos,
+ int length) throws SQLException {
checkClosed();
- if (pos < 1)
- {
+ if (pos < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(pos)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
}
- if (length < 0)
- {
+ if (length < 0) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
Object[] msgArgs = {new Integer(length)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -277,36 +272,38 @@ public String getSubString(long pos, int length) throws SQLException
length = (int) (value.length() - pos);
// Note String.substring uses beginIndex and endIndex (not pos and length), so calculate endIndex.
- return value.substring((int)pos,(int)pos+length);
+ return value.substring((int) pos, (int) pos + length);
}
/**
* Retrieves the number of characters in the CLOB value designated by this Clob object.
- * @throws SQLException when an error occurs
+ *
+ * @throws SQLException
+ * when an error occurs
* @return length of the CLOB in characters
*/
- public long length() throws SQLException
- {
+ public long length() throws SQLException {
checkClosed();
return value.length();
}
/**
- * Retrieves the character position at which the specified Clob object searchstr appears in this Clob object.
- * The search begins at position start.
+ * Retrieves the character position at which the specified Clob object searchstr appears in this Clob object. The search begins at position start.
*
- * @param searchstr - the Clob for which to search
- * @param start - the position at which to begin searching; the first position is 1
+ * @param searchstr
+ * - the Clob for which to search
+ * @param start
+ * - the position at which to begin searching; the first position is 1
* @return the position at which the Clob object appears or -1 if it is not present; the first position is 1
- * @throws SQLException - if there is an error accessing the CLOB value or if start is less than 1
+ * @throws SQLException
+ * - if there is an error accessing the CLOB value or if start is less than 1
*/
- public long position(Clob searchstr, long start) throws SQLException
- {
+ public long position(Clob searchstr,
+ long start) throws SQLException {
checkClosed();
- if (start < 1)
- {
+ if (start < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(start)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -319,21 +316,22 @@ public long position(Clob searchstr, long start) throws SQLException
}
/**
- * Retrieves the character position at which the specified substring searchstr appears in the
- * SQL CLOB value represented by this Clob object. The search begins at position start.
+ * Retrieves the character position at which the specified substring searchstr appears in the SQL CLOB value represented by this Clob object. The
+ * search begins at position start.
*
- * @param searchstr - the substring for which to search
- * @param start - the position at which to begin searching; the first position is 1
- * @return the position at which the substring appears or -1 if it is not present;
- * the first position is 1
- * @throws SQLException - if there is an error accessing the CLOB value or if start is less than 1
+ * @param searchstr
+ * - the substring for which to search
+ * @param start
+ * - the position at which to begin searching; the first position is 1
+ * @return the position at which the substring appears or -1 if it is not present; the first position is 1
+ * @throws SQLException
+ * - if there is an error accessing the CLOB value or if start is less than 1
*/
- public long position(String searchstr, long start) throws SQLException
- {
+ public long position(String searchstr,
+ long start) throws SQLException {
checkClosed();
- if (start < 1)
- {
+ if (start < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(start)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -344,7 +342,7 @@ public long position(String searchstr, long start) throws SQLException
if (null == searchstr)
return -1;
- int pos = value.indexOf(searchstr, (int) (start-1));
+ int pos = value.indexOf(searchstr, (int) (start - 1));
if (-1 != pos)
return pos + 1;
@@ -355,37 +353,38 @@ public long position(String searchstr, long start) throws SQLException
/**
* Truncates the CLOB value that this Clob designates to have a length of len characters.
- * @param len the length, in characters, to which the CLOB value should be truncated
- * @throws SQLException when an error occurs
+ *
+ * @param len
+ * the length, in characters, to which the CLOB value should be truncated
+ * @throws SQLException
+ * when an error occurs
*/
- public void truncate(long len) throws SQLException
- {
+ public void truncate(long len) throws SQLException {
checkClosed();
- if (len < 0)
- {
+ if (len < 0) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
Object[] msgArgs = {new Long(len)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
}
if (len <= Integer.MAX_VALUE && value.length() > len)
- value = value.substring(0, (int)len);
+ value = value.substring(0, (int) len);
}
/**
- * Retrieves a stream to be used to write Ascii characters to the CLOB value that
- * this Clob object represents, starting at position pos.
- * @param pos the position at which to start writing to this CLOB object
- * @throws SQLException when an error occurs
+ * Retrieves a stream to be used to write Ascii characters to the CLOB value that this Clob object represents, starting at position pos.
+ *
+ * @param pos
+ * the position at which to start writing to this CLOB object
+ * @throws SQLException
+ * when an error occurs
* @return the stream to which ASCII encoded characters can be written
*/
- public java.io.OutputStream setAsciiStream(long pos) throws SQLException
- {
+ public java.io.OutputStream setAsciiStream(long pos) throws SQLException {
checkClosed();
- if (pos < 1)
- {
+ if (pos < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(pos)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -395,18 +394,18 @@ public java.io.OutputStream setAsciiStream(long pos) throws SQLException
}
/**
- * Retrieves a stream to be used to write a stream of Unicode characters to
- * the CLOB value that this Clob object represents, at position pos.
- * @param pos the position at which to start writing to the CLOB value
- * @throws SQLException when an error occurs
+ * Retrieves a stream to be used to write a stream of Unicode characters to the CLOB value that this Clob object represents, at position pos.
+ *
+ * @param pos
+ * the position at which to start writing to the CLOB value
+ * @throws SQLException
+ * when an error occurs
* @return a stream to which Unicode encoded characters can be written
*/
- public java.io.Writer setCharacterStream(long pos) throws SQLException
- {
+ public java.io.Writer setCharacterStream(long pos) throws SQLException {
checkClosed();
- if (pos < 1)
- {
+ if (pos < 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(pos)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -416,15 +415,18 @@ public java.io.Writer setCharacterStream(long pos) throws SQLException
}
/**
- * Writes the given Java String to the CLOB value that this Clob object designates
- * at the position pos.
- * @param pos the position at which to start writing to the CLOB
- * @param s the string to be written to the CLOB value that this Clob designates
- * @throws SQLException when an error occurs
+ * Writes the given Java String to the CLOB value that this Clob object designates at the position pos.
+ *
+ * @param pos
+ * the position at which to start writing to the CLOB
+ * @param s
+ * the string to be written to the CLOB value that this Clob designates
+ * @throws SQLException
+ * when an error occurs
* @return the number of characters written
*/
- public int setString(long pos, String s) throws SQLException
- {
+ public int setString(long pos,
+ String s) throws SQLException {
checkClosed();
if (null == s)
@@ -434,40 +436,42 @@ public int setString(long pos, String s) throws SQLException
}
/**
- * Writes len characters of str, starting at character offset, to the CLOB value that this Clob represents.
- * The string will overwrite the existing characters in the Clob object starting at the position pos.
- * If the end of the Clob value is reached while writing the given string, then the length of the Clob
- * value will be increased to accomodate the extra characters.
+ * Writes len characters of str, starting at character offset, to the CLOB value that this Clob represents. The string will overwrite the existing
+ * characters in the Clob object starting at the position pos. If the end of the Clob value is reached while writing the given string, then the
+ * length of the Clob value will be increased to accomodate the extra characters.
*
- * SQL Server behavior:
- * If the value specified for pos is greater than then length+1 of the CLOB value then a SQLException
- * is thrown.
+ * SQL Server behavior: If the value specified for pos is greater than then length+1 of the CLOB value then a SQLException is thrown.
*
- * @param pos - the position at which to start writing to this CLOB object; The first position is 1
- * @param str - the string to be written to the CLOB value that this Clob object represents
- * @param offset - the offset (0-based) into str to start reading the characters to be written
- * @param len - the number of characters to be written
+ * @param pos
+ * - the position at which to start writing to this CLOB object; The first position is 1
+ * @param str
+ * - the string to be written to the CLOB value that this Clob object represents
+ * @param offset
+ * - the offset (0-based) into str to start reading the characters to be written
+ * @param len
+ * - the number of characters to be written
* @return the number of characters written
- * @throws SQLException - if there is an error accessing the CLOB value or if pos is less than 1
+ * @throws SQLException
+ * - if there is an error accessing the CLOB value or if pos is less than 1
*/
- public int setString(long pos, String str, int offset, int len) throws SQLException
- {
+ public int setString(long pos,
+ String str,
+ int offset,
+ int len) throws SQLException {
checkClosed();
if (null == str)
SQLServerException.makeFromDriverError(con, null, SQLServerException.getErrString("R_cantSetNull"), null, true);
// Offset must be within incoming string str boundary.
- if (offset < 0 || offset > str.length())
- {
+ if (offset < 0 || offset > str.length()) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidOffset"));
Object[] msgArgs = {new Integer(offset)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
}
// len must be within incoming string str boundary.
- if (len < 0 || len > str.length() - offset)
- {
+ if (len < 0 || len > str.length() - offset) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLength"));
Object[] msgArgs = {new Integer(len)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -476,8 +480,7 @@ public int setString(long pos, String str, int offset, int len) throws SQLExcept
// Note position for Clob.setString is 1 based not zero based.
// Position must be in range of existing Clob data or exactly 1 character
// past the end of data to request "append" mode.
- if (pos < 1 || pos > value.length() + 1)
- {
+ if (pos < 1 || pos > value.length() + 1) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPositionIndex"));
Object[] msgArgs = {new Long(pos)};
SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true);
@@ -487,36 +490,34 @@ public int setString(long pos, String str, int offset, int len) throws SQLExcept
pos--;
// Overwrite past end of value case.
- if (len >= value.length() - pos)
- {
+ if (len >= value.length() - pos) {
// Make sure the new value length wouldn't exceed the maximum allowed
DataTypes.getCheckedLength(con, getJdbcType(), pos + len, false);
assert pos + len <= Integer.MAX_VALUE;
// Start with the original value, up to the starting position
- StringBuilder sb = new StringBuilder((int)pos+len);
- sb.append(value.substring(0,(int)pos));
+ StringBuilder sb = new StringBuilder((int) pos + len);
+ sb.append(value.substring(0, (int) pos));
// Append the new value
- sb.append(str.substring(offset,offset+len));
+ sb.append(str.substring(offset, offset + len));
// Use the combined string as the new value
value = sb.toString();
}
// Overwrite internal to value case.
- else
- {
+ else {
// Start with the original value, up to the starting position
StringBuilder sb = new StringBuilder(value.length());
- sb.append(value.substring(0,(int)pos));
+ sb.append(value.substring(0, (int) pos));
// Append the new value
- sb.append(str.substring(offset,offset+len));
+ sb.append(str.substring(offset, offset + len));
// Append the remainder of the original value
// that was not replaced by the new value
- sb.append(value.substring((int)pos + len));
+ sb.append(value.substring((int) pos + len));
// Use the combined string as the new value
value = sb.toString();
@@ -526,119 +527,127 @@ public int setString(long pos, String str, int offset, int len) throws SQLExcept
}
}
-// SQLServerClobWriter is a simple java.io.Writer interface implementing class that
-// forwards all calls to SQLServerClob.setString. This class is returned to caller by
+// SQLServerClobWriter is a simple java.io.Writer interface implementing class that
+// forwards all calls to SQLServerClob.setString. This class is returned to caller by
// SQLServerClob class when setCharacterStream is called.
//
-// SQLServerClobWriter starts writing at postion streamPos and continues to write
-// in a forward only manner. There is no reset with java.io.Writer.
+// SQLServerClobWriter starts writing at postion streamPos and continues to write
+// in a forward only manner. There is no reset with java.io.Writer.
//
-final class SQLServerClobWriter extends java.io.Writer
-{
- private SQLServerClobBase parentClob = null;
- private long streamPos;
- SQLServerClobWriter(SQLServerClobBase parentClob, long streamPos)
- {
- this.parentClob = parentClob;
- this.streamPos = streamPos;
- }
- public void write(char[] cbuf) throws IOException
- {
- if (null == cbuf) return;
- write(new String(cbuf));
- }
- public void write(char[] cbuf, int off, int len) throws IOException
- {
- if (null == cbuf) return;
- write(new String(cbuf,off,len));
- }
- public void write(int b) throws java.io.IOException
- {
- char [] c = new char[1];
- c[0] = (char)b;
- write(new String(c));
- }
- public void write(String str, int off, int len) throws IOException
- {
- checkClosed();
- try
- {
- // Call parent's setString and update position.
- // setString can throw a SQLServerException, we translate
- // this to an IOException here.
- int charsWritten = parentClob.setString(streamPos,str,off,len);
- streamPos += charsWritten;
- }
- catch (SQLException ex)
- {
- throw new IOException(ex.getMessage());
- }
- }
- public void write(String str) throws IOException
- {
- if (null == str) return;
- write(str,0,str.length());
- }
- public void flush() throws IOException
- {
- checkClosed();
- }
- public void close() throws IOException
- {
- checkClosed();
- parentClob = null;
- }
- private void checkClosed() throws IOException
- {
- if (null==parentClob)
- throw new IOException(SQLServerException.getErrString("R_streamIsClosed"));
- }
+final class SQLServerClobWriter extends java.io.Writer {
+ private SQLServerClobBase parentClob = null;
+ private long streamPos;
+
+ SQLServerClobWriter(SQLServerClobBase parentClob,
+ long streamPos) {
+ this.parentClob = parentClob;
+ this.streamPos = streamPos;
+ }
+
+ public void write(char[] cbuf) throws IOException {
+ if (null == cbuf)
+ return;
+ write(new String(cbuf));
+ }
+
+ public void write(char[] cbuf,
+ int off,
+ int len) throws IOException {
+ if (null == cbuf)
+ return;
+ write(new String(cbuf, off, len));
+ }
+
+ public void write(int b) throws java.io.IOException {
+ char[] c = new char[1];
+ c[0] = (char) b;
+ write(new String(c));
+ }
+
+ public void write(String str,
+ int off,
+ int len) throws IOException {
+ checkClosed();
+ try {
+ // Call parent's setString and update position.
+ // setString can throw a SQLServerException, we translate
+ // this to an IOException here.
+ int charsWritten = parentClob.setString(streamPos, str, off, len);
+ streamPos += charsWritten;
+ }
+ catch (SQLException ex) {
+ throw new IOException(ex.getMessage());
+ }
+ }
+
+ public void write(String str) throws IOException {
+ if (null == str)
+ return;
+ write(str, 0, str.length());
+ }
+
+ public void flush() throws IOException {
+ checkClosed();
+ }
+
+ public void close() throws IOException {
+ checkClosed();
+ parentClob = null;
+ }
+
+ private void checkClosed() throws IOException {
+ if (null == parentClob)
+ throw new IOException(SQLServerException.getErrString("R_streamIsClosed"));
+ }
}
-// SQLServerClobAsciiOutputStream is a simple java.io.OutputStream interface implementing class that
-// forwards all calls to SQLServerClob.setString. This class is returned to caller by
+// SQLServerClobAsciiOutputStream is a simple java.io.OutputStream interface implementing class that
+// forwards all calls to SQLServerClob.setString. This class is returned to caller by
// SQLServerClob class when setAsciiStream is called.
//
-// SQLServerClobAsciiOutputStream starts writing at character postion streamPos and continues to write
-// in a forward only manner. Reset/mark are not supported.
+// SQLServerClobAsciiOutputStream starts writing at character postion streamPos and continues to write
+// in a forward only manner. Reset/mark are not supported.
//
-final class SQLServerClobAsciiOutputStream extends java.io.OutputStream
-{
- private SQLServerClobBase parentClob = null;
- private long streamPos;
- SQLServerClobAsciiOutputStream(SQLServerClobBase parentClob, long streamPos)
- {
- this.parentClob = parentClob;
- this.streamPos = streamPos;
- }
- public void write(byte[] b) throws IOException
- {
- if (null == b) return;
- write(b,0,b.length);
- }
- public void write(byte[] b, int off, int len) throws IOException
- {
- if (null == b) return;
- try
- {
- // Convert bytes to string using US-ASCII translation.
- String s = new String(b, off, len, "US-ASCII");
-
- // Call parent's setString and update position.
- // setString can throw a SQLServerException, we translate
- // this to an IOException here.
- int charsWritten = parentClob.setString(streamPos, s);
- streamPos += charsWritten;
- }
- catch (SQLException ex)
- {
- throw new IOException(ex.getMessage());
- }
- }
- private byte [] bSingleByte = new byte[1];
- public void write(int b) throws java.io.IOException
- {
- bSingleByte[0] = (byte)(b&0xFF);
- write(bSingleByte,0,bSingleByte.length);
- }
+final class SQLServerClobAsciiOutputStream extends java.io.OutputStream {
+ private SQLServerClobBase parentClob = null;
+ private long streamPos;
+
+ SQLServerClobAsciiOutputStream(SQLServerClobBase parentClob,
+ long streamPos) {
+ this.parentClob = parentClob;
+ this.streamPos = streamPos;
+ }
+
+ public void write(byte[] b) throws IOException {
+ if (null == b)
+ return;
+ write(b, 0, b.length);
+ }
+
+ public void write(byte[] b,
+ int off,
+ int len) throws IOException {
+ if (null == b)
+ return;
+ try {
+ // Convert bytes to string using US-ASCII translation.
+ String s = new String(b, off, len, "US-ASCII");
+
+ // Call parent's setString and update position.
+ // setString can throw a SQLServerException, we translate
+ // this to an IOException here.
+ int charsWritten = parentClob.setString(streamPos, s);
+ streamPos += charsWritten;
+ }
+ catch (SQLException ex) {
+ throw new IOException(ex.getMessage());
+ }
+ }
+
+ private byte[] bSingleByte = new byte[1];
+
+ public void write(int b) throws java.io.IOException {
+ bSingleByte[0] = (byte) (b & 0xFF);
+ write(bSingleByte, 0, bSingleByte.length);
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java
index 11ff89d84..191729b59 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionAzureKeyVaultProvider.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerColumnEncryptionAzureKeyVaultProvider.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import static java.nio.charset.StandardCharsets.UTF_16LE;
@@ -40,542 +29,530 @@
import com.microsoft.azure.keyvault.webkey.JsonWebKeySignatureAlgorithm;
/**
- * Provides implementation similar to certificate store provider.
- * A CEK encrypted with certificate store provider should be decryptable by this provider and vice versa.
+ * Provides implementation similar to certificate store provider. A CEK encrypted with certificate store provider should be decryptable by this
+ * provider and vice versa.
*
- * Envolope Format for the encrypted column encryption key
- * version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- * version: A single byte indicating the format version.
- * keyPathLength: Length of the keyPath.
- * ciphertextLength: ciphertext length
- * keyPath: keyPath used to encrypt the column encryption key. This is only used for troubleshooting purposes and is not verified during decryption.
- * ciphertext: Encrypted column encryption key
- * signature: Signature of the entire byte array. Signature is validated before decrypting the column encryption key.
+ * Envolope Format for the encrypted column encryption key version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature version: A
+ * single byte indicating the format version. keyPathLength: Length of the keyPath. ciphertextLength: ciphertext length keyPath: keyPath used to
+ * encrypt the column encryption key. This is only used for troubleshooting purposes and is not verified during decryption. ciphertext: Encrypted
+ * column encryption key signature: Signature of the entire byte array. Signature is validated before decrypting the column encryption key.
*/
-public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerColumnEncryptionKeyStoreProvider{
-
- /**
- * Column Encryption Key Store Provider string
- */
- String name = "AZURE_KEY_VAULT";
-
- private final String azureKeyVaultDomainName = "vault.azure.net";
-
- private final String rsaEncryptionAlgorithmWithOAEPForAKV="RSA-OAEP";
-
- /**
- * Algorithm version
- */
- private final byte[] firstVersion = new byte[] { 0x01 };
-
- private KeyVaultClient keyVaultClient;
-
- private KeyVaultCredential credential;
-
-
- public void setName(String name)
- {
- this.name = name;
- }
-
- public String getName()
- {
- return this.name;
- }
-
- /**
- * Constructor that takes a callback function to authenticate to AAD. This is used by KeyVaultClient at runtime
- * to authenticate to Azure Key Vault.
- *
- * @param authenticationCallback - Callback function used for authenticating to AAD.
- * @param executorService - The ExecutorService used to create the keyVaultClient
- * @throws SQLServerException when an error occurs
- */
- public SQLServerColumnEncryptionAzureKeyVaultProvider(SQLServerKeyVaultAuthenticationCallback authenticationCallback, ExecutorService executorService) throws SQLServerException{
- if(null == authenticationCallback){
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue"));
- Object[] msgArgs1 = { "SQLServerKeyVaultAuthenticationCallback" };
- throw new SQLServerException(form.format(msgArgs1), null);
- }
- credential = new KeyVaultCredential(authenticationCallback);
- HttpClientBuilder builder = HttpClientBuilder.create();
- keyVaultClient = new KeyVaultClientImpl(builder, executorService, credential);
- }
-
- /**
- * This function uses the asymmetric key specified by the key path
- * and decrypts an encrypted CEK with RSA encryption algorithm.
- *
- * @param masterKeyPath - Complete path of an asymmetric key in AKV
- * @param encryptionAlgorithm - Asymmetric Key Encryption Algorithm
- * @param encryptedColumnEncryptionKey - Encrypted Column Encryption Key
- * @return Plain text column encryption key
- */
- @Override
- public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
- byte[] encryptedColumnEncryptionKey) throws SQLServerException {
-
- // Validate the input parameters
- this.ValidateNonEmptyAKVPath(masterKeyPath);
-
- if (null == encryptedColumnEncryptionKey)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"), null);
- }
-
- if (0 == encryptedColumnEncryptionKey.length)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_EmptyEncryptedColumnEncryptionKey"), null);
- }
-
- // Validate encryptionAlgorithm
- encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = getAKVKeySize(masterKeyPath);
-
- // Validate and decrypt the EncryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- //
- // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and
- // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
-
- // Validate the version byte
- if (encryptedColumnEncryptionKey[0] != firstVersion[0])
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidEcryptionAlgorithmVersion"));
- Object[] msgArgs = {String.format("%02X ", encryptedColumnEncryptionKey[0]), String.format("%02X ", firstVersion[0])};
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
-
- // Get key path length
- int currentIndex = firstVersion.length;
- short keyPathLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex);
- // We just read 2 bytes
- currentIndex += 2;
-
- // Get ciphertext length
- short cipherTextLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex);
- currentIndex += 2;
-
- // Skip KeyPath
- // KeyPath exists only for troubleshooting purposes and doesnt need validation.
- currentIndex += keyPathLength;
-
- // validate the ciphertext length
- if (cipherTextLength != keySizeInBytes)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVKeyLengthError"));
- Object[] msgArgs = {cipherTextLength, keySizeInBytes, masterKeyPath};
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
-
- // Validate the signature length
- int signatureLength = encryptedColumnEncryptionKey.length - currentIndex - cipherTextLength;
-
- if (signatureLength != keySizeInBytes)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVSignatureLengthError"));
- Object[] msgArgs = {signatureLength, keySizeInBytes, masterKeyPath};
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
-
- // Get ciphertext
- byte[] cipherText = new byte[cipherTextLength];
- System.arraycopy(encryptedColumnEncryptionKey,currentIndex,cipherText,0,cipherTextLength);
- currentIndex += cipherTextLength;
-
- // Get signature
- byte[] signature = new byte[signatureLength];
- System.arraycopy(encryptedColumnEncryptionKey,currentIndex,signature,0,signatureLength);
-
- // Compute the hash to validate the signature
- byte[] hash = new byte[encryptedColumnEncryptionKey.length - signature.length];
-
- System.arraycopy(encryptedColumnEncryptionKey,0,hash,0,encryptedColumnEncryptionKey.length - signature.length);
-
-
- MessageDigest md = null;
- try {
- md = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), null);
- }
- md.update(hash);
- byte dataToVerify[] = md.digest();
-
- if (null == dataToVerify)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null);
- }
-
- // Validate the signature
- if (!AzureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CEKSignatureNotMatchCMK"));
- Object[] msgArgs = {masterKeyPath};
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
-
- // Decrypt the CEK
- byte[] decryptedCEK = this.AzureKeyVaultUnWrap(masterKeyPath, encryptionAlgorithm, cipherText);
-
- return decryptedCEK;
- }
-
- private short convertTwoBytesToShort(byte[] input, int index) throws SQLServerException
- {
-
- short shortVal = -1;
- if (index + 1 >= input.length)
- {
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_ByteToShortConversion"),
- null,
- 0,
- false);
- }
- ByteBuffer byteBuffer = ByteBuffer.allocate(2);
- byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
- byteBuffer.put(input[index]);
- byteBuffer.put(input[index + 1]);
- shortVal = byteBuffer.getShort(0);
- return shortVal;
-
- }
-
- /**
- * This function uses the asymmetric key specified by the key path
- * and encrypts CEK with RSA encryption algorithm.
- *
- * @param masterKeyPath - Complete path of an asymmetric key in AKV
- * @param encryptionAlgorithm - Asymmetric Key Encryption Algorithm
- * @param columnEncryptionKey - Plain text column encryption key
- * @return Encrypted column encryption key
- */
- @Override
- public byte[] encryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
- byte[] columnEncryptionKey) throws SQLServerException {
-
- // Validate the input parameters
- this.ValidateNonEmptyAKVPath(masterKeyPath);
-
- if (null == columnEncryptionKey)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_NullColumnEncryptionKey"), null);
- }
-
- if (0 == columnEncryptionKey.length)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_EmptyCEK"), null);
- }
-
- // Validate encryptionAlgorithm
- encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm);
-
- // Validate whether the key is RSA one or not and then get the key size
- int keySizeInBytes = getAKVKeySize(masterKeyPath);
-
- // Construct the encryptedColumnEncryptionKey
- // Format is
- // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
- //
- // We currently only support one version
- byte[] version = new byte[] { firstVersion[0] };
-
- // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
- byte[] masterKeyPathBytes = masterKeyPath.toLowerCase().getBytes(UTF_16LE);
-
- byte[] keyPathLength = new byte[2];
- keyPathLength[0] = (byte)(((short)masterKeyPathBytes.length) & 0xff);
- keyPathLength[1] = (byte)(((short)masterKeyPathBytes.length) >> 8 & 0xff);
-
- // Encrypt the plain text
- byte[] cipherText = this.AzureKeyVaultWrap(masterKeyPath, encryptionAlgorithm, columnEncryptionKey);
-
- byte[] cipherTextLength = new byte[2];
- cipherTextLength[0] = (byte)(((short)cipherText.length) & 0xff);
- cipherTextLength[1] = (byte)(((short)cipherText.length) >> 8 & 0xff);
-
- if (cipherText.length != keySizeInBytes)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_CipherTextLengthNotMatchRSASize"), null);
- }
-
- // Compute hash
- // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext)
- byte [] dataToHash=new byte[version.length + keyPathLength.length + cipherTextLength.length + masterKeyPathBytes.length + cipherText.length];
- int destinationPosition=version.length;
- System.arraycopy(version, 0, dataToHash, 0, version.length);
-
- System.arraycopy(keyPathLength, 0, dataToHash, destinationPosition, keyPathLength.length);
- destinationPosition+=keyPathLength.length;
-
- System.arraycopy(cipherTextLength, 0, dataToHash, destinationPosition, cipherTextLength.length);
- destinationPosition+=cipherTextLength.length;
-
- System.arraycopy(masterKeyPathBytes, 0, dataToHash, destinationPosition, masterKeyPathBytes.length);
- destinationPosition+=masterKeyPathBytes.length;
-
- System.arraycopy(cipherText, 0, dataToHash, destinationPosition, cipherText.length);
-
- MessageDigest md = null;
- try {
- md = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), null);
- }
- md.update(dataToHash);
- byte dataToSign[] = md.digest();
-
- // Sign the hash
- byte[] signedHash = null;
- signedHash = AzureKeyVaultSignHashedData(dataToSign, masterKeyPath);
-
- if (signedHash.length != keySizeInBytes)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null);
- }
-
- if (!this.AzureKeyVaultVerifySignature(dataToSign, signedHash, masterKeyPath))
- {
- throw new SQLServerException(SQLServerException.getErrString("R_InvalidSignatureComputed"), null);
- }
-
- // Construct the encrypted column encryption key
- // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
- int encryptedColumnEncryptionKeyLength = version.length + cipherTextLength.length + keyPathLength.length + cipherText.length + masterKeyPathBytes.length + signedHash.length;
- byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
-
- // Copy version byte
- int currentIndex = 0;
- System.arraycopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.length);
- currentIndex += version.length;
-
- // Copy key path length
- System.arraycopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.length);
- currentIndex += keyPathLength.length;
-
- // Copy ciphertext length
- System.arraycopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.length);
- currentIndex += cipherTextLength.length;
-
- // Copy key path
- System.arraycopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.length);
- currentIndex += masterKeyPathBytes.length;
-
- // Copy ciphertext
- System.arraycopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.length);
- currentIndex += cipherText.length;
-
- // copy the signature
- System.arraycopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.length);
-
- return encryptedColumnEncryptionKey;
- }
-
- /**
- * This function validates that the encryption algorithm is RSA_OAEP and if it is not,
- * then throws an exception
- * @param encryptionAlgorithm - Asymmetric key encryptio algorithm
- * @return The encryption algorithm that is going to be used.
- * @throws SQLServerException
- */
- private String validateEncryptionAlgorithm(String encryptionAlgorithm) throws SQLServerException
- {
-
- if (null == encryptionAlgorithm)
- {
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_NullKeyEncryptionAlgorithm"),
- null,
- 0,
- false);
- }
-
- // Transform to standard format (dash instead of underscore) to support both "RSA_OAEP" and "RSA-OAEP"
- if (encryptionAlgorithm.equalsIgnoreCase("RSA_OAEP"))
- {
- encryptionAlgorithm = "RSA-OAEP";
- }
-
- if (!rsaEncryptionAlgorithmWithOAEPForAKV.equalsIgnoreCase(encryptionAlgorithm.trim()))
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_InvalidKeyEncryptionAlgorithm"));
- Object[] msgArgs = { encryptionAlgorithm, rsaEncryptionAlgorithmWithOAEPForAKV };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
-
-
- return encryptionAlgorithm;
- }
-
- /**
- * Checks if the Azure Key Vault key path is Empty or Null (and raises exception if they are).
- *
- * @param masterKeyPath
- * @throws SQLServerException
- */
- private void ValidateNonEmptyAKVPath(String masterKeyPath) throws SQLServerException
- {
- // throw appropriate error if masterKeyPath is null or empty
- if (null == masterKeyPath || masterKeyPath.trim().isEmpty())
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVPathNull"));
- Object[] msgArgs = {masterKeyPath};
- throw new SQLServerException(null , form.format(msgArgs) , null, 0 , false);
- }
- else
- {
- URI parsedUri = null;
- try {
- parsedUri = new URI(masterKeyPath);
- } catch (URISyntaxException e) {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVURLInvalid"));
- Object[] msgArgs = {masterKeyPath};
- throw new SQLServerException(null , form.format(msgArgs) , null, 0 , false);
- }
-
- // A valid URI.
- // Check if it is pointing to AKV.
- if(!parsedUri.getHost().toLowerCase().endsWith(azureKeyVaultDomainName)){
- // Return an error indicating that the AKV url is invalid.
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVMasterKeyPathInvalid"));
- Object[] msgArgs = {masterKeyPath};
- throw new SQLServerException(null , form.format(msgArgs) , null, 0 , false);
- }
- }
- }
-
- /**
- * Encrypt the text using specified Azure Key Vault key.
- *
- * @param masterKeyPath - Azure Key Vault key url.
- * @param encryptionAlgorithm - Encryption Algorithm.
- * @param columnEncryptionKey - Plain text Column Encryption Key.
- * @return Returns an encrypted blob or throws an exception if there are any errors.
- * @throws SQLServerException
- */
- private byte[] AzureKeyVaultWrap(String masterKeyPath, String encryptionAlgorithm, byte[] columnEncryptionKey) throws SQLServerException
- {
- if (null == columnEncryptionKey)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_CEKNull"), null);
- }
-
- KeyOperationResult wrappedKey = null;
- try {
- wrappedKey = keyVaultClient.wrapKeyAsync(masterKeyPath, encryptionAlgorithm, columnEncryptionKey).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new SQLServerException(SQLServerException.getErrString("R_EncryptCEKError"), null);
- }
- return wrappedKey.getResult();
- }
-
- /**
- * Encrypt the text using specified Azure Key Vault key.
- *
- * @param masterKeyPath - Azure Key Vault key url.
- * @param encryptionAlgorithm - Encrypted Column Encryption Key.
- * @param encryptedColumnEncryptionKey - Encrypted Column Encryption Key.
- * @return Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.
- * @throws SQLServerException
- */
- private byte[] AzureKeyVaultUnWrap(String masterKeyPath, String encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) throws SQLServerException
- {
- if (null == encryptedColumnEncryptionKey)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_EncryptedCEKNull"), null);
- }
-
- if (0 == encryptedColumnEncryptionKey.length)
- {
- throw new SQLServerException(SQLServerException.getErrString("R_EmptyEncryptedCEK"), null);
- }
-
- KeyOperationResult unwrappedKey;
- try {
- unwrappedKey = keyVaultClient.unwrapKeyAsync(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new SQLServerException(SQLServerException.getErrString("R_DecryptCEKError"), null);
- }
- return unwrappedKey.getResult();
- }
-
- /**
- * Generates signature based on RSA PKCS#v1.5 scheme using a specified Azure Key Vault Key URL.
- *
- * @param dataToSign - Text to sign.
- * @param masterKeyPath - Azure Key Vault key url.
- * @return Signature
- * @throws SQLServerException
- */
- private byte[] AzureKeyVaultSignHashedData(byte[] dataToSign, String masterKeyPath) throws SQLServerException
- {
- assert((null != dataToSign) && (0 != dataToSign.length));
-
- KeyOperationResult signedData = null;
- try {
- signedData = keyVaultClient.signAsync(masterKeyPath, JsonWebKeySignatureAlgorithm.RS256, dataToSign).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new SQLServerException(SQLServerException.getErrString("R_GenerateSignature"), null);
- }
- return signedData.getResult();
- }
-
- /**
- * Verifies the given RSA PKCSv1.5 signature.
- *
- * @param dataToVerify
- * @param signature
- * @param masterKeyPath - Azure Key Vault key url.
- * @return true if signature is valid, false if it is not valid
- * @throws SQLServerException
- */
- private boolean AzureKeyVaultVerifySignature(byte[] dataToVerify, byte[] signature, String masterKeyPath) throws SQLServerException
- {
- assert((null != dataToVerify) && (0 != dataToVerify.length));
- assert((null != signature) && (0 != signature.length));
-
- boolean valid = false;
- try {
- valid = keyVaultClient.verifyAsync(masterKeyPath, JsonWebKeySignatureAlgorithm.RS256, dataToVerify, signature).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new SQLServerException(SQLServerException.getErrString("R_VerifySignature"), null);
- }
-
- return valid;
- }
-
- /**
- * Gets the public Key size in bytes
- *
- * @param masterKeyPath - Azure Key Vault Key path
- * @return Key size in bytes
- * @throws SQLServerException when an error occurs
- */
- private int getAKVKeySize(String masterKeyPath) throws SQLServerException
- {
-
- KeyBundle retrievedKey = null;
- try {
- retrievedKey = keyVaultClient.getKeyAsync(masterKeyPath).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new SQLServerException(SQLServerException.getErrString("R_GetAKVKeySize"), null);
- }
-
- if (!retrievedKey.getKey().getKty().equalsIgnoreCase("RSA") &&
- !retrievedKey.getKey().getKty().equalsIgnoreCase("RSA-HSM"))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NonRSAKey"));
- Object[] msgArgs = {retrievedKey.getKey().getKty()};
- throw new SQLServerException(null , form.format(msgArgs) , null, 0 , false);
- }
-
- return retrievedKey.getKey().getN().length;
- }
+public class SQLServerColumnEncryptionAzureKeyVaultProvider extends SQLServerColumnEncryptionKeyStoreProvider {
+
+ /**
+ * Column Encryption Key Store Provider string
+ */
+ String name = "AZURE_KEY_VAULT";
+
+ private final String azureKeyVaultDomainName = "vault.azure.net";
+
+ private final String rsaEncryptionAlgorithmWithOAEPForAKV = "RSA-OAEP";
+
+ /**
+ * Algorithm version
+ */
+ private final byte[] firstVersion = new byte[] {0x01};
+
+ private KeyVaultClient keyVaultClient;
+
+ private KeyVaultCredential credential;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Constructor that takes a callback function to authenticate to AAD. This is used by KeyVaultClient at runtime to authenticate to Azure Key
+ * Vault.
+ *
+ * @param authenticationCallback
+ * - Callback function used for authenticating to AAD.
+ * @param executorService
+ * - The ExecutorService used to create the keyVaultClient
+ * @throws SQLServerException
+ * when an error occurs
+ */
+ public SQLServerColumnEncryptionAzureKeyVaultProvider(SQLServerKeyVaultAuthenticationCallback authenticationCallback,
+ ExecutorService executorService) throws SQLServerException {
+ if (null == authenticationCallback) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NullValue"));
+ Object[] msgArgs1 = {"SQLServerKeyVaultAuthenticationCallback"};
+ throw new SQLServerException(form.format(msgArgs1), null);
+ }
+ credential = new KeyVaultCredential(authenticationCallback);
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ keyVaultClient = new KeyVaultClientImpl(builder, executorService, credential);
+ }
+
+ /**
+ * This function uses the asymmetric key specified by the key path and decrypts an encrypted CEK with RSA encryption algorithm.
+ *
+ * @param masterKeyPath
+ * - Complete path of an asymmetric key in AKV
+ * @param encryptionAlgorithm
+ * - Asymmetric Key Encryption Algorithm
+ * @param encryptedColumnEncryptionKey
+ * - Encrypted Column Encryption Key
+ * @return Plain text column encryption key
+ */
+ @Override
+ public byte[] decryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] encryptedColumnEncryptionKey) throws SQLServerException {
+
+ // Validate the input parameters
+ this.ValidateNonEmptyAKVPath(masterKeyPath);
+
+ if (null == encryptedColumnEncryptionKey) {
+ throw new SQLServerException(SQLServerException.getErrString("R_NullEncryptedColumnEncryptionKey"), null);
+ }
+
+ if (0 == encryptedColumnEncryptionKey.length) {
+ throw new SQLServerException(SQLServerException.getErrString("R_EmptyEncryptedColumnEncryptionKey"), null);
+ }
+
+ // Validate encryptionAlgorithm
+ encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm);
+
+ // Validate whether the key is RSA one or not and then get the key size
+ int keySizeInBytes = getAKVKeySize(masterKeyPath);
+
+ // Validate and decrypt the EncryptedColumnEncryptionKey
+ // Format is
+ // version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
+ //
+ // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and
+ // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
+
+ // Validate the version byte
+ if (encryptedColumnEncryptionKey[0] != firstVersion[0]) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidEcryptionAlgorithmVersion"));
+ Object[] msgArgs = {String.format("%02X ", encryptedColumnEncryptionKey[0]), String.format("%02X ", firstVersion[0])};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+
+ // Get key path length
+ int currentIndex = firstVersion.length;
+ short keyPathLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex);
+ // We just read 2 bytes
+ currentIndex += 2;
+
+ // Get ciphertext length
+ short cipherTextLength = convertTwoBytesToShort(encryptedColumnEncryptionKey, currentIndex);
+ currentIndex += 2;
+
+ // Skip KeyPath
+ // KeyPath exists only for troubleshooting purposes and doesnt need validation.
+ currentIndex += keyPathLength;
+
+ // validate the ciphertext length
+ if (cipherTextLength != keySizeInBytes) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVKeyLengthError"));
+ Object[] msgArgs = {cipherTextLength, keySizeInBytes, masterKeyPath};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+
+ // Validate the signature length
+ int signatureLength = encryptedColumnEncryptionKey.length - currentIndex - cipherTextLength;
+
+ if (signatureLength != keySizeInBytes) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVSignatureLengthError"));
+ Object[] msgArgs = {signatureLength, keySizeInBytes, masterKeyPath};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+
+ // Get ciphertext
+ byte[] cipherText = new byte[cipherTextLength];
+ System.arraycopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength);
+ currentIndex += cipherTextLength;
+
+ // Get signature
+ byte[] signature = new byte[signatureLength];
+ System.arraycopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signatureLength);
+
+ // Compute the hash to validate the signature
+ byte[] hash = new byte[encryptedColumnEncryptionKey.length - signature.length];
+
+ System.arraycopy(encryptedColumnEncryptionKey, 0, hash, 0, encryptedColumnEncryptionKey.length - signature.length);
+
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), null);
+ }
+ md.update(hash);
+ byte dataToVerify[] = md.digest();
+
+ if (null == dataToVerify) {
+ throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null);
+ }
+
+ // Validate the signature
+ if (!AzureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CEKSignatureNotMatchCMK"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+
+ // Decrypt the CEK
+ byte[] decryptedCEK = this.AzureKeyVaultUnWrap(masterKeyPath, encryptionAlgorithm, cipherText);
+
+ return decryptedCEK;
+ }
+
+ private short convertTwoBytesToShort(byte[] input,
+ int index) throws SQLServerException {
+
+ short shortVal = -1;
+ if (index + 1 >= input.length) {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_ByteToShortConversion"), null, 0, false);
+ }
+ ByteBuffer byteBuffer = ByteBuffer.allocate(2);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.put(input[index]);
+ byteBuffer.put(input[index + 1]);
+ shortVal = byteBuffer.getShort(0);
+ return shortVal;
+
+ }
+
+ /**
+ * This function uses the asymmetric key specified by the key path and encrypts CEK with RSA encryption algorithm.
+ *
+ * @param masterKeyPath
+ * - Complete path of an asymmetric key in AKV
+ * @param encryptionAlgorithm
+ * - Asymmetric Key Encryption Algorithm
+ * @param columnEncryptionKey
+ * - Plain text column encryption key
+ * @return Encrypted column encryption key
+ */
+ @Override
+ public byte[] encryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] columnEncryptionKey) throws SQLServerException {
+
+ // Validate the input parameters
+ this.ValidateNonEmptyAKVPath(masterKeyPath);
+
+ if (null == columnEncryptionKey) {
+ throw new SQLServerException(SQLServerException.getErrString("R_NullColumnEncryptionKey"), null);
+ }
+
+ if (0 == columnEncryptionKey.length) {
+ throw new SQLServerException(SQLServerException.getErrString("R_EmptyCEK"), null);
+ }
+
+ // Validate encryptionAlgorithm
+ encryptionAlgorithm = this.validateEncryptionAlgorithm(encryptionAlgorithm);
+
+ // Validate whether the key is RSA one or not and then get the key size
+ int keySizeInBytes = getAKVKeySize(masterKeyPath);
+
+ // Construct the encryptedColumnEncryptionKey
+ // Format is
+ // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
+ //
+ // We currently only support one version
+ byte[] version = new byte[] {firstVersion[0]};
+
+ // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
+ byte[] masterKeyPathBytes = masterKeyPath.toLowerCase().getBytes(UTF_16LE);
+
+ byte[] keyPathLength = new byte[2];
+ keyPathLength[0] = (byte) (((short) masterKeyPathBytes.length) & 0xff);
+ keyPathLength[1] = (byte) (((short) masterKeyPathBytes.length) >> 8 & 0xff);
+
+ // Encrypt the plain text
+ byte[] cipherText = this.AzureKeyVaultWrap(masterKeyPath, encryptionAlgorithm, columnEncryptionKey);
+
+ byte[] cipherTextLength = new byte[2];
+ cipherTextLength[0] = (byte) (((short) cipherText.length) & 0xff);
+ cipherTextLength[1] = (byte) (((short) cipherText.length) >> 8 & 0xff);
+
+ if (cipherText.length != keySizeInBytes) {
+ throw new SQLServerException(SQLServerException.getErrString("R_CipherTextLengthNotMatchRSASize"), null);
+ }
+
+ // Compute hash
+ // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext)
+ byte[] dataToHash = new byte[version.length + keyPathLength.length + cipherTextLength.length + masterKeyPathBytes.length + cipherText.length];
+ int destinationPosition = version.length;
+ System.arraycopy(version, 0, dataToHash, 0, version.length);
+
+ System.arraycopy(keyPathLength, 0, dataToHash, destinationPosition, keyPathLength.length);
+ destinationPosition += keyPathLength.length;
+
+ System.arraycopy(cipherTextLength, 0, dataToHash, destinationPosition, cipherTextLength.length);
+ destinationPosition += cipherTextLength.length;
+
+ System.arraycopy(masterKeyPathBytes, 0, dataToHash, destinationPosition, masterKeyPathBytes.length);
+ destinationPosition += masterKeyPathBytes.length;
+
+ System.arraycopy(cipherText, 0, dataToHash, destinationPosition, cipherText.length);
+
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), null);
+ }
+ md.update(dataToHash);
+ byte dataToSign[] = md.digest();
+
+ // Sign the hash
+ byte[] signedHash = null;
+ signedHash = AzureKeyVaultSignHashedData(dataToSign, masterKeyPath);
+
+ if (signedHash.length != keySizeInBytes) {
+ throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null);
+ }
+
+ if (!this.AzureKeyVaultVerifySignature(dataToSign, signedHash, masterKeyPath)) {
+ throw new SQLServerException(SQLServerException.getErrString("R_InvalidSignatureComputed"), null);
+ }
+
+ // Construct the encrypted column encryption key
+ // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
+ int encryptedColumnEncryptionKeyLength = version.length + cipherTextLength.length + keyPathLength.length + cipherText.length
+ + masterKeyPathBytes.length + signedHash.length;
+ byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
+
+ // Copy version byte
+ int currentIndex = 0;
+ System.arraycopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.length);
+ currentIndex += version.length;
+
+ // Copy key path length
+ System.arraycopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.length);
+ currentIndex += keyPathLength.length;
+
+ // Copy ciphertext length
+ System.arraycopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.length);
+ currentIndex += cipherTextLength.length;
+
+ // Copy key path
+ System.arraycopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.length);
+ currentIndex += masterKeyPathBytes.length;
+
+ // Copy ciphertext
+ System.arraycopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.length);
+ currentIndex += cipherText.length;
+
+ // copy the signature
+ System.arraycopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.length);
+
+ return encryptedColumnEncryptionKey;
+ }
+
+ /**
+ * This function validates that the encryption algorithm is RSA_OAEP and if it is not, then throws an exception
+ *
+ * @param encryptionAlgorithm
+ * - Asymmetric key encryptio algorithm
+ * @return The encryption algorithm that is going to be used.
+ * @throws SQLServerException
+ */
+ private String validateEncryptionAlgorithm(String encryptionAlgorithm) throws SQLServerException {
+
+ if (null == encryptionAlgorithm) {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_NullKeyEncryptionAlgorithm"), null, 0, false);
+ }
+
+ // Transform to standard format (dash instead of underscore) to support both "RSA_OAEP" and "RSA-OAEP"
+ if (encryptionAlgorithm.equalsIgnoreCase("RSA_OAEP")) {
+ encryptionAlgorithm = "RSA-OAEP";
+ }
+
+ if (!rsaEncryptionAlgorithmWithOAEPForAKV.equalsIgnoreCase(encryptionAlgorithm.trim())) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidKeyEncryptionAlgorithm"));
+ Object[] msgArgs = {encryptionAlgorithm, rsaEncryptionAlgorithmWithOAEPForAKV};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+
+ return encryptionAlgorithm;
+ }
+
+ /**
+ * Checks if the Azure Key Vault key path is Empty or Null (and raises exception if they are).
+ *
+ * @param masterKeyPath
+ * @throws SQLServerException
+ */
+ private void ValidateNonEmptyAKVPath(String masterKeyPath) throws SQLServerException {
+ // throw appropriate error if masterKeyPath is null or empty
+ if (null == masterKeyPath || masterKeyPath.trim().isEmpty()) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVPathNull"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
+ }
+ else {
+ URI parsedUri = null;
+ try {
+ parsedUri = new URI(masterKeyPath);
+ }
+ catch (URISyntaxException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVURLInvalid"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
+ }
+
+ // A valid URI.
+ // Check if it is pointing to AKV.
+ if (!parsedUri.getHost().toLowerCase().endsWith(azureKeyVaultDomainName)) {
+ // Return an error indicating that the AKV url is invalid.
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVMasterKeyPathInvalid"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
+ }
+ }
+ }
+
+ /**
+ * Encrypt the text using specified Azure Key Vault key.
+ *
+ * @param masterKeyPath
+ * - Azure Key Vault key url.
+ * @param encryptionAlgorithm
+ * - Encryption Algorithm.
+ * @param columnEncryptionKey
+ * - Plain text Column Encryption Key.
+ * @return Returns an encrypted blob or throws an exception if there are any errors.
+ * @throws SQLServerException
+ */
+ private byte[] AzureKeyVaultWrap(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] columnEncryptionKey) throws SQLServerException {
+ if (null == columnEncryptionKey) {
+ throw new SQLServerException(SQLServerException.getErrString("R_CEKNull"), null);
+ }
+
+ KeyOperationResult wrappedKey = null;
+ try {
+ wrappedKey = keyVaultClient.wrapKeyAsync(masterKeyPath, encryptionAlgorithm, columnEncryptionKey).get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_EncryptCEKError"), null);
+ }
+ return wrappedKey.getResult();
+ }
+
+ /**
+ * Encrypt the text using specified Azure Key Vault key.
+ *
+ * @param masterKeyPath
+ * - Azure Key Vault key url.
+ * @param encryptionAlgorithm
+ * - Encrypted Column Encryption Key.
+ * @param encryptedColumnEncryptionKey
+ * - Encrypted Column Encryption Key.
+ * @return Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.
+ * @throws SQLServerException
+ */
+ private byte[] AzureKeyVaultUnWrap(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] encryptedColumnEncryptionKey) throws SQLServerException {
+ if (null == encryptedColumnEncryptionKey) {
+ throw new SQLServerException(SQLServerException.getErrString("R_EncryptedCEKNull"), null);
+ }
+
+ if (0 == encryptedColumnEncryptionKey.length) {
+ throw new SQLServerException(SQLServerException.getErrString("R_EmptyEncryptedCEK"), null);
+ }
+
+ KeyOperationResult unwrappedKey;
+ try {
+ unwrappedKey = keyVaultClient.unwrapKeyAsync(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey).get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_DecryptCEKError"), null);
+ }
+ return unwrappedKey.getResult();
+ }
+
+ /**
+ * Generates signature based on RSA PKCS#v1.5 scheme using a specified Azure Key Vault Key URL.
+ *
+ * @param dataToSign
+ * - Text to sign.
+ * @param masterKeyPath
+ * - Azure Key Vault key url.
+ * @return Signature
+ * @throws SQLServerException
+ */
+ private byte[] AzureKeyVaultSignHashedData(byte[] dataToSign,
+ String masterKeyPath) throws SQLServerException {
+ assert ((null != dataToSign) && (0 != dataToSign.length));
+
+ KeyOperationResult signedData = null;
+ try {
+ signedData = keyVaultClient.signAsync(masterKeyPath, JsonWebKeySignatureAlgorithm.RS256, dataToSign).get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_GenerateSignature"), null);
+ }
+ return signedData.getResult();
+ }
+
+ /**
+ * Verifies the given RSA PKCSv1.5 signature.
+ *
+ * @param dataToVerify
+ * @param signature
+ * @param masterKeyPath
+ * - Azure Key Vault key url.
+ * @return true if signature is valid, false if it is not valid
+ * @throws SQLServerException
+ */
+ private boolean AzureKeyVaultVerifySignature(byte[] dataToVerify,
+ byte[] signature,
+ String masterKeyPath) throws SQLServerException {
+ assert ((null != dataToVerify) && (0 != dataToVerify.length));
+ assert ((null != signature) && (0 != signature.length));
+
+ boolean valid = false;
+ try {
+ valid = keyVaultClient.verifyAsync(masterKeyPath, JsonWebKeySignatureAlgorithm.RS256, dataToVerify, signature).get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_VerifySignature"), null);
+ }
+
+ return valid;
+ }
+
+ /**
+ * Gets the public Key size in bytes
+ *
+ * @param masterKeyPath
+ * - Azure Key Vault Key path
+ * @return Key size in bytes
+ * @throws SQLServerException
+ * when an error occurs
+ */
+ private int getAKVKeySize(String masterKeyPath) throws SQLServerException {
+
+ KeyBundle retrievedKey = null;
+ try {
+ retrievedKey = keyVaultClient.getKeyAsync(masterKeyPath).get();
+ }
+ catch (InterruptedException | ExecutionException e) {
+ throw new SQLServerException(SQLServerException.getErrString("R_GetAKVKeySize"), null);
+ }
+
+ if (!retrievedKey.getKey().getKty().equalsIgnoreCase("RSA") && !retrievedKey.getKey().getKty().equalsIgnoreCase("RSA-HSM")) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_NonRSAKey"));
+ Object[] msgArgs = {retrievedKey.getKey().getKty()};
+ throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
+ }
+
+ return retrievedKey.getKey().getN().length;
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
index c68f6c7fa..a2b13a443 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerColumnEncryptionCertificateStoreProvider.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
import java.io.File;
@@ -39,266 +28,219 @@
import javax.xml.bind.DatatypeConverter;
/**
- * The implementation of the key store provider for the Windows Certificate Store. This class enables using keys stored in the Windows Certificate Store as column master keys.
+ * The implementation of the key store provider for the Windows Certificate Store. This class enables using keys stored in the Windows Certificate
+ * Store as column master keys.
*
*/
-public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQLServerColumnEncryptionKeyStoreProvider
-{
- static final private java.util.logging.Logger windowsCertificateStoreLogger =
- java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionCertificateStoreProvider");
-
- static boolean isWindows;
-
- String name = "MSSQL_CERTIFICATE_STORE";
-
- static final String localMachineDirectory = "LocalMachine";
- static final String currentUserDirectory = "CurrentUser";
- static final String myCertificateStore = "My";
-
- static
- {
- if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows"))
- {
- isWindows = true;
- }
- else
- {
- isWindows = false;
- }
- }
- private Path keyStoreDirectoryPath = null;
-
- public SQLServerColumnEncryptionCertificateStoreProvider()
- {
- windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(), "SQLServerColumnEncryptionCertificateStoreProvider");
+public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQLServerColumnEncryptionKeyStoreProvider {
+ static final private java.util.logging.Logger windowsCertificateStoreLogger = java.util.logging.Logger
+ .getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionCertificateStoreProvider");
+
+ static boolean isWindows;
+
+ String name = "MSSQL_CERTIFICATE_STORE";
+
+ static final String localMachineDirectory = "LocalMachine";
+ static final String currentUserDirectory = "CurrentUser";
+ static final String myCertificateStore = "My";
+
+ static {
+ if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) {
+ isWindows = true;
+ }
+ else {
+ isWindows = false;
+ }
+ }
+ private Path keyStoreDirectoryPath = null;
+
+ public SQLServerColumnEncryptionCertificateStoreProvider() {
+ windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(),
+ "SQLServerColumnEncryptionCertificateStoreProvider");
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public byte[] encryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] plainTextColumnEncryptionKey) throws SQLServerException {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_InvalidWindowsCertificateStoreEncryption"), null, 0, false);
+ }
+
+ private byte[] decryptColumnEncryptionKeyWindows(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] encryptedColumnEncryptionKey) throws SQLServerException {
+ try {
+ return AuthenticationJNI.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey);
+ }
+ catch (DLLException e) {
+ DLLException.buildException(e.GetErrCode(), e.GetParam1(), e.GetParam2(), e.GetParam3());
+ return null;
+ }
}
-
- public void setName(String name)
- {
- this.name = name;
- }
-
- public String getName()
- {
- return this.name;
- }
-
-
- public byte[] encryptColumnEncryptionKey(
- String masterKeyPath, String encryptionAlgorithm, byte[] plainTextColumnEncryptionKey) throws SQLServerException
- {
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_InvalidWindowsCertificateStoreEncryption"),
- null,
- 0,
- false);
- }
-
- private byte[] decryptColumnEncryptionKeyWindows(
- String masterKeyPath, String encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) throws SQLServerException
- {
- try
- {
- return AuthenticationJNI.DecryptColumnEncryptionKey(
- masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey);
- }
- catch (DLLException e)
- {
- DLLException.buildException(e.GetErrCode(), e.GetParam1(), e.GetParam2(), e.GetParam3());
- return null;
- }
- }
-
- private CertificateDetails getCertificateDetails(String masterKeyPath) throws SQLServerException
- {
+
+ private CertificateDetails getCertificateDetails(String masterKeyPath) throws SQLServerException {
String storeLocation = null;
-
+
String[] certParts = masterKeyPath.split("/");
-
+
// Validate certificate path
// Certificate path should only contain 3 parts (Certificate Location, Certificate Store Name and Thumbprint)
- if (certParts.length > 3)
- {
+ if (certParts.length > 3) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AECertpathBad"));
Object[] msgArgs = {masterKeyPath};
throw new SQLServerException(form.format(msgArgs), null);
}
// Extract the store location where the cert is stored
- if (certParts.length > 2)
- {
- if (certParts[0].equalsIgnoreCase(localMachineDirectory))
- {
+ if (certParts.length > 2) {
+ if (certParts[0].equalsIgnoreCase(localMachineDirectory)) {
storeLocation = localMachineDirectory;
}
- else if (certParts[0].equalsIgnoreCase(currentUserDirectory))
- {
+ else if (certParts[0].equalsIgnoreCase(currentUserDirectory)) {
storeLocation = currentUserDirectory;
}
- else
- {
+ else {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AECertLocBad"));
Object[] msgArgs = {certParts[0], masterKeyPath};
throw new SQLServerException(form.format(msgArgs), null);
}
}
- // Parse the certificate store name. Only store name "My" is supported.
- if (certParts.length > 1)
- {
- if (!certParts[certParts.length - 2].equalsIgnoreCase(myCertificateStore))
- {
+ // Parse the certificate store name. Only store name "My" is supported.
+ if (certParts.length > 1) {
+ if (!certParts[certParts.length - 2].equalsIgnoreCase(myCertificateStore)) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AECertStoreBad"));
Object[] msgArgs = {certParts[certParts.length - 2], masterKeyPath};
throw new SQLServerException(form.format(msgArgs), null);
}
}
-
+
// Get thumpbrint
String thumbprint = certParts[certParts.length - 1];
- if ((null == thumbprint) || (0 == thumbprint.length()))
- {
- // An empty thumbprint specified
+ if ((null == thumbprint) || (0 == thumbprint.length())) {
+ // An empty thumbprint specified
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AECertHashEmpty"));
Object[] msgArgs = {masterKeyPath};
throw new SQLServerException(form.format(msgArgs), null);
}
// Find the certificate and return
- return getCertificateByThumbprint(storeLocation, thumbprint, masterKeyPath);
- }
-
- private String getThumbPrint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException
- {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- byte[] der = cert.getEncoded();
- md.update(der);
- byte[] digest = md.digest();
- return DatatypeConverter.printHexBinary(digest);
- }
-
-
- private CertificateDetails getCertificateByThumbprint(String storeLocation, String thumbprint, String masterKeyPath) throws SQLServerException
- {
+ return getCertificateByThumbprint(storeLocation, thumbprint, masterKeyPath);
+ }
+
+ private String getThumbPrint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] der = cert.getEncoded();
+ md.update(der);
+ byte[] digest = md.digest();
+ return DatatypeConverter.printHexBinary(digest);
+ }
+
+ private CertificateDetails getCertificateByThumbprint(String storeLocation,
+ String thumbprint,
+ String masterKeyPath) throws SQLServerException {
FileInputStream fis = null;
-
- if ((null == keyStoreDirectoryPath))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AEKeyPathEmptyOrReserved"));
- Object[] msgArgs = { keyStoreDirectoryPath };
- throw new SQLServerException(form.format(msgArgs), null);
+
+ if ((null == keyStoreDirectoryPath)) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AEKeyPathEmptyOrReserved"));
+ Object[] msgArgs = {keyStoreDirectoryPath};
+ throw new SQLServerException(form.format(msgArgs), null);
}
-
- Path keyStoreFullPath = keyStoreDirectoryPath.resolve(storeLocation);
+
+ Path keyStoreFullPath = keyStoreDirectoryPath.resolve(storeLocation);
KeyStore keyStore = null;
- try
- {
- keyStore = KeyStore.getInstance("PKCS12");
+ try {
+ keyStore = KeyStore.getInstance("PKCS12");
+ }
+ catch (KeyStoreException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CertificateError"));
+ Object[] msgArgs = {masterKeyPath, name};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
- catch(KeyStoreException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CertificateError"));
- Object[] msgArgs = { masterKeyPath, name };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+
+ File keyStoreDirectory = keyStoreFullPath.toFile();
+ File[] listOfFiles = keyStoreDirectory.listFiles();
+
+ if ((null == listOfFiles) || ((null != listOfFiles) && (0 == listOfFiles.length))) {
+ throw new SQLServerException(SQLServerException.getErrString("R_KeyStoreNotFound"), null);
+ }
+
+ for (File f : listOfFiles) {
+
+ if (f.isDirectory()) {
+ continue;
+ }
+
+ char[] password = "".toCharArray();
+ try {
+ fis = new FileInputStream(f);
+ keyStore.load(fis, password);
+ }
+ catch (IOException | CertificateException | NoSuchAlgorithmException e) {
+ // Cannot parse the current file, continue to the next.
+ continue;
+ }
+
+ // If we are here, we were able to load a PKCS12 file.
+ try {
+ for (Enumeration enumeration = keyStore.aliases(); enumeration.hasMoreElements();) {
+
+ String alias = enumeration.nextElement();
+
+ X509Certificate publicCertificate = (X509Certificate) keyStore.getCertificate(alias);
+
+ if (thumbprint.matches(getThumbPrint(publicCertificate))) {
+ // Found the right certificate
+ Key keyPrivate = null;
+ try {
+ keyPrivate = keyStore.getKey(alias, "".toCharArray());
+ if (null == keyPrivate) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrecoverableKeyAE"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ }
+ catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrecoverableKeyAE"));
+ Object[] msgArgs = {masterKeyPath};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
+ return new CertificateDetails(publicCertificate, keyPrivate);
+ }
+ }// end of for for alias
+ }
+ catch (CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CertificateError"));
+ Object[] msgArgs = {masterKeyPath, name};
+ throw new SQLServerException(form.format(msgArgs), e);
+ }
}
-
- File keyStoreDirectory = keyStoreFullPath.toFile();
- File[] listOfFiles = keyStoreDirectory.listFiles();
-
- if ((null == listOfFiles) ||
- ((null != listOfFiles) && (0 == listOfFiles.length)))
- {
- throw new SQLServerException(SQLServerException.getErrString("R_KeyStoreNotFound"), null);
- }
-
- for(File f : listOfFiles){
-
- if (f.isDirectory())
- {
- continue;
- }
-
- char[] password = "".toCharArray();
- try
- {
- fis = new FileInputStream(f);
- keyStore.load(fis, password);
- }
- catch ( IOException |
- CertificateException |
- NoSuchAlgorithmException e)
- {
- // Cannot parse the current file, continue to the next.
- continue;
- }
-
- // If we are here, we were able to load a PKCS12 file.
- try
- {
- for (Enumeration enumeration = keyStore.aliases(); enumeration.hasMoreElements();)
- {
-
- String alias = enumeration.nextElement();
-
- X509Certificate publicCertificate = (X509Certificate) keyStore.getCertificate(alias);
-
- if (thumbprint.matches(getThumbPrint(publicCertificate)))
- {
- // Found the right certificate
- Key keyPrivate = null;
- try
- {
- keyPrivate = keyStore.getKey(
- alias,
- "".toCharArray());
- if (null == keyPrivate)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrecoverableKeyAE"));
- Object[] msgArgs = { masterKeyPath };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- }
- catch(UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrecoverableKeyAE"));
- Object[] msgArgs = { masterKeyPath };
- throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
- }
- return new CertificateDetails(publicCertificate, keyPrivate);
- }
- }// end of for for alias
- }
- catch (CertificateException |
- NoSuchAlgorithmException |
- KeyStoreException e)
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_CertificateError"));
- Object[] msgArgs = { masterKeyPath, name};
- throw new SQLServerException(form.format(msgArgs), e);
- }
- }
- // Looped over all files, haven't found the certificate
- throw new SQLServerException(SQLServerException.getErrString("R_KeyStoreNotFound"), null);
+ // Looped over all files, haven't found the certificate
+ throw new SQLServerException(SQLServerException.getErrString("R_KeyStoreNotFound"), null);
}
-
-
- public byte[] decryptColumnEncryptionKey(
- String masterKeyPath, String encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) throws SQLServerException
- {
- windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(), "decryptColumnEncryptionKey", "Decrypting Column Encryption Key.");
- byte[] plainCek = null;
- if (isWindows)
- {
- plainCek = decryptColumnEncryptionKeyWindows(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey);
- }
- else
- {
- throw new SQLServerException(SQLServerException.getErrString("R_notSupported"), null);
- }
- windowsCertificateStoreLogger.exiting(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(), "decryptColumnEncryptionKey", "Finished decrypting Column Encryption Key.");
+
+ public byte[] decryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] encryptedColumnEncryptionKey) throws SQLServerException {
+ windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(), "decryptColumnEncryptionKey",
+ "Decrypting Column Encryption Key.");
+ byte[] plainCek = null;
+ if (isWindows) {
+ plainCek = decryptColumnEncryptionKeyWindows(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey);
+ }
+ else {
+ throw new SQLServerException(SQLServerException.getErrString("R_notSupported"), null);
+ }
+ windowsCertificateStoreLogger.exiting(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(), "decryptColumnEncryptionKey",
+ "Finished decrypting Column Encryption Key.");
return plainCek;
-}
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java
index f9f3ec886..4de4f19b1 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionJavaKeyStoreProvider.java
@@ -1,24 +1,12 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerColumnEncryptionJavaKeyStoreProvider.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
-package com.microsoft.sqlserver.jdbc;
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+package com.microsoft.sqlserver.jdbc;
import static java.nio.charset.StandardCharsets.UTF_16LE;
@@ -47,332 +35,294 @@
/**
*
- * The implementation of the key store provider for Java Key Store. This class enables using certificates stored in the Java keystore as column master keys.
+ * The implementation of the key store provider for Java Key Store. This class enables using certificates stored in the Java keystore as column master
+ * keys.
*
*/
-public class SQLServerColumnEncryptionJavaKeyStoreProvider extends SQLServerColumnEncryptionKeyStoreProvider
-{
- String name = "MSSQL_JAVA_KEYSTORE";
- String keyStorePath = null;
- char[] keyStorePwd = null;
-
- static final private java.util.logging.Logger javaKeyStoreLogger =
- java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider");
-
- public void setName(String name)
- {
- this.name = name;
- }
-
- public String getName()
- {
- return this.name;
- }
-
- /**
+public class SQLServerColumnEncryptionJavaKeyStoreProvider extends SQLServerColumnEncryptionKeyStoreProvider {
+ String name = "MSSQL_JAVA_KEYSTORE";
+ String keyStorePath = null;
+ char[] keyStorePwd = null;
+
+ static final private java.util.logging.Logger javaKeyStoreLogger = java.util.logging.Logger
+ .getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider");
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ /**
* Key store provider for the Java Key Store.
- * @param keyStoreLocation specifies the location of the keystore
- * @param keyStoreSecret specifies the secret used for keystore
- * @throws SQLServerException when an error occurs
+ *
+ * @param keyStoreLocation
+ * specifies the location of the keystore
+ * @param keyStoreSecret
+ * specifies the secret used for keystore
+ * @throws SQLServerException
+ * when an error occurs
*/
- public SQLServerColumnEncryptionJavaKeyStoreProvider(String keyStoreLocation, char[] keyStoreSecret) throws SQLServerException
- {
- javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "SQLServerColumnEncryptionJavaKeyStoreProvider");
+ public SQLServerColumnEncryptionJavaKeyStoreProvider(String keyStoreLocation,
+ char[] keyStoreSecret) throws SQLServerException {
+ javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "SQLServerColumnEncryptionJavaKeyStoreProvider");
- if ((null == keyStoreLocation) || (0 == keyStoreLocation.length()))
- {
+ if ((null == keyStoreLocation) || (0 == keyStoreLocation.length())) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidConnectionSetting"));
Object[] msgArgs = {"keyStoreLocation", keyStoreLocation};
throw new SQLServerException(form.format(msgArgs), null);
- }
-
- this.keyStorePath = keyStoreLocation;
-
- if (javaKeyStoreLogger.isLoggable(java.util.logging.Level.FINE)){
- javaKeyStoreLogger.fine("Path of key store provider is set.");
- }
-
- // Password can be null or empty, PKCS12 type allows that.
- if (null == keyStoreSecret)
- {
- keyStoreSecret = "".toCharArray();
- }
-
- this.keyStorePwd = new char[keyStoreSecret.length];
- System.arraycopy(keyStoreSecret, 0, this.keyStorePwd, 0, keyStoreSecret.length);
-
- if (javaKeyStoreLogger.isLoggable(java.util.logging.Level.FINE)){
- javaKeyStoreLogger.fine("Password for key store provider is set.");
- }
-
- javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "SQLServerColumnEncryptionJavaKeyStoreProvider");
+ }
+
+ this.keyStorePath = keyStoreLocation;
+
+ if (javaKeyStoreLogger.isLoggable(java.util.logging.Level.FINE)) {
+ javaKeyStoreLogger.fine("Path of key store provider is set.");
+ }
+
+ // Password can be null or empty, PKCS12 type allows that.
+ if (null == keyStoreSecret) {
+ keyStoreSecret = "".toCharArray();
+ }
+
+ this.keyStorePwd = new char[keyStoreSecret.length];
+ System.arraycopy(keyStoreSecret, 0, this.keyStorePwd, 0, keyStoreSecret.length);
+
+ if (javaKeyStoreLogger.isLoggable(java.util.logging.Level.FINE)) {
+ javaKeyStoreLogger.fine("Password for key store provider is set.");
+ }
+
+ javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "SQLServerColumnEncryptionJavaKeyStoreProvider");
}
-
+
@Override
- public byte[] decryptColumnEncryptionKey(
- String masterKeyPath,
- String encryptionAlgorithm,
- byte[] encryptedColumnEncryptionKey) throws SQLServerException
- {
- javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "decryptColumnEncryptionKey", "Decrypting Column Encryption Key.");
-
- KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
+ public byte[] decryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] encryptedColumnEncryptionKey) throws SQLServerException {
+ javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "decryptColumnEncryptionKey",
+ "Decrypting Column Encryption Key.");
+
+ KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
- byte[] plainCEK = KeyStoreProviderCommon.decryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey, certificateDetails);
-
- javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "decryptColumnEncryptionKey", "Finished decrypting Column Encryption Key.");
+ byte[] plainCEK = KeyStoreProviderCommon.decryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey,
+ certificateDetails);
+
+ javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), "decryptColumnEncryptionKey",
+ "Finished decrypting Column Encryption Key.");
return plainCEK;
}
-
-
- private CertificateDetails getCertificateDetails(String masterKeyPath) throws SQLServerException
- {
+ private CertificateDetails getCertificateDetails(String masterKeyPath) throws SQLServerException {
FileInputStream fis = null;
KeyStore keyStore = null;
- CertificateDetails certificateDetails=null;
-
- try
- {
- if (null == masterKeyPath || 0 == masterKeyPath.length())
- {
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_InvalidMasterKeyDetails"),
- null,
- 0,
- false);
+ CertificateDetails certificateDetails = null;
+
+ try {
+ if (null == masterKeyPath || 0 == masterKeyPath.length()) {
+ throw new SQLServerException(null, SQLServerException.getErrString("R_InvalidMasterKeyDetails"), null, 0, false);
}
- try
- {
+ try {
// Try to load JKS first, if fails try PKCS12
- keyStore = KeyStore.getInstance("JKS");
- fis = new FileInputStream(keyStorePath);
- keyStore.load(fis, keyStorePwd);
+ keyStore = KeyStore.getInstance("JKS");
+ fis = new FileInputStream(keyStorePath);
+ keyStore.load(fis, keyStorePwd);
}
- catch(IOException e)
- {
- if (null != fis) fis.close();
-
- // Loading as JKS failed, try to load as PKCS12
- keyStore = KeyStore.getInstance("PKCS12");
- fis = new FileInputStream(keyStorePath);
- keyStore.load(fis, keyStorePwd);
+ catch (IOException e) {
+ if (null != fis)
+ fis.close();
+
+ // Loading as JKS failed, try to load as PKCS12
+ keyStore = KeyStore.getInstance("PKCS12");
+ fis = new FileInputStream(keyStorePath);
+ keyStore.load(fis, keyStorePwd);
}
-
- certificateDetails=getCertificateDetailsByAlias(keyStore, masterKeyPath);
- }
- catch(FileNotFoundException fileNotFound)
- {
- throw new SQLServerException(this, SQLServerException.getErrString("R_KeyStoreNotFound"), null, 0, false);
- }
- catch ( IOException |
- CertificateException |
- NoSuchAlgorithmException |
- KeyStoreException e)
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_invalidKeyStoreFile"));
+
+ certificateDetails = getCertificateDetailsByAlias(keyStore, masterKeyPath);
+ }
+ catch (FileNotFoundException fileNotFound) {
+ throw new SQLServerException(this, SQLServerException.getErrString("R_KeyStoreNotFound"), null, 0, false);
+ }
+ catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidKeyStoreFile"));
Object[] msgArgs = {keyStorePath};
throw new SQLServerException(form.format(msgArgs), e);
}
- finally
- {
- try
- {
- if(null != fis) fis.close();
- }
- // Ignore the exception as we are cleaning up.
- catch(IOException e){}
+ finally {
+ try {
+ if (null != fis)
+ fis.close();
+ }
+ // Ignore the exception as we are cleaning up.
+ catch (IOException e) {
+ }
}
-
+
return certificateDetails;
}
-
- private CertificateDetails getCertificateDetailsByAlias(
- KeyStore keyStore,
- String alias) throws SQLServerException
- {
- try
- {
+
+ private CertificateDetails getCertificateDetailsByAlias(KeyStore keyStore,
+ String alias) throws SQLServerException {
+ try {
X509Certificate publicCertificate = (X509Certificate) keyStore.getCertificate(alias);
- Key keyPrivate = keyStore.getKey(alias,keyStorePwd);
- if (null == publicCertificate )
- {
+ Key keyPrivate = keyStore.getKey(alias, keyStorePwd);
+ if (null == publicCertificate) {
// Certificate not found. Throw an exception.
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_CertificateNotFoundForAlias"));
- Object[] msgArgs = { alias, "MSSQL_JAVA_KEYSTORE" };
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CertificateNotFoundForAlias"));
+ Object[] msgArgs = {alias, "MSSQL_JAVA_KEYSTORE"};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
-
- // found certificate but corresponding private key not found, throw exception
- if (null == keyPrivate) {
- throw new UnrecoverableKeyException();
- }
-
+
+ // found certificate but corresponding private key not found, throw exception
+ if (null == keyPrivate) {
+ throw new UnrecoverableKeyException();
+ }
+
return new CertificateDetails(publicCertificate, keyPrivate);
- }
+ }
catch (UnrecoverableKeyException unrecoverableKeyException) {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_UnrecoverableKeyAE"));
- Object[] msgArgs = { alias };
- throw new SQLServerException(this, form.format(msgArgs), null, 0,false);
- }
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrecoverableKeyAE"));
+ Object[] msgArgs = {alias};
+ throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
+ }
catch (NoSuchAlgorithmException | KeyStoreException e) {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_CertificateError"));
- Object[] msgArgs = { alias, name };
- throw new SQLServerException(form.format(msgArgs), e);
- }
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CertificateError"));
+ Object[] msgArgs = {alias, name};
+ throw new SQLServerException(form.format(msgArgs), e);
+ }
}
@Override
public byte[] encryptColumnEncryptionKey(String masterKeyPath,
- String encryptionAlgorithm, byte[] plainTextColumnEncryptionKey) throws SQLServerException
- {
- javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(), "Encrypting Column Encryption Key.");
-
- byte[] version = KeyStoreProviderCommon.version;
+ String encryptionAlgorithm,
+ byte[] plainTextColumnEncryptionKey) throws SQLServerException {
+ javaKeyStoreLogger.entering(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
+ Thread.currentThread().getStackTrace()[1].getMethodName(), "Encrypting Column Encryption Key.");
+
+ byte[] version = KeyStoreProviderCommon.version;
KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);
-
- if(null == plainTextColumnEncryptionKey){
-
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_NullColumnEncryptionKey"),
- null,
- 0,
- false);
-
+
+ if (null == plainTextColumnEncryptionKey) {
+
+ throw new SQLServerException(null, SQLServerException.getErrString("R_NullColumnEncryptionKey"), null, 0, false);
+
}
- else if (0==plainTextColumnEncryptionKey.length) {
-
- throw new SQLServerException(
- null,
- SQLServerException.getErrString("R_EmptyColumnEncryptionKey"),
- null,
- 0,
- false);
-
+ else if (0 == plainTextColumnEncryptionKey.length) {
+
+ throw new SQLServerException(null, SQLServerException.getErrString("R_EmptyColumnEncryptionKey"), null, 0, false);
+
}
-
- KeyStoreProviderCommon.validateEncryptionAlgorithm(encryptionAlgorithm, true);
-
- CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
- byte [] cipherText=encryptRSAOAEP(plainTextColumnEncryptionKey, certificateDetails);
- byte[] cipherTextLength=getLittleEndianBytesFromShort((short)cipherText.length);
- byte[] masterKeyPathBytes = masterKeyPath.toLowerCase().getBytes(UTF_16LE);
-
- byte[] keyPathLength=getLittleEndianBytesFromShort((short)masterKeyPathBytes.length);
-
- byte [] dataToSign=new byte[version.length+keyPathLength.length+cipherTextLength.length+masterKeyPathBytes.length+cipherText.length];
- int destinationPosition=version.length;
- System.arraycopy(version, 0, dataToSign, 0, version.length);
-
- System.arraycopy(keyPathLength, 0, dataToSign, destinationPosition, keyPathLength.length);
- destinationPosition+=keyPathLength.length;
-
- System.arraycopy(cipherTextLength, 0, dataToSign, destinationPosition, cipherTextLength.length);
- destinationPosition+=cipherTextLength.length;
-
- System.arraycopy(masterKeyPathBytes, 0, dataToSign, destinationPosition, masterKeyPathBytes.length);
- destinationPosition+=masterKeyPathBytes.length;
-
- System.arraycopy(cipherText, 0, dataToSign, destinationPosition, cipherText.length);
- byte[] signedHash = rsaSignHashedData(dataToSign, certificateDetails);
-
- int encryptedColumnEncryptionKeyLength = version.length + cipherTextLength.length + keyPathLength.length + cipherText.length + masterKeyPathBytes.length + signedHash.length;
- byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
-
- int currentIndex = 0;
- System.arraycopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.length);
- currentIndex += version.length;
-
- System.arraycopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.length);
- currentIndex += keyPathLength.length;
-
- System.arraycopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.length);
- currentIndex += cipherTextLength.length;
-
- System.arraycopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.length);
- currentIndex += masterKeyPathBytes.length;
-
- System.arraycopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.length);
- currentIndex += cipherText.length;
-
- System.arraycopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.length);
-
- javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(), Thread.currentThread().getStackTrace()[1].getMethodName(), "Finished encrypting Column Encryption Key.");
- return encryptedColumnEncryptionKey;
-
+
+ KeyStoreProviderCommon.validateEncryptionAlgorithm(encryptionAlgorithm, true);
+
+ CertificateDetails certificateDetails = getCertificateDetails(masterKeyPath);
+ byte[] cipherText = encryptRSAOAEP(plainTextColumnEncryptionKey, certificateDetails);
+ byte[] cipherTextLength = getLittleEndianBytesFromShort((short) cipherText.length);
+ byte[] masterKeyPathBytes = masterKeyPath.toLowerCase().getBytes(UTF_16LE);
+
+ byte[] keyPathLength = getLittleEndianBytesFromShort((short) masterKeyPathBytes.length);
+
+ byte[] dataToSign = new byte[version.length + keyPathLength.length + cipherTextLength.length + masterKeyPathBytes.length + cipherText.length];
+ int destinationPosition = version.length;
+ System.arraycopy(version, 0, dataToSign, 0, version.length);
+
+ System.arraycopy(keyPathLength, 0, dataToSign, destinationPosition, keyPathLength.length);
+ destinationPosition += keyPathLength.length;
+
+ System.arraycopy(cipherTextLength, 0, dataToSign, destinationPosition, cipherTextLength.length);
+ destinationPosition += cipherTextLength.length;
+
+ System.arraycopy(masterKeyPathBytes, 0, dataToSign, destinationPosition, masterKeyPathBytes.length);
+ destinationPosition += masterKeyPathBytes.length;
+
+ System.arraycopy(cipherText, 0, dataToSign, destinationPosition, cipherText.length);
+ byte[] signedHash = rsaSignHashedData(dataToSign, certificateDetails);
+
+ int encryptedColumnEncryptionKeyLength = version.length + cipherTextLength.length + keyPathLength.length + cipherText.length
+ + masterKeyPathBytes.length + signedHash.length;
+ byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
+
+ int currentIndex = 0;
+ System.arraycopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.length);
+ currentIndex += version.length;
+
+ System.arraycopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.length);
+ currentIndex += keyPathLength.length;
+
+ System.arraycopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.length);
+ currentIndex += cipherTextLength.length;
+
+ System.arraycopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.length);
+ currentIndex += masterKeyPathBytes.length;
+
+ System.arraycopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.length);
+ currentIndex += cipherText.length;
+
+ System.arraycopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.length);
+
+ javaKeyStoreLogger.exiting(SQLServerColumnEncryptionJavaKeyStoreProvider.class.getName(),
+ Thread.currentThread().getStackTrace()[1].getMethodName(), "Finished encrypting Column Encryption Key.");
+ return encryptedColumnEncryptionKey;
+
}
-
+
/**
* Encrypt plainText with the certificate provided
- * @param plainText plain CEK to be encrypted
- * @param certificateDetails
+ *
+ * @param plainText
+ * plain CEK to be encrypted
+ * @param certificateDetails
* @return encrypted CEK
- * @throws SQLServerException
+ * @throws SQLServerException
*/
- private byte[] encryptRSAOAEP(byte[] plainText, CertificateDetails certificateDetails) throws SQLServerException
- {
+ private byte[] encryptRSAOAEP(byte[] plainText,
+ CertificateDetails certificateDetails) throws SQLServerException {
byte[] cipherText = null;
- try
- {
- Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
+ try {
+ Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
rsa.init(Cipher.ENCRYPT_MODE, certificateDetails.certificate.getPublicKey());
rsa.update(plainText);
cipherText = rsa.doFinal();
}
- catch (InvalidKeyException |
- NoSuchAlgorithmException |
- IllegalBlockSizeException |
- NoSuchPaddingException |
- BadPaddingException e)
- {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_EncryptionFailed"));
- Object[] msgArgs = { e.getMessage() };
+ catch (InvalidKeyException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | BadPaddingException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
+ Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
}
return cipherText;
}
-
- private byte [] rsaSignHashedData(byte[] dataToSign, CertificateDetails certificateDetails) throws SQLServerException{
- Signature signature;
- byte [] signedHash=null;
-
- try {
- signature= Signature.getInstance("SHA256withRSA");
- signature.initSign((PrivateKey)certificateDetails.privateKey);
- signature.update(dataToSign);
- signedHash=signature.sign();
- }catch (InvalidKeyException | NoSuchAlgorithmException| SignatureException e) {
- MessageFormat form = new MessageFormat(
- SQLServerException.getErrString("R_EncryptionFailed"));
- Object[] msgArgs = { e.getMessage() };
+
+ private byte[] rsaSignHashedData(byte[] dataToSign,
+ CertificateDetails certificateDetails) throws SQLServerException {
+ Signature signature;
+ byte[] signedHash = null;
+
+ try {
+ signature = Signature.getInstance("SHA256withRSA");
+ signature.initSign((PrivateKey) certificateDetails.privateKey);
+ signature.update(dataToSign);
+ signedHash = signature.sign();
+ }
+ catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
+ MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
+ Object[] msgArgs = {e.getMessage()};
throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
-
- }
- return signedHash;
-
-
+
+ }
+ return signedHash;
+
}
-
-
-
-
+
private byte[] getLittleEndianBytesFromShort(short value) {
- ByteBuffer byteBuffer = ByteBuffer.allocate(2);
- byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
- byte[] byteValue = byteBuffer.putShort(value).array();
- return byteValue;
+ ByteBuffer byteBuffer = ByteBuffer.allocate(2);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ byte[] byteValue = byteBuffer.putShort(value).array();
+ return byteValue;
- }
+ }
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionKeyStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionKeyStoreProvider.java
index 094b93132..705f9d3bc 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionKeyStoreProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionKeyStoreProvider.java
@@ -1,22 +1,11 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerColumnEncryptionKeyStoreProvider.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
/**
@@ -25,38 +14,56 @@
*
*/
public abstract class SQLServerColumnEncryptionKeyStoreProvider {
-
- /**
- * Sets the name of this key store provider.
- * @param name value to be set for the key store provider.
- */
- public abstract void setName(String name);
-
- /**
- * Retrieves the name of this key store provider.
- * @return the name of this key store provider.
- */
- public abstract String getName();
-
- /**
- * Base class method for decrypting the specified encrypted value of a column encryption key.
- * The encrypted value is expected to be encrypted using the column master key with the specified key path and using the specified algorithm.
- * @param masterKeyPath The column master key path.
- * @param encryptionAlgorithm the specific encryption algorithm.
- * @param encryptedColumnEncryptionKey the encrypted column encryption key
- * @return the decrypted value of column encryption key.
- * @throws SQLServerException when an error occurs while decrypting the CEK
- */
- public abstract byte [] decryptColumnEncryptionKey(String masterKeyPath,String encryptionAlgorithm,byte [] encryptedColumnEncryptionKey) throws SQLServerException;
-
- /**
- * Base class method for encrypting a column encryption key using the column master key with the specified key path and using the specified algorithm.
- * @param masterKeyPath The column master key path.
- * @param encryptionAlgorithm the specific encryption algorithm.
- * @param columnEncryptionKey column encryption key to be encrypted.
- * @return the encrypted column encryption key.
- * @throws SQLServerException when an error occurs while encrypting the CEK
- */
- public abstract byte [] encryptColumnEncryptionKey(String masterKeyPath,String encryptionAlgorithm,byte [] columnEncryptionKey) throws SQLServerException;
+
+ /**
+ * Sets the name of this key store provider.
+ *
+ * @param name
+ * value to be set for the key store provider.
+ */
+ public abstract void setName(String name);
+
+ /**
+ * Retrieves the name of this key store provider.
+ *
+ * @return the name of this key store provider.
+ */
+ public abstract String getName();
+
+ /**
+ * Base class method for decrypting the specified encrypted value of a column encryption key. The encrypted value is expected to be encrypted
+ * using the column master key with the specified key path and using the specified algorithm.
+ *
+ * @param masterKeyPath
+ * The column master key path.
+ * @param encryptionAlgorithm
+ * the specific encryption algorithm.
+ * @param encryptedColumnEncryptionKey
+ * the encrypted column encryption key
+ * @return the decrypted value of column encryption key.
+ * @throws SQLServerException
+ * when an error occurs while decrypting the CEK
+ */
+ public abstract byte[] decryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] encryptedColumnEncryptionKey) throws SQLServerException;
+
+ /**
+ * Base class method for encrypting a column encryption key using the column master key with the specified key path and using the specified
+ * algorithm.
+ *
+ * @param masterKeyPath
+ * The column master key path.
+ * @param encryptionAlgorithm
+ * the specific encryption algorithm.
+ * @param columnEncryptionKey
+ * column encryption key to be encrypted.
+ * @return the encrypted column encryption key.
+ * @throws SQLServerException
+ * when an error occurs while encrypting the CEK
+ */
+ public abstract byte[] encryptColumnEncryptionKey(String masterKeyPath,
+ String encryptionAlgorithm,
+ byte[] columnEncryptionKey) throws SQLServerException;
}
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
index 217746a99..ff051241f 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
@@ -1,23 +1,13 @@
-//---------------------------------------------------------------------------------------------------------------------------------
-// File: SQLServerConnection.java
-//
-//
-// Microsoft JDBC Driver for SQL Server
-// Copyright(c) Microsoft Corporation
-// All rights reserved.
-// MIT License
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"),
-// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
-// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-//---------------------------------------------------------------------------------------------------------------------------------
-
-
+/*
+ * Microsoft JDBC Driver for SQL Server
+ *
+ * Copyright(c) Microsoft Corporation All rights reserved.
+ *
+ * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
+ */
+
package com.microsoft.sqlserver.jdbc;
+
import static java.nio.charset.StandardCharsets.UTF_16LE;
import java.io.IOException;
@@ -64,5594 +54,5121 @@
import javax.xml.bind.DatatypeConverter;
/**
- * SQLServerConnection implements a JDBC connection to SQL Server.
- * SQLServerConnections support JDBC connection pooling and may be either physical JDBC connections
- * or logical JDBC connections.
+ * SQLServerConnection implements a JDBC connection to SQL Server. SQLServerConnections support JDBC connection pooling and may be either physical
+ * JDBC connections or logical JDBC connections.
*
- * SQLServerConnection manages transaction control for all statements that were created from it.
- * SQLServerConnection may participate in XA distributed transactions managed via an XAResource adapter.
+ * SQLServerConnection manages transaction control for all statements that were created from it. SQLServerConnection may participate in XA distributed
+ * transactions managed via an XAResource adapter.
*
- * SQLServerConnection instantiates a new TDSChannel object for use by itself and all statement objects that
- * are created under this connection. SQLServerConnection is thread safe.
+ * SQLServerConnection instantiates a new TDSChannel object for use by itself and all statement objects that are created under this connection.
+ * SQLServerConnection is thread safe.
*
- * SQLServerConnection manages a pool of prepared statement handles. Prepared statements are prepared
- * once and typically executed many times with different data values for their parameters.
- * Prepared statements are also maintained across logical (pooled) connection closes.
+ * SQLServerConnection manages a pool of prepared statement handles. Prepared statements are prepared once and typically executed many times with
+ * different data values for their parameters. Prepared statements are also maintained across logical (pooled) connection closes.
*
- * SQLServerConnection is not thread safe, however multiple statements created from a single
- * connection can be processing simultaneously in concurrent threads.
+ * SQLServerConnection is not thread safe, however multiple statements created from a single connection can be processing simultaneously in concurrent
+ * threads.
*
* This class's public functions need to be kept identical to the SQLServerConnectionPoolProxy's.
*
- * The API javadoc for JDBC API methods that this class implements are not repeated here. Please
- * see Sun's JDBC API interfaces javadoc for those details.
+ * The API javadoc for JDBC API methods that this class implements are not repeated here. Please see Sun's JDBC API interfaces javadoc for those
+ * details.
*/
// Note all the public functions in this class also need to be defined in SQLServerConnectionPoolProxy.
-public class SQLServerConnection implements ISQLServerConnection
-{
- long timerExpire;
- boolean attemptRefreshTokenLocked = false;
-
- private boolean fedAuthRequiredByUser = false;
- private boolean fedAuthRequiredPreLoginResponse = false;
- private boolean federatedAuthenticationAcknowledged = false;
- private boolean federatedAuthenticationRequested = false;
- private boolean federatedAuthenticationInfoRequested = false; // Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more info
-
- private FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData = null;
- private String authenticationString = null;
- private byte[] accessTokenInByte = null;
-
- private SqlFedAuthToken fedAuthToken = null;
-
- SqlFedAuthToken getAuthenticationResult(){
- return fedAuthToken;
- }
-
- /**
- * Struct encapsulating the data to be sent to the server as part of
- * Federated Authentication Feature Extension.
- */
- class FederatedAuthenticationFeatureExtensionData{
- boolean fedAuthRequiredPreLoginResponse;
- int libraryType = -1;
- byte[] accessToken = null;
- SqlAuthentication authentication = null;
-
- FederatedAuthenticationFeatureExtensionData(int libraryType, String authenticationString, boolean fedAuthRequiredPreLoginResponse) throws SQLServerException{
- this.libraryType = libraryType;
- this.fedAuthRequiredPreLoginResponse = fedAuthRequiredPreLoginResponse;
-
- switch(authenticationString.toUpperCase(Locale.ENGLISH).trim()){
- case "ACTIVEDIRECTORYPASSWORD":
- this.authentication = SqlAuthentication.ActiveDirectoryPassword;
- break;
- case "ACTIVEDIRECTORYINTEGRATED":
- this.authentication = SqlAuthentication.ActiveDirectoryIntegrated;
- break;
- default:
- assert(false);
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidConnectionSetting"));
- Object[] msgArgs = {"authentication", authenticationString};
- throw new SQLServerException(null , form.format(msgArgs) , null, 0 , false);
- }
- }
-
- FederatedAuthenticationFeatureExtensionData(int libraryType, boolean fedAuthRequiredPreLoginResponse, byte[] accessToken){
- this.libraryType = libraryType;
- this.fedAuthRequiredPreLoginResponse = fedAuthRequiredPreLoginResponse;
- this.accessToken = accessToken;
- }
- }
-
- class SqlFedAuthInfo {
- private String spn;
- private String stsurl;
-
- @Override
- public String toString() {
- return "STSURL: " + stsurl + ", SPN: " + spn;
- }
- }
-
- final class SqlFedAuthToken {
- private final Date expiresOn;
- private final String accessToken;
-
- SqlFedAuthToken(final String accessToken, final long expiresIn) {
- this.accessToken = accessToken;
-
- Date now = new Date();
- now.setTime(now.getTime() + (expiresIn * 1000));
- this.expiresOn = now;
- }
-
- Date getExpiresOnDate() {
- return expiresOn;
- }
- }
-
- private class ActiveDirectoryAuthentication
- {
- private static final String jdbcFedauthClientId = "7f98cb04-cd1e-40df-9140-3bf7e2cea4db";
- private static final String AdalGetAccessTokenFunctionName = "ADALGetAccessToken";
- private static final int GetAccessTokenSuccess = 0;
- private static final int GetAccessTokenInvalidGrant = 1;
- private static final int GetAccessTokenTansisentError = 2;
- private static final int GetAccessTokenOtherError = 3;
- }
-
- /**
- * denotes the state of the SqlServerConnection
- */
- private enum State
- {
- Initialized,//default value on calling SQLServerConnection constructor
- Connected, //indicates that the TCP connection has completed
- Opened, //indicates that the prelogin, login have completed, the database session established and the connection is ready for use.
- Closed //indicates that the connection has been closed.
- }
-
- private final static float TIMEOUTSTEP = 0.08F; // fraction of timeout to use for fast failover connections
- final static int TnirFirstAttemptTimeoutMs = 500; // fraction of timeout to use for fast failover connections
-
- /* Connection state variables.
- NB If new state is added then logical connections derived from a physical connection must
- inherit the same state. If state variables are added they must be added also in connection
- cloning method clone()
- */
-
- private final static int INTERMITTENT_TLS_MAX_RETRY = 5;
-
- //Indicates if we received a routing ENVCHANGE in the current connection attempt
- private boolean isRoutedInCurrentAttempt = false;
-
- //Contains the routing info received from routing ENVCHANGE
- private ServerPortPlaceHolder routingInfo = null;
-
- ServerPortPlaceHolder getRoutingInfo(){
- return routingInfo;
- }
-
- // Permission targets
- // currently only callAbort is implemented
- private static final String callAbortPerm = "callAbort";
-
- private boolean sendStringParametersAsUnicode = SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.getDefaultValue(); //see connection properties doc (default is false).
- boolean sendStringParametersAsUnicode() { return sendStringParametersAsUnicode; }
- private boolean lastUpdateCount; //see connection properties doc
- final boolean useLastUpdateCount() { return lastUpdateCount; }
-
- // Translates the serverName from Unicode to ASCII Compatible Encoding (ACE), as defined by the ToASCII operation of RFC 3490
- private boolean serverNameAsACE = SQLServerDriverBooleanProperty.SERVER_NAME_AS_ACE.getDefaultValue();
- boolean serverNameAsACE() { return serverNameAsACE; }
-
- //see feature_connection_director_multi_subnet_JDBC.docx
- private boolean multiSubnetFailover;
- final boolean getMultiSubnetFailover()
- {
- return multiSubnetFailover;
- }
-
- private boolean transparentNetworkIPResolution;
- final boolean getTransparentNetworkIPResolution()
- {
- return transparentNetworkIPResolution;
- }
-
-
- private ApplicationIntent applicationIntent = null;
- final ApplicationIntent getApplicationIntent()
- {
- return applicationIntent;
- }
-
- private int nLockTimeout; //see connection properties doc
- private String selectMethod; //see connection properties doc 4.0 new property
- final String getSelectMethod() { return selectMethod; }
- private String responseBuffering;
- final String getResponseBuffering() { return responseBuffering; }
- private int queryTimeoutSeconds ;
- final int getQueryTimeoutSeconds() { return queryTimeoutSeconds; }
- private int socketTimeoutMilliseconds ;
- final int getSocketTimeoutMilliseconds() { return socketTimeoutMilliseconds; }
-
- private boolean sendTimeAsDatetime = SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue();
-
- /**
- * Checks the sendTimeAsDatetime property.
- * @return boolean value of sendTimeAsDatetime
- */
- public synchronized final boolean getSendTimeAsDatetime()
- {
- return !isKatmaiOrLater() || sendTimeAsDatetime;
- }
-
- final int baseYear()
- {
- return getSendTimeAsDatetime() ? TDS.BASE_YEAR_1970 : TDS.BASE_YEAR_1900;
- }
-
- private byte requestedEncryptionLevel = TDS.ENCRYPT_INVALID;
- final byte getRequestedEncryptionLevel()
- {
- assert TDS.ENCRYPT_INVALID != requestedEncryptionLevel;
- return requestedEncryptionLevel;
- }
-
-
- private boolean trustServerCertificate;
- final boolean trustServerCertificate() { return trustServerCertificate; }
-
- private byte negotiatedEncryptionLevel = TDS.ENCRYPT_INVALID;
- final byte getNegotiatedEncryptionLevel()
- {
- assert TDS.ENCRYPT_INVALID != negotiatedEncryptionLevel;
- return negotiatedEncryptionLevel;
- }
-
- static final String RESERVED_PROVIDER_NAME_PREFIX = "MSSQL_";
- String columnEncryptionSetting = null;
- boolean isColumnEncryptionSettingEnabled() {
- return (columnEncryptionSetting.equalsIgnoreCase(ColumnEncryptionSetting.Enabled.toString()));
- }
-
- String keyStoreAuthentication = null;
- String keyStoreSecret = null;
- String keyStoreLocation = null;
-
- private boolean serverSupportsColumnEncryption = false;
-
- boolean getServerSupportsColumnEncryption() {
- return serverSupportsColumnEncryption;
- }
- static boolean isWindows;
- static Map globalSystemColumnEncryptionKeyStoreProviders =
- new HashMap();
- static
- {
- if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows"))
- {
- isWindows = true;
- SQLServerColumnEncryptionCertificateStoreProvider provider = new SQLServerColumnEncryptionCertificateStoreProvider();
- globalSystemColumnEncryptionKeyStoreProviders.put(
- provider.getName(),
- provider);
- }
- else
- {
- isWindows = false;
- }
- }
- static Map globalCustomColumnEncryptionKeyStoreProviders = null;
- // This is a per-connection store provider. It can be JKS or AKV.
- Map systemColumnEncryptionKeyStoreProvider =
- new HashMap();
-
- /**
- * Registers key store providers in the globalCustomColumnEncryptionKeyStoreProviders.
- * @param clientKeyStoreProviders a map containing the store providers information.
- * @throws SQLServerException when an error occurs
- */
- public static synchronized void registerColumnEncryptionKeyStoreProviders(
- Map clientKeyStoreProviders) throws SQLServerException
- {
- loggerExternal.entering(SQLServerConnection.class.getName(), "registerColumnEncryptionKeyStoreProviders", "Registering Column Encryption Key Store Providers");
-
- if (null == clientKeyStoreProviders)
- {
- throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderMapNull"), null, 0, false);
- }
-
- if (null != globalCustomColumnEncryptionKeyStoreProviders)
- {
- throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderSetOnce"), null, 0, false);
- }
-
- globalCustomColumnEncryptionKeyStoreProviders = new HashMap();
-
- for (Map.Entry entry : clientKeyStoreProviders.entrySet())
- {
- String providerName = entry.getKey();
- if (null == providerName || 0 == providerName.length())
- {
- throw new SQLServerException(null, SQLServerException.getErrString("R_EmptyCustomKeyStoreProviderName"), null, 0, false);
- }
- if ((providerName.substring(0, 6).equalsIgnoreCase(RESERVED_PROVIDER_NAME_PREFIX)))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidCustomKeyStoreProviderName"));
- Object[] msgArgs = { providerName, RESERVED_PROVIDER_NAME_PREFIX};
- throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
- }
- if (null == entry.getValue())
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_CustomKeyStoreProviderValueNull"));
- Object[] msgArgs = { providerName, RESERVED_PROVIDER_NAME_PREFIX};
- throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
- }
- globalCustomColumnEncryptionKeyStoreProviders.put(entry.getKey(), entry.getValue());
- }
-
- loggerExternal.exiting(
- SQLServerConnection.class.getName(),
- "registerColumnEncryptionKeyStoreProviders",
- "Number of Key store providers that are registered:" + globalCustomColumnEncryptionKeyStoreProviders.size());
- }
-
-
- static synchronized SQLServerColumnEncryptionKeyStoreProvider getGlobalSystemColumnEncryptionKeyStoreProvider(String providerName)
- {
- if (null != globalSystemColumnEncryptionKeyStoreProviders &&
- globalSystemColumnEncryptionKeyStoreProviders.containsKey(providerName))
- {
- return globalSystemColumnEncryptionKeyStoreProviders.get(providerName);
- }
- return null;
- }
-
- static synchronized String getAllGlobalCustomSystemColumnEncryptionKeyStoreProviders()
- {
- if (null != globalCustomColumnEncryptionKeyStoreProviders)
- return globalCustomColumnEncryptionKeyStoreProviders.keySet().toString();
- else
- return null;
- }
-
- synchronized String getAllSystemColumnEncryptionKeyStoreProviders()
- {
- String keyStores = "";
- if (0 != systemColumnEncryptionKeyStoreProvider.size())
- keyStores = systemColumnEncryptionKeyStoreProvider.keySet().toString();
- if (0 != SQLServerConnection.globalSystemColumnEncryptionKeyStoreProviders.size())
- keyStores += "," + SQLServerConnection.globalSystemColumnEncryptionKeyStoreProviders.keySet().toString();
- return keyStores;
- }
-
- static synchronized SQLServerColumnEncryptionKeyStoreProvider getGlobalCustomColumnEncryptionKeyStoreProvider(String providerName)
- {
- if (null != globalCustomColumnEncryptionKeyStoreProviders &&
- globalCustomColumnEncryptionKeyStoreProviders.containsKey(providerName))
- {
- return globalCustomColumnEncryptionKeyStoreProviders.get(providerName);
- }
- return null;
- }
-
- synchronized SQLServerColumnEncryptionKeyStoreProvider getSystemColumnEncryptionKeyStoreProvider(String providerName)
- {
- if ((null != systemColumnEncryptionKeyStoreProvider) &&
- (systemColumnEncryptionKeyStoreProvider.containsKey(providerName)))
- {
- return systemColumnEncryptionKeyStoreProvider.get(providerName);
- }
- else
- {
- return null;
- }
- }
-
- private String trustedServerNameAE = null;
- private static Map> columnEncryptionTrustedMasterKeyPaths=new HashMap>();
-
- /**
- * Sets Trusted Master Key Paths in the columnEncryptionTrustedMasterKeyPaths.
- * @param trustedKeyPaths all master key paths that are trusted
- */
- public static synchronized void setColumnEncryptionTrustedMasterKeyPaths(Map> trustedKeyPaths)
- {
- loggerExternal.entering(SQLServerConnection.class.getName(), "setColumnEncryptionTrustedMasterKeyPaths", "Setting Trusted Master Key Paths");
-
- // Use upper case for server and instance names.
- columnEncryptionTrustedMasterKeyPaths.clear();
- for (Map.Entry> entry : trustedKeyPaths.entrySet())
- {
- columnEncryptionTrustedMasterKeyPaths.put(entry.getKey().toUpperCase(), entry.getValue());
- }
-
- loggerExternal.exiting(SQLServerConnection.class.getName(), "setColumnEncryptionTrustedMasterKeyPaths", "Number of Trusted Master Key Paths: " + columnEncryptionTrustedMasterKeyPaths.size());
- }
-
- /**
- * Updates the columnEncryptionTrustedMasterKeyPaths with the new Server and trustedKeyPaths.
- * @param server String server name
- * @param trustedKeyPaths all master key paths that are trusted
- */
- public static synchronized void updateColumnEncryptionTrustedMasterKeyPaths(String server, List trustedKeyPaths)
- {
- loggerExternal.entering(SQLServerConnection.class.getName(), "updateColumnEncryptionTrustedMasterKeyPaths", "Updating Trusted Master Key Paths");
-
- // Use upper case for server and instance names.
- columnEncryptionTrustedMasterKeyPaths.put(server.toUpperCase(), trustedKeyPaths);
-
- loggerExternal.exiting(SQLServerConnection.class.getName(), "updateColumnEncryptionTrustedMasterKeyPaths", "Number of Trusted Master Key Paths: " + columnEncryptionTrustedMasterKeyPaths.size());
- }
-
- /**
- * Removes the trusted Master key Path from the columnEncryptionTrustedMasterKeyPaths.
- * @param server String server name
- */
- public static synchronized void removeColumnEncryptionTrustedMasterKeyPaths(String server)
- {
- loggerExternal.entering(SQLServerConnection.class.getName(), "removeColumnEncryptionTrustedMasterKeyPaths", "Removing Trusted Master Key Paths");
-
- // Use upper case for server and instance names.
- columnEncryptionTrustedMasterKeyPaths.remove(server.toUpperCase());
-
- loggerExternal.exiting(SQLServerConnection.class.getName(), "removeColumnEncryptionTrustedMasterKeyPaths", "Number of Trusted Master Key Paths: " + columnEncryptionTrustedMasterKeyPaths.size());
- }
-
- /**
- * Retrieves the Trusted Master Key Paths.
- * @return columnEncryptionTrustedMasterKeyPaths.
- */
- public static synchronized Map> getColumnEncryptionTrustedMasterKeyPaths()
- {
- loggerExternal.entering(SQLServerConnection.class.getName(), "getColumnEncryptionTrustedMasterKeyPaths", "Getting Trusted Master Key Paths");
-
- Map> masterKeyPathCopy = new HashMap>();
-
- for(Map.Entry> entry : columnEncryptionTrustedMasterKeyPaths.entrySet()){
- masterKeyPathCopy.put(entry.getKey(), entry.getValue());
- }
-
- loggerExternal.exiting(SQLServerConnection.class.getName(), "getColumnEncryptionTrustedMasterKeyPaths", "Number of Trusted Master Key Paths: " + masterKeyPathCopy.size());
-
- return masterKeyPathCopy;
- }
-
- static synchronized List getColumnEncryptionTrustedMasterKeyPaths(String server, Boolean[] hasEntry)
- {
- if(columnEncryptionTrustedMasterKeyPaths.containsKey(server))
- {
- hasEntry[0] = true;
- return columnEncryptionTrustedMasterKeyPaths.get(server);
- }
- else
- {
- hasEntry[0] = false;
- return null;
- }
- }
-
- Properties activeConnectionProperties; //the active set of connection properties
- private boolean integratedSecurity = SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.getDefaultValue();
- private AuthenticationScheme intAuthScheme = AuthenticationScheme.nativeAuthentication;
- // This is the current connect place holder this should point one of the primary or failover place holder
- ServerPortPlaceHolder currentConnectPlaceHolder=null;
-
- String sqlServerVersion; //SQL Server version string
- boolean xopenStates; //XOPEN or SQL 92 state codes?
- private boolean databaseAutoCommitMode ;
- private boolean inXATransaction = false; // Set to true when in an XA transaction.
- private byte[] transactionDescriptor = new byte[8];
-
- // Flag (Yukon and later) set to true whenever a transaction is rolled back.
- // The flag's value is reset to false when a new transaction starts or when the autoCommit mode changes.
- private boolean rolledBackTransaction;
- final boolean rolledBackTransaction() { return rolledBackTransaction; }
-
- private State state = State.Initialized; //connection state
-
- private void setState(State state)
- {
- this.state = state;
- }
-
- //This function actually represents whether a database session is not open.
- //The session is not available before the session is established and
- //after the session is closed.
- final boolean isSessionUnAvailable() { return !(state.equals(State.Opened)); }
-
- final static int maxDecimalPrecision = 38; //@@max_precision for SQL 2000 and 2005 is 38.
- final static int defaultDecimalPrecision = 18;
- final String traceID;
-
-
- /** Limit for the size of data (in bytes) returned for value on this connection */
- private int maxFieldSize ; // default: 0 --> no limit
- final void setMaxFieldSize(int limit) throws SQLServerException
- {
- // assert limit >= 0;
- if (maxFieldSize != limit)
- {
- if(loggerExternal.isLoggable(Level.FINER) && Util.IsActivityTraceOn())
- {
- loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
- }
- // If no limit on field size, set text size to max (2147483647), NOT default (0 --> 4K)
- connectionCommand("SET TEXTSIZE " + ((0 == limit) ? 2147483647 : limit), "setMaxFieldSize");
- maxFieldSize = limit;
- }
- }
-
-
-
-
- // This function is used both to init the values on creation of connection
- // and resetting the values after the connection is released to the pool for reuse.
- final void initResettableValues()
- {
- rolledBackTransaction = false;
- transactionIsolationLevel = Connection.TRANSACTION_READ_COMMITTED;//default isolation level
- maxFieldSize = 0; // default: 0 --> no limit
- maxRows =0; // default: 0 --> no limit
- nLockTimeout = -1;
- databaseAutoCommitMode = true;//auto commit mode
- holdability = ResultSet.HOLD_CURSORS_OVER_COMMIT;
- sqlWarnings=null;
- sCatalog = originalCatalog;
- databaseMetaData = null;
- }
-
- /** Limit for the maximum number of rows returned from queries on this connection */
- private int maxRows ; // default: 0 --> no limit
- final void setMaxRows(int limit) throws SQLServerException
- {
- // assert limit >= 0;
- if (maxRows != limit)
- {
- if(loggerExternal.isLoggable(Level.FINER) && Util.IsActivityTraceOn())
- {
- loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
- }
- connectionCommand("SET ROWCOUNT " + limit, "setMaxRows");
- maxRows = limit;
- }
- }
-
- private SQLCollation databaseCollation; // Default database collation read from ENVCHANGE_SQLCOLLATION token.
- final SQLCollation getDatabaseCollation() { return databaseCollation; }
-
- static private final AtomicInteger baseConnectionID = new AtomicInteger(0); //connection id dispenser
- // This is the current catalog
- private String sCatalog = "master"; //the database catalog
- // This is the catalog immediately after login.
- private String originalCatalog = "master";
-
- private int transactionIsolationLevel ;
- private SQLServerPooledConnection pooledConnectionParent;
- private DatabaseMetaData databaseMetaData; //the meta data for this connection
- private int nNextSavePointId = 10000; //first save point id
-
- static final private java.util.logging.Logger connectionlogger =
- java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerConnection");
- static final private java.util.logging.Logger loggerExternal =
- java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.Connection");
- private final String loggingClassName ;
-
- // there are three ways to get a failover partner
- // connection string, from the failover map, the connecting server returned
- // the following variable only stores the serverReturned failver information.
- private String failoverPartnerServerProvided = null;
-
- private int holdability;
- final int getHoldabilityInternal() { return holdability; }
-
- // Default TDS packet size used after logon if no other value was set via
- // the packetSize connection property. The value was chosen to take maximum
- // advantage of SQL Server's default page size.
- private int tdsPacketSize = TDS.INITIAL_PACKET_SIZE;
- private int requestedPacketSize = TDS.DEFAULT_PACKET_SIZE;
- final int getTDSPacketSize() { return tdsPacketSize; }
-
- private TDSChannel tdsChannel;
-
- private TDSCommand currentCommand = null;
-
- private int tdsVersion = TDS.VER_UNKNOWN;
-
- final boolean isKatmaiOrLater()
- {
- assert TDS.VER_UNKNOWN != tdsVersion;
- assert tdsVersion >= TDS.VER_YUKON;
- return tdsVersion >= TDS.VER_KATMAI;
- }
-
- final boolean isDenaliOrLater()
- {
- return tdsVersion >= TDS.VER_DENALI;
- }
-
- private int serverMajorVersion;
-
- int getServerMajorVersion(){
- return serverMajorVersion;
- }
-
- private SQLServerConnectionPoolProxy proxy;
-
- private UUID clientConnectionId = null;
-
- /**
- * Retrieves the clientConnectionID.
- * @throws SQLServerException when an error occurs
- */
- public UUID getClientConnectionId() throws SQLServerException
- {
- // If the connection is closed, we do not allow external application to get
- // ClientConnectionId.
- checkClosed();
- return clientConnectionId;
- }
-
- // This function is called internally, e.g. when login process fails, we
- // need to append the ClientConnectionId to error string.
- final UUID getClientConIdInternal()
- {
- return clientConnectionId;
- }
-
- final boolean attachConnId()
- {
- return state.equals(State.Connected);
- }
-
-
- @SuppressWarnings("unused")
- SQLServerConnection(String parentInfo) throws SQLServerException
- {
- int connectionID = nextConnectionID(); //sequential connection id
- traceID = "ConnectionID:" +connectionID;
- loggingClassName = "com.microsoft.sqlserver.jdbc.SQLServerConnection:" + connectionID;
- if (connectionlogger.isLoggable(Level.FINE))
- connectionlogger.fine(toString() + " created by (" + parentInfo + ")" );
- initResettableValues();
-
- // JDBC 3 driver only works with 1.5 JRE
- if (3 == DriverJDBCVersion.major && !Util.SYSTEM_SPEC_VERSION.equals("1.5"))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedJREVersion"));
- Object[] msgArgs = { Util.SYSTEM_SPEC_VERSION };
- String message = form.format(msgArgs);
- connectionlogger.severe(message);
- throw new UnsupportedOperationException(message);
- }
- }
-
- void setFailoverPartnerServerProvided(String partner)
- {
- failoverPartnerServerProvided = partner;
- // after login this info should be added to the map
- }
-
- final void setAssociatedProxy(SQLServerConnectionPoolProxy proxy)
- {
- this.proxy = proxy;
- }
-
- /*
- This function is used by the functions that return a connection object to outside world.
- E.g. stmt.getConnection, these functions should return the proxy not the actual physical
- connection when the physical connection is pooled and the user should be accessing
- the connection functions via the proxy object.
- */
- final Connection getConnection()
- {
- if(null != proxy)
- return proxy;
- else
- return this;
- }
-
- final void resetPooledConnection()
- {
- tdsChannel.resetPooledConnection();
- initResettableValues();
- }
-
-
- /**
- * Generate the next unique connection id.
- * @return the next conn id
- */
- /*L0*/ private static int nextConnectionID() {
- return baseConnectionID.incrementAndGet(); //4.04 Ensure thread safe id allocation
- }
- java.util.logging.Logger getConnectionLogger()
- {
- return connectionlogger;
- }
-
- String getClassNameLogging()
- {
- return loggingClassName;
- }
-
- /**
- * This is a helper function to provide an ID string suitable for tracing.
- */
- public String toString()
- {
- if(null != clientConnectionId)
- return traceID + " ClientConnectionId: " + clientConnectionId.toString();
- else
- return traceID;
- }
-
-
-
- /**
- * Throw a not implemeneted exception.
- * @throws SQLServerException
- */
- /*L0*/ void NotImplemented() throws SQLServerException {
- SQLServerException.makeFromDriverError(this, this, SQLServerException.getErrString("R_notSupported"), null, false);
- }
-
- /**
- * Check if the connection is closed
- * Create a new connection if it's a fedauth connection and the access token is going to expire.
- * @throws SQLServerException
- */
- /*L0*/ void checkClosed() throws SQLServerException
- {
- if (isSessionUnAvailable())
- {
- SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_connectionIsClosed"), null, false);
- }
-
- if(null != fedAuthToken){
- if(Util.checkIfNeedNewAccessToken(this))
- {
- connect(this.activeConnectionProperties, null);
- }
- }
- }
-
- /**
- * Check if a string property is enabled.
- * @param propName the string property name
- * @param propValue the string property value.
- * @return false if p == null (meaning take default).
- * @return true if p == "true" (case-insensitive).
- * @return false if p == "false" (case-insensitive).
- * @exception SQLServerException thrown if value is not recognized.
- */
- /*L0*/ private boolean booleanPropertyOn(String propName, String propValue) throws SQLServerException {
- // Null means take the default of false.
- if (null==propValue) return false;
- String lcpropValue = propValue.toLowerCase(Locale.US);
-
- if (lcpropValue.equals("true")) return true;
- if (lcpropValue.equals("false")) return false;
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidBooleanValue"));
- Object[] msgArgs = {propName};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- // Adding return false here for compiler's sake, this code is unreachable.
- return false;
- }
-
- // Maximum number of wide characters for a SQL login record name (such as instance name, application name, etc...).
- // See TDS specification, "Login Data Validation Rules" section.
- final static int MAX_SQL_LOGIN_NAME_WCHARS = 128;
-
- /**
- * Validates propName against maximum allowed length MAX_SQL_LOGIN_NAME_WCHARS.
- * Throws exception if name length exceeded.
- * @param propName the name of the property.
- * @param propValue the value of the property.
- * @throws SQLServerException
- */
- void ValidateMaxSQLLoginName(String propName, String propValue) throws SQLServerException
- {
- if (propValue != null && propValue.length() > MAX_SQL_LOGIN_NAME_WCHARS)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_propertyMaximumExceedsChars"));
- Object[] msgArgs = {propName, Integer.toString(MAX_SQL_LOGIN_NAME_WCHARS)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
-
- Connection connect(Properties propsIn, SQLServerPooledConnection pooledConnection) throws SQLServerException
- {
- int loginTimeoutSeconds = 0; // Will be set during the first retry attempt.
- long start = System.currentTimeMillis();
-
- for(int retryAttempt = 0; ;)
- {
- try
- {
- return connectInternal(propsIn, pooledConnection);
- }
- catch(SQLServerException e)
- {
- // Catch only the TLS 1.2 specific intermittent error.
- if (SQLServerException.DRIVER_ERROR_INTERMITTENT_TLS_FAILED != e.getDriverErrorCode())
- {
- // Re-throw all other exceptions.
- throw e;
- }
- else
- {
- // Special handling of the retry logic for TLS 1.2 intermittent issue.
-
- // If timeout is not set yet, set it once.
- if (0 == retryAttempt)
- {
- // We do not need to check for exceptions here, as the connection properties are already
- // verified during the first try. Also, we would like to do this calculation
- // only for the TLS 1.2 exception case.
- loginTimeoutSeconds = SQLServerDriverIntProperty.LOGIN_TIMEOUT.getDefaultValue(); // if the user does not specify a default timeout, default is 15 per spec
- String sPropValue = propsIn.getProperty(SQLServerDriverIntProperty.LOGIN_TIMEOUT.toString());
- if (null != sPropValue && sPropValue.length() > 0)
- {
- loginTimeoutSeconds = Integer.parseInt(sPropValue);
- }
- }
-
- retryAttempt++;
- long elapsedSeconds = ((System.currentTimeMillis() - start) / 1000L);
-
- if (INTERMITTENT_TLS_MAX_RETRY < retryAttempt)
- {
- // Re-throw the exception if we have reached the maximum retry attempts.
- if(connectionlogger.isLoggable(Level.FINE))
- {
- connectionlogger.fine("Connection failed during SSL handshake. Maximum retry attempt (" + INTERMITTENT_TLS_MAX_RETRY + ") reached. ");
- }
- throw e;
- }
- else if (elapsedSeconds >= loginTimeoutSeconds)
- {
- // Re-throw the exception if we do not have any time left to retry.
- if(connectionlogger.isLoggable(Level.FINE))
- {
- connectionlogger.fine("Connection failed during SSL handshake. Not retrying as timeout expired.");
- }
- throw e;
- }
- else
- {
- // Retry the connection.
- if(connectionlogger.isLoggable(Level.FINE))
- {
- connectionlogger.fine("Connection failed during SSL handshake. Retrying due to an intermittent TLS 1.2 failure issue. Retry attempt = " + retryAttempt + ".");
- }
- }
- }
- }
- }
- }
-
- private void registerKeyStoreProviderOnConnection(String keyStoreAuth, String keyStoreSecret, String keyStoreLocation) throws SQLServerException
- {
- if (null == keyStoreAuth)
- {
- // secret and location must be null too.
- if ((null != keyStoreSecret))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_keyStoreAuthenticationNotSet"));
- Object[] msgArgs = {"keyStoreSecret"};
- throw new SQLServerException(form.format(msgArgs), null);
- }
- if (null != keyStoreLocation)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_keyStoreAuthenticationNotSet"));
- Object[] msgArgs = {"keyStoreLocation"};
- throw new SQLServerException(form.format(msgArgs), null);
- }
- }
- else
- {
- KeyStoreAuthentication keyStoreAuthentication = KeyStoreAuthentication.valueOfString(keyStoreAuth);
- switch(keyStoreAuthentication)
- {
- case JavaKeyStorePassword:
- // both secret and location must be set for JKS.
- if ((null == keyStoreSecret) || (null == keyStoreLocation))
- {
- throw new SQLServerException(SQLServerException.getErrString("R_keyStoreSecretOrLocationNotSet"), null);
- }
- else
- {
- SQLServerColumnEncryptionJavaKeyStoreProvider provider = new SQLServerColumnEncryptionJavaKeyStoreProvider(keyStoreLocation, keyStoreSecret.toCharArray());
- systemColumnEncryptionKeyStoreProvider.put(
- provider.getName(),
- provider);
- }
- break;
-
- default:
- // valueOfString would throw an exception if the keyStoreAuthentication is not valid.
- break;
- }
- }
- }
-
-
- /**
- * Establish a physical database connection based on the user specified connection
- * properties. Logon to the database.
- *
- * @param propsIn the connection properties
- * @param pooledConnection a parent pooled connection if this is a logical connection
- * @throws SQLServerException
- * @return the database connection
- */
- Connection connectInternal(Properties propsIn, SQLServerPooledConnection pooledConnection) throws SQLServerException
- {
- try
- {
- activeConnectionProperties = (Properties)propsIn.clone();
-
- pooledConnectionParent = pooledConnection;
-
- String sPropKey=null;
- String sPropValue=null;
-
- sPropKey = SQLServerDriverStringProperty.USER.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = SQLServerDriverStringProperty.USER.getDefaultValue();
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
- ValidateMaxSQLLoginName(sPropKey, sPropValue);
-
-
- sPropKey = SQLServerDriverStringProperty.PASSWORD.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = SQLServerDriverStringProperty.PASSWORD.getDefaultValue();
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
- ValidateMaxSQLLoginName(sPropKey, sPropValue);
-
- sPropKey = SQLServerDriverStringProperty.DATABASE_NAME.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- ValidateMaxSQLLoginName(sPropKey, sPropValue);
-
-
- int loginTimeoutSeconds = SQLServerDriverIntProperty.LOGIN_TIMEOUT.getDefaultValue(); // if the user does not specify a default timeout, default is 15 per spec
- sPropValue = activeConnectionProperties.getProperty(SQLServerDriverIntProperty.LOGIN_TIMEOUT.toString());
- if (null != sPropValue && sPropValue.length() > 0)
- {
- try
- {
- loginTimeoutSeconds = Integer.parseInt(sPropValue);
- }
- catch (NumberFormatException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidTimeOut"));
- Object[] msgArgs = {sPropValue};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
-
- if (loginTimeoutSeconds < 0 || loginTimeoutSeconds > 65535)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidTimeOut"));
- Object[] msgArgs = {sPropValue};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
-
- // Translates the serverName from Unicode to ASCII Compatible Encoding (ACE), as defined by the ToASCII operation of RFC 3490.
- sPropKey = SQLServerDriverBooleanProperty.SERVER_NAME_AS_ACE.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.SERVER_NAME_AS_ACE.getDefaultValue());
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
- serverNameAsACE = booleanPropertyOn(sPropKey,sPropValue);
-
- // get the server name from the properties if it has instance name in it, getProperty the instance name
- // if there is a port number specified do not get the port number from the instance name
- sPropKey = SQLServerDriverStringProperty.SERVER_NAME.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
-
- if (sPropValue == null)
- {
- sPropValue = "localhost";
- }
-
- String sPropKeyPort = SQLServerDriverIntProperty.PORT_NUMBER.toString();
- String sPropValuePort = activeConnectionProperties.getProperty(sPropKeyPort);
-
- int px = sPropValue.indexOf('\\');
-
- String instancePort=null;
- String instanceValue=null;
-
- String instanceNameProperty = SQLServerDriverStringProperty.INSTANCE_NAME.toString();
- // found the instance name with the severname
- if (px>=0)
- {
- instanceValue = sPropValue.substring(px+1, sPropValue.length());
- ValidateMaxSQLLoginName(instanceNameProperty, instanceValue);
- sPropValue = sPropValue.substring(0, px);
- }
- trustedServerNameAE = sPropValue;
-
- if(true == serverNameAsACE)
- {
- try
- {
- sPropValue = IDN.toASCII(sPropValue);
- }
- catch(IllegalArgumentException ex)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_InvalidConnectionSetting"));
- Object[] msgArgs = {"serverNameAsACE", sPropValue};
- throw new SQLServerException(form.format(msgArgs), ex);
- }
- }
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
-
- String instanceValueFromProp = activeConnectionProperties.getProperty(instanceNameProperty);
- // property takes precedence
- if(null != instanceValueFromProp)
- instanceValue = instanceValueFromProp;
-
- if (instanceValue != null)
- {
- ValidateMaxSQLLoginName(instanceNameProperty, instanceValue);
- // only get port if the port is not specified
- activeConnectionProperties.setProperty(instanceNameProperty, instanceValue);
- trustedServerNameAE += "\\" + instanceValue;
- }
-
- if (null != sPropValuePort)
- {
- trustedServerNameAE += ":" + sPropValuePort;
- }
-
- sPropKey = SQLServerDriverStringProperty.APPLICATION_NAME.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue != null)
- ValidateMaxSQLLoginName(sPropKey, sPropValue);
- else
- activeConnectionProperties.setProperty(sPropKey, SQLServerDriver.DEFAULT_APP_NAME);
-
- sPropKey = SQLServerDriverBooleanProperty.LAST_UPDATE_COUNT.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.LAST_UPDATE_COUNT.getDefaultValue());
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
-
- sPropKey = SQLServerDriverStringProperty.COLUMN_ENCRYPTION.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (null == sPropValue)
- {
- sPropValue = SQLServerDriverStringProperty.COLUMN_ENCRYPTION.getDefaultValue();
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
- columnEncryptionSetting = ColumnEncryptionSetting.valueOfString(sPropValue).toString();
-
- sPropKey = SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (null != sPropValue)
- {
- keyStoreAuthentication = KeyStoreAuthentication.valueOfString(sPropValue).toString();
- }
-
- sPropKey = SQLServerDriverStringProperty.KEY_STORE_SECRET.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (null != sPropValue)
- {
- keyStoreSecret = sPropValue;
- }
-
- sPropKey = SQLServerDriverStringProperty.KEY_STORE_LOCATION.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (null != sPropValue)
- {
- keyStoreLocation = sPropValue;
- }
-
- registerKeyStoreProviderOnConnection(keyStoreAuthentication, keyStoreSecret, keyStoreLocation);
-
-
- sPropKey = SQLServerDriverBooleanProperty.MULTI_SUBNET_FAILOVER.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.MULTI_SUBNET_FAILOVER.getDefaultValue());
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
- multiSubnetFailover = booleanPropertyOn(sPropKey,sPropValue);
-
- sPropKey = SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.getDefaultValue());
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
- transparentNetworkIPResolution = booleanPropertyOn(sPropKey,sPropValue);
-
- sPropKey = SQLServerDriverBooleanProperty.ENCRYPT.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.ENCRYPT.getDefaultValue());
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
-
- // Set requestedEncryptionLevel according to the value of the encrypt connection property
- requestedEncryptionLevel = booleanPropertyOn(sPropKey,sPropValue) ? TDS.ENCRYPT_ON : TDS.ENCRYPT_OFF;
-
- sPropKey = SQLServerDriverBooleanProperty.TRUST_SERVER_CERTIFICATE.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.TRUST_SERVER_CERTIFICATE.getDefaultValue());
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
-
- trustServerCertificate = booleanPropertyOn(sPropKey,sPropValue);
-
- sPropKey = SQLServerDriverStringProperty.SELECT_METHOD.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null) sPropValue = SQLServerDriverStringProperty.SELECT_METHOD.getDefaultValue();
- if (sPropValue.equalsIgnoreCase("cursor") || sPropValue.equalsIgnoreCase("direct"))
- {
- activeConnectionProperties.setProperty(sPropKey, sPropValue.toLowerCase());
- }
- else
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidselectMethod"));
- Object[] msgArgs = {sPropValue};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
-
- sPropKey = SQLServerDriverStringProperty.RESPONSE_BUFFERING.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null) sPropValue = SQLServerDriverStringProperty.RESPONSE_BUFFERING.getDefaultValue();
- if (sPropValue.equalsIgnoreCase("full") || sPropValue.equalsIgnoreCase("adaptive"))
- {
- activeConnectionProperties.setProperty(sPropKey, sPropValue.toLowerCase());
- }
- else
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidresponseBuffering"));
- Object[] msgArgs = {sPropValue};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
-
-
- sPropKey = SQLServerDriverStringProperty.APPLICATION_INTENT.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null) sPropValue = SQLServerDriverStringProperty.APPLICATION_INTENT.getDefaultValue();
- applicationIntent = ApplicationIntent.valueOfString(sPropValue);
- activeConnectionProperties.setProperty(sPropKey, applicationIntent.toString());
-
- sPropKey = SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue());
- activeConnectionProperties.setProperty(sPropKey, sPropValue);
- }
-
- sendTimeAsDatetime = booleanPropertyOn(sPropKey,sPropValue);
-
- sPropKey = SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue != null) // if the user does not set it, it is ok but if set the value can only be true
- if (false == booleanPropertyOn(sPropKey, sPropValue))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invaliddisableStatementPooling"));
- Object[] msgArgs = {new String(sPropValue)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
-
- sPropKey = SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue != null)
- {
- integratedSecurity= booleanPropertyOn(sPropKey, sPropValue);
- }
-
- // Ignore authenticationScheme setting if integrated authentication not specified
- if(integratedSecurity)
- {
- sPropKey = SQLServerDriverStringProperty.AUTHENTICATION_SCHEME.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue != null)
- {
- intAuthScheme = AuthenticationScheme.valueOfString(sPropValue);
- }
- }
-
- sPropKey = SQLServerDriverStringProperty.AUTHENTICATION.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (sPropValue == null)
- {
- sPropValue = SQLServerDriverStringProperty.AUTHENTICATION.getDefaultValue();
- }
- authenticationString = SqlAuthentication.valueOfString(sPropValue).toString();
-
- if ((true == integratedSecurity) && (!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())))
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_SetAuthenticationWhenIntegratedSecurityTrue"));
- throw new SQLServerException(SQLServerException.getErrString("R_SetAuthenticationWhenIntegratedSecurityTrue"), null);
- }
-
- if(authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())
- &&
- ((!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty())
- || (!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty())))
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_IntegratedAuthenticationWithUserPassword"));
- throw new SQLServerException(SQLServerException.getErrString("R_IntegratedAuthenticationWithUserPassword"), null);
- }
-
- if(authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())
- && ((activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty())
- ||(activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty())))
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_NoUserPasswordForActivePassword"));
- throw new SQLServerException(SQLServerException.getErrString("R_NoUserPasswordForActivePassword"), null);
- }
-
- if(authenticationString.equalsIgnoreCase(SqlAuthentication.SqlPassword.toString())
- && ((activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty())
- ||(activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty())))
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_NoUserPasswordForSqlPassword"));
- throw new SQLServerException(SQLServerException.getErrString("R_NoUserPasswordForSqlPassword"), null);
- }
-
- sPropKey = SQLServerDriverStringProperty.ACCESS_TOKEN.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (null != sPropValue)
- {
- accessTokenInByte = sPropValue.getBytes(UTF_16LE);
- }
-
- if((null != accessTokenInByte) && 0 == accessTokenInByte.length)
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_AccessTokenCannotBeEmpty"));
- throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenCannotBeEmpty"), null);
- }
-
- if ((true == integratedSecurity) && (null != accessTokenInByte))
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_SetAccesstokenWhenIntegratedSecurityTrue"));
- throw new SQLServerException(SQLServerException.getErrString("R_SetAccesstokenWhenIntegratedSecurityTrue"), null);
- }
-
- if((!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString()))
- && (null != accessTokenInByte))
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_SetBothAuthenticationAndAccessToken"));
- throw new SQLServerException(SQLServerException.getErrString("R_SetBothAuthenticationAndAccessToken"), null);
- }
-
- if((null != accessTokenInByte)
- &&
- ((!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty())
- || (!activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()).isEmpty())))
- {
- connectionlogger.severe(toString() + " " + SQLServerException.getErrString("R_AccessTokenWithUserPassword"));
- throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenWithUserPassword"), null);
- }
-
- if((!System.getProperty("os.name").toLowerCase().startsWith("windows"))
- && ((null != accessTokenInByte) || (!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())))){
- throw new SQLServerException(SQLServerException.getErrString("R_FedAuthOnNonWindows"), null);
- }
-
- sPropKey = SQLServerDriverStringProperty.WORKSTATION_ID.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- ValidateMaxSQLLoginName(sPropKey, sPropValue);
-
- int nPort=0;
- sPropKey = SQLServerDriverIntProperty.PORT_NUMBER.toString();
- try
- {
- String strPort = activeConnectionProperties.getProperty(sPropKey);
- if (null!=strPort)
- {
- nPort = (new Integer(strPort)).intValue();
-
- if ((nPort<0) || (nPort>65535))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPortNumber"));
- Object[] msgArgs = {Integer.toString(nPort)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
- }
- catch (NumberFormatException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPortNumber"));
- Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
-
- // Handle optional packetSize property
- sPropKey = SQLServerDriverIntProperty.PACKET_SIZE.toString();
- sPropValue = activeConnectionProperties.getProperty(sPropKey);
- if (null != sPropValue && sPropValue.length() > 0)
- {
- try
- {
- requestedPacketSize = Integer.parseInt(sPropValue);
-
- // -1 --> Use server default
- if (-1 == requestedPacketSize)
- requestedPacketSize = TDS.SERVER_PACKET_SIZE;
-
- // 0 --> Use maximum size
- else if (0 == requestedPacketSize)
- requestedPacketSize = TDS.MAX_PACKET_SIZE;
- }
- catch (NumberFormatException e)
- {
- // Ensure that an invalid prop value results in an invalid packet size that
- // is not acceptable to the server.
- requestedPacketSize = TDS.INVALID_PACKET_SIZE;
- }
-
- if (TDS.SERVER_PACKET_SIZE != requestedPacketSize)
- {
- // Complain if the packet size is not in the range acceptable to the server.
- if (requestedPacketSize < TDS.MIN_PACKET_SIZE || requestedPacketSize > TDS.MAX_PACKET_SIZE)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPacketSize"));
- Object[] msgArgs = {sPropValue};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
- }
-
- // Note booleanPropertyOn will throw exception if parsed value is not valid.
-
- // have to check for null before calling booleanPropertyOn, because booleanPropertyOn
- // assumes that the null property defaults to false.
- sPropKey = SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.toString();
- if (null == activeConnectionProperties.getProperty(sPropKey))
- {
- sendStringParametersAsUnicode = SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.getDefaultValue();
- }
- else
- {
- sendStringParametersAsUnicode = booleanPropertyOn(sPropKey,activeConnectionProperties.getProperty(sPropKey));
- }
-
- sPropKey = SQLServerDriverBooleanProperty.LAST_UPDATE_COUNT.toString();
- lastUpdateCount = booleanPropertyOn(sPropKey,activeConnectionProperties.getProperty(sPropKey));
- sPropKey = SQLServerDriverBooleanProperty.XOPEN_STATES.toString();
- xopenStates = booleanPropertyOn(sPropKey,activeConnectionProperties.getProperty(sPropKey));
-
- sPropKey = SQLServerDriverStringProperty.SELECT_METHOD.toString();
- selectMethod = null;
- if (activeConnectionProperties.getProperty(sPropKey) != null &&
- activeConnectionProperties.getProperty(sPropKey).length() > 0)
- {
- selectMethod = activeConnectionProperties.getProperty(sPropKey);
- }
-
- sPropKey = SQLServerDriverStringProperty.RESPONSE_BUFFERING.toString();
- responseBuffering = null;
- if (activeConnectionProperties.getProperty(sPropKey) != null &&
- activeConnectionProperties.getProperty(sPropKey).length() > 0)
- {
- responseBuffering = activeConnectionProperties.getProperty(sPropKey);
- }
-
- sPropKey = SQLServerDriverIntProperty.LOCK_TIMEOUT.toString();
- int defaultLockTimeOut = SQLServerDriverIntProperty.LOCK_TIMEOUT.getDefaultValue();
- nLockTimeout = defaultLockTimeOut; //Wait forever
- if (activeConnectionProperties.getProperty(sPropKey) != null &&
- activeConnectionProperties.getProperty(sPropKey).length() > 0)
- {
- try
- {
- int n = (new Integer(activeConnectionProperties.getProperty(sPropKey))).intValue();
- if (n>=defaultLockTimeOut)
- nLockTimeout = n;
- else
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLockTimeOut"));
- Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
- catch (NumberFormatException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidLockTimeOut"));
- Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
-
- sPropKey = SQLServerDriverIntProperty.QUERY_TIMEOUT.toString();
- int defaultQueryTimeout = SQLServerDriverIntProperty.QUERY_TIMEOUT.getDefaultValue();
- queryTimeoutSeconds = defaultQueryTimeout; //Wait forever
- if (activeConnectionProperties.getProperty(sPropKey) != null &&
- activeConnectionProperties.getProperty(sPropKey).length() > 0)
- {
- try
- {
- int n = (new Integer(activeConnectionProperties.getProperty(sPropKey))).intValue();
- if (n>=defaultQueryTimeout){
- queryTimeoutSeconds = n;
- }
- else
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidQueryTimeout"));
- Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
- catch (NumberFormatException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidQueryTimeout"));
- Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
-
- sPropKey = SQLServerDriverIntProperty.SOCKET_TIMEOUT.toString();
- int defaultSocketTimeout = SQLServerDriverIntProperty.SOCKET_TIMEOUT.getDefaultValue();
- socketTimeoutMilliseconds = defaultSocketTimeout; //Wait forever
- if (activeConnectionProperties.getProperty(sPropKey) != null &&
- activeConnectionProperties.getProperty(sPropKey).length() > 0)
- {
- try
- {
- int n = (new Integer(activeConnectionProperties.getProperty(sPropKey))).intValue();
- if (n>=defaultSocketTimeout){
- socketTimeoutMilliseconds = n;
- }
- else
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidSocketTimeout"));
- Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
- catch (NumberFormatException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidSocketTimeout"));
- Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
-
- FailoverInfo fo =null;
- String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString();
- String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString();
- String failOverPartnerProperty = SQLServerDriverStringProperty.FAILOVER_PARTNER.toString();
- String failOverPartnerPropertyValue = activeConnectionProperties.getProperty(failOverPartnerProperty);
-
- //failoverPartner and multiSubnetFailover=true cannot be used together
- if(multiSubnetFailover && failOverPartnerPropertyValue != null)
- {
- SQLServerException.makeFromDriverError(this, this, SQLServerException.getErrString("R_dbMirroringWithMultiSubnetFailover"), null, false);
- }
-
- // transparentNetworkIPResolution is ignored if multiSubnetFailover or DBMirroring is true.
- if (multiSubnetFailover || (null != failOverPartnerPropertyValue))
- {
- transparentNetworkIPResolution = false;
- }
-
- //failoverPartner and applicationIntent=ReadOnly cannot be used together
- if((applicationIntent!=null) && applicationIntent.equals(ApplicationIntent.READ_ONLY) && failOverPartnerPropertyValue != null)
- {
- SQLServerException.makeFromDriverError(this, this, SQLServerException.getErrString("R_dbMirroringWithReadOnlyIntent"), null, false);
- }
-
- // check to see failover specified without DB error here if not.
- if(null != activeConnectionProperties.getProperty(databaseNameProperty))
- {
- // look to see if there exists a failover
- fo = FailoverMapSingleton.getFailoverInfo(this, activeConnectionProperties.getProperty(serverNameProperty),
- activeConnectionProperties.getProperty(instanceNameProperty), activeConnectionProperties.getProperty(databaseNameProperty));
- }
- else
- {
- // it is an error to specify failover without db.
- if(null != failOverPartnerPropertyValue)
- SQLServerException.makeFromDriverError(this, this, SQLServerException.getErrString("R_failoverPartnerWithoutDB"), null, true);
- }
-
- String mirror=null;
- if(null == fo)
- mirror = failOverPartnerPropertyValue;
-
- long startTime = System.currentTimeMillis();
- login(activeConnectionProperties.getProperty(serverNameProperty), instanceValue, nPort, mirror, fo, loginTimeoutSeconds, startTime);
-
- // If SSL is to be used for the duration of the connection, then make sure
- // that the final negotiated TDS packet size is no larger than the SSL record size.
- if (TDS.ENCRYPT_ON == negotiatedEncryptionLevel || TDS.ENCRYPT_REQ == negotiatedEncryptionLevel)
- {
- // IBM (Websphere) security provider uses 8K SSL record size. All others use 16K.
- int sslRecordSize = Util.isIBM() ? 8192 : 16384;
-
- if (tdsPacketSize > sslRecordSize)
- {
- connectionlogger.finer(toString() + " Negotiated tdsPacketSize " + tdsPacketSize + " is too large for SSL with JRE " + Util.SYSTEM_JRE + " (max size is " + sslRecordSize + ")");
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_packetSizeTooBigForSSL"));
- Object[] msgArgs = {Integer.toString(sslRecordSize)};
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, form.format(msgArgs));
- }
- }
-
-
- state = State.Opened;
-
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.finer(toString() + " End of connect" );
- }
- }
- finally
- {
- //once we exit the connect function, the connection can be only in one of two
- //states, Opened or Closed(if an exception occurred)
- if(!state.equals(State.Opened))
- {
- //if connection is not closed, close it
- if(!state.equals(State.Closed))
- this.close();
- }
- }
-
- return this;
-
- }
-
- // This function is used by non failover and failover cases. Even when we make a standard connection the server can provide us with its
- // FO partner.
- // If no FO information is available a standard connection is made.
- // If the server returns a failover upon connection, we shall store the FO in our cache.
- //
- private void login(String primary, String primaryInstanceName, int primaryPortNumber, String mirror, FailoverInfo foActual, int timeout
- , long timerStart) throws SQLServerException
- {
- //standardLogin would be false only for db mirroring scenarios. It would be true
- //for all other cases, including multiSubnetFailover
- final boolean isDBMirroring = (null == mirror && null == foActual)?false:true;
- int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
- long timeoutUnitInterval;
-
- boolean useFailoverHost = false;
- FailoverInfo tempFailover=null;
- // This is the failover server info place holder
- ServerPortPlaceHolder currentFOPlaceHolder=null;
- // This is the primary server placeHolder
- ServerPortPlaceHolder currentPrimaryPlaceHolder=null;
-
- if(null != foActual)
- {
- tempFailover = foActual;
- useFailoverHost = foActual.getUseFailoverPartner();
- }
- else
- {
- if(isDBMirroring)
- // Create a temporary class with the mirror info from the user
- tempFailover = new FailoverInfo(mirror, this, false);
- }
-
- //useParallel is set to true only for the first connection
- //when multiSubnetFailover is set to true. In all other cases, it is set
- //to false.
- boolean useParallel = getMultiSubnetFailover();
- boolean useTnir = getTransparentNetworkIPResolution();
-
- long intervalExpire;
-
- if (0 == timeout)
- {
- timeout = SQLServerDriverIntProperty.LOGIN_TIMEOUT.getDefaultValue();
- }
- long timerTimeout = timeout*1000; // ConnectTimeout is in seconds, we need timer millis
- timerExpire = timerStart + timerTimeout;
-
- //For non-dbmirroring, non-tnir and non-multisubnetfailover scenarios, full time out would be used as time slice.
- if(isDBMirroring || useParallel || useTnir)
- {
- timeoutUnitInterval = (long)(TIMEOUTSTEP * timerTimeout);
- }
- else
- {
- timeoutUnitInterval = timerTimeout;
- }
- intervalExpire = timerStart + timeoutUnitInterval;
- // This is needed when the host resolves to more than 64 IP addresses. In that case, TNIR is ignored
- // and the original timeout is used instead of the timeout slice.
- long intervalExpireFullTimeout = timerStart + timerTimeout;
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.finer(toString() + " Start time: " + timerStart + " Time out time: " + timerExpire + " Timeout Unit Interval: " + timeoutUnitInterval);
- }
-
- // Initialize loop variables
- int attemptNumber = 0;
-
- //indicates the no of times the connection was routed to a different server
- int noOfRedirections = 0;
-
- // Only three ways out of this loop:
- // 1) Successfully connected
- // 2) Parser threw exception while main timer was expired
- // 3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
- //
- // Of these methods, only #1 exits normally. This preserves the call stack on the exception
- // back into the parser for the error cases.
- while (true)
- {
- clientConnectionId = null;
- state = State.Initialized;
-
- try
- {
- if(isDBMirroring && useFailoverHost)
-
- {
- if (null == currentFOPlaceHolder)
- {
- //integrated security flag passed here to verify that the linked dll can be loaded
- currentFOPlaceHolder = tempFailover.failoverPermissionCheck(this, integratedSecurity);
- }
- currentConnectPlaceHolder = currentFOPlaceHolder;
- }
- else
- {
- if(routingInfo != null)
- {
- currentPrimaryPlaceHolder = routingInfo;
- routingInfo = null;
- }
- else if(null == currentPrimaryPlaceHolder)
- {
- currentPrimaryPlaceHolder = primaryPermissionCheck(primary, primaryInstanceName, primaryPortNumber);
- }
- currentConnectPlaceHolder = currentPrimaryPlaceHolder;
- }
-
- // logging code
- if (connectionlogger.isLoggable(Level.FINE))
- {
- connectionlogger.fine(toString() + " This attempt server name: " + currentConnectPlaceHolder.getServerName()+
- " port: " + currentConnectPlaceHolder.getPortNumber()
- + " InstanceName: " + currentConnectPlaceHolder.getInstanceName()
- + " useParallel: " + useParallel);
- connectionlogger.fine(toString() + " This attempt endtime: " + intervalExpire );
- connectionlogger.fine(toString() + " This attempt No: " + attemptNumber);
- }
- // end logging code
-
- // Attempt login.
- // use Place holder to make sure that the failoverdemand is done.
-
- connectHelper(
- currentConnectPlaceHolder,
- TimerRemaining(intervalExpire),
- timeout,
- useParallel,
- useTnir,
- (0 == attemptNumber), // Is this the TNIR first attempt
- TimerRemaining(intervalExpireFullTimeout)); // Only used when host resolves to >64 IPs
-
-
- if(isRoutedInCurrentAttempt)
- {
- //we ignore the failoverpartner ENVCHANGE, if we got routed.
- //So, no error needs to be thrown for that case.
- if(isDBMirroring)
- {
- String msg = SQLServerException.getErrString("R_invalidRoutingInfo");
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
- }
-
- noOfRedirections++;
-
- if(noOfRedirections > 1)
- {
- String msg = SQLServerException.getErrString("R_multipleRedirections");
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
- }
-
- // close tds channel
- if(tdsChannel!=null)
- tdsChannel.close();
-
- initResettableValues();
-
- //reset all params that could have been changed due to ENVCHANGE tokens
- // to defaults, excluding those changed due to routing ENVCHANGE token
- resetNonRoutingEnvchangeValues();
-
- //increase the attempt number. This is not really necessary
- //(in fact it does not matter whether we increase it or not) as
- //we do not use any timeslicing for multisubnetfailover. However, this
- //is done just to be consistent with the rest of the logic.
- attemptNumber++;
-
- //set isRoutedInCurrentAttempt to false for the next attempt
- isRoutedInCurrentAttempt = false;
-
- //useParallel and useTnir should be set to false once we get routed
- useParallel = false;
- useTnir = false;
-
- //When connection is routed for read only application, remaining timer duration is used as a one full interval
- intervalExpire = timerExpire;
-
- //if timeout expired, throw.
- if(timerHasExpired(timerExpire))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
- Object[] msgArgs = {currentConnectPlaceHolder.getServerName(), Integer.toString(currentConnectPlaceHolder.getPortNumber()), SQLServerException.getErrString("R_timedOutBeforeRouting")};
- String msg = form.format(msgArgs);
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
- }
- else
- {
- continue;
- }
- }
- else
- break; // leave the while loop -- we've successfully connected
- }
- catch (SQLServerException sqlex)
- {
- if ((SQLServerException.LOGON_FAILED == sqlex.getErrorCode()) // actual logon failed, i.e. bad password
- || (SQLServerException.PASSWORD_EXPIRED == sqlex.getErrorCode()) // actual logon failed, i.e. password isExpired
- || (SQLServerException.DRIVER_ERROR_INVALID_TDS == sqlex.getDriverErrorCode()) // invalid TDS received from server
- || (SQLServerException.DRIVER_ERROR_SSL_FAILED == sqlex.getDriverErrorCode()) // failure negotiating SSL
- || (SQLServerException.DRIVER_ERROR_INTERMITTENT_TLS_FAILED == sqlex.getDriverErrorCode()) // failure TLS1.2
- || (SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG == sqlex.getDriverErrorCode()) // unsupported configuration (e.g. Sphinx, invalid packet size, etc.)
- || timerHasExpired(timerExpire)// no more time to try again
- || (state.equals(State.Connected)&& !isDBMirroring)
- //for non-dbmirroring cases, do not retry after tcp socket connection succeeds
- )
- {
-
- //close the connection and throw the error back
- close();
- throw sqlex;
- }
- else
- {
- // Close the TDS channel from the failed connection attempt so that we don't
- // hold onto network resources any longer than necessary.
- if (null != tdsChannel)
- tdsChannel.close();
- }
-
- // For standard connections and MultiSubnetFailover connections, change the sleep interval after every attempt.
- // For DB Mirroring, we only sleep after every other attempt.
- if (!isDBMirroring || 1 == attemptNumber % 2)
- {
- // Check sleep interval to make sure we won't exceed the timeout
- // Do this in the catch block so we can re-throw the current exception
- long remainingMilliseconds = TimerRemaining(timerExpire);
- if (remainingMilliseconds <= sleepInterval)
- {
- throw sqlex;
- }
- }
- }
-
- // We only get here when we failed to connect, but are going to re-try
- // After trying to connect to both servers fails, sleep for a bit to prevent clogging
- // the network with requests, then update sleep interval for next iteration (max 1 second interval)
- // We have to sleep for every attempt in case of non-dbMirroring scenarios (including multisubnetfailover),
- // Whereas for dbMirroring, we sleep for every two attempts as each attempt is to a different server.
- if ( !isDBMirroring || (1 == attemptNumber % 2))
- {
- if (connectionlogger.isLoggable(Level.FINE))
- {
- connectionlogger.fine(toString() + " sleeping milisec: " + sleepInterval);
- }
- try
- {
- Thread.sleep(sleepInterval);
- }catch (InterruptedException e)
- {
- // continue if the thread is interrupted. This really should not happen.
- }
- sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
- }
-
- // Update timeout interval (but no more than the point where we're supposed to fail: timerExpire)
- attemptNumber++;
-
- if(useParallel || useTnir)
- {
- intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * (attemptNumber + 1));
- }
- else if(isDBMirroring)
- {
- intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * ((attemptNumber / 2) + 1));
- }
- else
- intervalExpire = timerExpire;
- //Due to the below condition and the timerHasExpired check in catch block,
- //the multiSubnetFailover case or any other standardLogin case where timeOutInterval is full timeout would also be handled correctly.
- if (intervalExpire > timerExpire)
- {
- intervalExpire = timerExpire;
- }
- // try again, this time swapping primary/secondary servers
- if(isDBMirroring)
- useFailoverHost = !useFailoverHost;
- }
-
- // If we get here, connection/login succeeded! Just a few more checks & record-keeping
- // if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
- if (useFailoverHost && null == failoverPartnerServerProvided)
- {
- String curserverinfo = currentConnectPlaceHolder.getServerName();
- if(null != currentFOPlaceHolder.getInstanceName())
- {
- curserverinfo = curserverinfo + "\\";
- curserverinfo = curserverinfo + currentFOPlaceHolder.getInstanceName();
- }
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPartnerConfiguration"));
- Object[] msgArgs = { activeConnectionProperties.getProperty(SQLServerDriverStringProperty.DATABASE_NAME.toString()),
- curserverinfo};
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, form.format(msgArgs));
- }
-
- if(null != failoverPartnerServerProvided)
- {
- //if server returns failoverPartner when multiSubnetFailover keyword is used, fail
- if(multiSubnetFailover)
- {
- String msg = SQLServerException.getErrString("R_dbMirroringWithMultiSubnetFailover");
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
- }
-
- //if server returns failoverPartner and applicationIntent=ReadOnly, fail
- if((applicationIntent!=null) && applicationIntent.equals(ApplicationIntent.READ_ONLY))
- {
- String msg = SQLServerException.getErrString("R_dbMirroringWithReadOnlyIntent");
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
- }
-
-
- if(null == tempFailover)
- tempFailover = new FailoverInfo(failoverPartnerServerProvided, this, false);
- // if the failover is not from the map already out this in the map, if it is from the map just make sure that we change the
- if (null != foActual)
- {
- // We must wait for CompleteLogin to finish for to have the
- // env change from the server to know its designated failover
- // partner; saved in failoverPartnerServerProvided
- foActual.failoverAdd(this, useFailoverHost, failoverPartnerServerProvided);
- }
- else
- {
- String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString();
- String instanceNameProperty = SQLServerDriverStringProperty.INSTANCE_NAME.toString();
- String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString();
-
- if (connectionlogger.isLoggable(Level.FINE))
- {
- connectionlogger.fine(toString() + " adding new failover info server: " + activeConnectionProperties.getProperty(serverNameProperty) +
- " instance: " + activeConnectionProperties.getProperty(instanceNameProperty) +
- " database: " + activeConnectionProperties.getProperty(databaseNameProperty)+
- " server provided failover: " + failoverPartnerServerProvided);
- }
-
- tempFailover.failoverAdd(this, useFailoverHost, failoverPartnerServerProvided);
- FailoverMapSingleton.putFailoverInfo(this, primary,activeConnectionProperties.getProperty(instanceNameProperty),
- activeConnectionProperties.getProperty(databaseNameProperty) , tempFailover , useFailoverHost, failoverPartnerServerProvided);
- }
- }
- }
-
- // reset all params that could have been changed due to ENVCHANGE tokens to defaults,
- // excluding those changed due to routing ENVCHANGE token
- void resetNonRoutingEnvchangeValues()
- {
- tdsPacketSize = TDS.INITIAL_PACKET_SIZE;
- databaseCollation= null;
- rolledBackTransaction = false;
- Arrays.fill(getTransactionDescriptor(), (byte)0);
- sCatalog = originalCatalog;
- failoverPartnerServerProvided = null;
- }
-
- static final int DEFAULTPORT = SQLServerDriverIntProperty.PORT_NUMBER.getDefaultValue();
- // This code should be similar to the code in FailOverInfo class's failoverPermissionCheck
- // Only difference is that this gets the instance port if the port number is zero where as failover
- // does not have port number available.
- ServerPortPlaceHolder primaryPermissionCheck(String primary, String primaryInstanceName, int primaryPortNumber) throws SQLServerException
- {
- String instancePort;
- // look to see primary port number is specified
- if(0==primaryPortNumber)
- {
- if(null != primaryInstanceName)
- {
- instancePort = getInstancePort(primary, primaryInstanceName);
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.fine(toString() + " SQL Server port returned by SQL Browser: " + instancePort);
- try
- {
- if (null!= instancePort)
- {
- primaryPortNumber = (new Integer(instancePort)).intValue();
-
- if ((primaryPortNumber<0) || (primaryPortNumber>65535))
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPortNumber"));
- Object[] msgArgs = {Integer.toString(primaryPortNumber)};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
- else
- primaryPortNumber = DEFAULTPORT;
- }
-
- catch (NumberFormatException e)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPortNumber"));
- Object[] msgArgs = {primaryPortNumber};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
- }
- }
- else
- primaryPortNumber = DEFAULTPORT;
- }
-
- // now we have determined the right port set the connection property back
- activeConnectionProperties.setProperty(SQLServerDriverIntProperty.PORT_NUMBER.toString(), String.valueOf(primaryPortNumber));
- return new ServerPortPlaceHolder(primary, primaryPortNumber, primaryInstanceName, integratedSecurity);
- }
-
- static boolean timerHasExpired(long timerExpire)
- {
- boolean result = System.currentTimeMillis() > timerExpire;
- return result;
- }
-
- static int TimerRemaining(long timerExpire)
- {
- long timerNow = System.currentTimeMillis();
- long result = timerExpire - timerNow;
- // maximum timeout the socket takes is int max.
- if(result > Integer.MAX_VALUE )
- result = Integer.MAX_VALUE;
- // we have to make sure that we return atleast one ms
- // we want atleast one attempt to happen with a positive timeout passed by the user.
- if (result <= 0)
- result = 1;
- return (int)result;
- }
-
-
- /**
- * This is a helper function to connect
- * this gets the port of the server to connect and the server name to connect and the timeout
- * This function achieves one connection attempt
- * Create a prepared statement for internal use by the driver.
- * @param serverInfo
- * @param timeOutSliceInMillis -timeout value in milli seconds for one try
- * @param timeOutFullInSeconds - whole timeout value specified by the user in seconds
- * @param useParallel - It is used to indicate whether a parallel algorithm should be tried or not
- * for resolving a hostName. Note that useParallel is set to false
- * for a routed connection even if multiSubnetFailover is set to true.
- * @param useTnir
- * @param isTnirFirstAttempt
- * @param timeOutsliceInMillisForFullTimeout
- * @throws SQLServerException
- */
- private void connectHelper(
- ServerPortPlaceHolder serverInfo,
- int timeOutsliceInMillis,
- int timeOutFullInSeconds,
- boolean
- useParallel,
- boolean useTnir,
- boolean isTnirFirstAttempt,
- int timeOutsliceInMillisForFullTimeout) throws SQLServerException
- {
- // Make the initial tcp-ip connection.
-
- if (connectionlogger.isLoggable(Level.FINE))
- {
- connectionlogger.fine(toString() + " Connecting with server: " + serverInfo.getServerName()
- + " port: " + serverInfo.getPortNumber()
- + " Timeout slice: " + timeOutsliceInMillis + " Timeout Full: " + timeOutFullInSeconds);
- }
- // if the timeout is infinite slices are infinite too.
- tdsChannel = new TDSChannel(this);
- if(0== timeOutFullInSeconds)
- tdsChannel.open(serverInfo.getServerName(), serverInfo.getPortNumber(), 0, useParallel, useTnir, isTnirFirstAttempt, timeOutsliceInMillisForFullTimeout);
- else
- tdsChannel.open(serverInfo.getServerName(), serverInfo.getPortNumber(), timeOutsliceInMillis, useParallel, useTnir, isTnirFirstAttempt, timeOutsliceInMillisForFullTimeout);
-
-
- setState(State.Connected);
-
-
- clientConnectionId = UUID.randomUUID();
- assert null != clientConnectionId;
-
-
- Prelogin(serverInfo.getServerName(), serverInfo.getPortNumber());
-
- // If prelogin negotiated SSL encryption then, enable it on the TDS channel.
- if (TDS.ENCRYPT_NOT_SUP != negotiatedEncryptionLevel){
- tdsChannel.enableSSL(serverInfo.getServerName(), serverInfo.getPortNumber());
- }
-
- // We have successfully connected, now do the login. logon takes seconds timeout
- executeCommand(new LogonCommand());
- }
-
- /**
- * Negotiates prelogin information with the server
- */
- void Prelogin(String serverName, int portNumber) throws SQLServerException
- {
- // Build a TDS Pre-Login packet to send to the server.
- if((!authenticationString.trim().equalsIgnoreCase(SqlAuthentication.NotSpecified.toString()))
- || (null != accessTokenInByte))
- {
- fedAuthRequiredByUser = true;
- }
-
- // Message length (incl. header)
- final byte messageLength;
- final byte fedAuthOffset;
- if(fedAuthRequiredByUser)
- {
- messageLength = TDS.B_PRELOGIN_MESSAGE_LENGTH_WITH_FEDAUTH;
- requestedEncryptionLevel = TDS.ENCRYPT_ON;
-
- //since we added one more line for prelogin option with fedauth,
- //we also needed to modify the offsets above, by adding 5 to each offset,
- //since the data session of each option is push 5 bytes behind.
- fedAuthOffset = 5;
- }
- else
- {
- messageLength = TDS.B_PRELOGIN_MESSAGE_LENGTH;
- fedAuthOffset = 0;
- }
-
- final byte[] preloginRequest = new byte[messageLength];
-
- int preloginRequestOffset = 0;
-
- byte[] bufferHeader = {
- // Buffer Header
- TDS.PKT_PRELOGIN, // Message Type
- TDS.STATUS_BIT_EOM,
- 0, messageLength,
- 0, 0, // SPID (not used)
- 0, // Packet (not used)
- 0, // Window (not used)
- };
-
- System.arraycopy(bufferHeader, 0, preloginRequest, preloginRequestOffset, bufferHeader.length);
- preloginRequestOffset = preloginRequestOffset + bufferHeader.length;
-
- byte[] preloginOptionsBeforeFedAuth = {
- // OPTION_TOKEN (BYTE), OFFSET (USHORT), LENGTH (USHORT)
- TDS.B_PRELOGIN_OPTION_VERSION, 0, (byte) (16 + fedAuthOffset), 0, 6, // UL_VERSION + US_SUBBUILD
- TDS.B_PRELOGIN_OPTION_ENCRYPTION, 0, (byte) (22 + fedAuthOffset), 0, 1, // B_FENCRYPTION
- TDS.B_PRELOGIN_OPTION_TRACEID, 0, (byte) (23 + fedAuthOffset), 0, 36, // ClientConnectionId + ActivityId
- };
- System.arraycopy(preloginOptionsBeforeFedAuth, 0, preloginRequest, preloginRequestOffset, preloginOptionsBeforeFedAuth.length);
- preloginRequestOffset = preloginRequestOffset + preloginOptionsBeforeFedAuth.length;
-
- if(fedAuthRequiredByUser){
- byte[] preloginOptions2 = {
- TDS.B_PRELOGIN_OPTION_FEDAUTHREQUIRED, 0, 64, 0, 1,
- };
- System.arraycopy(preloginOptions2, 0, preloginRequest, preloginRequestOffset, preloginOptions2.length);
- preloginRequestOffset = preloginRequestOffset + preloginOptions2.length;
- }
-
- preloginRequest[preloginRequestOffset] = TDS.B_PRELOGIN_OPTION_TERMINATOR;
- preloginRequestOffset++;
-
- // PL_OPTION_DATA
- byte[] preloginOptionData = {
- // - Server version -
- // (out param, filled in by the server in the prelogin response).
- 0, 0, 0, 0, 0, 0,
-
- // - Encryption -
- requestedEncryptionLevel,
-
- // TRACEID Data Session (ClientConnectionId + ActivityId) - Initialize to 0
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- };
- System.arraycopy(preloginOptionData, 0, preloginRequest, preloginRequestOffset, preloginOptionData.length);
- preloginRequestOffset = preloginRequestOffset + preloginOptionData.length;
-
- //If the client’s PRELOGIN request message contains the FEDAUTHREQUIRED option,
- //the client MUST specify 0x01 as the B_FEDAUTHREQUIRED value
- if(fedAuthRequiredByUser){
- preloginRequest[preloginRequestOffset] = 1;
- preloginRequestOffset = preloginRequestOffset + 1;
- }
-
- final byte[] preloginResponse = new byte[TDS.INITIAL_PACKET_SIZE];
- String preloginErrorLogString = " Prelogin error: host " + serverName + " port " + portNumber;
-
- ActivityId activityId = ActivityCorrelator.getNext();
- final byte[] actIdByteArray = Util.asGuidByteArray(activityId.getId());
- final byte[] conIdByteArray = Util.asGuidByteArray(clientConnectionId);
-
- int offset;
-
- if(fedAuthRequiredByUser){
- offset = preloginRequest.length - 36 - 1; // point to the TRACEID Data Session (one more byte for fedauth data session)
- }
- else
- {
- offset = preloginRequest.length - 36; // point to the TRACEID Data Session
- }
-
- // copy ClientConnectionId
- System.arraycopy(conIdByteArray, 0, preloginRequest, offset, conIdByteArray.length);
- offset += conIdByteArray.length;
-
- // copy ActivityId
- System.arraycopy(actIdByteArray, 0, preloginRequest, offset, actIdByteArray.length);
- offset += actIdByteArray.length;
-
- long seqNum = activityId.getSequence();
- Util.writeInt((int)seqNum, preloginRequest, offset);
- offset +=4;
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.finer(toString() + " Requesting encryption level:" + TDS.getEncryptionLevel(requestedEncryptionLevel));
- connectionlogger.finer(toString() + " ActivityId " + activityId.toString());
- }
-
- // Write the entire prelogin request
- if (tdsChannel.isLoggingPackets())
- tdsChannel.logPacket(preloginRequest, 0, preloginRequest.length, toString() + " Prelogin request");
-
- try
- {
- tdsChannel.write(preloginRequest, 0, preloginRequest.length);
- tdsChannel.flush();
- }
- catch (SQLServerException e)
- {
- connectionlogger.warning(toString() + preloginErrorLogString + " Error sending prelogin request: " + e.getMessage());
- throw e;
- }
-
- ActivityCorrelator.setCurrentActivityIdSentFlag(); // indicate current ActivityId is sent
-
- // Read the entire prelogin response
- int responseLength = preloginResponse.length;
- int responseBytesRead = 0;
- boolean processedResponseHeader = false;
- while (responseBytesRead < responseLength)
- {
- int bytesRead;
-
- try
- {
- bytesRead = tdsChannel.read(preloginResponse, responseBytesRead, responseLength - responseBytesRead);
- }
- catch (SQLServerException e)
- {
- connectionlogger.warning(toString() + preloginErrorLogString + " Error reading prelogin response: " + e.getMessage());
- throw e;
- }
-
- // If we reached EOF before the end of the prelogin response then something is wrong.
- //
- // Special case: If there was no response at all (i.e. the server closed the connection),
- // then maybe we are just trying to talk to an older server that doesn't support prelogin
- // (and that we don't support with this driver).
- if (-1 == bytesRead)
- {
- connectionlogger.warning(toString() + preloginErrorLogString + " Unexpected end of prelogin response after " + responseBytesRead + " bytes read");
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
- Object[] msgArgs = {serverName, Integer.toString(portNumber), SQLServerException.getErrString("R_notSQLServer")};
- terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
- }
-
- // Otherwise, we must have read some bytes...
- assert bytesRead >= 0;
- assert bytesRead <= responseLength - responseBytesRead;
-
- if (tdsChannel.isLoggingPackets())
- tdsChannel.logPacket(preloginResponse, responseBytesRead, bytesRead, toString() + " Prelogin response");
-
- responseBytesRead += bytesRead;
-
- // Validate the response header if we haven't already done so and
- // we've read enough of the response to do it.
- if (!processedResponseHeader && responseBytesRead >= TDS.PACKET_HEADER_SIZE)
- {
- // Verify that the response is actually a response...
- if (TDS.PKT_REPLY != preloginResponse[0])
- {
- connectionlogger.warning(toString() + preloginErrorLogString + " Unexpected response type:" + preloginResponse[0]);
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
- Object[] msgArgs = {serverName, Integer.toString(portNumber), SQLServerException.getErrString("R_notSQLServer")};
- terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
- }
-
- // Verify that the response claims to only be one TDS packet long.
- // In theory, it can be longer, but in current practice it isn't, as all of the
- // prelogin response items easily fit into a single 4K packet.
- if (TDS.STATUS_BIT_EOM != (TDS.STATUS_BIT_EOM & preloginResponse[1]))
- {
- connectionlogger.warning(toString() + preloginErrorLogString + " Unexpected response status:" + preloginResponse[1]);
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
- Object[] msgArgs = {serverName, Integer.toString(portNumber), SQLServerException.getErrString("R_notSQLServer")};
- terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
- }
-
- // Verify that the length of the response claims to be small enough to fit in the allocated area
- responseLength = Util.readUnsignedShortBigEndian(preloginResponse, 2);
- assert responseLength >= 0;
-
- if (responseLength >= preloginResponse.length)
- {
- connectionlogger.warning(toString() + preloginErrorLogString + " Response length:" + responseLength + " is greater than allowed length:" + preloginResponse.length);
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_tcpipConnectionFailed"));
- Object[] msgArgs = {serverName, Integer.toString(portNumber), SQLServerException.getErrString("R_notSQLServer")};
- terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, form.format(msgArgs));
- }
-
- processedResponseHeader = true;
- }
- }
-
- // Walk the response for prelogin options received. We expect at least to get
- // back the server version and the encryption level.
- boolean receivedVersionOption = false;
- negotiatedEncryptionLevel = TDS.ENCRYPT_INVALID;
-
- int responseIndex = TDS.PACKET_HEADER_SIZE;
- while (true)
- {
- // Get the option token
- if (responseIndex >= responseLength)
- {
- connectionlogger.warning(toString() + " Option token not found");
- throwInvalidTDS();
- }
- byte optionToken = preloginResponse[responseIndex++];
-
- // When we reach the option terminator, we're done processing option tokens
- if (TDS.B_PRELOGIN_OPTION_TERMINATOR == optionToken)
- break;
-
- // Get the offset and length that follows the option token
- if (responseIndex + 4 >= responseLength)
- {
- connectionlogger.warning(toString() + " Offset/Length not found for option:" + optionToken);
- throwInvalidTDS();
- }
-
- int optionOffset = Util.readUnsignedShortBigEndian(preloginResponse, responseIndex) + TDS.PACKET_HEADER_SIZE;
- responseIndex += 2;
- assert optionOffset >= 0;
-
- int optionLength = Util.readUnsignedShortBigEndian(preloginResponse, responseIndex);
- responseIndex += 2;
- assert optionLength >= 0;
-
- if (optionOffset + optionLength > responseLength)
- {
- connectionlogger.warning(toString() + " Offset:" + optionOffset + " and length:" + optionLength + " exceed response length:" + responseLength);
- throwInvalidTDS();
- }
-
- switch (optionToken)
- {
- case TDS.B_PRELOGIN_OPTION_VERSION:
- if (receivedVersionOption)
- {
- connectionlogger.warning(toString() + " Version option already received");
- throwInvalidTDS();
- }
-
- if (6 != optionLength)
- {
- connectionlogger.warning(toString() + " Version option length:" + optionLength + " is incorrect. Correct value is 6.");
- throwInvalidTDS();
- }
-
- serverMajorVersion = preloginResponse[optionOffset];
- if (serverMajorVersion < 9)
- {
- connectionlogger.warning(toString() + " Server major version:" + serverMajorVersion + " is not supported by this driver.");
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedServerVersion"));
- Object[] msgArgs = {Integer.toString(preloginResponse[optionOffset])};
- terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, form.format(msgArgs));
- }
-
- if (connectionlogger.isLoggable(Level.FINE))
- connectionlogger.fine(toString() + " Server returned major version:" + preloginResponse[optionOffset]);
-
- receivedVersionOption = true;
- break;
-
- case TDS.B_PRELOGIN_OPTION_ENCRYPTION:
- if (TDS.ENCRYPT_INVALID != negotiatedEncryptionLevel)
- {
- connectionlogger.warning(toString() + " Encryption option already received");
- throwInvalidTDS();
- }
-
- if (1 != optionLength)
- {
- connectionlogger.warning(toString() + " Encryption option length:" + optionLength + " is incorrect. Correct value is 1.");
- throwInvalidTDS();
- }
-
- negotiatedEncryptionLevel = preloginResponse[optionOffset];
-
- // If the server did not return a valid encryption level, terminate the connection.
- if (TDS.ENCRYPT_OFF != negotiatedEncryptionLevel &&
- TDS.ENCRYPT_ON != negotiatedEncryptionLevel &&
- TDS.ENCRYPT_REQ != negotiatedEncryptionLevel &&
- TDS.ENCRYPT_NOT_SUP != negotiatedEncryptionLevel)
- {
- connectionlogger.warning(toString() + " Server returned " + TDS.getEncryptionLevel(negotiatedEncryptionLevel));
- throwInvalidTDS();
- }
-
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString() + " Negotiated encryption level:" + TDS.getEncryptionLevel(negotiatedEncryptionLevel));
-
- // If we requested SSL encryption and the server does not support it, then terminate the connection.
- if (TDS.ENCRYPT_ON == requestedEncryptionLevel &&
- TDS.ENCRYPT_ON != negotiatedEncryptionLevel &&
- TDS.ENCRYPT_REQ != negotiatedEncryptionLevel)
- {
- terminate(SQLServerException.DRIVER_ERROR_SSL_FAILED, SQLServerException.getErrString("R_sslRequiredNoServerSupport"));
- }
-
- // If we say we don't support SSL and the server doesn't accept unencrypted connections,
- // then terminate the connection.
- if (TDS.ENCRYPT_NOT_SUP == requestedEncryptionLevel &&
- TDS.ENCRYPT_NOT_SUP != negotiatedEncryptionLevel)
- {
- // If the server required an encrypted connection then terminate with an appropriate error.
- if (TDS.ENCRYPT_REQ == negotiatedEncryptionLevel)
- terminate(SQLServerException.DRIVER_ERROR_SSL_FAILED, SQLServerException.getErrString("R_sslRequiredByServer"));
-
- connectionlogger.warning(toString() +
- " Client requested encryption level: " + TDS.getEncryptionLevel(requestedEncryptionLevel) +
- " Server returned unexpected encryption level: " + TDS.getEncryptionLevel(negotiatedEncryptionLevel));
- throwInvalidTDS();
- }
- break;
-
- case TDS.B_PRELOGIN_OPTION_FEDAUTHREQUIRED:
- // Only 0x00 and 0x01 are accepted values from the server.
- if (0 != preloginResponse[optionOffset] && 1 != preloginResponse[optionOffset]) {
- connectionlogger.severe(toString() + " Server sent an unexpected value for FedAuthRequired PreLogin Option. Value was " + preloginResponse[optionOffset]);
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_FedAuthRequiredPreLoginResponseInvalidValue"));
- throw new SQLServerException(form.format(new Object[] {preloginResponse[optionOffset]}), null);
- }
-
- // We must NOT use the response for the FEDAUTHREQUIRED PreLogin option, if the connection string option
- // was not using the new Authentication keyword or in other words, if Authentication=NotSpecified
- // Or AccessToken is not null, mean token based authentication is used.
- if (((null != authenticationString)
- && (!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())))
- || (null != accessTokenInByte))
- {
- fedAuthRequiredPreLoginResponse = (preloginResponse[optionOffset] == 1 ? true : false);
- }
- break;
-
- default:
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString() + " Ignoring prelogin response option:" + optionToken);
- break;
- }
- }
-
- if (!receivedVersionOption || TDS.ENCRYPT_INVALID == negotiatedEncryptionLevel)
- {
- connectionlogger.warning(toString() + " Prelogin response is missing version and/or encryption option.");
- throwInvalidTDS();
- }
- }
-
- final void throwInvalidTDS() throws SQLServerException
- {
- terminate(SQLServerException.DRIVER_ERROR_INVALID_TDS, SQLServerException.getErrString("R_invalidTDS"));
- }
-
- final void throwInvalidTDSToken(String tokenName) throws SQLServerException
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unexpectedToken"));
- Object[] msgArgs = {tokenName};
- String message = SQLServerException.getErrString("R_invalidTDS") + form.format(msgArgs);
- terminate(SQLServerException.DRIVER_ERROR_INVALID_TDS, message);
- }
-
- /**
- * Terminates the connection and throws an exception detailing the reason for termination.
- *
- * This method is similar to SQLServerException.makeFromDriverError, except that it always terminates
- * the connection, and does so with the appropriate state code.
- */
- final void terminate(int driverErrorCode, String message) throws SQLServerException
- {
- terminate(driverErrorCode, message, null);
- }
-
- final void terminate(int driverErrorCode, String message, Throwable throwable) throws SQLServerException
- {
- String state =
- this.state.equals(State.Opened) ?
- SQLServerException.EXCEPTION_XOPEN_CONNECTION_FAILURE :
- SQLServerException.EXCEPTION_XOPEN_CONNECTION_CANT_ESTABLISH;
-
- if (!xopenStates)
- state = SQLServerException.mapFromXopen(state);
-
- SQLServerException ex =
- new SQLServerException(
- this,
- SQLServerException.checkAndAppendClientConnId(message, this),
- state, // X/Open or SQL99 SQLState
- 0, // database error number (0 -> driver error)
- true); // include stack trace in log
-
- if (null != throwable)
- ex.initCause(throwable);
-
- ex.setDriverErrorCode(driverErrorCode);
-
- notifyPooledConnection(ex);
-
- close();
-
- throw ex;
- }
-
- private final Object schedulerLock = new Object();
-
- /**
- * Executes a command through the scheduler.
- *
- * @param newCommand the command to execute
- */
- boolean executeCommand(TDSCommand newCommand) throws SQLServerException
- {
- synchronized (schedulerLock)
- {
- // Detach (buffer) the response from any previously executing
- // command so that we can execute the new command.
- //
- // Note that detaching the response does not process it. Detaching just
- // buffers the response off of the wire to clear the TDS channel.
- if (null != currentCommand)
- {
- currentCommand.detach();
- currentCommand = null;
- }
-
- // The implementation of this scheduler is pretty simple...
- // Since only one command at a time may use a connection
- // (to avoid TDS protocol errors), just synchronize to
- // serialize command execution.
- boolean commandComplete = false;
- try
- {
- commandComplete = newCommand.execute(
- tdsChannel.getWriter(),
- tdsChannel.getReader(newCommand));
- }
- finally
- {
- // We should never displace an existing currentCommand
- //assert null == currentCommand;
-
- // If execution of the new command left response bytes on the wire
- // (e.g. a large ResultSet or complex response with multiple results)
- // then remember it as the current command so that any subsequent call
- // to executeCommand will detach it before executing another new command.
- if (!commandComplete && !isSessionUnAvailable())
- currentCommand = newCommand;
- }
-
- return commandComplete;
- }
- }
-
- void resetCurrentCommand() throws SQLServerException
- {
- if (null != currentCommand)
- {
- currentCommand.detach();
- currentCommand = null;
- }
- }
-
- /*
- * Executes a connection-level command
- */
- private void connectionCommand(String sql, String logContext) throws SQLServerException
- {
- final class ConnectionCommand extends UninterruptableTDSCommand
- {
- final String sql;
-
- ConnectionCommand(
- String sql,
- String logContext)
- {
- super(logContext);
- this.sql = sql;
- }
-
- final boolean doExecute() throws SQLServerException
- {
- startRequest(TDS.PKT_QUERY).writeString(sql);
- TDSParser.parse(startResponse(), getLogContext());
- return true;
- }
- }
-
- executeCommand(new ConnectionCommand(sql, logContext));
- }
-
- /**
- * Build the syntax to initialize the connection at the database side.
- * @return the syntax string
- */
- /*L0*/ private String sqlStatementToInitialize() {
- String s = null;
- if (nLockTimeout > -1)
- s = " set lock_timeout "+nLockTimeout;
- return s;
- }
-
- /**
- * Return the syntax to set the database calatog to use.
- * @param sDB the new catalog
- * @return the required syntax
- */
- /*L0*/ void setCatalogName(String sDB)
- {
- if (sDB != null)
- {
- if (sDB.length() > 0)
- {
- sCatalog = sDB;
- }
- }
- }
-
- /**
- * Return the syntax to set the database isolation level.
- * @return the required syntax
- */
- /*L0*/ String sqlStatementToSetTransactionIsolationLevel() throws SQLServerException {
- String sql = "set transaction isolation level ";
-
- switch (transactionIsolationLevel) {
- case Connection.TRANSACTION_READ_UNCOMMITTED: {
- sql = sql + " read uncommitted ";
- break;
- }
- case Connection.TRANSACTION_READ_COMMITTED: {
- sql = sql + " read committed ";
- break;
- }
- case Connection.TRANSACTION_REPEATABLE_READ: {
- sql = sql + " repeatable read ";
- break;
- }
- case Connection.TRANSACTION_SERIALIZABLE: {
- sql = sql + " serializable ";
- break;
- }
- case SQLServerConnection.TRANSACTION_SNAPSHOT:
- {
- sql = sql + " snapshot ";
- break;
- }
- default: {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidTransactionLevel"));
- Object[] msgArgs = {Integer.toString(transactionIsolationLevel)};
- SQLServerException.makeFromDriverError(this, this,
- form.format(msgArgs), null, false);
- }
- }
- return sql;
- }
-
- /**
- * Return the syntax to set the database commit mode.
- * @return the required syntax
- */
- static String sqlStatementToSetCommit(boolean autoCommit)
- {
- return (true == autoCommit) ?
- "set implicit_transactions off " :
- "set implicit_transactions on ";
- }
-
-
- /*L0*/ public Statement createStatement() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "createStatement");
- Statement st = createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
- loggerExternal.exiting(getClassNameLogging(), "createStatement", st);
- return st;
- }
-
- /*L0*/ public PreparedStatement prepareStatement(String sql) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "prepareStatement", sql);
- PreparedStatement pst = prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
- loggerExternal.exiting(getClassNameLogging(), "prepareStatement", pst);
- return pst;
- }
-
- /*L0*/ public CallableStatement prepareCall(String sql) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "prepareCall", sql);
- CallableStatement st = prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
- loggerExternal.exiting(getClassNameLogging(), "prepareCall", st);
- return st;
- }
-
- /*L0*/ public String nativeSQL(String sql) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "nativeSQL", sql);
- checkClosed();
- loggerExternal.exiting(getClassNameLogging(), "nativeSQL", sql);
- return sql;
- }
-
- public void setAutoCommit(boolean newAutoCommitMode) throws SQLServerException
- {
- if(loggerExternal.isLoggable(Level.FINER))
- {
- loggerExternal.entering(getClassNameLogging(), "setAutoCommit", Boolean.valueOf(newAutoCommitMode));
- if(Util.IsActivityTraceOn())
- loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
- }
- String commitPendingTransaction = "";
- checkClosed();
-
- if (newAutoCommitMode == databaseAutoCommitMode) // No Change
- return;
-
- // When changing to auto-commit from inside an existing transaction,
- // commit that transaction first.
- if (newAutoCommitMode == true)
- commitPendingTransaction = "IF @@TRANCOUNT > 0 COMMIT TRAN ";
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.finer(
- toString()+ " Autocommitmode current :" +databaseAutoCommitMode +
- " new: " +newAutoCommitMode);
- }
-
- rolledBackTransaction = false;
- connectionCommand(commitPendingTransaction + sqlStatementToSetCommit(newAutoCommitMode), "setAutoCommit");
- databaseAutoCommitMode = newAutoCommitMode;
- loggerExternal.exiting(getClassNameLogging(), "setAutoCommit");
- }
-
- /*L0*/ public boolean getAutoCommit() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "getAutoCommit");
- checkClosed();
- boolean res = !inXATransaction && databaseAutoCommitMode;
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.exiting(getClassNameLogging(), "getAutoCommit", Boolean.valueOf(res));
- return res;
- }
-
-
- /*LO*/ final byte[] getTransactionDescriptor()
- {
- return transactionDescriptor;
- }
-
- /**
- * Commit a transcation.
- * Per our transaction spec, see also SDT#410729, a commit in autocommit mode = true is a NO-OP.
- *
- * @throws SQLServerException if no transaction exists.
- */
- public void commit() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "commit");
- if(loggerExternal.isLoggable(Level.FINER) && Util.IsActivityTraceOn())
- {
- loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
- }
-
- checkClosed();
- if (!databaseAutoCommitMode)
- connectionCommand("IF @@TRANCOUNT > 0 COMMIT TRAN", "Connection.commit");
- loggerExternal.exiting(getClassNameLogging(), "commit");
- }
-
- /**
- * Rollback a transcation.
- *
- * @throws SQLServerException if no transaction exists or if the connection is in auto-commit mode.
- */
- public void rollback() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "rollback");
- if(loggerExternal.isLoggable(Level.FINER) && Util.IsActivityTraceOn())
- {
- loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
- }
- checkClosed();
-
- if (databaseAutoCommitMode)
- {
- SQLServerException.makeFromDriverError(
- this,
- this,
- SQLServerException.getErrString("R_cantInvokeRollback"), null, true);
- }
- else
- connectionCommand("IF @@TRANCOUNT > 0 ROLLBACK TRAN", "Connection.rollback");
- loggerExternal.exiting(getClassNameLogging(), "rollback");
- }
-
- public void abort(Executor executor) throws SQLException
- {
- loggerExternal.entering(getClassNameLogging(), "abort", executor);
-
- DriverJDBCVersion.checkSupportsJDBC41();
-
- // nop if connection is closed
- if (isClosed())
- return;
-
- if (null == executor)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidArgument"));
- Object[] msgArgs = {"executor"};
- SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, false);
- }
-
- // check for callAbort permission
- SecurityManager secMgr = System.getSecurityManager();
- if (secMgr != null)
- {
- try
- {
- SQLPermission perm = new SQLPermission(callAbortPerm);
- secMgr.checkPermission(perm);
- }
- catch (SecurityException ex)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_permissionDenied"));
- Object[] msgArgs = {callAbortPerm};
- SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, true);
- }
- }
-
- setState(State.Closed);
-
- executor.execute(new Runnable() {
- public void run()
- {
- if (null != tdsChannel)
- {
- tdsChannel.close();
- }
- }
- });
-
- loggerExternal.exiting(getClassNameLogging(), "abort");
- }
-
- public void close() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "close");
-
- // Always report the connection as closed for any further use, no matter
- // what happens when we try to clean up the physical resources associated
- // with the connection.
- setState(State.Closed);
-
- // Close the TDS channel. When the channel is closed, the server automatically
- // rolls back any pending transactions and closes associated resources like
- // prepared handles.
- if (null != tdsChannel)
- {
- tdsChannel.close();
- }
- loggerExternal.exiting(getClassNameLogging(), "close");
- }
-
- // This function is used by the proxy for notifying the pool manager that this connection proxy is closed
- // This event will pool the connection
- final void poolCloseEventNotify() throws SQLServerException
- {
- if(state.equals(State.Opened) && null != pooledConnectionParent)
- {
- //autocommit = true => nothing to do when app closes connection
- //XA = true => the transaction manager is the only one who can invoke transactional APIs
-
- //Non XA and autocommit off =>
- //If there is a pending BEGIN TRAN from the last commit or rollback, dont propagate it to
- //the next allocated connection.
- //Also if the app closes a connection handle before committing or rolling back the uncompleted
- //transaction may lock other updates/queries so close the transaction now.
- if (!databaseAutoCommitMode && !(pooledConnectionParent instanceof XAConnection))
- {
- connectionCommand("IF @@TRANCOUNT > 0 ROLLBACK TRAN" /*+close connection*/,
- "close connection");
- }
- notifyPooledConnection(null);
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.finer( toString() + " Connection closed and returned to connection pool");
- }
- }
-
- }
-
-
- /*L0*/ public boolean isClosed() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "isClosed");
- loggerExternal.exiting(getClassNameLogging(), "isClosed", Boolean.valueOf(isSessionUnAvailable()));
- return isSessionUnAvailable();
- }
-
- /*L0*/ public DatabaseMetaData getMetaData() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "getMetaData");
- checkClosed();
- if (databaseMetaData == null)
- {
- databaseMetaData = new SQLServerDatabaseMetaData(this);
- }
- loggerExternal.exiting(getClassNameLogging(), "getMetaData", databaseMetaData);
- return databaseMetaData;
- }
-
- /*L0*/ public void setReadOnly (boolean readOnly) throws SQLServerException
- {
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "setReadOnly", Boolean.valueOf(readOnly));
- checkClosed();
- // do nothing per spec
- loggerExternal.exiting(getClassNameLogging(), "setReadOnly");
- }
-
- /*L0*/ public boolean isReadOnly() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "isReadOnly");
- checkClosed();
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.exiting(getClassNameLogging(), "isReadOnly", Boolean.valueOf(false));
- return false;
- }
-
- /*L0*/ public void setCatalog(String catalog) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "setCatalog", catalog);
- if(loggerExternal.isLoggable(Level.FINER) && Util.IsActivityTraceOn())
- {
- loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
- }
- checkClosed();
- if (catalog != null)
- {
- connectionCommand("use " + Util.escapeSQLId(catalog), "setCatalog");
- sCatalog = catalog;
- }
- loggerExternal.exiting(getClassNameLogging(), "setCatalog");
- }
-
- /*L0*/ public String getCatalog() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "getCatalog");
- checkClosed();
- loggerExternal.exiting(getClassNameLogging(), "getCatalog", sCatalog);
- return sCatalog;
- }
-
- /*L0*/ public void setTransactionIsolation(int level) throws SQLServerException
- {
- if(loggerExternal.isLoggable(Level.FINER))
- {
- loggerExternal.entering(getClassNameLogging(), "setTransactionIsolation", new Integer(level));
- if(Util.IsActivityTraceOn())
- {
- loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString());
- }
- }
-
- checkClosed();
- if (level == Connection.TRANSACTION_NONE)
- return;
- String sql;
- transactionIsolationLevel = level;
- sql = sqlStatementToSetTransactionIsolationLevel();
- connectionCommand(sql, "setTransactionIsolation");
- loggerExternal.exiting(getClassNameLogging(), "setTransactionIsolation");
- }
-
- /*L0*/ public int getTransactionIsolation() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "getTransactionIsolation");
- checkClosed();
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.exiting(getClassNameLogging(), "getTransactionIsolation", new Integer(transactionIsolationLevel));
- return transactionIsolationLevel;
- }
-
- volatile SQLWarning sqlWarnings; //the SQL warnings chain
- Object warningSynchronization = new Object();
-
- // Think about returning a copy when we implement additional warnings.
- /*L0*/ public SQLWarning getWarnings() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "getWarnings");
- checkClosed();
- // check null warn wont crash
- loggerExternal.exiting(getClassNameLogging(), "getWarnings", sqlWarnings);
- return sqlWarnings;
- }
-
- // Any changes to SQLWarnings should be synchronized.
- private void addWarning(String warningString )
- {
- synchronized(warningSynchronization)
- {
- SQLWarning warning =
- new SQLWarning(warningString);
-
- if (null == sqlWarnings)
- {
- sqlWarnings = warning;
- }
- else
- {
- sqlWarnings.setNextWarning(warning);
- }
- }
- }
-
- /*L2*/ public void clearWarnings() throws SQLServerException
- {
- synchronized(warningSynchronization)
- {
- loggerExternal.entering(getClassNameLogging(), "clearWarnings");
- checkClosed();
- sqlWarnings=null;
- loggerExternal.exiting(getClassNameLogging(), "clearWarnings");
- }
- }
-
- //--------------------------JDBC 2.0-----------------------------
- public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLServerException
- {
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "createStatement", new Object[]{new Integer(resultSetType), new Integer(resultSetConcurrency)});
- checkClosed();
- Statement st = new SQLServerStatement(this, resultSetType, resultSetConcurrency, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
- loggerExternal.exiting(getClassNameLogging(), "createStatement", st);
- return st;
- }
-
- public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLServerException
- {
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "prepareStatement", new Object[]{sql, new Integer(resultSetType), new Integer(resultSetConcurrency)});
- checkClosed();
-
- PreparedStatement st = null;
-
- if(Util.use42Wrapper()){
- st = new SQLServerPreparedStatement42(
- this, sql, resultSetType, resultSetConcurrency,
- SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
- }
- else{
- st = new SQLServerPreparedStatement(
- this, sql, resultSetType, resultSetConcurrency,
- SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
- }
-
- loggerExternal.exiting(getClassNameLogging(), "prepareStatement", st);
- return st;
- }
-
- private PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException
- {
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "prepareStatement", new Object[]{sql, new Integer(resultSetType), new Integer(resultSetConcurrency), stmtColEncSetting});
- checkClosed();
-
- PreparedStatement st = null;
-
- if(Util.use42Wrapper()){
- st = new SQLServerPreparedStatement42(
- this, sql, resultSetType, resultSetConcurrency, stmtColEncSetting);
- }
- else{
- st = new SQLServerPreparedStatement(
- this, sql, resultSetType, resultSetConcurrency, stmtColEncSetting);
- }
-
- loggerExternal.exiting(getClassNameLogging(), "prepareStatement", st);
- return st;
- }
-
- public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLServerException
- {
- if(loggerExternal.isLoggable(Level.FINER))
- loggerExternal.entering(getClassNameLogging(), "prepareCall", new Object[]{sql, new Integer(resultSetType), new Integer(resultSetConcurrency)});
- checkClosed();
-
- CallableStatement st = null;
-
- if(Util.use42Wrapper()){
- st = new SQLServerCallableStatement42(
- this,
- sql,
- resultSetType,
- resultSetConcurrency,
- SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
- }
- else{
- st = new SQLServerCallableStatement(
- this,
- sql,
- resultSetType,
- resultSetConcurrency,
- SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
- }
-
- loggerExternal.exiting(getClassNameLogging(), "prepareCall", st);
- return st;
- }
-
- /*L2*/ public void setTypeMap(java.util.Map> map) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "setTypeMap", map);
- checkClosed();
- if(map != null && (map instanceof java.util.HashMap))
- {
- // we return an empty Hash map if the user gives this back make sure we accept it.
- if(map.isEmpty())
- {
- loggerExternal.exiting(getClassNameLogging(), "setTypeMap");
- return;
- }
-
- }
- NotImplemented();
- }
-
- public java.util.Map> getTypeMap() throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "getTypeMap");
- checkClosed();
- java.util.Map> mp = new java.util.HashMap>();
- loggerExternal.exiting(getClassNameLogging(), "getTypeMap", mp);
- return mp;
- }
-
- /* ---------------------- Logon --------------------------- */
-
- int writeAEFeatureRequest (boolean write, TDSWriter tdsWriter) throws SQLServerException /* if false just calculates the length */
- {
- // This includes the length of the terminator byte. If there are other extension features, re-adjust accordingly.
- int len = 6; // (1byte = featureID, 4bytes = featureData length, 1 bytes = Version)
-
- if (write)
- {
- tdsWriter.writeByte((byte) TDS.TDS_FEATURE_EXT_AE); //FEATUREEXT_TCE
- tdsWriter.writeInt(1);
- tdsWriter.writeByte((byte) TDS.MAX_SUPPORTED_TCE_VERSION);
- }
- return len;
- }
-
- int writeFedAuthFeatureRequest(boolean write, TDSWriter tdsWriter, FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData) throws SQLServerException { /* if false just calculates the length */
-
- assert (fedAuthFeatureExtensionData.libraryType == TDS.TDS_FEDAUTH_LIBRARY_ADAL || fedAuthFeatureExtensionData.libraryType == TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN);
-
- int dataLen = 0;
- int totalLen = 0;
-
- // set dataLen and totalLen
- switch (fedAuthFeatureExtensionData.libraryType) {
- case TDS.TDS_FEDAUTH_LIBRARY_ADAL:
- dataLen = 2; // length of feature data = 1 byte for library and echo + 1 byte for workflow
- break;
- case TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN:
- assert null != fedAuthFeatureExtensionData.accessToken;
- dataLen = 1 + 4 + fedAuthFeatureExtensionData.accessToken.length; // length of feature data = 1 byte for library and echo, security token length and sizeof(int) for token lengh itself
- break;
- default:
- assert(false); //Unrecognized library type for fedauth feature extension request"
- break;
- }
-
- totalLen = dataLen + 5; // length of feature id (1 byte), data length field (4 bytes), and feature data (dataLen)
-
- // write feature id
- if (write) {
- tdsWriter.writeByte((byte) TDS.TDS_FEATURE_EXT_FEDAUTH); //FEATUREEXT_TCE
-
- // set options
- byte options = 0x00;
-
- // set upper 7 bits of options to indicate fed auth library type
- switch (fedAuthFeatureExtensionData.libraryType) {
- case TDS.TDS_FEDAUTH_LIBRARY_ADAL:
- assert federatedAuthenticationInfoRequested == true;
- options |= TDS.TDS_FEDAUTH_LIBRARY_ADAL << 1;
- break;
- case TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN:
- assert federatedAuthenticationRequested == true;
- options |= TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN << 1;
- break;
- default:
- assert(false); //Unrecognized library type for fedauth feature extension request
- break;
- }
-
- options |= (byte)(fedAuthFeatureExtensionData.fedAuthRequiredPreLoginResponse == true ? 0x01 : 0x00);
-
- // write FeatureDataLen
- tdsWriter.writeInt(dataLen);
-
- // write FeatureData
- //write option
- tdsWriter.writeByte(options);
-
- // write workflow for FedAuthLibrary.ADAL
- // write accessToken for FedAuthLibrary.SecurityToken
- switch (fedAuthFeatureExtensionData.libraryType) {
- case TDS.TDS_FEDAUTH_LIBRARY_ADAL:
- byte workflow = 0x00;
- switch (fedAuthFeatureExtensionData.authentication) {
- case ActiveDirectoryPassword:
- workflow = TDS.ADALWORKFLOW_ACTIVEDIRECTORYPASSWORD;
- break;
- case ActiveDirectoryIntegrated:
- workflow = TDS.ADALWORKFLOW_ACTIVEDIRECTORYINTEGRATED;
- break;
- default:
- assert(false); //Unrecognized Authentication type for fedauth ADAL request
- break;
- }
-
- tdsWriter.writeByte(workflow);
- break;
- case TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN:
- tdsWriter.writeInt(fedAuthFeatureExtensionData.accessToken.length);
- tdsWriter.writeBytes(fedAuthFeatureExtensionData.accessToken, 0, fedAuthFeatureExtensionData.accessToken.length);
- break;
- default:
- assert(false); //Unrecognized FedAuthLibrary type for feature extension request
- break;
- }
- }
- return totalLen;
- }
-
- private final class LogonCommand extends UninterruptableTDSCommand
- {
- LogonCommand()
- {
- super("logon");
- }
-
- final boolean doExecute() throws SQLServerException
- {
- logon(this);
- return true;
- }
- }
-
- /*L0*/ private void logon(LogonCommand command) throws SQLServerException
- {
- SSPIAuthentication authentication = null;
- if(integratedSecurity && AuthenticationScheme.nativeAuthentication == intAuthScheme)
- authentication = new AuthenticationJNI(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber());
- if (integratedSecurity && AuthenticationScheme.javaKerberos == intAuthScheme)
- authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber());
-
- // If the workflow being used is Active Directory Password or Active Directory Integrated and server's prelogin response
- // for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth Feature Extension
- // in Login7, indicating the intent to use Active Directory Authentication Library for SQL Server.
- if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())
- || (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()) && fedAuthRequiredPreLoginResponse)) {
- federatedAuthenticationInfoRequested = true;
- fedAuthFeatureExtensionData = new FederatedAuthenticationFeatureExtensionData(
- TDS.TDS_FEDAUTH_LIBRARY_ADAL,
- authenticationString,
- fedAuthRequiredPreLoginResponse);
- }
-
- if (null != accessTokenInByte) {
- fedAuthFeatureExtensionData = new FederatedAuthenticationFeatureExtensionData(
- TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN,
- fedAuthRequiredPreLoginResponse,
- accessTokenInByte
- );
- // No need any further info from the server for token based authentication. So set _federatedAuthenticationRequested to true
- federatedAuthenticationRequested = true;
- }
-
- try
- {
- sendLogon(command, authentication, fedAuthFeatureExtensionData);
-
- //If we got routed in the current attempt,
- //the server closes the connection. So, we should not
- //be sending anymore commands to the server in that case.
- if(!isRoutedInCurrentAttempt)
- {
- originalCatalog = sCatalog;
- String sqlStmt = sqlStatementToInitialize();
- if(sqlStmt != null)
- {
- connectionCommand(sqlStmt, "Change Settings");
- }
- }
- }
- finally
- {
- if(integratedSecurity)
- {
- if(null != authentication)
- authentication.ReleaseClientContext();
- authentication = null;
- }
- }
- }
-
-
- private static final int ENVCHANGE_DATABASE = 1;
- private static final int ENVCHANGE_LANGUAGE = 2;
- private static final int ENVCHANGE_CHARSET = 3;
- private static final int ENVCHANGE_PACKETSIZE = 4;
- private static final int ENVCHANGE_SORTLOCALEID = 5;
- private static final int ENVCHANGE_SORTFLAGS = 6;
- private static final int ENVCHANGE_SQLCOLLATION = 7;
- private static final int ENVCHANGE_XACT_BEGIN = 8;
- private static final int ENVCHANGE_XACT_COMMIT = 9;
- private static final int ENVCHANGE_XACT_ROLLBACK = 10;
- private static final int ENVCHANGE_DTC_ENLIST = 11;
- private static final int ENVCHANGE_DTC_DEFECT = 12;
- private static final int ENVCHANGE_CHANGE_MIRROR = 13;
- private static final int ENVCHANGE_UNUSED_14 = 14;
- private static final int ENVCHANGE_DTC_PROMOTE = 15;
- private static final int ENVCHANGE_DTC_MGR_ADDR = 16;
- private static final int ENVCHANGE_XACT_ENDED = 17;
- private static final int ENVCHANGE_RESET_COMPLETE = 18;
- private static final int ENVCHANGE_USER_INFO = 19;
- private static final int ENVCHANGE_ROUTING = 20;
-
- final void processEnvChange(TDSReader tdsReader) throws SQLServerException
- {
- tdsReader.readUnsignedByte(); // token type
- final int envValueLength = tdsReader.readUnsignedShort();
-
- TDSReaderMark mark = tdsReader.mark();
- int envchange = tdsReader.readUnsignedByte();
- switch (envchange)
- {
- case ENVCHANGE_PACKETSIZE:
- // Set NEW value as new TDS packet size
- try
- {
- tdsPacketSize = Integer.parseInt(tdsReader.readUnicodeString(tdsReader.readUnsignedByte()));
- }
- catch (NumberFormatException e)
- {
- tdsReader.throwInvalidTDS();
- }
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString()+ " Network packet size is " + tdsPacketSize + " bytes");
- break;
-
- case ENVCHANGE_SQLCOLLATION:
- if (SQLCollation.tdsLength() != tdsReader.readUnsignedByte())
- tdsReader.throwInvalidTDS();
-
- try
- {
- databaseCollation = new SQLCollation(tdsReader);
- }
- catch (UnsupportedEncodingException e)
- {
- terminate(SQLServerException.DRIVER_ERROR_INVALID_TDS, e.getMessage(), e);
- }
-
- break;
-
- case ENVCHANGE_DTC_ENLIST:
- case ENVCHANGE_XACT_BEGIN:
- rolledBackTransaction = false;
- byte[] transactionDescriptor = getTransactionDescriptor();
-
- if (transactionDescriptor.length != tdsReader.readUnsignedByte())
- tdsReader.throwInvalidTDS();
-
- tdsReader.readBytes(transactionDescriptor, 0, transactionDescriptor.length);
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- String op;
- if (ENVCHANGE_XACT_BEGIN == envchange)
- op = " started";
- else
- op = " enlisted";
-
- connectionlogger.finer(
- toString() + op);
- }
- break;
-
- case ENVCHANGE_XACT_ROLLBACK:
- rolledBackTransaction = true;
-
- if(inXATransaction)
- {
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString() + " rolled back. (DTC)");
-
- // Do not clear the transaction descriptor if the connection is in DT.
- // For a DTC transaction, a ENV_ROLLBACKTRAN token won't cleanup the xactID previously cached on the connection
- // because user is required to explicitly un-enlist/defect a connection from a DTC.
- // A ENV_DEFECTTRAN token though will clean the DTC xactID on the connection.
- }
- else
- {
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString() + " rolled back");
-
-
- Arrays.fill(getTransactionDescriptor(), (byte)0);
- }
-
- break;
-
- case ENVCHANGE_XACT_COMMIT:
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString() + " committed");
-
- Arrays.fill(getTransactionDescriptor(), (byte)0);
-
- break;
-
- case ENVCHANGE_DTC_DEFECT:
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString() + " defected");
-
- Arrays.fill(getTransactionDescriptor(), (byte)0);
-
- break;
-
- case ENVCHANGE_DATABASE:
- setCatalogName(tdsReader.readUnicodeString(tdsReader.readUnsignedByte()));
- break;
-
- case ENVCHANGE_CHANGE_MIRROR:
- setFailoverPartnerServerProvided(tdsReader.readUnicodeString(tdsReader.readUnsignedByte()));
- break;
- // Skip unsupported, ENVCHANGES
- case ENVCHANGE_LANGUAGE:
- case ENVCHANGE_CHARSET:
- case ENVCHANGE_SORTLOCALEID:
- case ENVCHANGE_SORTFLAGS:
- case ENVCHANGE_DTC_PROMOTE:
- case ENVCHANGE_DTC_MGR_ADDR:
- case ENVCHANGE_XACT_ENDED:
- case ENVCHANGE_RESET_COMPLETE:
- case ENVCHANGE_USER_INFO:
- if (connectionlogger.isLoggable(Level.FINER))
- connectionlogger.finer(toString()+ " Ignored env change: " + envchange);
- break;
- case ENVCHANGE_ROUTING:
-
- //initialize to invalid values
- int routingDataValueLength, routingProtocol, routingPortNumber, routingServerNameLength;
- routingDataValueLength = routingProtocol = routingPortNumber = routingServerNameLength = -1;
-
- String routingServerName = null;
-
- try
- {
- routingDataValueLength = tdsReader.readUnsignedShort();
- if(routingDataValueLength <= 5)//(5 is the no of bytes in protocol + port number+ length field of server name)
- {
- throwInvalidTDS();
- }
-
- routingProtocol = tdsReader.readUnsignedByte();
- if(routingProtocol != 0)
- {
- throwInvalidTDS();
- }
-
- routingPortNumber = tdsReader.readUnsignedShort();
- if(routingPortNumber <=0 || routingPortNumber > 65535)
- {
- throwInvalidTDS();
- }
-
- routingServerNameLength = tdsReader.readUnsignedShort();
- if(routingServerNameLength <= 0 || routingServerNameLength > 1024)
- {
- throwInvalidTDS();
- }
-
- routingServerName = tdsReader.readUnicodeString(routingServerNameLength);
- assert routingServerName != null;
-
- }
- finally
- {
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.finer(toString()+ " Received routing ENVCHANGE with the following values."
- + " routingDataValueLength:" + routingDataValueLength
- + " protocol:" + routingProtocol
- + " portNumber:" + routingPortNumber
- + " serverNameLength:" + routingServerNameLength
- + " serverName:" + ((routingServerName!=null)?routingServerName:"null")
- );
- }
- }
-
- // Check if the hostNameInCertificate needs to be updated to handle the rerouted subdomain in Azure
- String currentHostName = activeConnectionProperties.getProperty("hostNameInCertificate");
- if(null != currentHostName && currentHostName.startsWith("*")
- && (null != routingServerName) /*skip the check for hostNameInCertificate if routingServerName is null*/
- && routingServerName.indexOf('.') != -1)
- {
- char[] currentHostNameCharArray = currentHostName.toCharArray();
- char[] routingServerNameCharArray = routingServerName.toCharArray();
- boolean hostNameNeedsUpdate = true;
-
- /*
- * Check if routingServerName and hostNameInCertificate are from same domain by verifying each
- * character in currentHostName from last until it reaches the character before the wildcard symbol
- * (i.e. currentHostNameCharArray[1])
- */
- for(int i = currentHostName.length()-1, j = routingServerName.length()-1; i>0 && j>0; i--,j--)
- {
- if(routingServerNameCharArray[j] != currentHostNameCharArray[i])
- {
- hostNameNeedsUpdate = false;
- break;
- }
- }
-
- if(hostNameNeedsUpdate)
- {
- String newHostName = "*" + routingServerName.substring(routingServerName.indexOf('.'));
- activeConnectionProperties.setProperty("hostNameInCertificate", newHostName);
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.finer(toString() + "Using new host to validate the SSL certificate");
- }
- }
- }
-
- isRoutedInCurrentAttempt = true;
- routingInfo = new ServerPortPlaceHolder(routingServerName, routingPortNumber, null, integratedSecurity);
-
- break;
-
- // Error on unrecognized, unused ENVCHANGES
- default:
- connectionlogger.warning(toString() + " Unknown environment change: " + envchange);
- throwInvalidTDS();
- break;
- }
-
- // After extracting whatever value information we need, skip over whatever is left
- // that we're not interested in.
- tdsReader.reset(mark);
- tdsReader.readBytes(new byte[envValueLength], 0, envValueLength);
- }
-
- final void processFedAuthInfo(TDSReader tdsReader, TDSTokenHandler tdsTokenHandler) throws SQLServerException {
- SqlFedAuthInfo sqlFedAuthInfo = new SqlFedAuthInfo();
-
- tdsReader.readUnsignedByte(); // token type, 0xEE
-
- //TdsParser.TryGetTokenLength, for FEDAUTHINFO, it uses TryReadInt32()
- int tokenLen = tdsReader.readInt();
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " FEDAUTHINFO token stream length = " + tokenLen);
- }
-
- if (tokenLen < 4)
- {
- // the token must at least contain a DWORD(length is 4 bytes) indicating the number of info IDs
- connectionlogger.severe(toString() + "FEDAUTHINFO token stream length too short for CountOfInfoIDs.");
- throw new SQLServerException(SQLServerException.getErrString("R_FedAuthInfoLengthTooShortForCountOfInfoIds"), null);
- }
-
- // read how many FedAuthInfo options there are
- int optionsCount = tdsReader.readInt();
-
- tokenLen = tokenLen - 4; // remaining length is shortened since we read optCount, 4 is the size of int
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " CountOfInfoIDs = " + optionsCount);
- }
-
- if (tokenLen > 0) {
- // read the rest of the token
- byte[] tokenData = new byte[tokenLen];
-
- tdsReader.readBytes(tokenData, 0, tokenLen);
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " Read rest of FEDAUTHINFO token stream: " + Arrays.toString(tokenData));
- }
-
- // each FedAuthInfoOpt is 9 bytes:
- // 1 byte for FedAuthInfoID
- // 4 bytes for FedAuthInfoDataLen
- // 4 bytes for FedAuthInfoDataOffset
- // So this is the index in tokenData for the i-th option
- final int optionSize = 9;
-
- // the total number of bytes for all FedAuthInfoOpts together
- int totalOptionsSize = optionsCount * optionSize;
-
- for (int i = 0; i < optionsCount; i++) {
- int currentOptionOffset = i * optionSize;
-
- byte id = tokenData[currentOptionOffset];
- byte[] buffer = new byte[4];
- buffer[3] = tokenData[currentOptionOffset + 1];
- buffer[2] = tokenData[currentOptionOffset + 2];
- buffer[1] = tokenData[currentOptionOffset + 3];
- buffer[0] = tokenData[currentOptionOffset + 4];
- java.nio.ByteBuffer wrapped = java.nio.ByteBuffer.wrap(buffer); // big-endian by default
- int dataLen = wrapped.getInt();
-
- buffer = new byte[4];
- buffer[3] = tokenData[currentOptionOffset + 5];
- buffer[2] = tokenData[currentOptionOffset + 6];
- buffer[1] = tokenData[currentOptionOffset + 7];
- buffer[0] = tokenData[currentOptionOffset + 8];
- wrapped = java.nio.ByteBuffer.wrap(buffer); // big-endian by default
- int dataOffset = wrapped.getInt();
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " FedAuthInfoOpt: ID=" + id + ", DataLen=" + dataLen + ", Offset=" + dataOffset);
- }
-
- // offset is measured from optCount, so subtract to make offset measured
- // from the beginning of tokenData, 4 is the size of int
- dataOffset = dataOffset - 4;
-
- // if dataOffset points to a region within FedAuthInfoOpt or after the end of the token, throw
- if (dataOffset < totalOptionsSize || dataOffset >= tokenLen) {
- connectionlogger.severe(toString() + "FedAuthInfoDataOffset points to an invalid location.");
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_FedAuthInfoInvalidOffset"));
- throw new SQLServerException(form.format(new Object[] {dataOffset}), null);
- }
-
- // try to read data and throw if the arguments are bad, meaning the server sent us a bad token
- String data = null;
- try {
- byte[] dataArray = new byte[dataLen];
- System.arraycopy(tokenData, dataOffset, dataArray, 0, dataLen);
- data = new String(dataArray, UTF_16LE);
- }
- catch(Exception e){
- connectionlogger.severe(toString() + "Failed to read FedAuthInfoData.");
- throw new SQLServerException(SQLServerException.getErrString("R_FedAuthInfoFailedToReadData"), null);
- }
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " FedAuthInfoData: " + data);
- }
-
- // store data in tempFedAuthInfo
- switch (id) {
- case TDS.FEDAUTH_INFO_ID_SPN:
- sqlFedAuthInfo.spn = data;
- break;
- case TDS.FEDAUTH_INFO_ID_STSURL:
- sqlFedAuthInfo.stsurl = data;
- break;
- default:
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " Ignoring unknown federated authentication info option: " + id);
- }
- break;
- }
- }
- }
- else {
- connectionlogger.severe(toString() + "FEDAUTHINFO token stream is not long enough to contain the data it claims to.");
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_FedAuthInfoLengthTooShortForData"));
- throw new SQLServerException(form.format(new Object[] {tokenLen}), null);
- }
-
- if(null == sqlFedAuthInfo.spn || null == sqlFedAuthInfo.stsurl || sqlFedAuthInfo.spn.trim().isEmpty() || sqlFedAuthInfo.stsurl.trim().isEmpty()){
- // We should be receiving both stsurl and spn
- connectionlogger.severe(toString() + "FEDAUTHINFO token stream does not contain both STSURL and SPN.");
- throw new SQLServerException(SQLServerException.getErrString("R_FedAuthInfoDoesNotContainStsurlAndSpn"), null);
- }
-
- onFedAuthInfo(sqlFedAuthInfo, tdsTokenHandler);
- }
-
-
- final class FedAuthTokenCommand extends UninterruptableTDSCommand
- {
- TDSTokenHandler tdsTokenHandler = null;
- SqlFedAuthToken fedAuthToken = null;
- FedAuthTokenCommand(SqlFedAuthToken fedAuthToken, TDSTokenHandler tdsTokenHandler)
- {
- super("FedAuth");
- this.tdsTokenHandler = tdsTokenHandler;
- this.fedAuthToken = fedAuthToken;
- }
-
- final boolean doExecute() throws SQLServerException
- {
- sendFedAuthToken(this, fedAuthToken, tdsTokenHandler);
- return true;
- }
- }
-
- /**
- * Generates (if appropriate) and sends a Federated Authentication Access token to the server,
- * using the Federated Authentication Info.
- */
- void onFedAuthInfo(SqlFedAuthInfo fedAuthInfo, TDSTokenHandler tdsTokenHandler) throws SQLServerException{
- assert (null != activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString())
- && null != activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()))
- || ((authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()) && fedAuthRequiredPreLoginResponse));
- assert null != fedAuthInfo;
-
- attemptRefreshTokenLocked = true;
- fedAuthToken = getFedAuthToken(fedAuthInfo);
- attemptRefreshTokenLocked = false;
-
- //fedAuthToken cannot be null.
- assert null != fedAuthToken;
-
- TDSCommand fedAuthCommand = new FedAuthTokenCommand(fedAuthToken, tdsTokenHandler);
- fedAuthCommand.execute(tdsChannel.getWriter(), tdsChannel.getReader(fedAuthCommand));
- }
-
- private SqlFedAuthToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throws SQLServerException {
- //fedAuthInfo should not be null.
- assert null != fedAuthInfo;
-
- // No:of milliseconds to sleep for the inital back off.
- int sleepInterval = 100;
-
- // No:of attempts, for tracing purposes, if we underwent retries.
- int numberOfAttempts = 0;
-
- FedAuthDllInfo dllInfo = null;
-
- String user = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString());
- String password = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString());
-
- long expirationFileTime = 0;
-
- while(true){
- numberOfAttempts++;
-
- try{
- if(authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())){
- dllInfo = AuthenticationJNI.getAccessToken(user, password, fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(), ActiveDirectoryAuthentication.jdbcFedauthClientId, expirationFileTime);
- }
- else if(authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())){
- dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(), ActiveDirectoryAuthentication.jdbcFedauthClientId, expirationFileTime);
- }
-
- //AccessToken should not be null.
- assert null != dllInfo.accessTokenBytes;
-
- // Break out of the retry loop in successful case.
- break;
- }
- catch(DLLException adalException){
-
- //the sqljdbc_auth.dll return -1 for errorCategory, if unable to load the adalsql.dll
- int errorCategory = adalException.GetCategory();
- if(-1 == errorCategory){
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableLoadADALSqlDll"));
- Object[] msgArgs = {Integer.toHexString(adalException.GetState())};
- throw new SQLServerException(form.format(msgArgs), null);
- }
-
- int millisecondsRemaining = TimerRemaining(timerExpire);
- if (ActiveDirectoryAuthentication.GetAccessTokenTansisentError != errorCategory
- || timerHasExpired(timerExpire) || (sleepInterval >= millisecondsRemaining) ){
-
- String errorStatus = Integer.toHexString(adalException.GetStatus());
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken.AdalException category:" + errorCategory + " error: " + errorStatus);
- }
-
- MessageFormat form1 = new MessageFormat(SQLServerException.getErrString("R_ADALAuthenticationMiddleErrorMessage"));
- String errorCode = Integer.toHexString(adalException.GetStatus()).toUpperCase();
- Object[] msgArgs1 = {errorCode, adalException.GetState()};
- SQLServerException middleException = new SQLServerException(form1.format(msgArgs1), adalException);
-
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution"));
- Object[] msgArgs = {user, authenticationString};
- throw new SQLServerException(form.format(msgArgs), null, 0, middleException);
- }
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken sleeping: " + sleepInterval + " milliseconds.");
- connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken remaining: " + millisecondsRemaining + " milliseconds.");
- }
-
- try {
- Thread.sleep(sleepInterval);
- } catch (InterruptedException e1) {
- // continue if the thread is interrupted. This really should not happen.
- }
- sleepInterval = sleepInterval *2;
- }
- }
-
- byte[] accessTokenFromDLL = dllInfo.accessTokenBytes;
-
- String accessToken = new String(accessTokenFromDLL, UTF_16LE);
-
- SqlFedAuthToken fedAuthToken = new SqlFedAuthToken(accessToken, dllInfo.expiresIn);
-
- return fedAuthToken;
- }
-
- /**
- * Send the access token to the server.
- */
- private void sendFedAuthToken(FedAuthTokenCommand fedAuthCommand, SqlFedAuthToken fedAuthToken, TDSTokenHandler tdsTokenHandler) throws SQLServerException {
- assert null != fedAuthToken;
- assert null != fedAuthToken.accessToken;
-
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " Sending federated authentication token.");
- }
-
- TDSWriter tdsWriter = fedAuthCommand.startRequest(TDS.PKT_FEDAUTH_TOKEN_MESSAGE);
-
- byte[] accessToken = fedAuthToken.accessToken.getBytes(UTF_16LE);
-
- // Send total length (length of token plus 4 bytes for the token length field)
- // If we were sending a nonce, this would include that length as well
- tdsWriter.writeInt(accessToken.length + 4);
-
- // Send length of token
- tdsWriter.writeInt(accessToken.length);
-
- // Send federated authentication access token.
- tdsWriter.writeBytes(accessToken, 0, accessToken.length);
-
- TDSReader tdsReader;
- tdsReader = fedAuthCommand.startResponse();
-
- federatedAuthenticationRequested = true;
-
- TDSParser.parse(tdsReader, tdsTokenHandler);
- }
-
- final void processFeatureExtAck(TDSReader tdsReader) throws SQLServerException
- {
- tdsReader.readUnsignedByte(); // Reading FEATUREEXTACK_TOKEN 0xAE
-
- // read feature ID
- byte featureId;
- do {
- featureId = (byte) tdsReader.readUnsignedByte();
-
- if (featureId != TDS.FEATURE_EXT_TERMINATOR) {
- int dataLen;
- dataLen = tdsReader.readInt();
-
- byte[] data = new byte[dataLen];
- if (dataLen > 0) {
- tdsReader.readBytes(data, 0, dataLen);
- }
- onFeatureExtAck(featureId, data);
- }
- } while (featureId != TDS.FEATURE_EXT_TERMINATOR);
- }
-
- private void onFeatureExtAck(int featureId, byte[] data) throws SQLServerException {
- if (null != routingInfo) {
- return;
- }
-
- switch (featureId) {
- case TDS.TDS_FEATURE_EXT_FEDAUTH: {
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " Received feature extension acknowledgement for federated authentication.");
- }
-
- if (!federatedAuthenticationRequested) {
- connectionlogger.severe(toString() + " Did not request federated authentication.");
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrequestedFeatureAckReceived"));
- Object[] msgArgs = {featureId};
- throw new SQLServerException(form.format(msgArgs), null);
- }
-
- //_fedAuthFeatureExtensionData must not be null when _federatedAuthenticatonRequested == true
- assert null != fedAuthFeatureExtensionData;
-
- switch (fedAuthFeatureExtensionData.libraryType) {
- case TDS.TDS_FEDAUTH_LIBRARY_ADAL:
- case TDS.TDS_FEDAUTH_LIBRARY_SECURITYTOKEN:
- // The server shouldn't have sent any additional data with the ack (like a nonce)
- if (0 != data.length) {
- connectionlogger.severe(toString() + " Federated authentication feature extension ack for ADAL and Security Token includes extra data.");
- throw new SQLServerException(SQLServerException.getErrString("R_FedAuthFeatureAckContainsExtraData"), null);
- }
- break;
-
- default:
- assert false; //Unknown _fedAuthLibrary type
- connectionlogger.severe(toString() + " Attempting to use unknown federated authentication library.");
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_FedAuthFeatureAckUnknownLibraryType"));
- Object[] msgArgs = {fedAuthFeatureExtensionData.libraryType};
- throw new SQLServerException(form.format(msgArgs), null);
- }
- federatedAuthenticationAcknowledged = true;
-
- break;
- }
- case TDS.TDS_FEATURE_EXT_AE: {
- if (connectionlogger.isLoggable(Level.FINER))
- {
- connectionlogger.fine(toString() + " Received feature extension acknowledgement for AE.");
- }
-
- if (1 > data.length) {
- connectionlogger.severe(toString() + " Unknown version number for AE.");
- throw new SQLServerException(SQLServerException.getErrString("R_InvalidAEVersionNumber"), null);
- }
-
- byte supportedTceVersion = data[0];
- if (0 == supportedTceVersion || supportedTceVersion > TDS.MAX_SUPPORTED_TCE_VERSION) {
- connectionlogger.severe(toString() + " Invalid version number for AE.");
- throw new SQLServerException(SQLServerException.getErrString("R_InvalidAEVersionNumber"), null);
- }
-
- assert supportedTceVersion == TDS.MAX_SUPPORTED_TCE_VERSION; //Client support TCE version 1
- serverSupportsColumnEncryption = true;
- break;
- }
-
- default: {
- // Unknown feature ack
- connectionlogger.severe(toString() + " Unknown feature ack.");
- throw new SQLServerException(SQLServerException.getErrString("R_UnknownFeatureAck"), null);
- }
- }
- }
-
- /*
- * Executes a DTC command
- */
- private void executeDTCCommand(int requestType, byte[] payload, String logContext) throws SQLServerException
- {
- final class DTCCommand extends UninterruptableTDSCommand
- {
- private final int requestType;
- private final byte[] payload;
-
- DTCCommand(
- int requestType,
- byte[] payload,
- String logContext)
- {
- super(logContext);
- this.requestType = requestType;
- this.payload = payload;
- }
-
- final boolean doExecute() throws SQLServerException
- {
- TDSWriter tdsWriter = startRequest(TDS.PKT_DTC);
-
- tdsWriter.writeShort((short) requestType);
- if (null == payload)
- {
- tdsWriter.writeShort((short) 0);
- }
- else
- {
- assert payload.length <= Short.MAX_VALUE;
- tdsWriter.writeShort((short) payload.length);
- tdsWriter.writeBytes(payload);
- }
-
- TDSParser.parse(startResponse(), getLogContext());
- return true;
- }
- }
-
- executeCommand(new DTCCommand(requestType, payload, logContext));
- }
-
- /**
- * Unenlist the local transaction with DTC.
- * @throws SQLServerException
- */
- final void JTAUnenlistConnection() throws SQLServerException
- {
- // Unenlist the connection
- executeDTCCommand(TDS.TM_PROPAGATE_XACT, null, "MS_DTC unenlist connection");
- inXATransaction = false;
- }
-
- /**
- * Enlist this connection's local transaction with MS DTC
- * @param cookie the cookie identifying the transaction
- * @throws SQLServerException
- */
- final void JTAEnlistConnection(byte cookie[]) throws SQLServerException
- {
- // Enlist the connection
- executeDTCCommand(TDS.TM_PROPAGATE_XACT, cookie, "MS_DTC enlist connection");
-
- // DTC sets the enlisted connection's isolation level to SERIALIZABLE by default.
- // Set the isolation level the way the app wants it.
- connectionCommand(sqlStatementToSetTransactionIsolationLevel(), "JTAEnlistConnection");
- inXATransaction = true;
- }
-
-
-
-
- /**
- * Convert to a String UCS16 encoding.
- * @param s the string
- * @throws SQLServerException
- * @return the encoded data
- */
- /*L0*/ private byte[] toUCS16(String s) throws SQLServerException {
- if (s==null)
- return new byte[0];
- int l = s.length();
- byte data[] = new byte[l*2];
- int offset=0;
- for (int i=0; i> 8) & 0xFF); // Unicode MSB
- }
- return data;
- }
-
- /**
- * Encrypt a password for the SQL Server logon.
- * @param pwd the password
- * @return the encryption
- */
- /*L0*/ private byte[] encryptPassword(String pwd) {
- // Changed to handle non ascii passwords
- if (pwd == null) pwd="";
- int len = pwd.length();
- byte data[] = new byte[len * 2];
- for (int i1 = 0; i1 < len; i1++) {
- int j1 = pwd.charAt(i1) ^ 0x5a5a;
- j1 = (j1 & 0xf) << 4 | (j1 & 0xf0) >> 4 | (j1 & 0xf00) << 4 | (j1 & 0xf000) >> 4;
- byte b1 = (byte) ((j1 & 0xFF00)>>8);
- data[(i1*2)+1] = b1;
- byte b2 = (byte) ((j1 & 0x00FF));
- data[(i1*2)+0] = b2;
- }
- return data;
- }
-
- /**
- * Send a TDS 7.x logon packet.
- * @param secsTimeout (optional) if non-zero, seconds to wait for logon to be sent.
- * @throws SQLServerException
- */
- private void sendLogon(LogonCommand logonCommand, SSPIAuthentication authentication, FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData) throws SQLServerException
- {
- // TDS token handler class for processing logon responses.
- //
- // Note:
- // As a local inner class, LogonProcessor implicitly has access to private
- // members of SQLServerConnection. Certain JVM implementations generate
- // package scope accessors to any private members touched by this class,
- // effectively changing visibility of such members from private to package.
- // Therefore, it is IMPORTANT then for this class not to touch private
- // member variables in SQLServerConnection that contain secure information.
- final class LogonProcessor extends TDSTokenHandler
- {
- private final SSPIAuthentication auth;
- private byte[] secBlobOut = null;
- StreamLoginAck loginAckToken;
-
- LogonProcessor(SSPIAuthentication auth)
- {
- super("logon");
- this.auth = auth;
- this.loginAckToken = null;
- }
-
- boolean onSSPI(TDSReader tdsReader) throws SQLServerException
- {
- StreamSSPI ack = new StreamSSPI();
- ack.setFromTDS(tdsReader);
-
- // Extract SSPI data from the response. If another round trip is
- // required then we will start it after we finish processing the
- // rest of this response.
- boolean [ ] done = {false};
- secBlobOut = auth.GenerateClientContext(ack.sspiBlob, done);
- return true;
- }
-
- boolean onLoginAck(TDSReader tdsReader) throws SQLServerException
- {
- loginAckToken = new StreamLoginAck();
- loginAckToken.setFromTDS(tdsReader);
- sqlServerVersion = loginAckToken.sSQLServerVersion;
- tdsVersion = loginAckToken.tdsVersion;
- return true;
- }
-
- final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQLServerException
- {
- // If we have the login ack already then we're done processing.
- if (null != loginAckToken)
- return true;
-
- // No login ack yet. Check if there is more SSPI handshake to do...
- if(null != secBlobOut && 0 != secBlobOut.length)
- {
- // Yes, there is. So start the next SSPI round trip and indicate to
- // our caller that it needs to keep the processing loop going.
- logonCommand.startRequest(TDS.PKT_SSPI).writeBytes(secBlobOut, 0, secBlobOut.length);
- return false;
- }
-
- // The login ack comes in its own complete TDS response message.
- // So integrated auth effectively receives more response messages from
- // the server than it sends request messages from the driver.
- // To ensure that the rest of the response can be read, fake another
- // request to the server so that the channel sees int auth login
- // as a symmetric conversation.
- logonCommand.startRequest(TDS.PKT_SSPI);
- logonCommand.onRequestComplete();
- ++tdsChannel.numMsgsSent;
-
- TDSParser.parse(tdsReader, this);
- return true;
- }
- }
-
- //Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option.
- assert !(integratedSecurity && fedAuthRequiredPreLoginResponse);
- //Cannot use both SSPI and FedAuth
- assert (!integratedSecurity) || !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested);
- //fedAuthFeatureExtensionData provided without fed auth feature request
- assert (null == fedAuthFeatureExtensionData) || (federatedAuthenticationInfoRequested || federatedAuthenticationRequested);
- //Fed Auth feature requested without specifying fedAuthFeatureExtensionData.
- assert (null != fedAuthFeatureExtensionData || !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested));
-
- String hostName = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.WORKSTATION_ID.toString());
- String sUser = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString());
- String sPwd = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString());
- String appName = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString());
- String interfaceLibName = "Microsoft JDBC Driver "+ SQLJdbcVersion.major + "." + SQLJdbcVersion.minor;
- String interfaceLibVersion = generateInterfaceLibVersion();
- String databaseName = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.DATABASE_NAME.toString());
- String serverName = null;
- // currentConnectPlaceHolder should not be null here. Still doing the check for extra security.
- if (null != currentConnectPlaceHolder)
- {
- serverName = currentConnectPlaceHolder.getServerName();
- }
- else
- {
- serverName = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.SERVER_NAME.toString());
- }
-
- if(serverName != null && serverName.length() > 128)
- serverName = serverName.substring(0, 128);
-
- if(hostName == null || hostName.length() == 0)
- {
- hostName = Util.lookupHostName();
- }
-
- byte []secBlob = new byte[0];
- boolean [ ] done = {false};
- if(null != authentication)
- {
- secBlob = authentication.GenerateClientContext(secBlob, done);
- sUser = null;
- sPwd = null;
- }
-
- byte hostnameBytes[] = toUCS16(hostName);
- byte userBytes[] = toUCS16(sUser);
- byte passwordBytes[] = encryptPassword(sPwd);
- int passwordLen = passwordBytes != null ? passwordBytes.length : 0;
- byte appNameBytes[] = toUCS16(appName);
- byte serverNameBytes[] = toUCS16(serverName);
- byte interfaceLibNameBytes[] = toUCS16(interfaceLibName);
- byte interfaceLibVersionBytes[] = DatatypeConverter.parseHexBinary(interfaceLibVersion);
- byte databaseNameBytes[] = toUCS16(databaseName);
- byte netAddress[] = new byte[6];
- int len2 = 0;
- int dataLen=0;
-
- final int TDS_LOGIN_REQUEST_BASE_LEN = 94;
-
- if (serverMajorVersion >= 11) //Denali --> TDS 7.4
- {
- tdsVersion = TDS.VER_DENALI;
- }
- else if (serverMajorVersion >= 10) //Katmai (10.0) & later 7.3B
- {
- tdsVersion = TDS.VER_KATMAI;
- }
- else
- if (serverMajorVersion >= 9) // Yukon (9.0) --> TDS 7.2 // Prelogin disconnects anything older
- {
- tdsVersion = TDS.VER_YUKON;
- }
- else // Shiloh (8.x) --> TDS 7.1
- {
- assert false:"prelogin did not disconnect for the old version: " + serverMajorVersion;
- }
-
- TDSWriter tdsWriter = logonCommand.startRequest(TDS.PKT_LOGON70);
-
- len2 = TDS_LOGIN_REQUEST_BASE_LEN +
- hostnameBytes.length +
- appNameBytes.length +
- serverNameBytes.length +
- interfaceLibNameBytes.length +
- databaseNameBytes.length +
- secBlob.length +
- 4;// AE is always on;
-
- // only add lengths of password and username if not using SSPI or requesting federated authentication info
- if(!integratedSecurity && !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested)){
- len2 = len2 + passwordLen + userBytes.length;
- }
-
- int aeOffset = len2;
- // AE is always ON
- len2 += writeAEFeatureRequest(false, tdsWriter);
- if(federatedAuthenticationInfoRequested || federatedAuthenticationRequested)
- {
- len2 = len2 + writeFedAuthFeatureRequest(false, tdsWriter, fedAuthFeatureExtensionData);
- }
-
- len2 = len2 + 1; // add 1 to length becaue of FeatureEx terminator
-
- // Length of entire Login 7 packet
- tdsWriter.writeInt(len2);
- tdsWriter.writeInt(tdsVersion);
- tdsWriter.writeInt(requestedPacketSize);
- tdsWriter.writeBytes(interfaceLibVersionBytes); //writeBytes() is little endian
- tdsWriter.writeInt(0); // Client process ID (0 = ??)
- tdsWriter.writeInt(0); // Primary server connection ID
-
- tdsWriter.writeByte((byte)( // OptionFlags1:
- TDS.LOGIN_OPTION1_ORDER_X86 | // X86 byte order for numeric & datetime types
- TDS.LOGIN_OPTION1_CHARSET_ASCII | // ASCII character set
- TDS.LOGIN_OPTION1_FLOAT_IEEE_754 | // IEEE 754 floating point representation
- TDS.LOGIN_OPTION1_DUMPLOAD_ON | // Require dump/load BCP capabilities
- TDS.LOGIN_OPTION1_USE_DB_OFF | // No ENVCHANGE after USE DATABASE
- TDS.LOGIN_OPTION1_INIT_DB_FATAL | // Fail connection if initial database change fails
- TDS.LOGIN_OPTION1_SET_LANG_ON // Warn on SET LANGUAGE stmt
- ));
-
- tdsWriter.writeByte((byte)( // OptionFlags2:
- TDS.LOGIN_OPTION2_INIT_LANG_FATAL | // Fail connection if initial language change fails
- TDS.LOGIN_OPTION2_ODBC_ON | // Use ODBC defaults (ANSI_DEFAULTS ON, IMPLICIT_TRANSACTIONS OFF, TEXTSIZE inf, ROWCOUNT inf)
- (integratedSecurity ? // Use integrated security if requested
- TDS.LOGIN_OPTION2_INTEGRATED_SECURITY_ON :
- TDS.LOGIN_OPTION2_INTEGRATED_SECURITY_OFF)
- ));
-
- // TypeFlags
- tdsWriter.writeByte((byte)(TDS.LOGIN_SQLTYPE_DEFAULT |
- ( applicationIntent!=null
- && applicationIntent.equals(ApplicationIntent.READ_ONLY)
- ? TDS.LOGIN_READ_ONLY_INTENT
- : TDS.LOGIN_READ_WRITE_INTENT
- )
- ));
-
- // OptionFlags3
- byte colEncSetting = 0x00;
- // AE is always ON
- {
- colEncSetting = TDS.LOGIN_OPTION3_FEATURE_EXTENSION;
- }
- tdsWriter.writeByte((byte)(
- TDS.LOGIN_OPTION3_DEFAULT | colEncSetting |
- ((serverMajorVersion >= 10) ? TDS.LOGIN_OPTION3_UNKNOWN_COLLATION_HANDLING : 0) // Accept unknown collations from Katmai & later servers
- ));
-
- tdsWriter.writeInt((byte) 0); // Client time zone
- tdsWriter.writeInt((byte) 0); // Client LCID
-
- tdsWriter.writeShort( (short) TDS_LOGIN_REQUEST_BASE_LEN);
-
- // Hostname
- tdsWriter.writeShort((short) (hostName==null ? 0:hostName.length()));
- dataLen += hostnameBytes.length;
-
- // Only send user/password over if not fSSPI or fed auth ADAL... If both user/password and SSPI are in login
- // rec, only SSPI is used.
- if(!integratedSecurity && !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested ))
- {
- // User and Password
- tdsWriter.writeShort( (short) (TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- tdsWriter.writeShort( (short) (sUser == null ? 0 : sUser.length()));
- dataLen += userBytes.length;
-
- tdsWriter.writeShort( (short) (TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- tdsWriter.writeShort( (short) (sPwd == null ? 0 : sPwd.length()));
- dataLen += passwordLen;
-
- }
- else
- {
- // User and Password are null
- tdsWriter.writeShort( (short) (0));
- tdsWriter.writeShort( (short) (0));
- tdsWriter.writeShort( (short) (0));
- tdsWriter.writeShort( (short) (0));
- }
-
- // App name
- tdsWriter.writeShort((short)(TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- tdsWriter.writeShort((short) (appName==null ? 0 : appName.length()));
- dataLen += appNameBytes.length;
-
- // Server name
- tdsWriter.writeShort((short)(TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- tdsWriter.writeShort((short) (serverName==null ? 0:serverName.length()));
- dataLen += serverNameBytes.length;
-
- // Unused
- tdsWriter.writeShort((short)(TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- // AE is always ON
- {
- tdsWriter.writeShort((short) 4);
- dataLen += 4;
- }
-
-
- // Interface library name
- assert null != interfaceLibName;
- tdsWriter.writeShort((short)(TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- tdsWriter.writeShort((short)(interfaceLibName.length()));
- dataLen += interfaceLibNameBytes.length;
-
- // Language
- tdsWriter.writeShort((short)0);
- tdsWriter.writeShort((short)0);
-
- // Database
- tdsWriter.writeShort((short)(TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- tdsWriter.writeShort((short) (databaseName==null ? 0:databaseName.length()));
- dataLen += databaseNameBytes.length;
-
- // Client ID (from MAC addr)
- tdsWriter.writeBytes(netAddress);
-
- final int USHRT_MAX = 65535;
- // SSPI data
- if(!integratedSecurity )
- {
- tdsWriter.writeShort((short)0);
- tdsWriter.writeShort((short)0);
- }
- else
- {
- tdsWriter.writeShort((short)(TDS_LOGIN_REQUEST_BASE_LEN + dataLen));
- if(USHRT_MAX <= secBlob.length)
- {
- tdsWriter.writeShort((short)(USHRT_MAX));
- }
- else
- tdsWriter.writeShort((short)(secBlob.length));
- }
-
- // Database to attach during connection process
- tdsWriter.writeShort((short)0);
- tdsWriter.writeShort((short)0);
-
- if (tdsVersion >= TDS.VER_YUKON)
- {
- // TDS 7.2: Password change
- tdsWriter.writeShort((short)0);
- tdsWriter.writeShort((short)0);
-
- // TDS 7.2: 32-bit SSPI byte count (used if 16 bits above were not sufficient)
- if( USHRT_MAX <= secBlob.length)
- tdsWriter.writeInt(secBlob.length);
- else
- tdsWriter.writeInt((short)0);
- }
-
- tdsWriter.writeBytes(hostnameBytes);
-
- // Don't allow user credentials to be logged
- tdsWriter.setDataLoggable(false);
-
- //if we are using SSPI or fed auth ADAL, do not send over username/password, since we will use SSPI instead
- if(!integratedSecurity && !(federatedAuthenticationInfoRequested || federatedAuthenticationRequested ))
- {
- tdsWriter.writeBytes(userBytes); // Username
- tdsWriter.writeBytes(passwordBytes); // Password (encrapted)
- }
- tdsWriter.setDataLoggable(true);
-
- tdsWriter.writeBytes(appNameBytes); //application name
- tdsWriter.writeBytes(serverNameBytes); //server name
-
- // AE is always ON
- {
- tdsWriter.writeInt(aeOffset);
- }
-
- tdsWriter.writeBytes(interfaceLibNameBytes); //interfaceLibName
- tdsWriter.writeBytes(databaseNameBytes); //databaseName
-
- // Don't allow user credentials to be logged
- tdsWriter.setDataLoggable(false);
- if(integratedSecurity )
- tdsWriter.writeBytes(secBlob, 0, secBlob.length);
-
- // AE is always ON
- {
- writeAEFeatureRequest(true, tdsWriter);
- }
-
- if(federatedAuthenticationInfoRequested || federatedAuthenticationRequested){
- writeFedAuthFeatureRequest(true, tdsWriter, fedAuthFeatureExtensionData);
- }
-
- tdsWriter.writeByte((byte)TDS.FEATURE_EXT_TERMINATOR);
- tdsWriter.setDataLoggable(true);
-
- LogonProcessor logonProcessor = new LogonProcessor(authentication);
- TDSReader tdsReader = null;
- do
- {
- tdsReader = logonCommand.startResponse();
- TDSParser.parse(tdsReader, logonProcessor);
- }
- while (!logonProcessor.complete(logonCommand, tdsReader));
- }
-
- private String generateInterfaceLibVersion() {
-
- StringBuffer outputInterfaceLibVersion = new StringBuffer();
-
- String interfaceLibMajor = Integer.toHexString(SQLJdbcVersion.major);
- String interfaceLibMinor = Integer.toHexString(SQLJdbcVersion.minor);
- String interfaceLibPatch = Integer.toHexString(SQLJdbcVersion.patch);
- String interfaceLibBuild = Integer.toHexString(SQLJdbcVersion.build);
-
-
- // build the interface lib name
- // 2 characters reserved for build
- // 2 characters reserved for patch
- // 2 characters reserved for minor
- // 2 characters reserved for major
- if(2 == interfaceLibBuild.length()){
- outputInterfaceLibVersion.append(interfaceLibBuild);
- }
- else{
- outputInterfaceLibVersion.append("0");
- outputInterfaceLibVersion.append(interfaceLibBuild);
- }
- if(2 == interfaceLibPatch.length()){
- outputInterfaceLibVersion.append(interfaceLibPatch);
- }
- else{
- outputInterfaceLibVersion.append("0");
- outputInterfaceLibVersion.append(interfaceLibPatch);
- }
- if(2 == interfaceLibMinor.length()){
- outputInterfaceLibVersion.append(interfaceLibMinor);
- }
- else{
- outputInterfaceLibVersion.append("0");
- outputInterfaceLibVersion.append(interfaceLibMinor);
- }
- if(2 == interfaceLibMajor.length()){
- outputInterfaceLibVersion.append(interfaceLibMajor);
- }
- else{
- outputInterfaceLibVersion.append("0");
- outputInterfaceLibVersion.append(interfaceLibMajor);
- }
-
- return outputInterfaceLibVersion.toString();
- }
-
- /* --------------- JDBC 3.0 ------------- */
-
- /**
- * Checks that the holdability argument is one of the values allowed by the JDBC spec
- * and by this driver.
- */
- private void checkValidHoldability(int holdability) throws SQLServerException
- {
- if (holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT &&
- holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT)
- {
- MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidHoldability"));
- SQLServerException.makeFromDriverError(
- this, this,
- form.format(new Object[] {holdability}),
- null, true);
- }
- }
-
- /**
- * Checks that the proposed statement holdability matches this connection's current holdability.
- *
- * SQL Server doesn't support per-statement holdability, so the statement's
- * proposed holdability must match its parent connection's. Note that this
- * doesn't stop anyone from changing the holdability of the connection after
- * creating the statement. Apps should always call Statement.getResultSetHoldability
- * to check the holdability of ResultSets that would be created, and/or
- * ResultSet.getHoldability to check the holdability of an existing ResultSet.
- */
- private void checkMatchesCurrentHoldability(int resultSetHoldability) throws SQLServerException
- {
- if (resultSetHoldability != this.holdability)
- {
- SQLServerException.makeFromDriverError(
- this, this,
- SQLServerException.getErrString("R_sqlServerHoldability"),
- null, false);
- }
- }
-
- public Statement createStatement(int nType, int nConcur, int resultSetHoldability) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "createStatement", new Object[]{new Integer(nType), new Integer(nConcur), resultSetHoldability});
- Statement st = createStatement(nType, nConcur, resultSetHoldability, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
- loggerExternal.exiting(getClassNameLogging(), "createStatement", st);
- return st;
- }
-
- public Statement createStatement(int nType, int nConcur, int resultSetHoldability, SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "createStatement", new Object[]{new Integer(nType), new Integer(nConcur), resultSetHoldability, stmtColEncSetting});
- checkClosed();
- checkValidHoldability(resultSetHoldability);
- checkMatchesCurrentHoldability(resultSetHoldability);
- Statement st = new SQLServerStatement(this, nType, nConcur, stmtColEncSetting);
- loggerExternal.exiting(getClassNameLogging(), "createStatement", st);
- return st;
- }
-
- /*L3*/ public PreparedStatement prepareStatement(java.lang.String sql, int nType, int nConcur, int resultSetHoldability) throws SQLServerException
- {
- loggerExternal.entering(getClassNameLogging(), "prepareStatement", new Object[]{new Integer(nType), new Integer(nConcur), resultSetHoldability});
- PreparedStatement st = prepareStatement(
- sql,
- nType,
- nConcur,
- resultSetHoldability,
- SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
- loggerExternal.exiting(getClassNameLogging(), "prepareStatement", st);
- return st;
- }
-
- /**
- * Creates a