diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/AE.java b/src/main/java/com/microsoft/sqlserver/jdbc/AE.java index d24f6fe8e..6a4ddba13 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/AE.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/AE.java @@ -1,51 +1,38 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: AE.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.Vector; /** - * Represents a single encrypted value for a CEK. It contains the encrypted CEK,the store type, name,the key path and encryption algorithm. + * Represents a single encrypted value for a CEK. It contains the encrypted CEK,the store type, name,the key path and encryption algorithm. */ class EncryptionKeyInfo { - EncryptionKeyInfo( - byte[] encryptedKeyVal, - int dbId, - int keyId, - int keyVersion, - byte[] mdVersion, - String keyPathVal, - String keyStoreNameVal, - String algorithmNameVal) - { - encryptedKey = encryptedKeyVal; - databaseId = dbId; - cekId = keyId; - cekVersion = keyVersion; - cekMdVersion = mdVersion; - keyPath = keyPathVal; - keyStoreName = keyStoreNameVal; - algorithmName = algorithmNameVal; - } + EncryptionKeyInfo(byte[] encryptedKeyVal, + int dbId, + int keyId, + int keyVersion, + byte[] mdVersion, + String keyPathVal, + String keyStoreNameVal, + String algorithmNameVal) { + encryptedKey = encryptedKeyVal; + databaseId = dbId; + cekId = keyId; + cekVersion = keyVersion; + cekMdVersion = mdVersion; + keyPath = keyPathVal; + keyStoreName = keyStoreNameVal; + algorithmName = algorithmNameVal; + } - byte[] encryptedKey; // the encrypted "column encryption key" + byte[] encryptedKey; // the encrypted "column encryption key" int databaseId; int cekId; int cekVersion; @@ -53,80 +40,77 @@ class EncryptionKeyInfo { String keyPath; String keyStoreName; String algorithmName; - byte normalizationRuleVersion; + byte normalizationRuleVersion; } /** - * Represents a unique CEK as an entry in the CekTable. A unique (plaintext is unique) CEK can have multiple - * encrypted CEKs when using multiple CMKs. These encrypted CEKs are represented by a member vector. + * Represents a unique CEK as an entry in the CekTable. A unique (plaintext is unique) CEK can have multiple encrypted CEKs when using multiple CMKs. + * These encrypted CEKs are represented by a member vector. */ -class CekTableEntry -{ - static final private java.util.logging.Logger aeLogger = - java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.AE"); - - Vector columnEncryptionKeyValues; - int ordinal; - int databaseId; - int cekId; - int cekVersion; - byte[] cekMdVersion; - - Vector getColumnEncryptionKeyValues() { - return columnEncryptionKeyValues; - } +class CekTableEntry { + static final private java.util.logging.Logger aeLogger = java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.AE"); - int getOrdinal() { - return ordinal; - } + Vector columnEncryptionKeyValues; + int ordinal; + int databaseId; + int cekId; + int cekVersion; + byte[] cekMdVersion; - int getDatabaseId() { - return databaseId; - } + Vector getColumnEncryptionKeyValues() { + return columnEncryptionKeyValues; + } - int getCekId() { - return cekId; - } - - int getCekVersion() { - return cekVersion; - } + int getOrdinal() { + return ordinal; + } - byte[] getCekMdVersion() { - return cekMdVersion; - } - - CekTableEntry(int ordinalVal) - { + int getDatabaseId() { + return databaseId; + } + + int getCekId() { + return cekId; + } + + int getCekVersion() { + return cekVersion; + } + + byte[] getCekMdVersion() { + return cekMdVersion; + } + + CekTableEntry(int ordinalVal) { ordinal = ordinalVal; databaseId = 0; cekId = 0; cekVersion = 0; cekMdVersion = null; columnEncryptionKeyValues = new Vector(); - } - - int getSize(){ - return columnEncryptionKeyValues.size(); } - - void add(byte[] encryptedKey, int dbId, int keyId, int keyVersion, byte[] mdVersion, String keyPath, String keyStoreName, String algorithmName) { + + int getSize() { + return columnEncryptionKeyValues.size(); + } + + void add(byte[] encryptedKey, + int dbId, + int keyId, + int keyVersion, + byte[] mdVersion, + String keyPath, + String keyStoreName, + String algorithmName) { assert null != columnEncryptionKeyValues : "columnEncryptionKeyValues should already be initialized."; - if (aeLogger.isLoggable(java.util.logging.Level.FINE)){ - aeLogger.fine("Retrieving CEK values"); - } - - EncryptionKeyInfo encryptionKey = new EncryptionKeyInfo( - encryptedKey, - dbId, - keyId, - keyVersion, - mdVersion, - keyPath, - keyStoreName, - algorithmName); + if (aeLogger.isLoggable(java.util.logging.Level.FINE)) { + aeLogger.fine("Retrieving CEK values"); + } + + EncryptionKeyInfo encryptionKey = new EncryptionKeyInfo(encryptedKey, dbId, keyId, keyVersion, mdVersion, keyPath, keyStoreName, + algorithmName); columnEncryptionKeyValues.add(encryptionKey); if (0 == databaseId) { @@ -136,40 +120,36 @@ void add(byte[] encryptedKey, int dbId, int keyId, int keyVersion, byte[] mdVers cekMdVersion = mdVersion; } else { - assert(databaseId == dbId); - assert(cekId == keyId); - assert(cekVersion == keyVersion); + assert (databaseId == dbId); + assert (cekId == keyId); + assert (cekVersion == keyVersion); assert ((null != cekMdVersion) && (null != mdVersion) && (cekMdVersion.length == mdVersion.length)); } - } + } } /** * Contains all CEKs, each row represents one unique CEK (represented by CekTableEntry). */ -class CekTable -{ - CekTableEntry[] keyList; - - CekTable(int tableSize) - { - keyList = new CekTableEntry[tableSize]; - } - - int getSize() - { - return keyList.length; - } - - CekTableEntry getCekTableEntry(int index) - { - return keyList[index]; - } - - void setCekTableEntry(int index, CekTableEntry entry) - { - keyList[index] = entry; - } +class CekTable { + CekTableEntry[] keyList; + + CekTable(int tableSize) { + keyList = new CekTableEntry[tableSize]; + } + + int getSize() { + return keyList.length; + } + + CekTableEntry getCekTableEntry(int index) { + return keyList[index]; + } + + void setCekTableEntry(int index, + CekTableEntry entry) { + keyList[index] = entry; + } } /** @@ -275,17 +255,16 @@ enum DescribeParameterEncryptionResultSet1 { KeyEncryptionAlgorithm; private int value; - + // Column indexing starts from 1; - static{ - for (int i = 0; i < values().length; ++i) - { - values()[i].value = i + 1; - } + static { + for (int i = 0; i < values().length; ++i) { + values()[i].value = i + 1; + } } - int value() - { - return value; + + int value() { + return value; } } @@ -301,17 +280,16 @@ enum DescribeParameterEncryptionResultSet2 { NormalizationRuleVersion; private int value; - + // Column indexing starts from 1; - static{ - for (int i = 0; i < values().length; ++i) - { - values()[i].value = i + 1; - } + static { + for (int i = 0; i < values().length; ++i) { + values()[i].value = i + 1; + } + } + + int value() { + return value; } - int value() - { - return value; - } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ActivityCorrelator.java b/src/main/java/com/microsoft/sqlserver/jdbc/ActivityCorrelator.java index 184bb4153..422ff7ca3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ActivityCorrelator.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ActivityCorrelator.java @@ -1,127 +1,100 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: ActivityCorrelator.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.lang.ThreadLocal; import java.util.UUID; /** * ActivityCorrelator provides the APIs to access the ActivityId in TLS */ -final class ActivityCorrelator -{ - - private static ThreadLocal ActivityIdTls = new ThreadLocal() - { - protected ActivityId initialValue() - { - return new ActivityId(); - } - }; - - // Get the current ActivityId in TLS - static ActivityId getCurrent() - { - // get the value in TLS, not reference - return ActivityIdTls.get(); - } - - // Increment the Sequence number of the ActivityId in TLS - // and return the ActivityId with new Sequence number - static ActivityId getNext() - { - // We need to call get() method on ThreadLocal to get - // the current value of ActivityId stored in TLS, - // then increment the sequence number. - - // Get the current ActivityId in TLS - ActivityId activityId = getCurrent(); - - // Increment the Sequence number - activityId.Increment(); - - - return activityId; - } - - static void setCurrentActivityIdSentFlag() - { - ActivityId activityId = getCurrent(); - activityId.setSentFlag(); - } - +final class ActivityCorrelator { + + private static ThreadLocal ActivityIdTls = new ThreadLocal() { + protected ActivityId initialValue() { + return new ActivityId(); + } + }; + + // Get the current ActivityId in TLS + static ActivityId getCurrent() { + // get the value in TLS, not reference + return ActivityIdTls.get(); + } + + // Increment the Sequence number of the ActivityId in TLS + // and return the ActivityId with new Sequence number + static ActivityId getNext() { + // We need to call get() method on ThreadLocal to get + // the current value of ActivityId stored in TLS, + // then increment the sequence number. + + // Get the current ActivityId in TLS + ActivityId activityId = getCurrent(); + + // Increment the Sequence number + activityId.Increment(); + + return activityId; + } + + static void setCurrentActivityIdSentFlag() { + ActivityId activityId = getCurrent(); + activityId.setSentFlag(); + } + } - + class ActivityId { - private final UUID Id; - private long Sequence; - private boolean isSentToServer; - - ActivityId() - { - Id = UUID.randomUUID(); - Sequence = 0; - isSentToServer= false; - } - - UUID getId() - { - return Id; - } - - long getSequence() - { - return Sequence; - } - - void Increment() - { - if (Sequence < 0xffffffffl) //to get to 32-bit unsigned - { - ++Sequence; - } - else - { - Sequence = 0; - } - - isSentToServer = false; - } - - void setSentFlag() - { - isSentToServer = true; - } - - boolean IsSentToServer() - { - return isSentToServer; - } - - @Override public String toString () - { - StringBuilder sb = new StringBuilder(); - sb.append(Id.toString()); - sb.append("-"); - sb.append(Sequence); - return sb.toString(); - } -} + private final UUID Id; + private long Sequence; + private boolean isSentToServer; + + ActivityId() { + Id = UUID.randomUUID(); + Sequence = 0; + isSentToServer = false; + } + + UUID getId() { + return Id; + } + + long getSequence() { + return Sequence; + } + + void Increment() { + if (Sequence < 0xffffffffl) // to get to 32-bit unsigned + { + ++Sequence; + } + else { + Sequence = 0; + } + isSentToServer = false; + } + + void setSentFlag() { + isSentToServer = true; + } + + boolean IsSentToServer() { + return isSentToServer; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(Id.toString()); + sb.append("-"); + sb.append(Sequence); + return sb.toString(); + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java index f63f960c8..f212d7674 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java @@ -1,169 +1,199 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: AuthenticationJNI.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; -class FedAuthDllInfo{ - byte[] accessTokenBytes = null; - long expiresIn = 0; +class FedAuthDllInfo { + byte[] accessTokenBytes = null; + long expiresIn = 0; - FedAuthDllInfo(byte[] accessTokenBytes, long expiresIn){ - this.accessTokenBytes = accessTokenBytes; - this.expiresIn = expiresIn; - } + FedAuthDllInfo(byte[] accessTokenBytes, + long expiresIn) { + this.accessTokenBytes = accessTokenBytes; + this.expiresIn = expiresIn; + } } /** * Encapsulation of the JNI native calls for trusted authentication. */ -final class AuthenticationJNI extends SSPIAuthentication -{ - private final static int maximumpointersize = 128; // we keep the SNI_Sec pointer - private static boolean enabled = false; - private static java.util.logging.Logger authLogger = java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.AuthenticationJNI"); - private static int sspiBlobMaxlen = 0; - private byte[] sniSec = new byte[maximumpointersize]; - private int sniSecLen[] = {0}; - private final String DNSName; - private final int port; - private SQLServerConnection con; - - private static final UnsatisfiedLinkError linkError; - static int GetMaxSSPIBlobSize(){return sspiBlobMaxlen;} - - static - { - UnsatisfiedLinkError temp=null; - // Load the DLL - try - { - String libName = "sqljdbc_auth"; - System.loadLibrary(libName); - int []pkg= new int[1]; - pkg[0]=0; - if(0==SNISecInitPackage(pkg, authLogger)) - { - sspiBlobMaxlen = pkg[0]; - } - else - throw new UnsatisfiedLinkError(); - enabled=true; - } - catch (UnsatisfiedLinkError e) - { - temp =e; - authLogger.warning("Failed to load the sqljdbc_auth.dll cause : " + e.getMessage()); - // This is not re-thrown on purpose - the constructor will terminate the properly with the appropriate error string - } - finally - { - linkError = temp; - } - - } - - AuthenticationJNI(SQLServerConnection con, String address, int serverport) throws SQLServerException - { - if(!enabled) - con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_notConfiguredForIntegrated"), linkError); - - this.con = con; - DNSName = GetDNSName(address); - port = serverport; - } - - static FedAuthDllInfo getAccessTokenForWindowsIntegrated(String stsURL, String servicePrincipalName, String clientConnectionId, String clientId, long expirationFileTime) throws DLLException{ - FedAuthDllInfo dllInfo = ADALGetAccessTokenForWindowsIntegrated(stsURL, servicePrincipalName, clientConnectionId, clientId, expirationFileTime, authLogger); - return dllInfo; - } - - static FedAuthDllInfo getAccessToken(String userName, String password, String stsURL, String servicePrincipalName, String clientConnectionId, String clientId, long expirationFileTime) throws DLLException{ - FedAuthDllInfo dllInfo = ADALGetAccessToken(userName, password, stsURL, servicePrincipalName, clientConnectionId, clientId, expirationFileTime, authLogger); - return dllInfo; - } - - - // InitDNSName should be called to initialize the DNSName before calling this function - byte[] GenerateClientContext(byte[] pin, boolean[] done ) throws SQLServerException - { - byte[]pOut; - int[] outsize; // This is where the size of the filled data returned - outsize = new int[1]; - outsize[0] = GetMaxSSPIBlobSize(); - pOut = new byte[outsize[0]]; - - // assert DNSName cant be null - assert DNSName != null; - - int failure = SNISecGenClientContext( sniSec, sniSecLen, pin, pin.length, pOut, outsize, done, DNSName, port, null, null, authLogger); - - if(failure != 0) - { - authLogger.warning(toString() + " Authentication failed code : " + failure); - con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), linkError); - } - // allocate space based on the size returned - byte output[] = new byte[outsize[0]]; - System.arraycopy( pOut, 0, output, 0, outsize[0] ); - return output; - } - - - /*L0*/ int ReleaseClientContext() - { - int success = 0; - if(sniSecLen[0] > 0) - { - success = SNISecReleaseClientContext(sniSec, sniSecLen[0], authLogger); - sniSecLen[0] = 0; - } - return success; - } - - // note we handle the failures of the GetDNSName in this function, this function will return an empty string if the underlying call fails. - private static String GetDNSName(String address) - { - String DNS[] = new String[1]; - if(GetDNSName(address, DNS, authLogger) !=0) - { - //Simply initialize the DNS to address - DNS[0] = address; - } - return DNS[0]; - } - - // we use arrays of size one in many places to retrieve output values - // Java Integer objects are immutable so we cant use them to get the output sizes. - // Same for String - /*L0*/private native static int SNISecGenClientContext( byte[] psec, int[] secptrsize, - byte[] pin, int insize, byte[] pOut, int[] outsize, - boolean[] done, String servername, int port, - String username, String password, java.util.logging.Logger log); - - /*L0*/ private native static int SNISecReleaseClientContext( byte[] psec, int secptrsize, java.util.logging.Logger log); - private native static int SNISecInitPackage(int[] pcbMaxToken, java.util.logging.Logger log); - private native static int SNISecTerminatePackage(java.util.logging.Logger log); - private native static int SNIGetSID(byte[] SID, java.util.logging.Logger log); - private native static boolean SNIIsEqualToCurrentSID(byte[] SID, java.util.logging.Logger log); - private native static int GetDNSName(String address, String[] DNSName, java.util.logging.Logger log); - private native static FedAuthDllInfo ADALGetAccessTokenForWindowsIntegrated(String stsURL, String servicePrincipalName, String clientConnectionId, String clientId, long expirationFileTime, java.util.logging.Logger log); - private native static FedAuthDllInfo ADALGetAccessToken(String userName, String password, String stsURL, String servicePrincipalName, String clientConnectionId, String clientId, long expirationFileTime, java.util.logging.Logger log); - native static byte[] DecryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) throws DLLException; +final class AuthenticationJNI extends SSPIAuthentication { + private final static int maximumpointersize = 128; // we keep the SNI_Sec pointer + private static boolean enabled = false; + private static java.util.logging.Logger authLogger = java.util.logging.Logger + .getLogger("com.microsoft.sqlserver.jdbc.internals.AuthenticationJNI"); + private static int sspiBlobMaxlen = 0; + private byte[] sniSec = new byte[maximumpointersize]; + private int sniSecLen[] = {0}; + private final String DNSName; + private final int port; + private SQLServerConnection con; + + private static final UnsatisfiedLinkError linkError; + + static int GetMaxSSPIBlobSize() { + return sspiBlobMaxlen; + } + + static { + UnsatisfiedLinkError temp = null; + // Load the DLL + try { + String libName = "sqljdbc_auth"; + System.loadLibrary(libName); + int[] pkg = new int[1]; + pkg[0] = 0; + if (0 == SNISecInitPackage(pkg, authLogger)) { + sspiBlobMaxlen = pkg[0]; + } + else + throw new UnsatisfiedLinkError(); + enabled = true; + } + catch (UnsatisfiedLinkError e) { + temp = e; + authLogger.warning("Failed to load the sqljdbc_auth.dll cause : " + e.getMessage()); + // This is not re-thrown on purpose - the constructor will terminate the properly with the appropriate error string + } + finally { + linkError = temp; + } + + } + + AuthenticationJNI(SQLServerConnection con, + String address, + int serverport) throws SQLServerException { + if (!enabled) + con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_notConfiguredForIntegrated"), linkError); + + this.con = con; + DNSName = GetDNSName(address); + port = serverport; + } + + static FedAuthDllInfo getAccessTokenForWindowsIntegrated(String stsURL, + String servicePrincipalName, + String clientConnectionId, + String clientId, + long expirationFileTime) throws DLLException { + FedAuthDllInfo dllInfo = ADALGetAccessTokenForWindowsIntegrated(stsURL, servicePrincipalName, clientConnectionId, clientId, + expirationFileTime, authLogger); + return dllInfo; + } + + static FedAuthDllInfo getAccessToken(String userName, + String password, + String stsURL, + String servicePrincipalName, + String clientConnectionId, + String clientId, + long expirationFileTime) throws DLLException { + FedAuthDllInfo dllInfo = ADALGetAccessToken(userName, password, stsURL, servicePrincipalName, clientConnectionId, clientId, + expirationFileTime, authLogger); + return dllInfo; + } + + // InitDNSName should be called to initialize the DNSName before calling this function + byte[] GenerateClientContext(byte[] pin, + boolean[] done) throws SQLServerException { + byte[] pOut; + int[] outsize; // This is where the size of the filled data returned + outsize = new int[1]; + outsize[0] = GetMaxSSPIBlobSize(); + pOut = new byte[outsize[0]]; + + // assert DNSName cant be null + assert DNSName != null; + + int failure = SNISecGenClientContext(sniSec, sniSecLen, pin, pin.length, pOut, outsize, done, DNSName, port, null, null, authLogger); + + if (failure != 0) { + authLogger.warning(toString() + " Authentication failed code : " + failure); + con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), linkError); + } + // allocate space based on the size returned + byte output[] = new byte[outsize[0]]; + System.arraycopy(pOut, 0, output, 0, outsize[0]); + return output; + } + + /* L0 */ int ReleaseClientContext() { + int success = 0; + if (sniSecLen[0] > 0) { + success = SNISecReleaseClientContext(sniSec, sniSecLen[0], authLogger); + sniSecLen[0] = 0; + } + return success; + } + + // note we handle the failures of the GetDNSName in this function, this function will return an empty string if the underlying call fails. + private static String GetDNSName(String address) { + String DNS[] = new String[1]; + if (GetDNSName(address, DNS, authLogger) != 0) { + // Simply initialize the DNS to address + DNS[0] = address; + } + return DNS[0]; + } + + // we use arrays of size one in many places to retrieve output values + // Java Integer objects are immutable so we cant use them to get the output sizes. + // Same for String + /* L0 */private native static int SNISecGenClientContext(byte[] psec, + int[] secptrsize, + byte[] pin, + int insize, + byte[] pOut, + int[] outsize, + boolean[] done, + String servername, + int port, + String username, + String password, + java.util.logging.Logger log); + + /* L0 */ private native static int SNISecReleaseClientContext(byte[] psec, + int secptrsize, + java.util.logging.Logger log); + + private native static int SNISecInitPackage(int[] pcbMaxToken, + java.util.logging.Logger log); + + private native static int SNISecTerminatePackage(java.util.logging.Logger log); + + private native static int SNIGetSID(byte[] SID, + java.util.logging.Logger log); + + private native static boolean SNIIsEqualToCurrentSID(byte[] SID, + java.util.logging.Logger log); + + private native static int GetDNSName(String address, + String[] DNSName, + java.util.logging.Logger log); + + private native static FedAuthDllInfo ADALGetAccessTokenForWindowsIntegrated(String stsURL, + String servicePrincipalName, + String clientConnectionId, + String clientId, + long expirationFileTime, + java.util.logging.Logger log); + + private native static FedAuthDllInfo ADALGetAccessToken(String userName, + String password, + String stsURL, + String servicePrincipalName, + String clientConnectionId, + String clientId, + long expirationFileTime, + java.util.logging.Logger log); + + native static byte[] DecryptColumnEncryptionKey(String masterKeyPath, + String encryptionAlgorithm, + byte[] encryptedColumnEncryptionKey) throws DLLException; } - diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Column.java b/src/main/java/com/microsoft/sqlserver/jdbc/Column.java index 75c1b6c72..06eaf1fb3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Column.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Column.java @@ -1,508 +1,472 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: Column.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; import java.util.Calendar; - /** * Column represents a database column definition (meta data) within a result set. */ final class Column { - private TypeInfo typeInfo; - private CryptoMetadata cryptoMetadata; - final TypeInfo getTypeInfo() { return typeInfo; } - private DTV updaterDTV; - private final DTV getterDTV=new DTV(); - - //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; - - // The column name, which may be an alias, that is used with value setters and getters. - private String columnName; - final void setColumnName(String name) { columnName = name; } - final String getColumnName() { return columnName; } - - // The base column name which is the actual column name in an underlying table. - // This name must be used, rather than the column name above, when inserting or - // updating rows in the table. - private String baseColumnName; - final void setBaseColumnName(String name) { baseColumnName = name; } - final String getBaseColumnName() { return baseColumnName; } - - private int tableNum; - final void setTableNum(int num) { tableNum = num; } - final int getTableNum() { return tableNum; } - - private int infoStatus; - final void setInfoStatus(int status) { infoStatus = status; } - final boolean hasDifferentName() { return 0 != (infoStatus & TDS.COLINFO_STATUS_DIFFERENT_NAME); } - final boolean isHidden() { return 0 != (infoStatus & TDS.COLINFO_STATUS_HIDDEN); } - final boolean isKey() { return 0 != (infoStatus & TDS.COLINFO_STATUS_KEY); } - final boolean isExpression() { return 0 != (infoStatus & TDS.COLINFO_STATUS_EXPRESSION); } - final boolean isUpdatable() - { - return - !isExpression() && - !isHidden() && - tableName.getObjectName().length() > 0; - } - - private SQLIdentifier tableName; - final void setTableName(SQLIdentifier name) { tableName = name; } - final SQLIdentifier getTableName() { return tableName; } - - ColumnFilter filter; - - /** - * Create a new column - * @param typeInfo the column TYPE_INFO - * @param columnName the column name - * @param tableName the column's table name - * @param cryptoMeta the column's crypto metadata - */ - Column(TypeInfo typeInfo, String columnName, SQLIdentifier tableName, CryptoMetadata cryptoMeta) - { - this.typeInfo = typeInfo; - this.columnName = columnName; - this.baseColumnName = columnName; - this.tableName = tableName; - this.cryptoMetadata = cryptoMeta; - } - - CryptoMetadata getCryptoMetadata(){ - return cryptoMetadata; - } - - /** - * Clears the values associated with this column. - */ - final void clear() - { - getterDTV.clear(); - } - - /** - * Skip this column. - * - * The column's value may or may not already be marked. If this - * column's value has not yet been marked, this function assumes - * that the value is located at the current position in the response. - */ - final void skipValue(TDSReader tdsReader, boolean isDiscard) throws SQLServerException - { - getterDTV.skipValue(typeInfo, tdsReader, isDiscard); - } - - /** - * Sets Null value on the getterDTV of a column - */ - final void initFromCompressedNull() - { - getterDTV.initFromCompressedNull(); - } - - void setFilter(ColumnFilter filter) - { - this.filter = filter; - } - - /** - * Returns whether the value of this column is SQL NULL. - * - * If the column has not yet been read from the response then - * this method returns false. - */ - final boolean isNull() - { - return getterDTV.isNull(); - } - - /** - * Returns true if the column value is initialized to - * some value by reading the stream from server - * i.e. it returns true, if impl of getterDTV is not set to null - */ - final boolean isInitialized() - { - return getterDTV.isInitialized(); - } - - /** - * Retrieves this colum's value. - * - * If the column has not yet been read from the response then - * this method reads it. - */ - Object getValue(JDBCType jdbcType, InputStreamGetterArgs getterArgs, Calendar cal, TDSReader tdsReader) throws SQLServerException - { - Object value = getterDTV.getValue(jdbcType, typeInfo.getScale(), getterArgs, cal, typeInfo, cryptoMetadata, tdsReader); - return (null != filter) ? filter.apply(value, jdbcType) : value; - } - - int getInt(TDSReader tdsReader) throws SQLServerException - { - return ((Integer) getValue(JDBCType.INTEGER, null, null, tdsReader)).intValue(); - } - - void updateValue( - JDBCType jdbcType, - Object value, - JavaType javaType, - StreamSetterArgs streamSetterArgs, - Calendar cal, - Integer scale, - SQLServerConnection con, - SQLServerStatementColumnEncryptionSetting stmtColumnEncriptionSetting, - Integer precision, - boolean forceEncrypt, - int parameterIndex) throws SQLServerException - { - SSType ssType = typeInfo.getSSType(); - - if (null != cryptoMetadata ) - { - if(SSType.VARBINARYMAX == cryptoMetadata.baseTypeInfo.getSSType() && JDBCType.BINARY == jdbcType){ - jdbcType = cryptoMetadata.baseTypeInfo.getSSType().getJDBCType(); - } - - if(null != value){ - //for encrypted tinyint, we need to convert short value to byte value, otherwise it would be sent as smallint - if(JDBCType.TINYINT == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() && javaType == JavaType.SHORT){ - if(value instanceof Boolean){ - if(true == ((boolean)value)){ - value = 1; - } - else{ - value = 0; - } - } - String stringValue = "" + value; - Short shortValue = Short.valueOf(stringValue); - - if(shortValue >= 0 && shortValue <= 255){ - value = shortValue.byteValue(); - javaType = JavaType.BYTE; - jdbcType = JDBCType.TINYINT; - } - } - } - //if the column is encrypted and value is null, get the real column type instead of binary types - else if (jdbcType.isBinary()){ - jdbcType = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType(); - } - } - - if(null == scale && null != cryptoMetadata){ - scale = cryptoMetadata.getBaseTypeInfo().getScale(); - } - - //if jdbcType is char or varchar, check if the column is actually char/varchar or nchar/nvarchar - //in order to make updateString() work with encrypted Nchar typpes - if(null != cryptoMetadata && (JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType)){ - if(JDBCType.NVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() - || JDBCType.NCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() - || JDBCType.LONGNVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()){ - jdbcType = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType(); - } - } - - if(Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, con)) - { - if((null == cryptoMetadata) && true == forceEncrypt){ - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumnRS")); - Object[] msgArgs = {parameterIndex}; - - throw new SQLServerException( - null, - form.format(msgArgs), - null, - 0, - false); - } - else{ - setJdbcTypeSetByUser(jdbcType); - - this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType); - - //for update encrypted nchar or nvarchar value on result set, must double the value length, - //otherwise, the data is truncated. - if(null != cryptoMetadata){ - if(JDBCType.NCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() - || JDBCType.NVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() - || JDBCType.LONGNVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()){ - this.valueLength = valueLength * 2; - } - } - } - } - else{ - if(true == forceEncrypt){ - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAEFalseRS")); - Object[] msgArgs = {parameterIndex}; - - throw new SQLServerException( - null, - form.format(msgArgs), - null, - 0, - false); - } - } - - if (null != streamSetterArgs) - { - if (!streamSetterArgs.streamType.convertsTo(typeInfo)) - DataTypes.throwConversionError(streamSetterArgs.streamType.toString(), ssType.toString()); - } - else - { - if(null != cryptoMetadata) - { - // For GUID, set the JDBCType before checking for conversion - if ((JDBCType.UNKNOWN == jdbcType) - && (value instanceof java.util.UUID)) - { - javaType = JavaType.STRING; - jdbcType = JDBCType.GUID; - setJdbcTypeSetByUser(jdbcType); - } - - SSType basicSSType = cryptoMetadata.baseTypeInfo.getSSType(); - if (!jdbcType.convertsTo(basicSSType)) - DataTypes.throwConversionError(jdbcType.toString(), ssType.toString()); - - JDBCType jdbcTypeFromSSType = getJDBCTypeFromBaseSSType(basicSSType, jdbcType); - - if( jdbcTypeFromSSType != jdbcType) - { - setJdbcTypeSetByUser(jdbcTypeFromSSType); - jdbcType = jdbcTypeFromSSType; - this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType); - } - } - else - { - if (!jdbcType.convertsTo(ssType)) - DataTypes.throwConversionError(jdbcType.toString(), ssType.toString()); - } - } - - // 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); - } - - // 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 ((null != cryptoMetadata) && (con.sendStringParametersAsUnicode()) && - (JavaType.STRING == javaType || - JavaType.READER == javaType || - JavaType.CLOB == javaType)) - { - jdbcType = getSSPAUJDBCType(jdbcType); - } - - // Cheesy checks determine whether updating is allowed, but do not determine HOW to do - // the update (i.e. what JDBC type to use for the update). The JDBC type to use depends - // on the SQL Server type of the column and the JDBC type requested. - // - // In most cases the JDBCType to use is just the requested JDBCType. But in some cases - // a client side type conversion is necessary because SQL Server does not directly support - // conversion from the requested JDBCType to the column SSType, or the driver needs to - // provide special data conversion. - - // Update of Unicode SSType from textual JDBCType: Use Unicode. - if ( - (SSType.NCHAR == ssType || - SSType.NVARCHAR == ssType || - SSType.NVARCHARMAX == ssType || - SSType.NTEXT == ssType || - SSType.XML == ssType) && - - (JDBCType.CHAR == jdbcType || - JDBCType.VARCHAR == jdbcType || - JDBCType.LONGVARCHAR == jdbcType || - JDBCType.CLOB == jdbcType) - ) - { - jdbcType = (JDBCType.CLOB == jdbcType) ? JDBCType.NCLOB : JDBCType.NVARCHAR; - } - - // Update of binary SSType from textual JDBCType: Convert hex to binary. - else if ( - (SSType.BINARY == ssType || - SSType.VARBINARY == ssType || - SSType.VARBINARYMAX == ssType || - SSType.IMAGE == ssType || - SSType.UDT == ssType) && - - (JDBCType.CHAR == jdbcType || - JDBCType.VARCHAR == jdbcType || - JDBCType.LONGVARCHAR == jdbcType) - ) - { - jdbcType = JDBCType.VARBINARY; - } - - // Update of textual SSType from temporal JDBCType requires - // client-side conversion from temporal to textual. - else if ( - (JDBCType.TIMESTAMP == jdbcType || - JDBCType.DATE == jdbcType || - JDBCType.TIME == jdbcType || - JDBCType.DATETIMEOFFSET == jdbcType) && - - (SSType.CHAR == ssType || - SSType.VARCHAR == ssType || - SSType.VARCHARMAX == ssType || - SSType.TEXT == ssType || - SSType.NCHAR == ssType || - SSType.NVARCHAR == ssType || - SSType.NVARCHARMAX == ssType || - SSType.NTEXT == ssType) - ) - { - jdbcType = JDBCType.NCHAR; - } - - // Lazily create the updater DTV on first update of the column - if (null == updaterDTV) - updaterDTV = new DTV(); - - // Set the column's value - - updaterDTV.setValue(typeInfo.getSQLCollation(), jdbcType, value, javaType, streamSetterArgs, cal, scale, con, false); - } - - /** - * 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; - } - } - - private static JDBCType getJDBCTypeFromBaseSSType(SSType basicSSType,JDBCType jdbcType) - { - switch (jdbcType) - { - case TIMESTAMP: - if(SSType.DATETIME == basicSSType) - return JDBCType.DATETIME; - else if (SSType.SMALLDATETIME == basicSSType) - return JDBCType.SMALLDATETIME; - return jdbcType; - - case NUMERIC: - case DECIMAL: - if(SSType.MONEY == basicSSType) - return JDBCType.MONEY; - if (SSType.SMALLMONEY == basicSSType) - return JDBCType.SMALLMONEY; - return jdbcType; - - case CHAR: - if(SSType.GUID == basicSSType) - return JDBCType.GUID; - if(SSType.VARCHARMAX == basicSSType) - return JDBCType.LONGVARCHAR; - - default: - return jdbcType; - } - } - - boolean hasUpdates() - { - return null != updaterDTV; - } - - void cancelUpdates() - { - updaterDTV = null; - } - - void sendByRPC( - TDSWriter tdsWriter, - SQLServerConnection conn) throws SQLServerException - { - // If the column has had no updates then there is nothing to send - if (null == updaterDTV) - return; - try{ - //this is for updateRow() stuff - updaterDTV.sendCryptoMetaData(cryptoMetadata, tdsWriter); - updaterDTV.jdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength()); - - // Otherwise, send the updated value via RPC - updaterDTV.sendByRPC( - baseColumnName, - typeInfo, - null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getSQLCollation():typeInfo.getSQLCollation(), - null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getPrecision():typeInfo.getPrecision(), - null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getScale():typeInfo.getScale(), - false, // isOutParameter (always false for column updates) - tdsWriter, - conn); - } - finally{ - //this is for updateRow() stuff - updaterDTV.sendCryptoMetaData(null, tdsWriter); - } - } - - JDBCType getJdbcTypeSetByUser() { - return jdbcTypeSetByUser; - } - void setJdbcTypeSetByUser(JDBCType jdbcTypeSetByUser) { - this.jdbcTypeSetByUser = jdbcTypeSetByUser; - } - - int getValueLength() { - return valueLength; - } + private TypeInfo typeInfo; + private CryptoMetadata cryptoMetadata; + + final TypeInfo getTypeInfo() { + return typeInfo; + } + + private DTV updaterDTV; + private final DTV getterDTV = new DTV(); + + // 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; + + // The column name, which may be an alias, that is used with value setters and getters. + private String columnName; + + final void setColumnName(String name) { + columnName = name; + } + + final String getColumnName() { + return columnName; + } + + // The base column name which is the actual column name in an underlying table. + // This name must be used, rather than the column name above, when inserting or + // updating rows in the table. + private String baseColumnName; + + final void setBaseColumnName(String name) { + baseColumnName = name; + } + + final String getBaseColumnName() { + return baseColumnName; + } + + private int tableNum; + + final void setTableNum(int num) { + tableNum = num; + } + + final int getTableNum() { + return tableNum; + } + + private int infoStatus; + + final void setInfoStatus(int status) { + infoStatus = status; + } + + final boolean hasDifferentName() { + return 0 != (infoStatus & TDS.COLINFO_STATUS_DIFFERENT_NAME); + } + + final boolean isHidden() { + return 0 != (infoStatus & TDS.COLINFO_STATUS_HIDDEN); + } + + final boolean isKey() { + return 0 != (infoStatus & TDS.COLINFO_STATUS_KEY); + } + + final boolean isExpression() { + return 0 != (infoStatus & TDS.COLINFO_STATUS_EXPRESSION); + } + + final boolean isUpdatable() { + return !isExpression() && !isHidden() && tableName.getObjectName().length() > 0; + } + + private SQLIdentifier tableName; + + final void setTableName(SQLIdentifier name) { + tableName = name; + } + + final SQLIdentifier getTableName() { + return tableName; + } + + ColumnFilter filter; + + /** + * Create a new column + * + * @param typeInfo + * the column TYPE_INFO + * @param columnName + * the column name + * @param tableName + * the column's table name + * @param cryptoMeta + * the column's crypto metadata + */ + Column(TypeInfo typeInfo, + String columnName, + SQLIdentifier tableName, + CryptoMetadata cryptoMeta) { + this.typeInfo = typeInfo; + this.columnName = columnName; + this.baseColumnName = columnName; + this.tableName = tableName; + this.cryptoMetadata = cryptoMeta; + } + + CryptoMetadata getCryptoMetadata() { + return cryptoMetadata; + } + + /** + * Clears the values associated with this column. + */ + final void clear() { + getterDTV.clear(); + } + + /** + * Skip this column. + * + * The column's value may or may not already be marked. If this column's value has not yet been marked, this function assumes that the value is + * located at the current position in the response. + */ + final void skipValue(TDSReader tdsReader, + boolean isDiscard) throws SQLServerException { + getterDTV.skipValue(typeInfo, tdsReader, isDiscard); + } + + /** + * Sets Null value on the getterDTV of a column + */ + final void initFromCompressedNull() { + getterDTV.initFromCompressedNull(); + } + + void setFilter(ColumnFilter filter) { + this.filter = filter; + } + + /** + * Returns whether the value of this column is SQL NULL. + * + * If the column has not yet been read from the response then this method returns false. + */ + final boolean isNull() { + return getterDTV.isNull(); + } + + /** + * Returns true if the column value is initialized to some value by reading the stream from server i.e. it returns true, if impl of getterDTV is + * not set to null + */ + final boolean isInitialized() { + return getterDTV.isInitialized(); + } + + /** + * Retrieves this colum's value. + * + * If the column has not yet been read from the response then this method reads it. + */ + Object getValue(JDBCType jdbcType, + InputStreamGetterArgs getterArgs, + Calendar cal, + TDSReader tdsReader) throws SQLServerException { + Object value = getterDTV.getValue(jdbcType, typeInfo.getScale(), getterArgs, cal, typeInfo, cryptoMetadata, tdsReader); + return (null != filter) ? filter.apply(value, jdbcType) : value; + } + + int getInt(TDSReader tdsReader) throws SQLServerException { + return ((Integer) getValue(JDBCType.INTEGER, null, null, tdsReader)).intValue(); + } + + void updateValue(JDBCType jdbcType, + Object value, + JavaType javaType, + StreamSetterArgs streamSetterArgs, + Calendar cal, + Integer scale, + SQLServerConnection con, + SQLServerStatementColumnEncryptionSetting stmtColumnEncriptionSetting, + Integer precision, + boolean forceEncrypt, + int parameterIndex) throws SQLServerException { + SSType ssType = typeInfo.getSSType(); + + if (null != cryptoMetadata) { + if (SSType.VARBINARYMAX == cryptoMetadata.baseTypeInfo.getSSType() && JDBCType.BINARY == jdbcType) { + jdbcType = cryptoMetadata.baseTypeInfo.getSSType().getJDBCType(); + } + + if (null != value) { + // for encrypted tinyint, we need to convert short value to byte value, otherwise it would be sent as smallint + if (JDBCType.TINYINT == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() && javaType == JavaType.SHORT) { + if (value instanceof Boolean) { + if (true == ((boolean) value)) { + value = 1; + } + else { + value = 0; + } + } + String stringValue = "" + value; + Short shortValue = Short.valueOf(stringValue); + + if (shortValue >= 0 && shortValue <= 255) { + value = shortValue.byteValue(); + javaType = JavaType.BYTE; + jdbcType = JDBCType.TINYINT; + } + } + } + // if the column is encrypted and value is null, get the real column type instead of binary types + else if (jdbcType.isBinary()) { + jdbcType = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType(); + } + } + + if (null == scale && null != cryptoMetadata) { + scale = cryptoMetadata.getBaseTypeInfo().getScale(); + } + + // if jdbcType is char or varchar, check if the column is actually char/varchar or nchar/nvarchar + // in order to make updateString() work with encrypted Nchar typpes + if (null != cryptoMetadata && (JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType)) { + if (JDBCType.NVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() + || JDBCType.NCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() + || JDBCType.LONGNVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()) { + jdbcType = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType(); + } + } + + if (Util.shouldHonorAEForParameters(stmtColumnEncriptionSetting, con)) { + if ((null == cryptoMetadata) && true == forceEncrypt) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAETrue_UnencryptedColumnRS")); + Object[] msgArgs = {parameterIndex}; + + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } + else { + setJdbcTypeSetByUser(jdbcType); + + this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType); + + // for update encrypted nchar or nvarchar value on result set, must double the value length, + // otherwise, the data is truncated. + if (null != cryptoMetadata) { + if (JDBCType.NCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() + || JDBCType.NVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType() + || JDBCType.LONGNVARCHAR == cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType()) { + this.valueLength = valueLength * 2; + } + } + } + } + else { + if (true == forceEncrypt) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ForceEncryptionTrue_HonorAEFalseRS")); + Object[] msgArgs = {parameterIndex}; + + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } + } + + if (null != streamSetterArgs) { + if (!streamSetterArgs.streamType.convertsTo(typeInfo)) + DataTypes.throwConversionError(streamSetterArgs.streamType.toString(), ssType.toString()); + } + else { + if (null != cryptoMetadata) { + // For GUID, set the JDBCType before checking for conversion + if ((JDBCType.UNKNOWN == jdbcType) && (value instanceof java.util.UUID)) { + javaType = JavaType.STRING; + jdbcType = JDBCType.GUID; + setJdbcTypeSetByUser(jdbcType); + } + + SSType basicSSType = cryptoMetadata.baseTypeInfo.getSSType(); + if (!jdbcType.convertsTo(basicSSType)) + DataTypes.throwConversionError(jdbcType.toString(), ssType.toString()); + + JDBCType jdbcTypeFromSSType = getJDBCTypeFromBaseSSType(basicSSType, jdbcType); + + if (jdbcTypeFromSSType != jdbcType) { + setJdbcTypeSetByUser(jdbcTypeFromSSType); + jdbcType = jdbcTypeFromSSType; + this.valueLength = Util.getValueLengthBaseOnJavaType(value, javaType, precision, scale, jdbcType); + } + } + else { + if (!jdbcType.convertsTo(ssType)) + DataTypes.throwConversionError(jdbcType.toString(), ssType.toString()); + } + } + + // 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); + } + + // 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 ((null != cryptoMetadata) && (con.sendStringParametersAsUnicode()) + && (JavaType.STRING == javaType || JavaType.READER == javaType || JavaType.CLOB == javaType)) { + jdbcType = getSSPAUJDBCType(jdbcType); + } + + // Cheesy checks determine whether updating is allowed, but do not determine HOW to do + // the update (i.e. what JDBC type to use for the update). The JDBC type to use depends + // on the SQL Server type of the column and the JDBC type requested. + // + // In most cases the JDBCType to use is just the requested JDBCType. But in some cases + // a client side type conversion is necessary because SQL Server does not directly support + // conversion from the requested JDBCType to the column SSType, or the driver needs to + // provide special data conversion. + + // Update of Unicode SSType from textual JDBCType: Use Unicode. + if ((SSType.NCHAR == ssType || SSType.NVARCHAR == ssType || SSType.NVARCHARMAX == ssType || SSType.NTEXT == ssType || SSType.XML == ssType) && + + (JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType || JDBCType.LONGVARCHAR == jdbcType || JDBCType.CLOB == jdbcType)) { + jdbcType = (JDBCType.CLOB == jdbcType) ? JDBCType.NCLOB : JDBCType.NVARCHAR; + } + + // Update of binary SSType from textual JDBCType: Convert hex to binary. + else if ((SSType.BINARY == ssType || SSType.VARBINARY == ssType || SSType.VARBINARYMAX == ssType || SSType.IMAGE == ssType + || SSType.UDT == ssType) && + + (JDBCType.CHAR == jdbcType || JDBCType.VARCHAR == jdbcType || JDBCType.LONGVARCHAR == jdbcType)) { + jdbcType = JDBCType.VARBINARY; + } + + // Update of textual SSType from temporal JDBCType requires + // client-side conversion from temporal to textual. + else if ((JDBCType.TIMESTAMP == jdbcType || JDBCType.DATE == jdbcType || JDBCType.TIME == jdbcType || JDBCType.DATETIMEOFFSET == jdbcType) && + + (SSType.CHAR == ssType || SSType.VARCHAR == ssType || SSType.VARCHARMAX == ssType || SSType.TEXT == ssType || SSType.NCHAR == ssType + || SSType.NVARCHAR == ssType || SSType.NVARCHARMAX == ssType || SSType.NTEXT == ssType)) { + jdbcType = JDBCType.NCHAR; + } + + // Lazily create the updater DTV on first update of the column + if (null == updaterDTV) + updaterDTV = new DTV(); + + // Set the column's value + + updaterDTV.setValue(typeInfo.getSQLCollation(), jdbcType, value, javaType, streamSetterArgs, cal, scale, con, false); + } + + /** + * 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; + } + } + + private static JDBCType getJDBCTypeFromBaseSSType(SSType basicSSType, + JDBCType jdbcType) { + switch (jdbcType) { + case TIMESTAMP: + if (SSType.DATETIME == basicSSType) + return JDBCType.DATETIME; + else if (SSType.SMALLDATETIME == basicSSType) + return JDBCType.SMALLDATETIME; + return jdbcType; + + case NUMERIC: + case DECIMAL: + if (SSType.MONEY == basicSSType) + return JDBCType.MONEY; + if (SSType.SMALLMONEY == basicSSType) + return JDBCType.SMALLMONEY; + return jdbcType; + + case CHAR: + if (SSType.GUID == basicSSType) + return JDBCType.GUID; + if (SSType.VARCHARMAX == basicSSType) + return JDBCType.LONGVARCHAR; + + default: + return jdbcType; + } + } + + boolean hasUpdates() { + return null != updaterDTV; + } + + void cancelUpdates() { + updaterDTV = null; + } + + void sendByRPC(TDSWriter tdsWriter, + SQLServerConnection conn) throws SQLServerException { + // If the column has had no updates then there is nothing to send + if (null == updaterDTV) + return; + try { + // this is for updateRow() stuff + updaterDTV.sendCryptoMetaData(cryptoMetadata, tdsWriter); + updaterDTV.jdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength()); + + // Otherwise, send the updated value via RPC + updaterDTV.sendByRPC(baseColumnName, typeInfo, + null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getSQLCollation() : typeInfo.getSQLCollation(), + null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getPrecision() : typeInfo.getPrecision(), + null != cryptoMetadata ? cryptoMetadata.getBaseTypeInfo().getScale() : typeInfo.getScale(), false, // isOutParameter (always false + // for column updates) + tdsWriter, conn); + } + finally { + // this is for updateRow() stuff + updaterDTV.sendCryptoMetaData(null, tdsWriter); + } + } + + JDBCType getJdbcTypeSetByUser() { + return jdbcTypeSetByUser; + } + + void setJdbcTypeSetByUser(JDBCType jdbcTypeSetByUser) { + this.jdbcTypeSetByUser = jdbcTypeSetByUser; + } + + int getValueLength() { + return valueLength; + } } -abstract class ColumnFilter -{ - abstract Object apply(Object value, JDBCType jdbcType) throws SQLServerException; +abstract class ColumnFilter { + abstract Object apply(Object value, + JDBCType jdbcType) throws SQLServerException; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index 6e7b167c0..8010c1b32 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -1,23 +1,13 @@ -//--------------------------------------------------------------------------------------------------------------------------------- -// File: DDC.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.BufferedReader; @@ -30,7 +20,6 @@ import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; -import java.math.RoundingMode; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -44,1454 +33,1364 @@ * Utility class for all Data Dependant Conversions (DDC). */ -final class DDC -{ - - /** - * Convert an Integer object to desired target user type. - * @param intvalue the value to convert. - * @param valueLength the value to convert. - * @param jdbcType the jdbc type required. - * @param streamType the type of stream required. - * @return the required object. - */ - static final Object convertIntegerToObject( - int intValue, - int valueLength, - JDBCType jdbcType, - StreamType streamType) - { - switch (jdbcType) - { - case INTEGER: - return new Integer(intValue); - case SMALLINT: //2.21 small and tinyint returned as short - case TINYINT: - return new Short((short) intValue); - case BIT: - case BOOLEAN: - return new Boolean(0 != intValue); - case BIGINT: - return new Long(intValue); - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return new BigDecimal(Integer.toString(intValue)); - case FLOAT: - case DOUBLE: - return new Double(intValue); - case REAL: - return new Float(intValue); - case BINARY: - return convertIntToBytes(intValue, valueLength); - default: - return Integer.toString(intValue); - } - } - - /** - * Convert a Long object to desired target user type. - * @param longVal the value to convert. - * @param jdbcType the jdbc type required. - * @param baseSSType the base SQLServer type. - * @param streamType the stream type. - * @return the required object. - */ - static final Object convertLongToObject(long longVal, JDBCType jdbcType, SSType baseSSType, StreamType streamType) - { - switch (jdbcType) - { - case BIGINT: - return new Long(longVal); - case INTEGER: - return new Integer((int) longVal); - case SMALLINT: // small and tinyint returned as short - case TINYINT: - return new Short((short) longVal); - case BIT: - case BOOLEAN: - return new Boolean(0 != longVal); - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return new BigDecimal(Long.toString(longVal)); - case FLOAT: - case DOUBLE: - return new Double(longVal); - case REAL: - return new Float(longVal); - case BINARY: - byte[] convertedBytes = convertLongToBytes(longVal); - int bytesToReturnLength = 0; - byte[] bytesToReturn; - - switch(baseSSType){ - case BIT: - case TINYINT: - bytesToReturnLength = 1; - bytesToReturn = new byte[bytesToReturnLength]; - System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength); - return bytesToReturn; - case SMALLINT: - bytesToReturnLength = 2; - bytesToReturn = new byte[bytesToReturnLength]; - System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength); - return bytesToReturn; - case INTEGER: - bytesToReturnLength = 4; - bytesToReturn = new byte[bytesToReturnLength]; - System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength); - return bytesToReturn; - case BIGINT: - bytesToReturnLength = 8; - bytesToReturn = new byte[bytesToReturnLength]; - System.arraycopy(convertedBytes, convertedBytes.length - bytesToReturnLength, bytesToReturn, 0, bytesToReturnLength); - return bytesToReturn; - default: - return convertedBytes; - } - - case VARBINARY: - switch(baseSSType) - { - case BIGINT: - return new Long(longVal); - case INTEGER: - return new Integer((int) longVal); - case SMALLINT: // small and tinyint returned as short - case TINYINT: - return new Short((short) longVal); - case BIT: - return new Boolean(0 != longVal); - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return new BigDecimal(Long.toString(longVal)); - case FLOAT: - return new Double(longVal); - case REAL: - return new Float(longVal); - case BINARY: - return convertLongToBytes(longVal); - default: - return Long.toString(longVal); - } - default: - return Long.toString(longVal); - } - } - - /** - * Encodes an integer value to a byte array in big-endian order. - * @param intValue the integer value to encode. - * @param valueLength the number of bytes to encode. - * @return the byte array containing the big-endian encoded value. - */ - static final byte[] convertIntToBytes(int intValue, int valueLength) - { - byte bytes[] = new byte[valueLength]; - for (int i = valueLength; i-- > 0; ) - { - bytes[i] = (byte)(intValue & 0xFF); - intValue >>= 8; - } - return bytes; - } - - /** - * Convert a Float object to desired target user type. - * @param floatVal the value to convert. - * @param jdbcType the jdbc type required. - * @param streamType the stream type. - * @return the required object. - */ - static final Object convertFloatToObject(float floatVal, JDBCType jdbcType, StreamType streamType) - { - switch (jdbcType) - { - case REAL: - return new Float(floatVal); - case INTEGER: - return new Integer((int) floatVal); - case SMALLINT: // small and tinyint returned as short - case TINYINT: - return new Short((short) floatVal); - case BIT: - case BOOLEAN: - return new Boolean(0 != Float.compare(0.0f, floatVal)); - case BIGINT: - return new Long((long) floatVal); - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return new BigDecimal(Float.toString(floatVal)); - case FLOAT: - case DOUBLE: - return new Double((new Float(floatVal)).doubleValue()); - case BINARY: - return convertIntToBytes(Float.floatToRawIntBits(floatVal), 4); - default: - return Float.toString(floatVal); - } - } - - /** - * Encodes a long value to a byte array in big-endian order. - * @param longValue the long value to encode. - * @return the byte array containing the big-endian encoded value. - */ - static final byte[] convertLongToBytes(long longValue) - { - byte bytes[] = new byte[8]; - for (int i = 8; i-- > 0; ) - { - bytes[i] = (byte)(longValue & 0xFF); - longValue >>= 8; - } - return bytes; - } - - /** - * Convert a Double object to desired target user type. - * @param doubleVal the value to convert. - * @param jdbcType the jdbc type required. - * @param streamType the stream type. - * @return the required object. - */ - static final Object convertDoubleToObject(double doubleVal, JDBCType jdbcType, StreamType streamType) - { - switch (jdbcType) - { - case FLOAT: - case DOUBLE: - return new Double(doubleVal); - case REAL: - return new Float((new Double(doubleVal)).floatValue()); - case INTEGER: - return new Integer((int) doubleVal); - case SMALLINT: // small and tinyint returned as short - case TINYINT: - return new Short((short) doubleVal); - case BIT: - case BOOLEAN: - return new Boolean(0 != Double.compare(0.0d, doubleVal)); - case BIGINT: - return new Long((long) doubleVal); - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return new BigDecimal(Double.toString(doubleVal)); - case BINARY: - return convertLongToBytes(Double.doubleToRawLongBits(doubleVal)); - default: - return Double.toString(doubleVal); - } - } - - static final byte[] convertBigDecimalToBytes(BigDecimal bigDecimalVal, int scale) - { - byte[] valueBytes; - - if (bigDecimalVal==null) - { - valueBytes = new byte[2]; - valueBytes[0] = (byte) scale; - valueBytes[1] = 0; // data length - } - else - { - boolean isNegative = (bigDecimalVal.signum() < 0); - - // NOTE: Handle negative scale as a special case for JDK 1.5 and later VMs. - if (bigDecimalVal.scale() < 0) - bigDecimalVal = bigDecimalVal.setScale(0); - - BigInteger bi = bigDecimalVal.unscaledValue(); - - if (isNegative) - bi=bi.negate(); - - byte[] unscaledBytes = bi.toByteArray(); - - valueBytes = new byte[unscaledBytes.length + 3]; - int j = 0; - valueBytes[j++] = (byte) bigDecimalVal.scale(); - valueBytes[j++] = (byte) (unscaledBytes.length + 1); // data length + sign - valueBytes[j++] = (byte) (isNegative ? 0 : 1); // 1 = +ve, 0 = -ve - for (int i = unscaledBytes.length - 1; i >= 0; i--) - valueBytes[j++] = unscaledBytes[i]; - } - - return valueBytes; - } - - /** - * Convert a BigDecimal object to desired target user type. - * @param bigDecimalVal the value to convert. - * @param jdbcType the jdbc type required. - * @param streamType the stream type. - * @return the required object. - */ - static final Object convertBigDecimalToObject(BigDecimal bigDecimalVal, JDBCType jdbcType, StreamType streamType) - { - switch (jdbcType) - { - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return bigDecimalVal; - case FLOAT: - case DOUBLE: - return new Double(bigDecimalVal.doubleValue()); - case REAL: - return new Float(bigDecimalVal.floatValue()); - case INTEGER: - return new Integer(bigDecimalVal.intValue()); - case SMALLINT: // small and tinyint returned as short - case TINYINT: - return new Short(bigDecimalVal.shortValue()); - case BIT: - case BOOLEAN: - return new Boolean(0 != bigDecimalVal.compareTo(BigDecimal.valueOf(0))); - case BIGINT: - return new Long(bigDecimalVal.longValue()); - case BINARY: - return convertBigDecimalToBytes(bigDecimalVal, bigDecimalVal.scale()); - default: - return bigDecimalVal.toString(); - } - } - - /** - * Convert a Money object to desired target user type. - * @param bigDecimalVal the value to convert. - * @param jdbcType the jdbc type required. - * @param streamType the stream type. - * @param numberOfBytes the number of bytes to convert - * @return the required object. - */ - static final Object convertMoneyToObject(BigDecimal bigDecimalVal, JDBCType jdbcType, StreamType streamType, int numberOfBytes) - { - switch (jdbcType) - { - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return bigDecimalVal; - case FLOAT: - case DOUBLE: - return new Double(bigDecimalVal.doubleValue()); - case REAL: - return new Float(bigDecimalVal.floatValue()); - case INTEGER: - return new Integer(bigDecimalVal.intValue()); - case SMALLINT: // small and tinyint returned as short - case TINYINT: - return new Short(bigDecimalVal.shortValue()); - case BIT: - case BOOLEAN: - return new Boolean(0 != bigDecimalVal.compareTo(BigDecimal.valueOf(0))); - case BIGINT: - return new Long(bigDecimalVal.longValue()); - case BINARY: - return convertToBytes(bigDecimalVal, bigDecimalVal.scale(), numberOfBytes); - default: - return bigDecimalVal.toString(); - } - } - - //converts big decimal to money and smallmoney - private static byte[] convertToBytes(BigDecimal value, int scale, int numBytes) - { - boolean isNeg = value.signum() < 0; - - value = value.setScale(scale); - - BigInteger bigInt = value.unscaledValue(); - - byte[] unscaledBytes = bigInt.toByteArray(); - - byte[] ret = new byte[numBytes]; - if (unscaledBytes.length < numBytes) - { - for (int i = 0; i < numBytes - unscaledBytes.length; ++i) - { - ret[i] = (byte) (isNeg ? -1 : 0); - } - } - int offset = numBytes - unscaledBytes.length; - for (int i = offset; i < numBytes; ++i) - { - ret[i] = unscaledBytes[i - offset]; - } - return ret; - } - - /** - * Convert a byte array to desired target user type. - * @param bytesValue the value to convert. - * @param jdbcType the jdbc type required. - * @param baseTypeInfo the type information associated with bytesValue. - * @return the required object. - * @throws SQLServerException when an error occurs. - */ - static final Object convertBytesToObject(byte[] bytesValue, JDBCType jdbcType, TypeInfo baseTypeInfo) throws SQLServerException - { - switch(jdbcType) - { - case CHAR: - String str = Util.bytesToHexString(bytesValue, bytesValue.length); - - if((SSType.BINARY ==baseTypeInfo.getSSType()) - && (str.length() < (baseTypeInfo.getPrecision()*2))){ - - StringBuffer strbuf = new StringBuffer(str); - - while(strbuf.length() < (baseTypeInfo.getPrecision()*2)){ - strbuf.append('0'); - } - return strbuf.toString(); - } - return str; - - case BINARY: - case VARBINARY: - case LONGVARBINARY: - if((SSType.BINARY == baseTypeInfo.getSSType()) - && (bytesValue.length < baseTypeInfo.getPrecision())){ - - byte[] newBytes = new byte[baseTypeInfo.getPrecision()]; - System.arraycopy(bytesValue, 0, newBytes, 0, bytesValue.length); - return newBytes; - } - - return bytesValue; - - default: - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionFromTo")); - throw new SQLServerException(form.format(new Object[] {baseTypeInfo.getSSType().name(), jdbcType}), null, 0, null); - } - } - - /** - * Convert a String object to desired target user type. - * @param stringVal the value to convert. - * @param charset the character set. - * @param jdbcType the jdbc type required. - * @return the required object. - */ - static final Object convertStringToObject( - String stringVal, - Charset charset, - JDBCType jdbcType, - StreamType streamType) throws UnsupportedEncodingException, IllegalArgumentException - { - switch (jdbcType) - { - // Convert String to Numeric types. - case DECIMAL: - case NUMERIC: - case MONEY: - case SMALLMONEY: - return new BigDecimal(stringVal.trim()); - case FLOAT: - case DOUBLE: - return Double.valueOf(stringVal.trim()); - case REAL: - return Float.valueOf(stringVal.trim()); - case INTEGER: - return Integer.valueOf(stringVal.trim()); - case SMALLINT: // small and tinyint returned as short - case TINYINT: - return Short.valueOf(stringVal.trim()); - case BIT: - case BOOLEAN: - String trimmedString = stringVal.trim(); - return (1 == trimmedString.length()) ? - Boolean.valueOf('1' == trimmedString.charAt(0)) : - Boolean.valueOf(trimmedString); - case BIGINT: - return Long.valueOf(stringVal.trim()); - - // Convert String to Temporal types. - case TIMESTAMP: - return java.sql.Timestamp.valueOf(stringVal.trim()); - case DATE: - return java.sql.Date.valueOf(getDatePart(stringVal.trim())); - case TIME: - { - // Accepted character formats for conversion to java.sql.Time are: - // hh:mm:ss[.nnnnnnnnn] - // YYYY-MM-DD hh:mm:ss[.nnnnnnnnn] - // - // To handle either of these formats: - // 1) Normalize and parse as a Timestamp - // 2) Round fractional seconds up to the nearest millisecond (max resolution of java.sql.Time) - // 3) Renormalize (as rounding may have changed the date) to a java.sql.Time - java.sql.Timestamp ts = java.sql.Timestamp.valueOf(TDS.BASE_DATE_1970 +" "+ getTimePart(stringVal.trim())); - GregorianCalendar cal = new GregorianCalendar(Locale.US); - cal.clear(); - cal.setTimeInMillis(ts.getTime()); - if (ts.getNanos() % Nanos.PER_MILLISECOND >= Nanos.PER_MILLISECOND / 2) - cal.add(Calendar.MILLISECOND, 1); - cal.set(TDS.BASE_YEAR_1970, Calendar.JANUARY, 1); - return new java.sql.Time(cal.getTimeInMillis()); - } - - case BINARY: - return stringVal.getBytes(charset); - - default: - // For everything else, just return either a string or appropriate stream. - switch (streamType) - { - case CHARACTER: - return new StringReader(stringVal); - case ASCII: - return new ByteArrayInputStream(stringVal.getBytes(US_ASCII)); - case BINARY: - return new ByteArrayInputStream(stringVal.getBytes()); - - default: - return stringVal; - } - } - } - - static final Object convertStreamToObject( - BaseInputStream stream, - TypeInfo typeInfo, - JDBCType jdbcType, - InputStreamGetterArgs getterArgs) throws SQLServerException - { - // Need to handle the simple case of a null value here, as it is not done - // outside this function. - if (null == stream) - return null; - - assert null != typeInfo; - assert null != getterArgs; - - SSType ssType = typeInfo.getSSType(); - - try - { - switch (jdbcType) - { - case CHAR: - case VARCHAR: - case LONGVARCHAR: - case NCHAR: - case NVARCHAR: - case LONGNVARCHAR: - default: - - // Binary streams to character types: - // - Direct conversion to ASCII stream - // - Convert as hexized value to other character types - if (SSType.BINARY == ssType || - SSType.VARBINARY == ssType || - SSType.VARBINARYMAX == ssType || - SSType.TIMESTAMP == ssType || - SSType.IMAGE == ssType || - SSType.UDT == ssType) - { - if (StreamType.ASCII == getterArgs.streamType) - { - return stream; - } - else - { - assert StreamType.CHARACTER == getterArgs.streamType || - StreamType.NONE == getterArgs.streamType; - - byte[] byteValue = stream.getBytes(); - if(JDBCType.GUID == jdbcType) - { - return Util.readGUID(byteValue); - } - else - { - String hexString = Util.bytesToHexString(byteValue, byteValue.length); - - if (StreamType.NONE == getterArgs.streamType) - return hexString; - - return new StringReader(hexString); - } - } - } - - // Handle streams converting to ASCII - if (StreamType.ASCII == getterArgs.streamType) - { - // Fast path for SBCS data that converts directly/easily to ASCII - if (typeInfo.supportsFastAsciiConversion()) - return new AsciiFilteredInputStream(stream); - - // Slightly less fast path for MBCS data that converts directly/easily to ASCII - if (getterArgs.isAdaptive) - { - return AsciiFilteredUnicodeInputStream.MakeAsciiFilteredUnicodeInputStream( - stream, - new BufferedReader(new InputStreamReader(stream, typeInfo.getCharset()))); - } - else - { - return new ByteArrayInputStream((new String(stream.getBytes(), typeInfo.getCharset())).getBytes(US_ASCII)); - } - } - else if (StreamType.CHARACTER == getterArgs.streamType || - StreamType.NCHARACTER == getterArgs.streamType) - { - if (getterArgs.isAdaptive) - return new BufferedReader(new InputStreamReader(stream, typeInfo.getCharset())); - else - return new StringReader(new String(stream.getBytes(), typeInfo.getCharset())); - } - - // None of the special/fast textual conversion cases applied. Just go the normal route of converting via String. - return convertStringToObject(new String(stream.getBytes(), typeInfo.getCharset()), typeInfo.getCharset(), jdbcType, getterArgs.streamType); - - case CLOB: - return new SQLServerClob(stream, typeInfo); - - case NCLOB: - return new SQLServerNClob(stream, typeInfo); - case SQLXML: - return new SQLServerSQLXML(stream, getterArgs, typeInfo); - - case BINARY: - case VARBINARY: - case LONGVARBINARY: - case BLOB: - - // Where allowed, streams convert directly to binary representation - - if (StreamType.BINARY == getterArgs.streamType) - return stream; - - if (JDBCType.BLOB == jdbcType) - return new SQLServerBlob(stream); - - return stream.getBytes(); - } - } - - // Conversion can throw either of these exceptions: - // - // UnsupportedEncodingException (binary conversions) - // IllegalArgumentException (any conversion - note: numerics throw NumberFormatException subclass) - // - // Catch them and translate them to a SQLException so that we don't propagate an unexpected exception - // type all the way up to the app, which may not catch it either... - catch (IllegalArgumentException e) - { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue")); - throw new SQLServerException(form.format(new Object[] {typeInfo.getSSType(), jdbcType}), null, 0, e); - } - catch (UnsupportedEncodingException e) - { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue")); - throw new SQLServerException(form.format(new Object[] {typeInfo.getSSType(), jdbcType}), null, 0, e); - } - } - - // Returns date portion of string. - // Expects one of "" or "