From 8fd821ba60f7fdbb4edf984b8a985a5d52501aad Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Mon, 28 Nov 2016 12:53:03 +0100 Subject: [PATCH 01/72] Added automatic detection of REALM in SPN needed for Cross Domain authentication. The original driver only computes the SPN without its REALM. That is why the driver fails in a cross-domain authentication (ex: user@REALM1 try to log on server@REALM2 while REALM2 and REALM1 are in trust). This commit solves this by trying to compute the REALM when REALM has not been provided in the SPN. Which includes both generated SPN and User-Provided SPN. It also enable Kerberos authentication when only IP is provided as long as reverse DNS are present since when SPN is provided, and REALM lookup did fail, it will also try with canonical name and if it works, override the hostname in SPN (feature not activated when user did provide an SPN) Fixed issue when SQL Server is the same machine as kdc --- .../sqlserver/jdbc/KerbAuthentication.java | 135 +++++++++++++++++- 1 file changed, 132 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index 6706d84a6..f82d913cf 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -8,14 +8,20 @@ package com.microsoft.sqlserver.jdbc; +import java.lang.reflect.Method; import java.net.IDN; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.security.auth.Subject; import javax.security.auth.login.AppConfigurationEntry; @@ -247,6 +253,7 @@ private String makeSpn(String server, // Get user provided SPN string; if not provided then build the generic one String userSuppliedServerSpn = con.activeConnectionProperties.getProperty(SQLServerDriverStringProperty.SERVER_SPN.toString()); + String spn; if (null != userSuppliedServerSpn) { // serverNameAsACE is true, translate the user supplied serverSPN to ASCII if (con.serverNameAsACE()) { @@ -260,11 +267,133 @@ private String makeSpn(String server, else { spn = makeSpn(address, port); } + this.spn = enrichSpnWithRealm(spn, null == userSuppliedServerSpn); + //DEBUG System.err.println("SPN before enrichment: " + spn + " ; AFTER enrichment: " + this.spn); + } + + private static final Pattern SPN_PATTERN = Pattern.compile("MSSQLSvc/(.*):([^:@]+)(@.+)?", Pattern.CASE_INSENSITIVE); + + private String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalization) { + if (spn == null) { + return spn; + } + Matcher m = SPN_PATTERN.matcher(spn); + if (!m.matches()) { + return spn; + } + if (m.group(3) != null) { + // Realm is already present, no need to enrich, the job has already been done + return spn; + } + String dnsName = m.group(1); + String portOrInstance = m.group(2); + RealmValidator realmValidator = getRealmValidator(); + String realm = findRealmFromHostname(realmValidator, dnsName); + if (realm == null && allowHostnameCanonicalization) { + // We failed, try with canonical host name to find a better match + try { + String canonicalHostName = InetAddress.getByName(dnsName).getCanonicalHostName(); + realm = findRealmFromHostname(realmValidator, canonicalHostName); + // Since we have a match, our hostname is the correct one (for instance of server + // name was an IP), so we override dnsName as well + dnsName = canonicalHostName; + } catch (UnknownHostException cannotCanonicalize) { + // ignored, but we are in a bad shape + } + } + if (realm == null) { + return spn; + } else { + StringBuilder sb = new StringBuilder("MSSQLSvc/"); + sb.append(dnsName).append(":").append(portOrInstance).append("@").append(realm.toUpperCase(Locale.ENGLISH)); + return sb.toString(); + } } - byte[] GenerateClientContext(byte[] pin, - boolean[] done) throws SQLServerException { - if (null == peerContext) { + private static RealmValidator validator; + + /** + * Find a suitable way of validating a REALM for given JVM. + * + * @return a not null realm Validator. + */ + static RealmValidator getRealmValidator() { + if (validator != null) { + return validator; + } + // JVM Specific, here Sun/Oracle JVM + try { + Class clz = Class.forName("sun.security.krb5.Config"); + Method getInstance = clz.getMethod("getInstance", new Class[0]); + final Method getKDCList = clz.getMethod("getKDCList", new Class[] { String.class }); + final Object instance = getInstance.invoke(null); + //DEBUG final Method getDefaultRealm = clz.getMethod("getDefaultRealm", new Class[0]); + //DEBUG final Object realmDefault = getDefaultRealm.invoke(instance); + //DEBUG System.err.println("default_realm="+realmDefault); + RealmValidator oracleRealmValidator = new RealmValidator() { + + @Override + public boolean isRealmValid(String realm) { + try { + //DEBUG System.err.println("RealmValidator.isRealmValid("+realm+")"); + Object ret = getKDCList.invoke(instance, realm); + return ret!=null; + } catch (Exception err) { + return false; + } + } + }; + validator = oracleRealmValidator; + return oracleRealmValidator; + } catch (ReflectiveOperationException notTheRightJVMException) { + // Ignored, we simply are not using the right JVM + } + // No implementation found, default one, not any realm is valid + validator = new RealmValidator() { + @Override + public boolean isRealmValid(String realm) { + return false; + } + }; + return validator; + } + + /** + * Try to find a REALM in the different parts of a host name. + * + * @param realmValidator a function that return true if REALM is valid and exists + * @param hostname the name we are looking a REALM for + * @return the realm if found, null otherwise + */ + private static String findRealmFromHostname(RealmValidator realmValidator, String hostname) { + if (hostname == null) { + return null; + } + int index = 0; + while (index != -1 && index < hostname.length() - 2) { + String realm = hostname.substring(index); + if (realmValidator.isRealmValid(realm)) { + return realm.toUpperCase(); + } + index = hostname.indexOf(".", index + 1); + if (index != -1){ + index = index + 1; + } + } + return null; + } + + /** + * JVM Specific implementation to decide whether a realm is valid or not + */ + interface RealmValidator { + boolean isRealmValid(String realm); + } + + byte[] GenerateClientContext(byte[] pin, boolean[] done ) throws SQLServerException + { + if(null == peerContext) + { intAuthInit(); } return intAuthHandShake(pin, done); From d6de0906f44242d0226ad26022a58d3fc5c4904e Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Wed, 22 Feb 2017 23:54:59 +0100 Subject: [PATCH 02/72] Added DNS Fallback for REALM resolution --- .../sqlserver/jdbc/KerbAuthentication.java | 45 ++++-- .../jdbc/dns/DNSKerberosLocator.java | 33 ++++ .../sqlserver/jdbc/dns/DNSRecordSRV.java | 142 ++++++++++++++++++ .../sqlserver/jdbc/dns/DNSUtilities.java | 63 ++++++++ .../sqlserver/jdbc/dns/DNSRealmsTest.java | 21 +++ 5 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index f82d913cf..e0a256703 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -23,6 +23,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.naming.NamingException; import javax.security.auth.Subject; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; @@ -36,6 +37,8 @@ import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; +import com.microsoft.sqlserver.jdbc.dns.DNSKerberosLocator; + /** * KerbAuthentication for int auth. */ @@ -268,7 +271,9 @@ private String makeSpn(String server, spn = makeSpn(address, port); } this.spn = enrichSpnWithRealm(spn, null == userSuppliedServerSpn); - //DEBUG System.err.println("SPN before enrichment: " + spn + " ; AFTER enrichment: " + this.spn); + if (!this.spn.equals(spn) && authLogger.isLoggable(Level.FINER)){ + authLogger.finer(toString() + "SPN enriched: " + spn + " := " + this.spn); + } } private static final Pattern SPN_PATTERN = Pattern.compile("MSSQLSvc/(.*):([^:@]+)(@.+)?", Pattern.CASE_INSENSITIVE); @@ -287,7 +292,7 @@ private String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalizat } String dnsName = m.group(1); String portOrInstance = m.group(2); - RealmValidator realmValidator = getRealmValidator(); + RealmValidator realmValidator = getRealmValidator(dnsName); String realm = findRealmFromHostname(realmValidator, dnsName); if (realm == null && allowHostnameCanonicalization) { // We failed, try with canonical host name to find a better match @@ -315,9 +320,10 @@ private String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalizat /** * Find a suitable way of validating a REALM for given JVM. * + * @param hostnameToTest an example hostname we are gonna use to test our realm validator. * @return a not null realm Validator. */ - static RealmValidator getRealmValidator() { + static RealmValidator getRealmValidator(String hostnameToTest) { if (validator != null) { return validator; } @@ -327,15 +333,11 @@ static RealmValidator getRealmValidator() { Method getInstance = clz.getMethod("getInstance", new Class[0]); final Method getKDCList = clz.getMethod("getKDCList", new Class[] { String.class }); final Object instance = getInstance.invoke(null); - //DEBUG final Method getDefaultRealm = clz.getMethod("getDefaultRealm", new Class[0]); - //DEBUG final Object realmDefault = getDefaultRealm.invoke(instance); - //DEBUG System.err.println("default_realm="+realmDefault); RealmValidator oracleRealmValidator = new RealmValidator() { @Override public boolean isRealmValid(String realm) { try { - //DEBUG System.err.println("RealmValidator.isRealmValid("+realm+")"); Object ret = getKDCList.invoke(instance, realm); return ret!=null; } catch (Exception err) { @@ -344,15 +346,28 @@ public boolean isRealmValid(String realm) { } }; validator = oracleRealmValidator; - return oracleRealmValidator; + // As explained here: https://github.com/Microsoft/mssql-jdbc/pull/40#issuecomment-281509304 + // The default Oracle Resolution mechanism is not bulletproof + // If it resolves a crappy name, drop it. + if (!validator.isRealmValid("this.might.not.exist." + hostnameToTest)){ + // Our realm validator is well working, return it + authLogger.fine("Kerberos Realm Validator: Using Built-in Oracle Realm Validation method."); + return oracleRealmValidator; + } + authLogger.fine("Kerberos Realm Validator: Detected buggy Oracle Realm Validator, using DNSKerberosLocator."); } catch (ReflectiveOperationException notTheRightJVMException) { // Ignored, we simply are not using the right JVM + authLogger.fine("Kerberos Realm Validator: No Oracle Realm Validator Available, using DNSKerberosLocator."); } // No implementation found, default one, not any realm is valid validator = new RealmValidator() { @Override public boolean isRealmValid(String realm) { - return false; + try { + return DNSKerberosLocator.isRealmValid(realm); + } catch (NamingException err){ + return false; + } } }; return validator; @@ -365,13 +380,16 @@ public boolean isRealmValid(String realm) { * @param hostname the name we are looking a REALM for * @return the realm if found, null otherwise */ - private static String findRealmFromHostname(RealmValidator realmValidator, String hostname) { + private String findRealmFromHostname(RealmValidator realmValidator, String hostname) { if (hostname == null) { return null; } int index = 0; while (index != -1 && index < hostname.length() - 2) { String realm = hostname.substring(index); + if (authLogger.isLoggable(Level.FINEST)) { + authLogger.finest(toString() + " looking up REALM candidate " + realm); + } if (realmValidator.isRealmValid(realm)) { return realm.toUpperCase(); } @@ -390,10 +408,9 @@ interface RealmValidator { boolean isRealmValid(String realm); } - 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); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java new file mode 100644 index 000000000..3e493a13c --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java @@ -0,0 +1,33 @@ +package com.microsoft.sqlserver.jdbc.dns; + +import java.util.Set; + +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; + +public final class DNSKerberosLocator { + + private DNSKerberosLocator() {} + + /** + * Tells whether a realm is valid. + * + * @param realmName the realm to test + * @return true if realm is valid, false otherwise + * @throws NamingException if DNS failed, so realm existence cannot be determined + */ + public static boolean isRealmValid(String realmName) throws NamingException { + if (realmName == null || realmName.length() < 2) { + return false; + } + if (realmName.startsWith(".")) { + realmName = realmName.substring(1); + } + try { + Set records = DNSUtilities.findSrvRecords("_kerberos._udp." + realmName); + return !records.isEmpty(); + } catch (NameNotFoundException wrongDomainException) { + return false; + } + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java new file mode 100644 index 000000000..08342981b --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java @@ -0,0 +1,142 @@ +package com.microsoft.sqlserver.jdbc.dns; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Describe an DNS SRV Record. + */ +public class DNSRecordSRV implements Comparable { + + private static final Pattern PATTERN = Pattern.compile("^([0-9]+) ([0-9]+) ([0-9]+) (.+)$"); + + private final int priority; + + /** + * Parse a DNS SRC Record from a DNS String record. + * + * @param record + * the record to parse + * @return a not null DNS Record + * @throws IllegalArgumentException + * if record is not correct and cannot be parsed + */ + public static DNSRecordSRV parseFromDNSRecord(String record) throws IllegalArgumentException { + Matcher m = PATTERN.matcher(record); + if (!m.matches()) { + throw new IllegalArgumentException("record '" + record + "' cannot be matched as a valid DNS SRV Record"); + } + try { + int priority = Integer.parseInt(m.group(1)); + int weight = Integer.parseInt(m.group(2)); + int port = Integer.parseInt(m.group(3)); + String serverName = m.group(4); + // Avoid issues with Kerberos SPN when fully qualified records ends with '.' + if (serverName.endsWith(".")) { + serverName = serverName.substring(0, serverName.length() - 1); + } + return new DNSRecordSRV(priority, weight, port, serverName); + } catch (IllegalArgumentException err) { + throw err; + } catch (Exception err) { + throw new IllegalArgumentException("Failed to parse DNS SRV record '" + record + "'", err); + } + } + + @Override + public String toString() { + return String.format("DNS.SRV[pri=%d w=%d port=%d h='%s']", priority, weight, port, serverName); + } + + /** + * Constructor. + * + * @param priority + * is lowest + * @param weight + * 1 at minimum + * @param port + * the port of service + * @param serverName + * the host + * @throws IllegalArgumentException + * if priority < 0 or weight <= 1 + */ + public DNSRecordSRV(int priority, int weight, int port, String serverName) throws IllegalArgumentException { + if (priority < 0) { + throw new IllegalArgumentException("priority must be >= 0, but was: " + priority); + } + this.priority = priority; + if (weight < 0) { + // Weight == 0 is OK to disable load balancing, but not below + throw new IllegalArgumentException("weight must be >= 0, but was: " + weight); + } + this.weight = weight; + if (port < 0 || port > 65535) { + throw new IllegalArgumentException("port must be between 0 and 65535, but was: " + port); + } + this.port = port; + if (serverName == null || serverName.trim().isEmpty()) { + throw new IllegalArgumentException("hostname is not supposed to be null or empty in a SRV Record"); + } + this.serverName = serverName; + } + + private final int weight; + private final int port; + private final String serverName; + + @Override + public int hashCode() { + return serverName.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof DNSRecordSRV)) { + return false; + } + + DNSRecordSRV r = (DNSRecordSRV) other; + return port == r.port && weight == r.weight && priority == r.priority && serverName.equals(r.serverName); + } + + @Override + public int compareTo(DNSRecordSRV o) { + if (o == null) { + return 1; + } + int p = Integer.compare(priority, o.priority); + if (p != 0) { + return p; + } + p = Integer.compare(weight, o.weight); + if (p != 0) { + return p; + } + p = Integer.compare(port, o.port); + if (p != 0) { + return p; + } + return serverName.compareTo(o.serverName); + } + + public int getPriority() { + return priority; + } + + public int getWeight() { + return weight; + } + + public int getPort() { + return port; + } + + public String getServerName() { + return serverName; + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java new file mode 100644 index 000000000..a923e6048 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java @@ -0,0 +1,63 @@ +package com.microsoft.sqlserver.jdbc.dns; + +import java.util.Hashtable; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +public class DNSUtilities { + + private final static Logger LOG = Logger.getLogger(DNSUtilities.class.getName()); + + private static final Level DNS_ERR_LOG_LEVEL = Level.FINE; + + /** + * Find all SRV Record using DNS. + * You can then use {@link DNSRecordsSRVCollection#getBestRecord()} to find + * the best candidate (for instance for Round-Robin calls) + * + * @param dnsSrvRecordToFind + * the DNS record, for instance: _ldap._tcp.dc._msdcs.DOMAIN.COM + * to find all LDAP servers in DOMAIN.COM + * @return the collection of records with facilities to find the best + * candidate + * @throws NamingException + * if DNS is not available + */ + public static Set findSrvRecords(final String dnsSrvRecordToFind) throws NamingException { + Hashtable env = new Hashtable(); + env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); + env.put("java.naming.provider.url", "dns:"); + DirContext ctx = new InitialDirContext(env); + Attributes attrs = ctx.getAttributes(dnsSrvRecordToFind, new String[] { "SRV" }); + NamingEnumeration allServers = attrs.getAll(); + TreeSet records = new TreeSet(); + while (allServers.hasMoreElements()) { + Attribute a = allServers.nextElement(); + NamingEnumeration srvRecord = a.getAll(); + while (srvRecord.hasMore()) { + final String record = String.valueOf(srvRecord.nextElement()); + try { + DNSRecordSRV rec = DNSRecordSRV.parseFromDNSRecord(record); + if (rec != null) { + records.add(rec); + } + } catch (IllegalArgumentException errorParsingRecord) { + if (LOG.isLoggable(DNS_ERR_LOG_LEVEL)) { + LOG.log(DNS_ERR_LOG_LEVEL, String.format("Failed to parse SRV DNS Record: '%s'", record), + errorParsingRecord); + } + } + } + } + return records; + } +} diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java new file mode 100644 index 000000000..9753dadff --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java @@ -0,0 +1,21 @@ +package com.microsoft.sqlserver.jdbc.dns; + +import javax.naming.NamingException; + +public class DNSRealmsTest { + + public static void main(String... args) { + if (args.length < 1) { + System.err.println("USAGE: list of domains to test for kerberos realms"); + } + for (String realmName : args) { + try { + System.out.print(DNSKerberosLocator.isRealmValid(realmName) ? "[ VALID ] " : "[INVALID] "); + } catch (NamingException err) { + System.err.print("[ FAILED] : " + err.getClass().getName() + ":" + err.getMessage()); + } + System.out.println(realmName); + } + } + +} From 2337105299b6611cc8ada83b4d90af050072ab41 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Mon, 3 Apr 2017 10:57:32 -0700 Subject: [PATCH 03/72] unclean fix works --- .../java/com/microsoft/sqlserver/jdbc/IOBuffer.java | 7 +++++++ .../java/com/microsoft/sqlserver/jdbc/Parameter.java | 10 ---------- .../microsoft/sqlserver/jdbc/SQLServerResource.java | 1 - 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 507e65cfa..766e5fbce 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4536,6 +4536,9 @@ void writeTVPRows(TVP value) throws SQLServerException { boolean isShortValue, isNull; int dataLength; + ByteBuffer tempStagingBuffer = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); + tempStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); + if (!value.isNull()) { Map columnMetadata = value.getColumnMetadata(); Iterator> columnsIterator; @@ -4749,8 +4752,12 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) } currentColumn++; } + tempStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); } } + stagingBuffer.clear(); + stagingBuffer.put(tempStagingBuffer.array(), 0, tempStagingBuffer.position()); + // TVP_END_TOKEN writeByte((byte) 0x00); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java index 6c12683c4..9595cab70 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java @@ -331,16 +331,6 @@ 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) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 53d9ddf6c..3503b6015 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -369,7 +369,6 @@ protected Object[][] getContents() { {"R_invalidKeyStoreFile", "Cannot parse \"{0}\". Either the file format is not valid or the password is not correct."}, // for JKS/PKCS {"R_invalidCEKCacheTtl", "Invalid column encryption key cache time-to-live specified. The columnEncryptionKeyCacheTtl value cannot be negative and timeUnit can only be DAYS, HOURS, MINUTES or SECONDS."}, {"R_sendTimeAsDateTimeForAE", "Use sendTimeAsDateTime=false with Always Encrypted."}, - {"R_invalidServerCursorForTVP" , "Use different Connection for source ResultSet and prepared query, if selectMethod is set to cursor for Table-Valued Parameter."}, {"R_TVPnotWorkWithSetObjectResultSet" , "setObject() with ResultSet is not supported for Table-Valued Parameter. Please use setStructured()"}, {"R_invalidQueryTimeout", "The queryTimeout {0} is not valid."}, {"R_invalidSocketTimeout", "The socketTimeout {0} is not valid."}, From dc99efd2958f5af154b7cd8b9caabe551735382d Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Mon, 3 Apr 2017 14:00:26 -0700 Subject: [PATCH 04/72] clean the logic --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 35 ++++++++++++++++--- .../sqlserver/jdbc/SQLServerResultSet.java | 4 +++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 766e5fbce..3e9f39431 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4535,11 +4535,29 @@ void writeTVP(TVP value) throws SQLServerException { void writeTVPRows(TVP value) throws SQLServerException { boolean isShortValue, isNull; int dataLength; + + boolean tdsWritterCached = false; - ByteBuffer tempStagingBuffer = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); - tempStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); + ByteBuffer cachedStagingBuffer = null; if (!value.isNull()) { + + // If TVP is set with ResultSet and Server Cursor is used, the tdsWriter of the calling preparedStatement is overwritten + // by the SQLServerResultSet#next() method if the preparedStatement and the ResultSet are created by the same connection. + // Therefore, we need to cache the tdsWriter's value and update it with new TDS values. + if (TVPType.ResultSet == value.tvpType) { + if ((null != value.sourceResultSet) && (value.sourceResultSet instanceof SQLServerResultSet)) { + SQLServerStatement src_stmt = (SQLServerStatement) ((SQLServerResultSet) value.sourceResultSet).getStatement(); + int resultSetServerCursorId = ((SQLServerResultSet) value.sourceResultSet).getServerCursorId(); + + if (con.equals(src_stmt.getConnection()) && 0 != resultSetServerCursorId) { + cachedStagingBuffer = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); + cachedStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); + tdsWritterCached = true; + } + } + } + Map columnMetadata = value.getColumnMetadata(); Iterator> columnsIterator; @@ -4752,11 +4770,18 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) } currentColumn++; } - tempStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); + + if (tdsWritterCached) { + cachedStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); + stagingBuffer.clear(); + } } } - stagingBuffer.clear(); - stagingBuffer.put(tempStagingBuffer.array(), 0, tempStagingBuffer.position()); + + if (tdsWritterCached) { + stagingBuffer.clear(); + stagingBuffer.put(cachedStagingBuffer.array(), 0, cachedStagingBuffer.position()); + } // TVP_END_TOKEN writeByte((byte) 0x00); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 33446ed72..cde46e195 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -83,6 +83,10 @@ String getClassNameLogging() { private boolean isClosed = false; private final int serverCursorId; + + int getServerCursorId() { + return serverCursorId; + } /** the intended fetch direction to optimize cursor performance */ private int fetchDirection; From 290b8bc437dec25f7688d9c457e86f21021524f9 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 28 Feb 2017 10:42:54 +0100 Subject: [PATCH 05/72] Allow authenticating using Kerberos and a Principal/Password. This patch allow to authenticate using kerberos using the previous methods (eg: keytab) or specifying user/password either in properties or in connect method. This allows to use GUIs for instance to connect using Kerberos without having a sql user. --- .../sqlserver/jdbc/KerbAuthentication.java | 5 +- .../sqlserver/jdbc/KerbCallback.java | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index d84058e58..d3fb4aa94 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -87,7 +87,6 @@ class SQLJDBCDriverConfig extends Configuration { 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)) @@ -144,14 +143,16 @@ private void intAuthInit() throws SQLServerException { AccessControlContext context = AccessController.getContext(); currentSubject = Subject.getSubject(context); if (null == currentSubject) { - lc = new LoginContext(CONFIGNAME); + lc = new LoginContext(CONFIGNAME, new KerbCallback(con)); lc.login(); // per documentation LoginContext will instantiate a new subject. currentSubject = lc.getSubject(); } } catch (LoginException le) { + authLogger.fine("Failed to login due to " + le.getClass().getName() + ":" + le.getMessage()); con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le); + return; } if (authLogger.isLoggable(Level.FINER)) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java new file mode 100644 index 000000000..941d71780 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java @@ -0,0 +1,53 @@ +package com.microsoft.sqlserver.jdbc; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Properties; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +public class KerbCallback implements CallbackHandler { + + private final SQLServerConnection con; + + KerbCallback(SQLServerConnection con) { + this.con = con; + } + + private static String getAnyOf(Callback callback, Properties properties, String... names) + throws UnsupportedCallbackException { + for (String name : names) { + String val = properties.getProperty(name); + if (val != null && !val.trim().isEmpty()) { + return val; + } + } + throw new UnsupportedCallbackException(callback, + "Cannot get any of properties: " + Arrays.toString(names) + " from con properties"); + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + Callback callback = callbacks[i]; + if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(getAnyOf(callback, con.activeConnectionProperties, + "user", SQLServerDriverStringProperty.USER.name())); + } else if (callback instanceof PasswordCallback) { + String password = getAnyOf(callback, con.activeConnectionProperties, + "password", SQLServerDriverStringProperty.PASSWORD.name()); + ((PasswordCallback) callbacks[i]) + .setPassword(password.toCharArray()); + + } else { + throw new UnsupportedCallbackException(callback, "Unrecognized Callback type: " + callback.getClass()); + } + } + + } + +} From c4082f81b1bf164d5acc608b1c66eff14b2c0a75 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 28 Feb 2017 23:47:11 +0100 Subject: [PATCH 06/72] When Kerberos credentials were wrong, authentication was looping for a long time. This commit solves this by stopping directly authentication when login() fails. It means that if keytab is wrong OR provided principal/password are wrong, the driver immediatly return and does not retry in a endless loop. --- .../sqlserver/jdbc/KerbAuthentication.java | 20 ++++++++++++++++--- .../sqlserver/jdbc/KerbCallback.java | 16 +++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index d3fb4aa94..6fdfd3cb5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -139,19 +139,33 @@ private void intAuthInit() throws SQLServerException { } else { Subject currentSubject = null; + KerbCallback callback = new KerbCallback(con); try { AccessControlContext context = AccessController.getContext(); currentSubject = Subject.getSubject(context); if (null == currentSubject) { - lc = new LoginContext(CONFIGNAME, new KerbCallback(con)); + lc = new LoginContext(CONFIGNAME, callback); lc.login(); // per documentation LoginContext will instantiate a new subject. currentSubject = lc.getSubject(); } } catch (LoginException le) { - authLogger.fine("Failed to login due to " + le.getClass().getName() + ":" + le.getMessage()); - con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le); + if (authLogger.isLoggable(Level.FINE)) { + authLogger.fine(toString() + "Failed to login using Kerberos due to " + le.getClass().getName() + ":" + le.getMessage()); + } + try { + // Not very clean since it raises an Exception, but we are sure we are cleaning well everything + con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le); + } catch (SQLServerException alwaysTriggered) { + String message = String.format("%s due to %s (%s)", alwaysTriggered.getMessage(), le.getClass().getName(), le.getMessage()); + if (callback.getUsernameRequested() != null) { + message = String.format("Login failed for Kerberos principal '%s'. %s", callback.getUsernameRequested(), message); + } + // By throwing Exception with LOGON_FAILED -> we avoid looping for connection + // In this case, authentication will never work anyway -> fail fast + throw new SQLServerException(message, alwaysTriggered.getSQLState(), SQLServerException.LOGON_FAILED, le); + } return; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java index 941d71780..bdb38e3e1 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java @@ -13,6 +13,7 @@ public class KerbCallback implements CallbackHandler { private final SQLServerConnection con; + private String usernameRequested = null; KerbCallback(SQLServerConnection con) { this.con = con; @@ -30,13 +31,22 @@ private static String getAnyOf(Callback callback, Properties properties, String. "Cannot get any of properties: " + Arrays.toString(names) + " from con properties"); } + /** + * If a name was retrieved By Kerberos, return it. + * @return null if callback was not called or username was not provided + */ + public String getUsernameRequested(){ + return usernameRequested; + } + @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { Callback callback = callbacks[i]; if (callback instanceof NameCallback) { - ((NameCallback) callback).setName(getAnyOf(callback, con.activeConnectionProperties, - "user", SQLServerDriverStringProperty.USER.name())); + usernameRequested = getAnyOf(callback, con.activeConnectionProperties, + "user", SQLServerDriverStringProperty.USER.name()); + ((NameCallback) callback).setName(usernameRequested); } else if (callback instanceof PasswordCallback) { String password = getAnyOf(callback, con.activeConnectionProperties, "password", SQLServerDriverStringProperty.PASSWORD.name()); @@ -47,7 +57,5 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback throw new UnsupportedCallbackException(callback, "Unrecognized Callback type: " + callback.getClass()); } } - } - } From 8d6098ffcdaf630d1d247ee9a2584c18b29301de Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 4 Apr 2017 01:21:20 +0200 Subject: [PATCH 07/72] Fixed missing this -> wrong semantics --- .../java/com/microsoft/sqlserver/testframework/DBCoercion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java b/src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java index 505cf1b25..e4cea5a53 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java @@ -45,7 +45,7 @@ public DBCoercion(Class type) { public DBCoercion(Class type, int[] tempflags) { name = type.toString(); - type = type; + this.type = type; for (int i = 0; i < tempflags.length; i++) flags.set(tempflags[i]); } From e178fca27952219dc17e4f93ee8099e88d3b8fff Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 4 Apr 2017 02:02:17 +0200 Subject: [PATCH 08/72] Fixed possible NullPointerException. This exception may occur at least in the following cases: * Race condition between free() and trying to open stream * Serialization (since it does not seems well implemented, I doubt activeStreams can be serialized properly) I am not sure at all serialization is working well. About the race condition, I am almost sure all of this is not thread-safe, but I am too lazy to read JDBC specification regarding blobs. --- src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java index 450fc357f..a48fe5df9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBlob.java @@ -154,6 +154,9 @@ public InputStream getBinaryStream() throws SQLException { return (InputStream) activeStreams.get(0); } else { + if (value == null) { + throw new SQLServerException("Unexpected Error: blob value is null while all streams are closed.", null); + } return getBinaryStreamInternal(0, value.length); } } From b0f1bbc1e7e1233c028934b53136521da51fa381 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Mon, 3 Apr 2017 18:49:55 -0700 Subject: [PATCH 09/72] detect if needs to read TVP response --- .../java/com/microsoft/sqlserver/jdbc/IOBuffer.java | 13 +++++++++++-- .../sqlserver/jdbc/SQLServerConnection.java | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 3e9f39431..bd958c402 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4781,6 +4781,8 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) if (tdsWritterCached) { stagingBuffer.clear(); stagingBuffer.put(cachedStagingBuffer.array(), 0, cachedStagingBuffer.position()); + + con.needsToReadTVPResponse = true; } // TVP_END_TOKEN @@ -6237,8 +6239,15 @@ private boolean nextPacket() throws SQLServerException { * that is trying to buffer it with TDSCommand.detach(). */ synchronized final boolean readPacket() throws SQLServerException { - if (null != command && !command.readingResponse()) - return false; + + if (null != command && !command.readingResponse()){ + + if(!con.needsToReadTVPResponse){ + return false; + } + + con.needsToReadTVPResponse = 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 diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index e3e4a6ba7..627167294 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -81,6 +81,8 @@ public class SQLServerConnection implements ISQLServerConnection { long timerExpire; boolean attemptRefreshTokenLocked = false; + + boolean needsToReadTVPResponse = false; private boolean fedAuthRequiredByUser = false; private boolean fedAuthRequiredPreLoginResponse = false; From f4725994ed1a5b8debc28c787c39111ceacf2178 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 4 Apr 2017 13:17:56 -0700 Subject: [PATCH 10/72] cache TDS command before overwriting it --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 24 +++++++++---------- .../sqlserver/jdbc/SQLServerConnection.java | 2 -- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index bd958c402..18f5b1218 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4539,12 +4539,14 @@ void writeTVPRows(TVP value) throws SQLServerException { boolean tdsWritterCached = false; ByteBuffer cachedStagingBuffer = null; + TDSCommand cachedCommand = null; if (!value.isNull()) { // If TVP is set with ResultSet and Server Cursor is used, the tdsWriter of the calling preparedStatement is overwritten // by the SQLServerResultSet#next() method if the preparedStatement and the ResultSet are created by the same connection. - // Therefore, we need to cache the tdsWriter's value and update it with new TDS values. + // Therefore, we need to cache the tdsWriter's values (stagingBuffer for sending data and command for retrieving data) and update + // stagingBuffer with new TDS values. if (TVPType.ResultSet == value.tvpType) { if ((null != value.sourceResultSet) && (value.sourceResultSet instanceof SQLServerResultSet)) { SQLServerStatement src_stmt = (SQLServerStatement) ((SQLServerResultSet) value.sourceResultSet).getStatement(); @@ -4553,6 +4555,9 @@ void writeTVPRows(TVP value) throws SQLServerException { if (con.equals(src_stmt.getConnection()) && 0 != resultSetServerCursorId) { cachedStagingBuffer = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); cachedStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); + + cachedCommand = this.command; + tdsWritterCached = true; } } @@ -4770,19 +4775,18 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) } currentColumn++; } - + if (tdsWritterCached) { cachedStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); stagingBuffer.clear(); } } } - + if (tdsWritterCached) { stagingBuffer.clear(); stagingBuffer.put(cachedStagingBuffer.array(), 0, cachedStagingBuffer.position()); - - con.needsToReadTVPResponse = true; + this.command = cachedCommand; } // TVP_END_TOKEN @@ -6239,14 +6243,8 @@ private boolean nextPacket() throws SQLServerException { * that is trying to buffer it with TDSCommand.detach(). */ synchronized final boolean readPacket() throws SQLServerException { - - if (null != command && !command.readingResponse()){ - - if(!con.needsToReadTVPResponse){ - return false; - } - - con.needsToReadTVPResponse = false; + if (null != command && !command.readingResponse()) { + return false; } // Number of packets in should always be less than number of packets out. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 627167294..e3e4a6ba7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -81,8 +81,6 @@ public class SQLServerConnection implements ISQLServerConnection { long timerExpire; boolean attemptRefreshTokenLocked = false; - - boolean needsToReadTVPResponse = false; private boolean fedAuthRequiredByUser = false; private boolean fedAuthRequiredPreLoginResponse = false; From c942b5c8f87e8facf35189ee7c84775f4676830a Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 4 Apr 2017 13:25:21 -0700 Subject: [PATCH 11/72] clean code --- src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 18f5b1218..014011691 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4537,7 +4537,6 @@ void writeTVPRows(TVP value) throws SQLServerException { int dataLength; boolean tdsWritterCached = false; - ByteBuffer cachedStagingBuffer = null; TDSCommand cachedCommand = null; @@ -6243,9 +6242,8 @@ private boolean nextPacket() throws SQLServerException { * that is trying to buffer it with TDSCommand.detach(). */ synchronized final boolean readPacket() throws SQLServerException { - if (null != command && !command.readingResponse()) { + 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 From 0503347cb6ed43557c22f9595bc38a60a43fef0a Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 4 Apr 2017 18:10:46 -0700 Subject: [PATCH 12/72] added tests --- .../jdbc/tvp/TVPResultSetCursorTest.java | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java new file mode 100644 index 000000000..405c08a5f --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -0,0 +1,177 @@ +/* + * 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.tvp; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.Properties; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.testframework.AbstractTest; + +@RunWith(JUnitPlatform.class) +public class TVPResultSetCursorTest extends AbstractTest { + + private static Connection conn = null; + static Statement stmt = null; + + static BigDecimal[] expectedBigDecimals = {new BigDecimal("12345.12345"), new BigDecimal("125.123"), new BigDecimal("45.12345")}; + static String[] expectedBigDecimalStrings = {"12345.12345", "125.12300", "45.12345"}; + + static String[] expectedStrings = {"hello", "world", "!!!"}; + + static Timestamp[] expectedTimestamps = {new Timestamp(1433338533461L), new Timestamp(14917485583999L), new Timestamp(1491123533000L)}; + static String[] expectedTimestampStrings = {"2015-06-03 06:35:33.4610000", "2442-09-18 18:59:43.9990000", "2017-04-02 01:58:53.0000000"}; + + private static String tvpName = "TVPResultSetCursorTest_TVP"; + private static String srcTable = "TVPResultSetCursorTest_SourceTable"; + private static String desTable = "TVPResultSetCursorTest_DestinationTable"; + + /** + * Test a previous failure when using server cursor and using the same connection to create TVP and result set. + * + * @throws SQLException + */ + @Test + public void testServerCursors() throws SQLException { + conn = DriverManager.getConnection(connectionString); + stmt = conn.createStatement(); + + dropTVPS(); + dropTables(); + + createTVPS(); + createTables(); + + populateSourceTable(); + + ResultSet rs = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE).executeQuery("select * from " + srcTable); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, rs); + pstmt.execute(); + + verifyDestinationTableData(); + + if (null != pstmt) { + pstmt.close(); + } + if (null != rs) { + rs.close(); + } + } + + /** + * Test a previous failure when setting SelectMethod to cursor and using the same connection to create TVP and result set. + * + * @throws SQLException + */ + @Test + public void testSelectMethodSetToCursor() throws SQLException { + Properties info = new Properties(); + info.setProperty("SelectMethod", "cursor"); + conn = DriverManager.getConnection(connectionString, info); + + stmt = conn.createStatement(); + + dropTVPS(); + dropTables(); + + createTVPS(); + createTables(); + + populateSourceTable(); + + ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, rs); + pstmt.execute(); + + verifyDestinationTableData(); + + if (null != pstmt) { + pstmt.close(); + } + if (null != rs) { + rs.close(); + } + } + + private static void verifyDestinationTableData() throws SQLException { + ResultSet rs = conn.createStatement().executeQuery("select * from " + desTable); + + int i = 0; + while (rs.next()) { + assertTrue(rs.getString(1).equals(expectedBigDecimalStrings[i])); + assertTrue(rs.getString(2).trim().equals(expectedStrings[i])); + assertTrue(rs.getString(3).equals(expectedTimestampStrings[i])); + i++; + } + + assertTrue(i == expectedBigDecimals.length); + } + + private static void populateSourceTable() throws SQLException { + String sql = "insert into " + srcTable + " values (?,?,?)"; + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement(sql); + + for (int i = 0; i < expectedBigDecimals.length; i++) { + pstmt.setBigDecimal(1, expectedBigDecimals[i]); + pstmt.setString(2, expectedStrings[i]); + pstmt.setTimestamp(3, expectedTimestamps[i]); + pstmt.execute(); + } + } + + private static void dropTables() throws SQLException { + stmt.executeUpdate("if object_id('" + srcTable + "','U') is not null" + " drop table " + srcTable); + stmt.executeUpdate("if object_id('" + desTable + "','U') is not null" + " drop table " + desTable); + } + + private static void createTables() throws SQLException { + String sql = "create table " + srcTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null);"; + stmt.execute(sql); + + sql = "create table " + desTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null);"; + stmt.execute(sql); + } + + private static void createTVPS() throws SQLException { + String TVPCreateCmd = "CREATE TYPE " + tvpName + " as table (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null)"; + stmt.executeUpdate(TVPCreateCmd); + } + + private static void dropTVPS() throws SQLException { + stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); + } + + @AfterEach + private void terminateVariation() throws SQLException { + if (null != conn) { + conn.close(); + } + if (null != stmt) { + stmt.close(); + } + } + +} \ No newline at end of file From 798021ae601d727d82824aceb358305006e4b1f7 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 09:00:02 -0700 Subject: [PATCH 13/72] print out expected value and actual value --- .../sqlserver/jdbc/tvp/TVPResultSetCursorTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java index 405c08a5f..169b7fad7 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -120,9 +120,12 @@ private static void verifyDestinationTableData() throws SQLException { int i = 0; while (rs.next()) { - assertTrue(rs.getString(1).equals(expectedBigDecimalStrings[i])); - assertTrue(rs.getString(2).trim().equals(expectedStrings[i])); - assertTrue(rs.getString(3).equals(expectedTimestampStrings[i])); + assertTrue(rs.getString(1).equals(expectedBigDecimalStrings[i]), + "Expected Value:" + expectedBigDecimalStrings[i] + ", Actual Value: " + rs.getString(1)); + assertTrue(rs.getString(2).trim().equals(expectedStrings[i]), + "Expected Value:" + expectedStrings[i] + ", Actual Value: " + rs.getString(2)); + assertTrue(rs.getString(3).equals(expectedTimestampStrings[i]), + "Expected Value:" + expectedTimestampStrings[i] + ", Actual Value: " + rs.getString(3)); i++; } From 207cf4bf11d405e62b90b046d82cbc1e65d7914b Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 09:21:33 -0700 Subject: [PATCH 14/72] use calendar to specify time zone --- .../sqlserver/jdbc/tvp/TVPResultSetCursorTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java index 169b7fad7..401938ffd 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -16,7 +16,9 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; +import java.util.Calendar; import java.util.Properties; +import java.util.TimeZone; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -38,7 +40,7 @@ public class TVPResultSetCursorTest extends AbstractTest { static String[] expectedStrings = {"hello", "world", "!!!"}; static Timestamp[] expectedTimestamps = {new Timestamp(1433338533461L), new Timestamp(14917485583999L), new Timestamp(1491123533000L)}; - static String[] expectedTimestampStrings = {"2015-06-03 06:35:33.4610000", "2442-09-18 18:59:43.9990000", "2017-04-02 01:58:53.0000000"}; + static String[] expectedTimestampStrings = {"2015-06-03 13:35:33.4610000", "2442-09-19 01:59:43.9990000", "2017-04-02 08:58:53.0000000"}; private static String tvpName = "TVPResultSetCursorTest_TVP"; private static String srcTable = "TVPResultSetCursorTest_SourceTable"; @@ -135,12 +137,14 @@ private static void verifyDestinationTableData() throws SQLException { private static void populateSourceTable() throws SQLException { String sql = "insert into " + srcTable + " values (?,?,?)"; + Calendar calGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement(sql); for (int i = 0; i < expectedBigDecimals.length; i++) { pstmt.setBigDecimal(1, expectedBigDecimals[i]); pstmt.setString(2, expectedStrings[i]); - pstmt.setTimestamp(3, expectedTimestamps[i]); + pstmt.setTimestamp(3, expectedTimestamps[i], calGMT); pstmt.execute(); } } From f998e19d13d246b046377bd09ef1d69b936031d1 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 10:35:22 -0700 Subject: [PATCH 15/72] use Utils.dropTableIfExists to drop source & dest tables --- .../microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java index 401938ffd..fcee06467 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -27,6 +27,7 @@ import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.Utils; @RunWith(JUnitPlatform.class) public class TVPResultSetCursorTest extends AbstractTest { @@ -150,8 +151,8 @@ private static void populateSourceTable() throws SQLException { } private static void dropTables() throws SQLException { - stmt.executeUpdate("if object_id('" + srcTable + "','U') is not null" + " drop table " + srcTable); - stmt.executeUpdate("if object_id('" + desTable + "','U') is not null" + " drop table " + desTable); + Utils.dropTableIfExists(srcTable, stmt); + Utils.dropTableIfExists(desTable, stmt); } private static void createTables() throws SQLException { From 253b852015c4e22d33e7da50772b52a91c54cdaf Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 11:25:06 -0700 Subject: [PATCH 16/72] add test with multiple prepared statements and result sets --- .../jdbc/tvp/TVPResultSetCursorTest.java | 92 +++++++++++++++++-- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java index fcee06467..7d935dc1f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -71,7 +71,7 @@ public void testServerCursors() throws SQLException { pstmt.setStructured(1, tvpName, rs); pstmt.execute(); - verifyDestinationTableData(); + verifyDestinationTableData(expectedBigDecimals.length); if (null != pstmt) { pstmt.close(); @@ -108,7 +108,7 @@ public void testSelectMethodSetToCursor() throws SQLException { pstmt.setStructured(1, tvpName, rs); pstmt.execute(); - verifyDestinationTableData(); + verifyDestinationTableData(expectedBigDecimals.length); if (null != pstmt) { pstmt.close(); @@ -118,21 +118,93 @@ public void testSelectMethodSetToCursor() throws SQLException { } } - private static void verifyDestinationTableData() throws SQLException { + /** + * test with multiple prepared statements and result sets + * + * @throws SQLException + */ + @Test + public void testMultiplePreparedStatementAndResultSet() throws SQLException { + conn = DriverManager.getConnection(connectionString); + + stmt = conn.createStatement(); + + dropTVPS(); + dropTables(); + + createTVPS(); + createTables(); + + populateSourceTable(); + + ResultSet rs = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE).executeQuery("select * from " + srcTable); + + SQLServerPreparedStatement pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + pstmt1.setStructured(1, tvpName, rs); + pstmt1.execute(); + verifyDestinationTableData(expectedBigDecimals.length); + + rs.beforeFirst(); + pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + pstmt1.setStructured(1, tvpName, rs); + pstmt1.execute(); + verifyDestinationTableData(expectedBigDecimals.length * 2); + + rs.beforeFirst(); + SQLServerPreparedStatement pstmt2 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + pstmt2.setStructured(1, tvpName, rs); + pstmt2.execute(); + verifyDestinationTableData(expectedBigDecimals.length * 3); + + String sql = "insert into " + desTable + " values (?,?,?)"; + Calendar calGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement(sql); + for (int i = 0; i < expectedBigDecimals.length; i++) { + pstmt1.setBigDecimal(1, expectedBigDecimals[i]); + pstmt1.setString(2, expectedStrings[i]); + pstmt1.setTimestamp(3, expectedTimestamps[i], calGMT); + pstmt1.execute(); + } + verifyDestinationTableData(expectedBigDecimals.length * 4); + + ResultSet rs2 = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE).executeQuery("select * from " + srcTable); + + pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + pstmt1.setStructured(1, tvpName, rs2); + pstmt1.execute(); + verifyDestinationTableData(expectedBigDecimals.length * 5); + + if (null != pstmt1) { + pstmt1.close(); + } + if (null != pstmt2) { + pstmt2.close(); + } + if (null != rs) { + rs.close(); + } + if (null != rs2) { + rs2.close(); + } + } + + private static void verifyDestinationTableData(int expectedNumberOfRows) throws SQLException { ResultSet rs = conn.createStatement().executeQuery("select * from " + desTable); + int expectedArrayLength = expectedBigDecimals.length; + int i = 0; while (rs.next()) { - assertTrue(rs.getString(1).equals(expectedBigDecimalStrings[i]), - "Expected Value:" + expectedBigDecimalStrings[i] + ", Actual Value: " + rs.getString(1)); - assertTrue(rs.getString(2).trim().equals(expectedStrings[i]), - "Expected Value:" + expectedStrings[i] + ", Actual Value: " + rs.getString(2)); - assertTrue(rs.getString(3).equals(expectedTimestampStrings[i]), - "Expected Value:" + expectedTimestampStrings[i] + ", Actual Value: " + rs.getString(3)); + assertTrue(rs.getString(1).equals(expectedBigDecimalStrings[i % expectedArrayLength]), + "Expected Value:" + expectedBigDecimalStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(1)); + assertTrue(rs.getString(2).trim().equals(expectedStrings[i % expectedArrayLength]), + "Expected Value:" + expectedStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(2)); + assertTrue(rs.getString(3).equals(expectedTimestampStrings[i % expectedArrayLength]), + "Expected Value:" + expectedTimestampStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(3)); i++; } - assertTrue(i == expectedBigDecimals.length); + assertTrue(i == expectedNumberOfRows); } private static void populateSourceTable() throws SQLException { From 4d181513640eab0774e640f59093c3588d9fa1df Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 16:55:00 -0700 Subject: [PATCH 17/72] Turn off TNIR for FedAuth if user does not set TNIR explicitly --- .../microsoft/sqlserver/jdbc/SQLServerConnection.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 700347dfa..d026c1a24 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -281,6 +281,8 @@ final int getQueryTimeoutSeconds() { final int getSocketTimeoutMilliseconds() { return socketTimeoutMilliseconds; } + + boolean userSetTNIR = true; private boolean sendTimeAsDatetime = SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue(); @@ -1126,6 +1128,7 @@ Connection connectInternal(Properties propsIn, sPropKey = SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (sPropValue == null) { + userSetTNIR = false; sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.TRANSPARENT_NETWORK_IP_RESOLUTION.getDefaultValue()); activeConnectionProperties.setProperty(sPropKey, sPropValue); } @@ -1286,6 +1289,13 @@ Connection connectInternal(Properties propsIn, && (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()))) { throw new SQLServerException(SQLServerException.getErrString("R_AADIntegratedOnNonWindows"), null); } + + // Turn off TNIR for FedAuth if user does not set TNIR explicitly + if (!userSetTNIR) { + if ((!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())) || (null != accessTokenInByte)) { + transparentNetworkIPResolution = false; + } + } sPropKey = SQLServerDriverStringProperty.WORKSTATION_ID.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); From 4cbaeddae884dcb48091192440e47fe8ba95c81c Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 17:01:12 -0700 Subject: [PATCH 18/72] Change the TNIR multiplier to 0.125 instead of 0.08 --- .../com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index d026c1a24..5e8c9a65f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -190,6 +190,7 @@ private enum State { } private final static float TIMEOUTSTEP = 0.08F; // fraction of timeout to use for fast failover connections + private final static float TIMEOUTSTEP_TNIR = 0.125F; final static int TnirFirstAttemptTimeoutMs = 500; // fraction of timeout to use for fast failover connections /* @@ -1591,9 +1592,12 @@ private void login(String primary, 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) { + if (isDBMirroring || useParallel) { timeoutUnitInterval = (long) (TIMEOUTSTEP * timerTimeout); } + else if (useTnir) { + timeoutUnitInterval = (long) (TIMEOUTSTEP_TNIR * timerTimeout); + } else { timeoutUnitInterval = timerTimeout; } From ac1283f55e6c12593a0dc63a11dd7b3d9ba379f4 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 17:07:25 -0700 Subject: [PATCH 19/72] Change the time slices to a multiple of 1,2,4 instead of 1,2,3,4 --- .../com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 5e8c9a65f..316afb147 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1785,12 +1785,16 @@ else if (null == currentPrimaryPlaceHolder) { // Update timeout interval (but no more than the point where we're supposed to fail: timerExpire) attemptNumber++; - if (useParallel || useTnir) { + if (useParallel) { intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * (attemptNumber + 1)); } else if (isDBMirroring) { intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * ((attemptNumber / 2) + 1)); } + else if (useTnir) { + long timeSlice = timeoutUnitInterval * (1 << (attemptNumber - 1)); + intervalExpire = System.currentTimeMillis() + timeSlice; + } else intervalExpire = timerExpire; // Due to the below condition and the timerHasExpired check in catch block, From 4ec29ca6079f5c8f4801def8ac0cdab4e86fa141 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 17:09:51 -0700 Subject: [PATCH 20/72] In case the timeout for the first slice is less than 500 ms then bump it up to 500 ms --- .../com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 316afb147..5fa31d516 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1793,6 +1793,12 @@ else if (isDBMirroring) { } else if (useTnir) { long timeSlice = timeoutUnitInterval * (1 << (attemptNumber - 1)); + + // In case the timeout for the first slice is less than 500 ms then bump it up to 500 ms + if ((1 == attemptNumber) && (500 > timeSlice)) { + timeSlice = 500; + } + intervalExpire = System.currentTimeMillis() + timeSlice; } else From 325b41ffec687431b149cc6bb4db83e1032f5028 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 17:20:54 -0700 Subject: [PATCH 21/72] Keep TNIR on if the user has explicitly specified it in the connection string. --- .../com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 5fa31d516..b3471bf89 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1478,9 +1478,11 @@ else if (0 == requestedPacketSize) false); } - // transparentNetworkIPResolution is ignored if multiSubnetFailover or DBMirroring is true. + // transparentNetworkIPResolution is ignored if multiSubnetFailover or DBMirroring is true and user does not set TNIR explicitly if (multiSubnetFailover || (null != failOverPartnerPropertyValue)) { - transparentNetworkIPResolution = false; + if (!userSetTNIR) { + transparentNetworkIPResolution = false; + } } // failoverPartner and applicationIntent=ReadOnly cannot be used together From a262878a7cf4498798175b809da6fb06bf984672 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 5 Apr 2017 17:26:41 -0700 Subject: [PATCH 22/72] Change the time slices to a multiple of 2,4,8 instead of 1,2,3,4 --- .../java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index b3471bf89..c4be44572 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1794,7 +1794,7 @@ else if (isDBMirroring) { intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * ((attemptNumber / 2) + 1)); } else if (useTnir) { - long timeSlice = timeoutUnitInterval * (1 << (attemptNumber - 1)); + long timeSlice = timeoutUnitInterval * (1 << attemptNumber); // In case the timeout for the first slice is less than 500 ms then bump it up to 500 ms if ((1 == attemptNumber) && (500 > timeSlice)) { From 2465f3d88b70941df9af2f268c77c4d82cd69f32 Mon Sep 17 00:00:00 2001 From: v-ahibr Date: Wed, 5 Apr 2017 18:07:00 -0700 Subject: [PATCH 23/72] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704c68f58..0908653d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## [Unreleased] +## Added + +## Changed + +## Fixed Issues + + ## [6.1.6] ### Added - Added constrained delegation to connection sample [#188](https://github.com/Microsoft/mssql-jdbc/pull/188) From 06fcf932686c7778d5210e8e5a7b592027f3a788 Mon Sep 17 00:00:00 2001 From: v-ahibr Date: Wed, 5 Apr 2017 18:07:31 -0700 Subject: [PATCH 24/72] Update CHANGELOG.md --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0908653d7..abcb36658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) ## [Unreleased] -## Added +### Added -## Changed +### Changed -## Fixed Issues +### Fixed Issues ## [6.1.6] From 115c7e9166b0393178a8276f624509aacbd0909d Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Fri, 7 Apr 2017 11:08:21 +0200 Subject: [PATCH 25/72] Reformat code in KerbCallback with formatter Added translated message for Kerberos login authentication errors. --- .../sqlserver/jdbc/KerbAuthentication.java | 7 +++-- .../sqlserver/jdbc/KerbCallback.java | 26 +++++++++---------- .../sqlserver/jdbc/SQLServerResource.java | 2 ++ 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index 6fdfd3cb5..868b20d7e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -13,6 +13,7 @@ import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; @@ -158,9 +159,11 @@ private void intAuthInit() throws SQLServerException { // Not very clean since it raises an Exception, but we are sure we are cleaning well everything con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le); } catch (SQLServerException alwaysTriggered) { - String message = String.format("%s due to %s (%s)", alwaysTriggered.getMessage(), le.getClass().getName(), le.getMessage()); + String message = MessageFormat.format(SQLServerException.getErrString("R_kerberosLoginFailed"), + alwaysTriggered.getMessage(), le.getClass().getName(), le.getMessage()); if (callback.getUsernameRequested() != null) { - message = String.format("Login failed for Kerberos principal '%s'. %s", callback.getUsernameRequested(), message); + message = MessageFormat.format(SQLServerException.getErrString("R_kerberosLoginFailedForUsername"), + callback.getUsernameRequested(), message); } // By throwing Exception with LOGON_FAILED -> we avoid looping for connection // In this case, authentication will never work anyway -> fail fast diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java index bdb38e3e1..8cdc4cca9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbCallback.java @@ -19,23 +19,24 @@ public class KerbCallback implements CallbackHandler { this.con = con; } - private static String getAnyOf(Callback callback, Properties properties, String... names) - throws UnsupportedCallbackException { + private static String getAnyOf(Callback callback, + Properties properties, + String... names) throws UnsupportedCallbackException { for (String name : names) { String val = properties.getProperty(name); if (val != null && !val.trim().isEmpty()) { return val; } } - throw new UnsupportedCallbackException(callback, - "Cannot get any of properties: " + Arrays.toString(names) + " from con properties"); + throw new UnsupportedCallbackException(callback, "Cannot get any of properties: " + Arrays.toString(names) + " from con properties"); } /** * If a name was retrieved By Kerberos, return it. + * * @return null if callback was not called or username was not provided */ - public String getUsernameRequested(){ + public String getUsernameRequested() { return usernameRequested; } @@ -44,16 +45,15 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback for (int i = 0; i < callbacks.length; i++) { Callback callback = callbacks[i]; if (callback instanceof NameCallback) { - usernameRequested = getAnyOf(callback, con.activeConnectionProperties, - "user", SQLServerDriverStringProperty.USER.name()); + usernameRequested = getAnyOf(callback, con.activeConnectionProperties, "user", SQLServerDriverStringProperty.USER.name()); ((NameCallback) callback).setName(usernameRequested); - } else if (callback instanceof PasswordCallback) { - String password = getAnyOf(callback, con.activeConnectionProperties, - "password", SQLServerDriverStringProperty.PASSWORD.name()); - ((PasswordCallback) callbacks[i]) - .setPassword(password.toCharArray()); + } + else if (callback instanceof PasswordCallback) { + String password = getAnyOf(callback, con.activeConnectionProperties, "password", SQLServerDriverStringProperty.PASSWORD.name()); + ((PasswordCallback) callbacks[i]).setPassword(password.toCharArray()); - } else { + } + else { throw new UnsupportedCallbackException(callback, "Unrecognized Callback type: " + callback.getClass()); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 84bc5d310..5d8c86d9b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -380,5 +380,7 @@ protected Object[][] getContents() { {"R_invalidFipsEncryptConfig", "Could not enable FIPS due to either encrypt is not true or using trusted certificate settings."}, {"R_invalidFipsProviderConfig", "Could not enable FIPS due to invalid FIPSProvider or TrustStoreType."}, {"R_serverPreparedStatementDiscardThreshold", "The serverPreparedStatementDiscardThreshold {0} is not valid."}, + {"R_kerberosLoginFailedForUsername", "Cannot login with Kerberos principal {0}, check your credentials. {1}"}, + {"R_kerberosLoginFailed", "Kerberos Login failed: {0} due to {1} ({2})"}, }; } From 97c768a32b77fc1e1fe63e7a3b51cf8869956169 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Fri, 7 Apr 2017 17:13:24 +0200 Subject: [PATCH 26/72] Use Microsoft code formatter for consistent indentation --- .../sqlserver/jdbc/KerbAuthentication.java | 40 ++++++++++++------- .../jdbc/dns/DNSKerberosLocator.java | 12 ++++-- .../sqlserver/jdbc/dns/DNSRecordSRV.java | 11 +++-- .../sqlserver/jdbc/dns/DNSUtilities.java | 18 ++++----- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index e0a256703..c0973007b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -275,10 +275,11 @@ private String makeSpn(String server, authLogger.finer(toString() + "SPN enriched: " + spn + " := " + this.spn); } } - + private static final Pattern SPN_PATTERN = Pattern.compile("MSSQLSvc/(.*):([^:@]+)(@.+)?", Pattern.CASE_INSENSITIVE); - private String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalization) { + private String enrichSpnWithRealm(String spn, + boolean allowHostnameCanonicalization) { if (spn == null) { return spn; } @@ -302,13 +303,15 @@ private String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalizat // Since we have a match, our hostname is the correct one (for instance of server // name was an IP), so we override dnsName as well dnsName = canonicalHostName; - } catch (UnknownHostException cannotCanonicalize) { + } + catch (UnknownHostException cannotCanonicalize) { // ignored, but we are in a bad shape } } if (realm == null) { return spn; - } else { + } + else { StringBuilder sb = new StringBuilder("MSSQLSvc/"); sb.append(dnsName).append(":").append(portOrInstance).append("@").append(realm.toUpperCase(Locale.ENGLISH)); return sb.toString(); @@ -320,7 +323,8 @@ private String enrichSpnWithRealm(String spn, boolean allowHostnameCanonicalizat /** * Find a suitable way of validating a REALM for given JVM. * - * @param hostnameToTest an example hostname we are gonna use to test our realm validator. + * @param hostnameToTest + * an example hostname we are gonna use to test our realm validator. * @return a not null realm Validator. */ static RealmValidator getRealmValidator(String hostnameToTest) { @@ -331,7 +335,7 @@ static RealmValidator getRealmValidator(String hostnameToTest) { try { Class clz = Class.forName("sun.security.krb5.Config"); Method getInstance = clz.getMethod("getInstance", new Class[0]); - final Method getKDCList = clz.getMethod("getKDCList", new Class[] { String.class }); + final Method getKDCList = clz.getMethod("getKDCList", new Class[] {String.class}); final Object instance = getInstance.invoke(null); RealmValidator oracleRealmValidator = new RealmValidator() { @@ -339,8 +343,9 @@ static RealmValidator getRealmValidator(String hostnameToTest) { public boolean isRealmValid(String realm) { try { Object ret = getKDCList.invoke(instance, realm); - return ret!=null; - } catch (Exception err) { + return ret != null; + } + catch (Exception err) { return false; } } @@ -349,13 +354,14 @@ public boolean isRealmValid(String realm) { // As explained here: https://github.com/Microsoft/mssql-jdbc/pull/40#issuecomment-281509304 // The default Oracle Resolution mechanism is not bulletproof // If it resolves a crappy name, drop it. - if (!validator.isRealmValid("this.might.not.exist." + hostnameToTest)){ + if (!validator.isRealmValid("this.might.not.exist." + hostnameToTest)) { // Our realm validator is well working, return it authLogger.fine("Kerberos Realm Validator: Using Built-in Oracle Realm Validation method."); return oracleRealmValidator; } authLogger.fine("Kerberos Realm Validator: Detected buggy Oracle Realm Validator, using DNSKerberosLocator."); - } catch (ReflectiveOperationException notTheRightJVMException) { + } + catch (ReflectiveOperationException notTheRightJVMException) { // Ignored, we simply are not using the right JVM authLogger.fine("Kerberos Realm Validator: No Oracle Realm Validator Available, using DNSKerberosLocator."); } @@ -365,7 +371,8 @@ public boolean isRealmValid(String realm) { public boolean isRealmValid(String realm) { try { return DNSKerberosLocator.isRealmValid(realm); - } catch (NamingException err){ + } + catch (NamingException err) { return false; } } @@ -376,11 +383,14 @@ public boolean isRealmValid(String realm) { /** * Try to find a REALM in the different parts of a host name. * - * @param realmValidator a function that return true if REALM is valid and exists - * @param hostname the name we are looking a REALM for + * @param realmValidator + * a function that return true if REALM is valid and exists + * @param hostname + * the name we are looking a REALM for * @return the realm if found, null otherwise */ - private String findRealmFromHostname(RealmValidator realmValidator, String hostname) { + private String findRealmFromHostname(RealmValidator realmValidator, + String hostname) { if (hostname == null) { return null; } @@ -394,7 +404,7 @@ private String findRealmFromHostname(RealmValidator realmValidator, String hostn return realm.toUpperCase(); } index = hostname.indexOf(".", index + 1); - if (index != -1){ + if (index != -1) { index = index + 1; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java index 3e493a13c..4b6c2fe2b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java @@ -7,14 +7,17 @@ public final class DNSKerberosLocator { - private DNSKerberosLocator() {} + private DNSKerberosLocator() { + } /** * Tells whether a realm is valid. * - * @param realmName the realm to test + * @param realmName + * the realm to test * @return true if realm is valid, false otherwise - * @throws NamingException if DNS failed, so realm existence cannot be determined + * @throws NamingException + * if DNS failed, so realm existence cannot be determined */ public static boolean isRealmValid(String realmName) throws NamingException { if (realmName == null || realmName.length() < 2) { @@ -26,7 +29,8 @@ public static boolean isRealmValid(String realmName) throws NamingException { try { Set records = DNSUtilities.findSrvRecords("_kerberos._udp." + realmName); return !records.isEmpty(); - } catch (NameNotFoundException wrongDomainException) { + } + catch (NameNotFoundException wrongDomainException) { return false; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java index 08342981b..73aa1658f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java @@ -36,9 +36,11 @@ public static DNSRecordSRV parseFromDNSRecord(String record) throws IllegalArgum serverName = serverName.substring(0, serverName.length() - 1); } return new DNSRecordSRV(priority, weight, port, serverName); - } catch (IllegalArgumentException err) { + } + catch (IllegalArgumentException err) { throw err; - } catch (Exception err) { + } + catch (Exception err) { throw new IllegalArgumentException("Failed to parse DNS SRV record '" + record + "'", err); } } @@ -62,7 +64,10 @@ public String toString() { * @throws IllegalArgumentException * if priority < 0 or weight <= 1 */ - public DNSRecordSRV(int priority, int weight, int port, String serverName) throws IllegalArgumentException { + public DNSRecordSRV(int priority, + int weight, + int port, + String serverName) throws IllegalArgumentException { if (priority < 0) { throw new IllegalArgumentException("priority must be >= 0, but was: " + priority); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java index a923e6048..d378fb127 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java @@ -21,14 +21,12 @@ public class DNSUtilities { /** * Find all SRV Record using DNS. - * You can then use {@link DNSRecordsSRVCollection#getBestRecord()} to find - * the best candidate (for instance for Round-Robin calls) + * + * You can then use {@link DNSRecordsSRVCollection#getBestRecord()} to find the best candidate (for instance for Round-Robin calls) * * @param dnsSrvRecordToFind - * the DNS record, for instance: _ldap._tcp.dc._msdcs.DOMAIN.COM - * to find all LDAP servers in DOMAIN.COM - * @return the collection of records with facilities to find the best - * candidate + * the DNS record, for instance: _ldap._tcp.dc._msdcs.DOMAIN.COM to find all LDAP servers in DOMAIN.COM + * @return the collection of records with facilities to find the best candidate * @throws NamingException * if DNS is not available */ @@ -37,7 +35,7 @@ public static Set findSrvRecords(final String dnsSrvRecordToFind) env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); env.put("java.naming.provider.url", "dns:"); DirContext ctx = new InitialDirContext(env); - Attributes attrs = ctx.getAttributes(dnsSrvRecordToFind, new String[] { "SRV" }); + Attributes attrs = ctx.getAttributes(dnsSrvRecordToFind, new String[] {"SRV"}); NamingEnumeration allServers = attrs.getAll(); TreeSet records = new TreeSet(); while (allServers.hasMoreElements()) { @@ -50,10 +48,10 @@ public static Set findSrvRecords(final String dnsSrvRecordToFind) if (rec != null) { records.add(rec); } - } catch (IllegalArgumentException errorParsingRecord) { + } + catch (IllegalArgumentException errorParsingRecord) { if (LOG.isLoggable(DNS_ERR_LOG_LEVEL)) { - LOG.log(DNS_ERR_LOG_LEVEL, String.format("Failed to parse SRV DNS Record: '%s'", record), - errorParsingRecord); + LOG.log(DNS_ERR_LOG_LEVEL, String.format("Failed to parse SRV DNS Record: '%s'", record), errorParsingRecord); } } } From 7b2a280d4679fae9de8e1a8160b6070df39e980e Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Mon, 10 Apr 2017 12:42:06 -0700 Subject: [PATCH 27/72] send TVP row by row to bypass MARS --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 014011691..c72962dac 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4537,23 +4537,22 @@ void writeTVPRows(TVP value) throws SQLServerException { int dataLength; boolean tdsWritterCached = false; - ByteBuffer cachedStagingBuffer = null; + ByteBuffer cachedTVPHeaders = null; TDSCommand cachedCommand = null; if (!value.isNull()) { - // If TVP is set with ResultSet and Server Cursor is used, the tdsWriter of the calling preparedStatement is overwritten - // by the SQLServerResultSet#next() method if the preparedStatement and the ResultSet are created by the same connection. - // Therefore, we need to cache the tdsWriter's values (stagingBuffer for sending data and command for retrieving data) and update - // stagingBuffer with new TDS values. + // If the preparedStatement and the ResultSet are created by the same connection, and TVP is set with ResultSet and Server Cursor + // is used, the tdsWriter of the calling preparedStatement is overwritten by the SQLServerResultSet#next() method when fetching new rows. + // Therefore, we need to send TVP data row by row before fetching new row. if (TVPType.ResultSet == value.tvpType) { if ((null != value.sourceResultSet) && (value.sourceResultSet instanceof SQLServerResultSet)) { SQLServerStatement src_stmt = (SQLServerStatement) ((SQLServerResultSet) value.sourceResultSet).getStatement(); int resultSetServerCursorId = ((SQLServerResultSet) value.sourceResultSet).getServerCursorId(); if (con.equals(src_stmt.getConnection()) && 0 != resultSetServerCursorId) { - cachedStagingBuffer = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); - cachedStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); + cachedTVPHeaders = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); + cachedTVPHeaders.put(stagingBuffer.array(), 0, stagingBuffer.position()); cachedCommand = this.command; @@ -4566,6 +4565,13 @@ void writeTVPRows(TVP value) throws SQLServerException { Iterator> columnsIterator; while (value.next()) { + + // restore TDS header that has been overwritten + if (tdsWritterCached) { + stagingBuffer.clear(); + writeBytes(cachedTVPHeaders.array(), 0, cachedTVPHeaders.position()); + } + Object[] rowData = value.getRowData(); // ROW @@ -4775,21 +4781,25 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) currentColumn++; } + // send this row and read its response if (tdsWritterCached) { - cachedStagingBuffer.put(stagingBuffer.array(), 0, stagingBuffer.position()); - stagingBuffer.clear(); + // TVP_END_TOKEN + writeByte((byte) 0x00); + + writePacket(TDS.STATUS_BIT_EOM); + + command = cachedCommand; + command.setReadingResponse(true); + while (tdsChannel.getReader(command).readPacket()) + ; } } } - if (tdsWritterCached) { - stagingBuffer.clear(); - stagingBuffer.put(cachedStagingBuffer.array(), 0, cachedStagingBuffer.position()); - this.command = cachedCommand; + if (!tdsWritterCached) { + // TVP_END_TOKEN + writeByte((byte) 0x00); } - - // TVP_END_TOKEN - writeByte((byte) 0x00); } private static byte[] toByteArray(String s) { @@ -7027,6 +7037,10 @@ boolean attentionPending() { // or by detaching. private volatile boolean readingResponse; + void setReadingResponse(boolean readingResponse) { + this.readingResponse = readingResponse; + } + final boolean readingResponse() { return readingResponse; } From f432926293f525757ad79c5ab347fb03c7c8ddcd Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Mon, 10 Apr 2017 13:27:19 -0700 Subject: [PATCH 28/72] fix assertion errors --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index c72962dac..71fc7aec7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4566,8 +4566,10 @@ void writeTVPRows(TVP value) throws SQLServerException { while (value.next()) { - // restore TDS header that has been overwritten + // restore command and TDS header, which have been overwritten by value.next() if (tdsWritterCached) { + command = cachedCommand; + stagingBuffer.clear(); writeBytes(cachedTVPHeaders.array(), 0, cachedTVPHeaders.position()); } @@ -4781,22 +4783,29 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) currentColumn++; } - // send this row and read its response + // send this row, read its response and reset command status if (tdsWritterCached) { // TVP_END_TOKEN writeByte((byte) 0x00); writePacket(TDS.STATUS_BIT_EOM); - command = cachedCommand; - command.setReadingResponse(true); while (tdsChannel.getReader(command).readPacket()) ; + + command.setInterruptsEnabled(true); + command.setRequestComplete(false); } } } - if (!tdsWritterCached) { + // reset command status which have been overwritten + if (tdsWritterCached) { + command.setRequestComplete(false); + command.setInterruptsEnabled(true); + command.setProcessedResponse(false); + } + else { // TVP_END_TOKEN writeByte((byte) 0x00); } @@ -7000,6 +7009,10 @@ final void log(Level level, // If the command is interrupted after interrupts have been disabled, then the // interrupt is ignored. private volatile boolean interruptsEnabled = false; + + void setInterruptsEnabled(boolean interruptsEnabled) { + this.interruptsEnabled = interruptsEnabled; + } // Flag set to indicate that an interrupt has happened. private volatile boolean wasInterrupted = false; @@ -7016,6 +7029,10 @@ private boolean wasInterrupted() { // 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; + + void setRequestComplete(boolean requestComplete) { + this.requestComplete = 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. @@ -7030,6 +7047,10 @@ boolean attentionPending() { // there may be unprocessed information left in the response, such as transaction // ENVCHANGE notifications. private volatile boolean processedResponse; + + void setProcessedResponse(boolean processedResponse) { + this.processedResponse = 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 @@ -7037,10 +7058,6 @@ boolean attentionPending() { // or by detaching. private volatile boolean readingResponse; - void setReadingResponse(boolean readingResponse) { - this.readingResponse = readingResponse; - } - final boolean readingResponse() { return readingResponse; } From 34cad3a1ee1aad8790a3b5924ea78eeb4eb3895e Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Mon, 10 Apr 2017 15:32:04 -0700 Subject: [PATCH 29/72] added tests for chars longer than 5000 --- .../jdbc/tvp/TVPResultSetCursorTest.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java index 7d935dc1f..23137f9a2 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -156,13 +156,14 @@ public void testMultiplePreparedStatementAndResultSet() throws SQLException { pstmt2.execute(); verifyDestinationTableData(expectedBigDecimals.length * 3); - String sql = "insert into " + desTable + " values (?,?,?)"; + String sql = "insert into " + desTable + " values (?,?,?,?)"; Calendar calGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT")); pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement(sql); for (int i = 0; i < expectedBigDecimals.length; i++) { pstmt1.setBigDecimal(1, expectedBigDecimals[i]); pstmt1.setString(2, expectedStrings[i]); pstmt1.setTimestamp(3, expectedTimestamps[i], calGMT); + pstmt1.setString(4, expectedStrings[i]); pstmt1.execute(); } verifyDestinationTableData(expectedBigDecimals.length * 4); @@ -201,6 +202,8 @@ private static void verifyDestinationTableData(int expectedNumberOfRows) throws "Expected Value:" + expectedStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(2)); assertTrue(rs.getString(3).equals(expectedTimestampStrings[i % expectedArrayLength]), "Expected Value:" + expectedTimestampStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(3)); + assertTrue(rs.getString(4).trim().equals(expectedStrings[i % expectedArrayLength]), + "Expected Value:" + expectedStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(4)); i++; } @@ -208,7 +211,7 @@ private static void verifyDestinationTableData(int expectedNumberOfRows) throws } private static void populateSourceTable() throws SQLException { - String sql = "insert into " + srcTable + " values (?,?,?)"; + String sql = "insert into " + srcTable + " values (?,?,?,?)"; Calendar calGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT")); @@ -218,6 +221,7 @@ private static void populateSourceTable() throws SQLException { pstmt.setBigDecimal(1, expectedBigDecimals[i]); pstmt.setString(2, expectedStrings[i]); pstmt.setTimestamp(3, expectedTimestamps[i], calGMT); + pstmt.setString(4, expectedStrings[i]); pstmt.execute(); } } @@ -228,20 +232,21 @@ private static void dropTables() throws SQLException { } private static void createTables() throws SQLException { - String sql = "create table " + srcTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null);"; + String sql = "create table " + srcTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null, c4 char(7000));"; stmt.execute(sql); - sql = "create table " + desTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null);"; + sql = "create table " + desTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null, c4 char(7000));"; stmt.execute(sql); } private static void createTVPS() throws SQLException { - String TVPCreateCmd = "CREATE TYPE " + tvpName + " as table (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null)"; - stmt.executeUpdate(TVPCreateCmd); + String TVPCreateCmd = "CREATE TYPE " + tvpName + + " as table (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null, c4 char(7000) null)"; + stmt.execute(TVPCreateCmd); } private static void dropTVPS() throws SQLException { - stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); + stmt.execute("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); } @AfterEach From 4a3435c0c18a3b316fb668d650ec3fd798b4192f Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 11 Apr 2017 15:07:36 -0700 Subject: [PATCH 30/72] fix issue with forward only cursor regarding to reading data from Result set --- .../com/microsoft/sqlserver/jdbc/IOBuffer.java | 15 ++++++++++----- .../sqlserver/jdbc/SQLServerResultSet.java | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 71fc7aec7..1eb67e88e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4547,8 +4547,9 @@ void writeTVPRows(TVP value) throws SQLServerException { // Therefore, we need to send TVP data row by row before fetching new row. if (TVPType.ResultSet == value.tvpType) { if ((null != value.sourceResultSet) && (value.sourceResultSet instanceof SQLServerResultSet)) { - SQLServerStatement src_stmt = (SQLServerStatement) ((SQLServerResultSet) value.sourceResultSet).getStatement(); - int resultSetServerCursorId = ((SQLServerResultSet) value.sourceResultSet).getServerCursorId(); + SQLServerResultSet sourceResultSet = (SQLServerResultSet) value.sourceResultSet; + SQLServerStatement src_stmt = (SQLServerStatement) sourceResultSet.getStatement(); + int resultSetServerCursorId = sourceResultSet.getServerCursorId(); if (con.equals(src_stmt.getConnection()) && 0 != resultSetServerCursorId) { cachedTVPHeaders = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); @@ -4557,6 +4558,10 @@ void writeTVPRows(TVP value) throws SQLServerException { cachedCommand = this.command; tdsWritterCached = true; + + if (sourceResultSet.isForwardOnly()) { + sourceResultSet.setFetchSize(1); + } } } } @@ -7010,7 +7015,7 @@ final void log(Level level, // interrupt is ignored. private volatile boolean interruptsEnabled = false; - void setInterruptsEnabled(boolean interruptsEnabled) { + protected void setInterruptsEnabled(boolean interruptsEnabled) { this.interruptsEnabled = interruptsEnabled; } @@ -7030,7 +7035,7 @@ private boolean wasInterrupted() { // After the request is complete, the interrupting thread must send the attention signal. private volatile boolean requestComplete; - void setRequestComplete(boolean requestComplete) { + protected void setRequestComplete(boolean requestComplete) { this.requestComplete = requestComplete; } @@ -7048,7 +7053,7 @@ boolean attentionPending() { // ENVCHANGE notifications. private volatile boolean processedResponse; - void setProcessedResponse(boolean processedResponse) { + protected void setProcessedResponse(boolean processedResponse) { this.processedResponse = processedResponse; } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index cde46e195..43d632bf7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -84,7 +84,7 @@ String getClassNameLogging() { private final int serverCursorId; - int getServerCursorId() { + protected int getServerCursorId() { return serverCursorId; } @@ -452,7 +452,7 @@ private void throwNotScrollable() throws SQLServerException { true); } - private boolean isForwardOnly() { + protected boolean isForwardOnly() { return TYPE_SS_DIRECT_FORWARD_ONLY == stmt.getSQLResultSetType() || TYPE_SS_SERVER_CURSOR_FORWARD_ONLY == stmt.getSQLResultSetType(); } From 326cd80f684dd29657f0b733b54931d47d13f266 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Tue, 11 Apr 2017 15:08:51 -0700 Subject: [PATCH 31/72] fix TVP Varchar4001 issue --- src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 435de47ac..32883a35b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4664,7 +4664,8 @@ void writeTVPRows(TVP value) throws SQLServerException { case VARCHAR: case NCHAR: case NVARCHAR: - isShortValue = (2 * columnPair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; + long columnPrecision = columnPair.getValue().precision; + isShortValue = (2 * columnPrecision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; isNull = (null == currentColumnStringValue); dataLength = isNull ? 0 : currentColumnStringValue.length() * 2; if (!isShortValue) { @@ -4840,7 +4841,8 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException { case NCHAR: case NVARCHAR: writeByte(TDSType.NVARCHAR.byteValue()); - isShortValue = (2 * pair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; + long columnPrecision = pair.getValue().precision; + isShortValue = (2 * columnPrecision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; // Use PLP encoding on Yukon and later with long values if (!isShortValue) // PLP { From 9d270e38c398674ebb98801317021c3da3767471 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 11 Apr 2017 15:30:48 -0700 Subject: [PATCH 32/72] added tests to test long characters and cursor is a combination of ResultSet.TYPE_FORWARD_ONLY and ResultSet.CONCUR_UPDATABLE --- .../sqlserver/jdbc/tvp/TVPResultSetCursorTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java index 23137f9a2..8269ebedc 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -54,6 +54,14 @@ public class TVPResultSetCursorTest extends AbstractTest { */ @Test public void testServerCursors() throws SQLException { + serverCursorsTest(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + serverCursorsTest(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + serverCursorsTest(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); + serverCursorsTest(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); + } + + private void serverCursorsTest(int resultSetType, + int resultSetConcurrency) throws SQLException { conn = DriverManager.getConnection(connectionString); stmt = conn.createStatement(); @@ -65,7 +73,7 @@ public void testServerCursors() throws SQLException { populateSourceTable(); - ResultSet rs = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE).executeQuery("select * from " + srcTable); + ResultSet rs = conn.createStatement(resultSetType, resultSetConcurrency).executeQuery("select * from " + srcTable); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); pstmt.setStructured(1, tvpName, rs); From 52faf8da2a518fd79f627ccc7481aa72cb048af3 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Tue, 11 Apr 2017 16:46:06 -0700 Subject: [PATCH 33/72] use 2L instead of creating long variable and added Shawn's repro test to pr --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 6 +- .../sqlserver/jdbc/tvp/TVPIssuesTest.java | 115 ++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 32883a35b..fc9e48a1c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4664,8 +4664,7 @@ void writeTVPRows(TVP value) throws SQLServerException { case VARCHAR: case NCHAR: case NVARCHAR: - long columnPrecision = columnPair.getValue().precision; - isShortValue = (2 * columnPrecision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; + isShortValue = (2L * columnPair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; isNull = (null == currentColumnStringValue); dataLength = isNull ? 0 : currentColumnStringValue.length() * 2; if (!isShortValue) { @@ -4841,8 +4840,7 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException { case NCHAR: case NVARCHAR: writeByte(TDSType.NVARCHAR.byteValue()); - long columnPrecision = pair.getValue().precision; - isShortValue = (2 * columnPrecision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; + isShortValue = (2L * pair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; // Use PLP encoding on Yukon and later with long values if (!isShortValue) // PLP { diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java new file mode 100644 index 000000000..51968371f --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java @@ -0,0 +1,115 @@ +/* + * 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.tvp; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.jdbc.SQLServerStatement; +import com.microsoft.sqlserver.testframework.AbstractTest; + +@RunWith(JUnitPlatform.class) +public class TVPIssuesTest extends AbstractTest { + + static Connection connection = null; + static Statement stmt = null; + private static String tvpName = "tryTVP_RS_varcharMax_4001_Issue"; + private static String srcTable = "tryTVP_RS_varcharMax_4001_Issue_src"; + private static String desTable = "tryTVP_RS_varcharMax_4001_Issue_dest"; + + @Test + public void tryTVP_RS_varcharMax_4001_Issue() throws Exception { + + setup(); + + SQLServerStatement st = (SQLServerStatement) connection.createStatement(); + ResultSet rs = st.executeQuery("select * from " + srcTable); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + + pstmt.setStructured(1, tvpName, rs); + pstmt.execute(); + + testDestinationTable(); + } + + private void testDestinationTable() throws SQLException, IOException { + ResultSet rs = connection.createStatement().executeQuery("select * from " + desTable); + while (rs.next()) { + assertEquals(rs.getString(1).length(), 4001, " The inserted length is truncated or not correct!"); + } + if (null != rs) { + rs.close(); + } + } + + private static void populateSourceTable() throws SQLException { + String sql = "insert into " + srcTable + " values (?)"; + + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 4001; i++) { + sb.append("a"); + } + String value = sb.toString(); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(sql); + + pstmt.setString(1, value); + pstmt.execute(); + } + + @BeforeAll + public static void beforeAll() throws SQLException { + + connection = DriverManager.getConnection(connectionString); + stmt = connection.createStatement(); + + stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); + stmt.executeUpdate("if object_id('" + srcTable + "','U') is not null" + " drop table " + srcTable); + stmt.executeUpdate("if object_id('" + desTable + "','U') is not null" + " drop table " + desTable); + String sql = "create table " + srcTable + " (c1 varchar(max) null);"; + stmt.execute(sql); + + sql = "create table " + desTable + " (c1 varchar(max) null);"; + stmt.execute(sql); + + String TVPCreateCmd = "CREATE TYPE " + tvpName + " as table (c1 varchar(max) null)"; + stmt.executeUpdate(TVPCreateCmd); + + populateSourceTable(); + } + + @AfterAll + public static void terminateVariation() throws SQLException { + + stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); + stmt.executeUpdate("if object_id('" + srcTable + "','U') is not null" + " drop table " + srcTable); + stmt.executeUpdate("if object_id('" + desTable + "','U') is not null" + " drop table " + desTable); + if (null != connection) { + connection.close(); + } + if (null != stmt) { + stmt.close(); + } + + } + +} From a35a4a94d6ed4c045c58b2734a70c1047d4ae287 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Tue, 11 Apr 2017 16:51:30 -0700 Subject: [PATCH 34/72] use Utils.dropTableIfExists to drop tables and tvps --- .../sqlserver/jdbc/tvp/TVPIssuesTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java index 51968371f..80f198379 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java @@ -25,6 +25,7 @@ import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.jdbc.SQLServerStatement; import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.Utils; @RunWith(JUnitPlatform.class) public class TVPIssuesTest extends AbstractTest { @@ -82,9 +83,10 @@ public static void beforeAll() throws SQLException { connection = DriverManager.getConnection(connectionString); stmt = connection.createStatement(); - stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); - stmt.executeUpdate("if object_id('" + srcTable + "','U') is not null" + " drop table " + srcTable); - stmt.executeUpdate("if object_id('" + desTable + "','U') is not null" + " drop table " + desTable); + Utils.dropTableIfExists(tvpName, stmt); + Utils.dropTableIfExists(srcTable, stmt); + Utils.dropTableIfExists(desTable, stmt); + String sql = "create table " + srcTable + " (c1 varchar(max) null);"; stmt.execute(sql); @@ -99,17 +101,14 @@ public static void beforeAll() throws SQLException { @AfterAll public static void terminateVariation() throws SQLException { - - stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); - stmt.executeUpdate("if object_id('" + srcTable + "','U') is not null" + " drop table " + srcTable); - stmt.executeUpdate("if object_id('" + desTable + "','U') is not null" + " drop table " + desTable); + Utils.dropTableIfExists(tvpName, stmt); + Utils.dropTableIfExists(srcTable, stmt); + Utils.dropTableIfExists(desTable, stmt); if (null != connection) { connection.close(); } if (null != stmt) { stmt.close(); } - } - } From 7bb968b986c9492f618877d8509dcb12b6d85cf1 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Tue, 11 Apr 2017 17:03:35 -0700 Subject: [PATCH 35/72] drop tvps in the test --- .../java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java index 80f198379..6da95f6c3 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java @@ -83,7 +83,7 @@ public static void beforeAll() throws SQLException { connection = DriverManager.getConnection(connectionString); stmt = connection.createStatement(); - Utils.dropTableIfExists(tvpName, stmt); + stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); Utils.dropTableIfExists(srcTable, stmt); Utils.dropTableIfExists(desTable, stmt); @@ -101,7 +101,7 @@ public static void beforeAll() throws SQLException { @AfterAll public static void terminateVariation() throws SQLException { - Utils.dropTableIfExists(tvpName, stmt); + stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); Utils.dropTableIfExists(srcTable, stmt); Utils.dropTableIfExists(desTable, stmt); if (null != connection) { From 87949e356904a1558eaf4b0a7afb232d4918b85e Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Tue, 11 Apr 2017 17:24:00 -0700 Subject: [PATCH 36/72] send decimal data wrt to scale in metadata --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 82 +++++++++---------- .../sqlserver/jdbc/SQLServerBulkCopy.java | 11 ++- .../com/microsoft/sqlserver/jdbc/Util.java | 8 +- 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 435de47ac..c8dac9340 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -19,6 +19,7 @@ import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; +import java.math.RoundingMode; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -3312,67 +3313,52 @@ void writeDouble(double value) throws SQLServerException { * the source JDBCType * @param precision * the precision of the data value + * @param scale + * the scale of the column + * @throws SQLServerException */ void writeBigDecimal(BigDecimal bigDecimalVal, int srcJdbcType, - int precision) throws SQLServerException { + int precision, + int scale) 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(); + /* + * setScale of all BigDecimal value based on metadata as scale is not sent seperately for individual value. Use the rounding used in Server. + * Say, for BigDecimal("0.1"), if scale in metdadata is 0, then ArithmeticException would be thrown if RoundingMode is not set + */ + bigDecimalVal = bigDecimalVal.setScale(scale, RoundingMode.HALF_UP); + + int bLength; if (9 >= precision) { - writeByte((byte) (BYTES4 + 1)); - writeByte((byte) (isNegative ? 0 : 1)); - writeInt(bi.intValue()); + bLength = BYTES4; } else if (19 >= precision) { - writeByte((byte) (BYTES8 + 1)); - writeByte((byte) (isNegative ? 0 : 1)); - writeLong(bi.longValue()); + bLength = BYTES8; + } + else if (28 >= precision) { + bLength = BYTES12; } 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); - } + bLength = BYTES16; + } - // Byte array to hold all the reversed and padding bytes. - byte[] bytes = new byte[bLength]; + // data length + 1 byte for sign + bLength += 1; + writeByte((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; + // Byte array to hold all the data and padding bytes. + byte[] bytes = new byte[bLength]; - // Reverse the bytes. - int i, j; - for (i = 0, j = unscaledBytes.length - 1; i < unscaledBytes.length;) - bytes[i++] = unscaledBytes[j--]; + byte[] valueBytes = DDC.convertBigDecimalToBytes(bigDecimalVal, scale); + // removing the precision and scale information from the valueBytes array + System.arraycopy(valueBytes, 2, bytes, 0, valueBytes.length - 2); - // Fill the rest of the array with zeros. - for (; i < remaining; i++) - bytes[i] = (byte) 0x00; - writeBytes(bytes); - } + writeBytes(bytes); } void writeSmalldatetime(String value) throws SQLServerException { @@ -4615,8 +4601,14 @@ void writeTVPRows(TVP value) throws SQLServerException { 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); + + /* + * setScale of all BigDecimal value based on metadata as scale is not sent seperately for individual value. Use the + * rounding used in Server. Say, for BigDecimal("0.1"), if scale in metdadata is 0, then ArithmeticException would be + * thrown if RoundingMode is not set + */ + 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 diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index 93a6ef268..0dbb41b1d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2132,7 +2132,16 @@ else if (null != sourceBulkRecord) { writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); } else { - tdsWriter.writeBigDecimal((BigDecimal) colValue, bulkJdbcType, bulkPrecision); + /* + * if the precision that user provides is smaller than the precision of the actual value, the driver assumes the precision that + * user provides is the correct precision, and throws exception + */ + if (bulkPrecision < Util.getValueLengthBaseOnJavaType(colValue, JavaType.of(colValue), null, null, JDBCType.of(bulkJdbcType))) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange")); + Object[] msgArgs = {SSType.DECIMAL}; + throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_LENGTH_MISMATCH, DriverError.NOT_SET, null); + } + tdsWriter.writeBigDecimal((BigDecimal) colValue, bulkJdbcType, bulkPrecision, bulkScale); } break; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java index 0e3093cd8..bac845335 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java @@ -853,8 +853,14 @@ else if (JDBCType.BINARY == jdbcType || JDBCType.VARBINARY == jdbcType) { else { if (0 == ((BigDecimal) value).intValue()) { String s = "" + value; - s = s.replaceAll("\\.", ""); s = s.replaceAll("\\-", ""); + if (s.startsWith("0.")) { + // remove the leading zero, eg., for 0.32, the precision should be 2 and not 3 + s = s.replaceAll("0\\.", ""); + } + else { + s = s.replaceAll("\\.", ""); + } length = s.length(); } // if the value is in scientific notation format From a556d8298d379f9bc9134a84b8ed7793e02850ab Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 11 Apr 2017 17:27:48 -0700 Subject: [PATCH 37/72] remove JNI method for ActiveDirectoryPassword Authentication, since we no longer use DLL for ActiveDirectoryPassword authentication --- .../sqlserver/jdbc/AuthenticationJNI.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java index f212d7674..730db8177 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java @@ -87,18 +87,6 @@ static FedAuthDllInfo getAccessTokenForWindowsIntegrated(String stsURL, 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 { @@ -184,15 +172,6 @@ private native static FedAuthDllInfo ADALGetAccessTokenForWindowsIntegrated(Stri 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; From d6706b92d48013ab96077b01f31acda6f7292241 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Thu, 13 Apr 2017 20:31:56 +0200 Subject: [PATCH 38/72] Added Javadoc --- .../sqlserver/jdbc/dns/DNSRecordSRV.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java index 73aa1658f..47182294f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java @@ -129,18 +129,34 @@ public int compareTo(DNSRecordSRV o) { return serverName.compareTo(o.serverName); } + /** + * Get the priority of DNS SRV record. + * @return a positive priority, where lowest values have to be considered first. + */ public int getPriority() { return priority; } + /** + * Get the weight of DNS record from 0 to 65535. + * @return The weight, hi value means higher probability of selecting the given record for a given priority. + */ public int getWeight() { return weight; } + /** + * IP port of record. + * @return a value from 1 to 65535. + */ public int getPort() { return port; } + /** + * The DNS server name. + * @return a not null server name. + */ public String getServerName() { return serverName; } From 0ab0394f328258437976446e57e3215286c98510 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Thu, 13 Apr 2017 20:36:00 +0200 Subject: [PATCH 39/72] Added MS license header --- .../microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java | 7 +++++++ .../com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java | 7 +++++++ .../com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java index 4b6c2fe2b..77bb67b0f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSKerberosLocator.java @@ -1,3 +1,10 @@ +/* + * 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.dns; import java.util.Set; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java index 47182294f..a72754dcc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java @@ -1,3 +1,10 @@ +/* + * 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.dns; import java.util.regex.Matcher; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java index d378fb127..f7eb6de0c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSUtilities.java @@ -1,3 +1,10 @@ +/* + * 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.dns; import java.util.Hashtable; From e1aed34fb64b0ea6084fc441d9613504f7a1bc7e Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Thu, 13 Apr 2017 20:39:04 +0200 Subject: [PATCH 40/72] Fixed typo in Javadoc --- .../java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java index a72754dcc..ba419bb75 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dns/DNSRecordSRV.java @@ -146,7 +146,7 @@ public int getPriority() { /** * Get the weight of DNS record from 0 to 65535. - * @return The weight, hi value means higher probability of selecting the given record for a given priority. + * @return The weight, higher value means higher probability of selecting the given record for a given priority. */ public int getWeight() { return weight; From 74de14080519b21ed820b56773a018886aa9012b Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Thu, 13 Apr 2017 22:01:53 +0200 Subject: [PATCH 41/72] Added header to test file --- .../com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java index 9753dadff..8a16cffc9 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/dns/DNSRealmsTest.java @@ -1,3 +1,10 @@ +/* + * 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.dns; import javax.naming.NamingException; From 9a20061f2f05742811b9a20a610d91481cf331fb Mon Sep 17 00:00:00 2001 From: Brett Wooldridge Date: Wed, 12 Apr 2017 23:11:33 +0900 Subject: [PATCH 42/72] Support Connection get/setNetworkTimeout(). --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 20 +++++++++++ .../sqlserver/jdbc/SQLServerConnection.java | 36 ++++++++++++++++--- .../jdbc/SQLServerConnectionPoolProxy.java | 8 ++--- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 435de47ac..ad36a1eaa 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -2154,6 +2154,26 @@ final void close() { packetLogger.finest(logMsg.toString()); } + + /** + * Get the current socket SO_TIMEOUT value. + * + * @return the current socket timeout value + * @throws IOException thrown if the socket timeout cannot be read + */ + final int getNetworkTimeout() throws IOException { + return tcpSocket.getSoTimeout(); + } + + /** + * Set the socket SO_TIMEOUT value. + * + * @param timeout the socket timeout in milliseconds + * @throws IOException thrown if the socket timeout cannot be set + */ + final void setNetworkTimeout(int timeout) throws IOException { + tcpSocket.setSoTimeout(timeout); + } } /** diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 700347dfa..54c5950c2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4636,14 +4636,42 @@ public void setHoldability(int holdability) throws SQLServerException { } public int getNetworkTimeout() throws SQLException { - // this operation is not supported - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + loggerExternal.entering(getClassNameLogging(), "getNetworkTimeout"); + + checkClosed(); + + int timeout = 0; + try { + timeout = tdsChannel.getNetworkTimeout(); + } + catch (IOException ioe) { + terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, ioe.getMessage(), ioe); + } + + loggerExternal.exiting(getClassNameLogging(), "getNetworkTimeout"); + return timeout; } public void setNetworkTimeout(Executor executor, int timeout) throws SQLException { - // this operation is not supported - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + loggerExternal.entering(getClassNameLogging(), "setNetworkTimeout", timeout); + + if (timeout < 0) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidSocketTimeout")); + Object[] msgArgs = {timeout}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false); + } + + checkClosed(); + + try { + tdsChannel.setNetworkTimeout(timeout); + } + catch (IOException ioe) { + terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, ioe.getMessage(), ioe); + } + + loggerExternal.exiting(getClassNameLogging(), "setNetworkTimeout"); } public String getSchema() throws SQLException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java index b92e93d28..b180a11c9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java @@ -554,14 +554,14 @@ public PreparedStatement prepareStatement(String sql, } public int getNetworkTimeout() throws SQLException { - // The driver currently does not implement the optional JDBC APIs - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + checkClosed(); + return wrappedConnection.getNetworkTimeout(); } public void setNetworkTimeout(Executor executor, int timeout) throws SQLException { - // The driver currently does not implement the optional JDBC APIs - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + checkClosed(); + wrappedConnection.setNetworkTimeout(executor, timeout); } public String getSchema() throws SQLException { From 02f1372933cfa6313d8b5ef1455412103aed3a13 Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 18 Apr 2017 10:40:14 +0200 Subject: [PATCH 43/72] Allow using multiple JAAS configurations and override the configuration per connection properties. We also set a different default LoginConfig for IBM JVM, so it should work well with user-provided passwords and username for Kerberos. Should solve https://github.com/Microsoft/mssql-jdbc/issues/66 for IBM JVM. --- .../sqlserver/jdbc/JaasConfiguration.java | 68 +++++++++++++++ .../sqlserver/jdbc/KerbAuthentication.java | 83 ++----------------- .../sqlserver/jdbc/SQLServerDriver.java | 3 + 3 files changed, 77 insertions(+), 77 deletions(-) create mode 100644 src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java b/src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java new file mode 100644 index 000000000..ea82b2ca8 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java @@ -0,0 +1,68 @@ +/* + * 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.HashMap; +import java.util.Map; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; + +/** + * This class overrides JAAS Configuration and always provide a configuration is not defined for default configuration. + */ +public class JaasConfiguration extends Configuration { + + private final Configuration delegate; + private AppConfigurationEntry[] defaultValue; + + private static AppConfigurationEntry[] generateDefaultConfiguration() { + if (Util.isIBM()) { + Map confDetailsWithoutPassword = new HashMap(); + confDetailsWithoutPassword.put("useDefaultCcache", "true"); + confDetailsWithoutPassword.put("moduleBanner", "false"); + Map confDetailsWithPassword = new HashMap(); + confDetailsWithPassword.putAll(confDetailsWithPassword); + confDetailsWithPassword.put("useDefaultCcache", "false"); + // We generated a two configurations fallback that is suitable for password and password-less authentication + return new AppConfigurationEntry[] { + new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, + confDetailsWithoutPassword), + new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, + confDetailsWithPassword)}; + } + else { + Map confDetails = new HashMap(); + confDetails.put("useTicketCache", "true"); + return new AppConfigurationEntry[] {new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, confDetails)}; + } + } + + /** + * Package protected constructor. + * + * @param delegate + * a possibly null delegate + */ + JaasConfiguration(Configuration delegate) { + this.delegate = delegate; + this.defaultValue = generateDefaultConfiguration(); + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + AppConfigurationEntry[] conf = delegate == null ? null : delegate.getAppConfigurationEntry(name); + // We return our configuration only if user requested default one + // In case where user did request another JAAS Configuration name, we expect he knows what he is doing. + if (conf == null && name.equals(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue())) { + return defaultValue; + } + return conf; + } +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index 01a347820..03323fa88 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -17,16 +17,13 @@ import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.text.MessageFormat; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.NamingException; import javax.security.auth.Subject; -import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; @@ -44,7 +41,6 @@ * 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"); @@ -57,78 +53,9 @@ final class KerbAuthentication extends SSPIAuthentication { 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 - // 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; - AppConfigurationEntry[] driverConf; - - SQLJDBCDriverConfig() { - try { - current = Configuration.getConfiguration(); - } - 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) { - 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)) - authLogger.finer(toString() + " SQLJDBCDriver configuration entry is not provided, setting driver default"); - - AppConfigurationEntry appConf; - if (Util.isIBM()) { - Map confDetails = new HashMap(); - 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"); - } - else { - Map confDetails = new HashMap(); - confDetails.put("useTicketCache", "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); - } - - } - - 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) - return current.getAppConfigurationEntry(name); - else - return null; - } - } - - public void refresh() { - if (null != current) - current.refresh(); - } - } - SQLJDBCDriverConfig driverconfig = new SQLJDBCDriverConfig(); + // Overrides the default JAAS configuration loader. + // This one will forward to the default one in all cases but the default configuration is empty. + Configuration.setConfiguration(new JaasConfiguration(Configuration.getConfiguration())); } private void intAuthInit() throws SQLServerException { @@ -148,13 +75,15 @@ private void intAuthInit() throws SQLServerException { peerContext.requestInteg(true); } else { + String configName = con.activeConnectionProperties.getProperty(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(), + SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue()); Subject currentSubject = null; KerbCallback callback = new KerbCallback(con); try { AccessControlContext context = AccessController.getContext(); currentSubject = Subject.getSubject(context); if (null == currentSubject) { - lc = new LoginContext(CONFIGNAME, callback); + lc = new LoginContext(configName, callback); lc.login(); // per documentation LoginContext will instantiate a new subject. currentSubject = lc.getSubject(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 9b80a033b..c5e3e4aea 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -218,6 +218,8 @@ public String toString() { } } + + enum SQLServerDriverStringProperty { APPLICATION_INTENT ("applicationIntent", ApplicationIntent.READ_WRITE.toString()), @@ -226,6 +228,7 @@ enum SQLServerDriverStringProperty FAILOVER_PARTNER ("failoverPartner", ""), HOSTNAME_IN_CERTIFICATE ("hostNameInCertificate", ""), INSTANCE_NAME ("instanceName", ""), + JAAS_CONFIG_NAME ("jaasConfigurationName", "SQLJDBCDriver"), PASSWORD ("password", ""), RESPONSE_BUFFERING ("responseBuffering", "adaptive"), SELECT_METHOD ("selectMethod", "direct"), From 14dae0d805991da5703b7d416dcbce439dab05ed Mon Sep 17 00:00:00 2001 From: Pierre Souchay Date: Tue, 18 Apr 2017 13:04:18 +0200 Subject: [PATCH 44/72] Simplified IBM Login module configuration --- .../microsoft/sqlserver/jdbc/JaasConfiguration.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java b/src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java index ea82b2ca8..6a4982752 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/JaasConfiguration.java @@ -25,16 +25,13 @@ private static AppConfigurationEntry[] generateDefaultConfiguration() { if (Util.isIBM()) { Map confDetailsWithoutPassword = new HashMap(); confDetailsWithoutPassword.put("useDefaultCcache", "true"); - confDetailsWithoutPassword.put("moduleBanner", "false"); Map confDetailsWithPassword = new HashMap(); - confDetailsWithPassword.putAll(confDetailsWithPassword); - confDetailsWithPassword.put("useDefaultCcache", "false"); // We generated a two configurations fallback that is suitable for password and password-less authentication + // See https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jgssDocs/jaas_login_user.html + final String ibmLoginModule = "com.ibm.security.auth.module.Krb5LoginModule"; return new AppConfigurationEntry[] { - new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, - confDetailsWithoutPassword), - new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, - confDetailsWithPassword)}; + new AppConfigurationEntry(ibmLoginModule, AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, confDetailsWithoutPassword), + new AppConfigurationEntry(ibmLoginModule, AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, confDetailsWithPassword)}; } else { Map confDetails = new HashMap(); From f1015968385d82c1f2bc9078fb8ec7093930bf72 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 18 Apr 2017 09:50:25 -0700 Subject: [PATCH 45/72] setNetworkTimeout checks for SQLPermission before proceeding --- .../sqlserver/jdbc/SQLServerConnection.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 54c5950c2..bb03f85fb 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -210,8 +210,9 @@ ServerPortPlaceHolder getRoutingInfo() { } // Permission targets - // currently only callAbort is implemented private static final String callAbortPerm = "callAbort"; + + private static final String SET_NETWORK_TIMEOUT_PERM = "setNetworkTimeout"; private boolean sendStringParametersAsUnicode = SQLServerDriverBooleanProperty.SEND_STRING_PARAMETERS_AS_UNICODE.getDefaultValue(); // see // connection @@ -4663,6 +4664,20 @@ public void setNetworkTimeout(Executor executor, } checkClosed(); + + // check for callAbort permission + SecurityManager secMgr = System.getSecurityManager(); + if (secMgr != null) { + try { + SQLPermission perm = new SQLPermission(SET_NETWORK_TIMEOUT_PERM); + secMgr.checkPermission(perm); + } + catch (SecurityException ex) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_permissionDenied")); + Object[] msgArgs = {SET_NETWORK_TIMEOUT_PERM}; + SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, true); + } + } try { tdsChannel.setNetworkTimeout(timeout); From 46c9f1c7d642a6ac7f1f050004de734eb5b56055 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 18 Apr 2017 10:24:36 -0700 Subject: [PATCH 46/72] fix comment --- .../java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index bb03f85fb..0cfdfd2b3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -4665,7 +4665,7 @@ public void setNetworkTimeout(Executor executor, checkClosed(); - // check for callAbort permission + // check for setNetworkTimeout permission SecurityManager secMgr = System.getSecurityManager(); if (secMgr != null) { try { From f2766233096ce0b1be59e0a1af3d9be5db245555 Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Tue, 18 Apr 2017 13:20:07 -0700 Subject: [PATCH 47/72] initialize XA resource --- .../sqlserver/jdbc/SQLServerXAResource.java | 83 +++++++++---------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java index d09def6ab..b2d194808 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java @@ -380,56 +380,52 @@ private String typeDisplay(int type) { SQLServerCallableStatement cs = null; try { synchronized (this) { - if (controlConnection == null) { + if (!xaInitDone) { try { synchronized (xaInitLock) { - if (!xaInitDone) { - SQLServerCallableStatement initCS = null; - - initCS = (SQLServerCallableStatement) controlConnection.prepareCall("{call master..xp_sqljdbc_xa_init_ex(?, ?,?)}"); - initCS.registerOutParameter(1, Types.INTEGER); // Return status - initCS.registerOutParameter(2, Types.CHAR); // Return error message - initCS.registerOutParameter(3, Types.CHAR); // Return version number + SQLServerCallableStatement initCS = null; + + initCS = (SQLServerCallableStatement) controlConnection.prepareCall("{call master..xp_sqljdbc_xa_init_ex(?, ?,?)}"); + initCS.registerOutParameter(1, Types.INTEGER); // Return status + initCS.registerOutParameter(2, Types.CHAR); // Return error message + initCS.registerOutParameter(3, Types.CHAR); // Return version number + try { + initCS.execute(); + } + catch (SQLServerException eX) { try { - initCS.execute(); - } - catch (SQLServerException eX) { - try { - initCS.close(); - // Mapping between control connection and xaresource is 1:1 - controlConnection.close(); - } - catch (SQLException e3) { - // we really want to ignore this failue - if (xaLogger.isLoggable(Level.FINER)) - xaLogger.finer(toString() + " Ignoring exception when closing failed execution. exception:" + e3); - } - if (xaLogger.isLoggable(Level.FINER)) - xaLogger.finer(toString() + " exception:" + eX); - throw eX; - } - - // Check for error response from xp_sqljdbc_xa_init. - int initStatus = initCS.getInt(1); - String initErr = initCS.getString(2); - String versionNumberXADLL = initCS.getString(3); - if (xaLogger.isLoggable(Level.FINE)) - xaLogger.fine(toString() + " Server XA DLL version:" + versionNumberXADLL); - initCS.close(); - if (XA_OK != initStatus) { - assert null != initErr && initErr.length() > 1; + initCS.close(); + // Mapping between control connection and xaresource is 1:1 controlConnection.close(); - - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToInitializeXA")); - Object[] msgArgs = {String.valueOf(initStatus), initErr}; - XAException xex = new XAException(form.format(msgArgs)); - xex.errorCode = initStatus; + } + catch (SQLException e3) { + // we really want to ignore this failue if (xaLogger.isLoggable(Level.FINER)) - xaLogger.finer(toString() + " exception:" + xex); - throw xex; + xaLogger.finer(toString() + " Ignoring exception when closing failed execution. exception:" + e3); } + if (xaLogger.isLoggable(Level.FINER)) + xaLogger.finer(toString() + " exception:" + eX); + throw eX; + } - xaInitDone = true; + // Check for error response from xp_sqljdbc_xa_init. + int initStatus = initCS.getInt(1); + String initErr = initCS.getString(2); + String versionNumberXADLL = initCS.getString(3); + if (xaLogger.isLoggable(Level.FINE)) + xaLogger.fine(toString() + " Server XA DLL version:" + versionNumberXADLL); + initCS.close(); + if (XA_OK != initStatus) { + assert null != initErr && initErr.length() > 1; + controlConnection.close(); + + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToInitializeXA")); + Object[] msgArgs = {String.valueOf(initStatus), initErr}; + XAException xex = new XAException(form.format(msgArgs)); + xex.errorCode = initStatus; + if (xaLogger.isLoggable(Level.FINER)) + xaLogger.finer(toString() + " exception:" + xex); + throw xex; } } } @@ -440,6 +436,7 @@ private String typeDisplay(int type) { xaLogger.finer(toString() + " exception:" + form.format(msgArgs)); SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true); } + xaInitDone = true; } } From a6d29b1149689ffb10d96c5e13a07bb066c93186 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 18 Apr 2017 16:24:34 -0700 Subject: [PATCH 48/72] fix log --- src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 1eb67e88e..2092a6064 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4576,6 +4576,7 @@ void writeTVPRows(TVP value) throws SQLServerException { command = cachedCommand; stagingBuffer.clear(); + logBuffer.clear(); writeBytes(cachedTVPHeaders.array(), 0, cachedTVPHeaders.position()); } From ed9dce6feac3ff3f4d939f434a6369d2a8bc8690 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 19 Apr 2017 13:45:05 -0700 Subject: [PATCH 49/72] after sending a row, throw exception in case of errors --- .../java/com/microsoft/sqlserver/jdbc/IOBuffer.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 2092a6064..d126d29dc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4789,15 +4789,22 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) currentColumn++; } - // send this row, read its response and reset command status + // send this row, read its response (throw exception in case of errors) and reset command status if (tdsWritterCached) { // TVP_END_TOKEN writeByte((byte) 0x00); writePacket(TDS.STATUS_BIT_EOM); - while (tdsChannel.getReader(command).readPacket()) - ; + TDSReader tdsReader = tdsChannel.getReader(command); + int tokenType = tdsReader.peekTokenType(); + + StreamError databaseError = new StreamError(); + databaseError.setFromTDS(tdsReader); + + if (TDS.TDS_ERR == tokenType) { + SQLServerException.makeFromDatabaseError(con, null, databaseError.getMessage(), databaseError, false); + } command.setInterruptsEnabled(true); command.setRequestComplete(false); From bd872d0c88cc1f23cc06018599a3580d0f90d19d Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 19 Apr 2017 14:16:42 -0700 Subject: [PATCH 50/72] fixed assertion error and added tests for invalid SP name and invalid TVP name --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 6 +- .../jdbc/tvp/TVPResultSetCursorTest.java | 154 ++++++++++++++++++ 2 files changed, 157 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index d126d29dc..efff14683 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4799,10 +4799,10 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) TDSReader tdsReader = tdsChannel.getReader(command); int tokenType = tdsReader.peekTokenType(); - StreamError databaseError = new StreamError(); - databaseError.setFromTDS(tdsReader); - if (TDS.TDS_ERR == tokenType) { + StreamError databaseError = new StreamError(); + databaseError.setFromTDS(tdsReader); + SQLServerException.makeFromDatabaseError(con, null, databaseError.getMessage(), databaseError, false); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java index 8269ebedc..1374dc5c9 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPResultSetCursorTest.java @@ -25,6 +25,8 @@ import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; +import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; +import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Utils; @@ -44,6 +46,7 @@ public class TVPResultSetCursorTest extends AbstractTest { static String[] expectedTimestampStrings = {"2015-06-03 13:35:33.4610000", "2442-09-19 01:59:43.9990000", "2017-04-02 08:58:53.0000000"}; private static String tvpName = "TVPResultSetCursorTest_TVP"; + private static String procedureName = "TVPResultSetCursorTest_SP"; private static String srcTable = "TVPResultSetCursorTest_SourceTable"; private static String desTable = "TVPResultSetCursorTest_DestinationTable"; @@ -126,6 +129,146 @@ public void testSelectMethodSetToCursor() throws SQLException { } } + /** + * Test a previous failure when setting SelectMethod to cursor and using the same connection to create TVP, SP and result set. + * + * @throws SQLException + */ + @Test + public void testSelectMethodSetToCursorWithSP() throws SQLException { + Properties info = new Properties(); + info.setProperty("SelectMethod", "cursor"); + conn = DriverManager.getConnection(connectionString, info); + + stmt = conn.createStatement(); + + dropProcedure(); + dropTVPS(); + dropTables(); + + createTVPS(); + createTables(); + createPreocedure(); + + populateSourceTable(); + + ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable); + + final String sql = "{call " + procedureName + "(?)}"; + SQLServerCallableStatement pstmt = (SQLServerCallableStatement) conn.prepareCall(sql); + pstmt.setStructured(1, tvpName, rs); + + try { + pstmt.execute(); + + verifyDestinationTableData(expectedBigDecimals.length); + } + finally { + if (null != pstmt) { + pstmt.close(); + } + if (null != rs) { + rs.close(); + } + + dropProcedure(); + } + } + + /** + * Test exception when giving invalid TVP name + * + * @throws SQLException + */ + @Test + public void testInvalidTVPName() throws SQLException { + Properties info = new Properties(); + info.setProperty("SelectMethod", "cursor"); + conn = DriverManager.getConnection(connectionString, info); + + stmt = conn.createStatement(); + + dropTVPS(); + dropTables(); + + createTVPS(); + createTables(); + + populateSourceTable(); + + ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;"); + pstmt.setStructured(1, "invalid" + tvpName, rs); + + try { + pstmt.execute(); + } + catch (SQLServerException e) { + if (!e.getMessage().contains("Cannot find data type")) { + throw e; + } + } + finally { + if (null != pstmt) { + pstmt.close(); + } + if (null != rs) { + rs.close(); + } + } + } + + /** + * Test exception when giving invalid stored procedure name + * + * @throws SQLException + */ + @Test + public void testInvalidStoredProcedureName() throws SQLException { + Properties info = new Properties(); + info.setProperty("SelectMethod", "cursor"); + conn = DriverManager.getConnection(connectionString, info); + + stmt = conn.createStatement(); + + dropProcedure(); + dropTVPS(); + dropTables(); + + createTVPS(); + createTables(); + createPreocedure(); + + populateSourceTable(); + + ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable); + + final String sql = "{call invalid" + procedureName + "(?)}"; + SQLServerCallableStatement pstmt = (SQLServerCallableStatement) conn.prepareCall(sql); + pstmt.setStructured(1, tvpName, rs); + + try { + pstmt.execute(); + } + catch (SQLServerException e) { + if (!e.getMessage().contains("Could not find stored procedure")) { + throw e; + } + } + finally { + + if (null != pstmt) { + pstmt.close(); + } + if (null != rs) { + rs.close(); + } + + dropProcedure(); + } + } + /** * test with multiple prepared statements and result sets * @@ -257,6 +400,17 @@ private static void dropTVPS() throws SQLException { stmt.execute("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); } + private static void dropProcedure() throws SQLException { + Utils.dropProcedureIfExists(procedureName, stmt); + } + + private static void createPreocedure() throws SQLException { + String sql = "CREATE PROCEDURE " + procedureName + " @InputData " + tvpName + " READONLY " + " AS " + " BEGIN " + " INSERT INTO " + desTable + + " SELECT * FROM @InputData" + " END"; + + stmt.execute(sql); + } + @AfterEach private void terminateVariation() throws SQLException { if (null != conn) { From d4d35238825c89d65b39eabcb271c783ec40b883 Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Wed, 19 Apr 2017 14:36:41 -0700 Subject: [PATCH 51/72] sending decimal with max length in BulkCopy like rest of the driver --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index c8dac9340..e8e97f0b5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -3323,8 +3323,7 @@ void writeBigDecimal(BigDecimal bigDecimalVal, int scale) 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. + * 8-, 12-, or 16-byte signed integer that represents the decimal value multiplied by 10^scale. */ /* @@ -3333,22 +3332,8 @@ void writeBigDecimal(BigDecimal bigDecimalVal, */ bigDecimalVal = bigDecimalVal.setScale(scale, RoundingMode.HALF_UP); - int bLength; - if (9 >= precision) { - bLength = BYTES4; - } - else if (19 >= precision) { - bLength = BYTES8; - } - else if (28 >= precision) { - bLength = BYTES12; - } - else { - bLength = BYTES16; - } - // data length + 1 byte for sign - bLength += 1; + int bLength = BYTES16 + 1; writeByte((byte) (bLength)); // Byte array to hold all the data and padding bytes. @@ -3357,7 +3342,6 @@ else if (28 >= precision) { byte[] valueBytes = DDC.convertBigDecimalToBytes(bigDecimalVal, scale); // removing the precision and scale information from the valueBytes array System.arraycopy(valueBytes, 2, bytes, 0, valueBytes.length - 2); - writeBytes(bytes); } From ce8a06af72e9add9ff6c2ea66505629c53479596 Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Thu, 20 Apr 2017 17:26:49 -0700 Subject: [PATCH 52/72] test cases for ISQLServerBulkRecord --- .../BulkCopyISQLServerBulkRecordTest.java | 226 ++++++++++++++++++ .../jdbc/bulkCopy/BulkCopyTestUtil.java | 85 ++++++- .../sqlserver/testframework/DBResultSet.java | 10 +- .../sqlserver/testframework/DBTable.java | 5 + .../sqlserver/testframework/Utils.java | 12 + .../testframework/sqlType/SqlDateTime2.java | 7 +- .../testframework/sqlType/SqlFloat.java | 7 +- .../testframework/sqlType/SqlReal.java | 6 + .../sqlType/SqlSmallDateTime.java | 3 +- .../testframework/sqlType/SqlTime.java | 21 +- .../testframework/sqlType/SqlType.java | 15 +- .../sqlType/VariableLengthType.java | 1 + 12 files changed, 364 insertions(+), 34 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java new file mode 100644 index 000000000..f0cbf3e53 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java @@ -0,0 +1,226 @@ +/* + * 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.bulkCopy; + +import java.sql.JDBCType; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord; +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.DBConnection; +import com.microsoft.sqlserver.testframework.DBStatement; +import com.microsoft.sqlserver.testframework.DBTable; +import com.microsoft.sqlserver.testframework.sqlType.SqlType; + +/** + * Test bulkcopy decimal sacle and precision + */ +@RunWith(JUnitPlatform.class) +@DisplayName("Test ISQLServerBulkRecord") +public class BulkCopyISQLServerBulkRecordTest extends AbstractTest { + + static DBConnection con = null; + static DBStatement stmt = null; + static DBTable dstTable = null; + + /** + * Create connection and statement + */ + @BeforeAll + static void setUpConnection() { + con = new DBConnection(connectionString); + stmt = con.createStatement(); + } + + @Test + void testISQLServerBulkRecord() { + dstTable = new DBTable(true); + dstTable.setTotalRows(1); + stmt.createTable(dstTable); + BulkData Bdata = new BulkData(); + + BulkCopyTestWrapper bulkWrapper = new BulkCopyTestWrapper(connectionString); + bulkWrapper.setUsingConnection((0 == ThreadLocalRandom.current().nextInt(2)) ? true : false); + BulkCopyTestUtil.performBulkCopy(bulkWrapper, Bdata, dstTable); + } + + /** + * drop source table after testing bulk copy + * + * @throws SQLException + */ + @AfterAll + static void tearConnection() throws SQLException { + stmt.close(); + con.close(); + } + + class BulkData implements ISQLServerBulkRecord { + + private class ColumnMetadata { + String columnName; + int columnType; + int precision; + int scale; + + ColumnMetadata(String name, + int type, + int precision, + int scale) { + columnName = name; + columnType = type; + this.precision = precision; + this.scale = scale; + } + } + + int totalColumn = 0; + int counter = 0; + int rowCount = 1; + Map columnMetadata; + List data; + + BulkData() { + columnMetadata = new HashMap(); + totalColumn = dstTable.totalColumns(); + + // add metadata + for (int i = 0; i < totalColumn; i++) { + SqlType sqlType = dstTable.getSqlType(i); + int precision = sqlType.getPrecision(); + if (JDBCType.TIMESTAMP == sqlType.getJdbctype()) { + // TODO: update the test to use correct precision once bulkCopy is fixed + precision = 50; + } + columnMetadata.put(i + 1, + new ColumnMetadata(sqlType.getName(), sqlType.getJdbctype().getVendorTypeNumber(), precision, sqlType.getScale())); + } + + // add data + rowCount = DBTable.getTotalRows(); + data = new ArrayList(rowCount); + for (int i = 0; i < rowCount; i++) { + Object[] CurrentRow = new Object[totalColumn]; + for (int j = 0; j < totalColumn; j++) { + SqlType sqlType = dstTable.getSqlType(j); + if (JDBCType.BIT == sqlType.getJdbctype()) { + CurrentRow[j] = ((0 == ThreadLocalRandom.current().nextInt(2)) ? Boolean.FALSE : Boolean.TRUE); + } + else + { + CurrentRow[j] = sqlType.createdata(); + } + } + data.add(CurrentRow); + } + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#getColumnOrdinals() + */ + @Override + public Set getColumnOrdinals() { + return columnMetadata.keySet(); + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#getColumnName(int) + */ + @Override + public String getColumnName(int column) { + return columnMetadata.get(column).columnName; + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#getColumnType(int) + */ + @Override + public int getColumnType(int column) { + return columnMetadata.get(column).columnType; + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#getPrecision(int) + */ + @Override + public int getPrecision(int column) { + return columnMetadata.get(column).precision; + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#getScale(int) + */ + @Override + public int getScale(int column) { + return columnMetadata.get(column).scale; + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#isAutoIncrement(int) + */ + @Override + public boolean isAutoIncrement(int column) { + return false; + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#getRowData() + */ + @Override + public Object[] getRowData() throws SQLServerException { + return data.get(counter++); + } + + /* + * (non-Javadoc) + * + * @see com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord#next() + */ + @Override + public boolean next() throws SQLServerException { + if (counter < rowCount) + return true; + return false; + } + + /** + * reset the counter when using the interface for validating the data + */ + public void reset() { + counter = 0; + } + } +} diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTestUtil.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTestUtil.java index c7d83f69f..37767e704 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTestUtil.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyTestUtil.java @@ -20,14 +20,15 @@ import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; -import java.util.Arrays; +import com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord; import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy; import com.microsoft.sqlserver.jdbc.bulkCopy.BulkCopyTestWrapper.ColumnMap; import com.microsoft.sqlserver.testframework.DBConnection; import com.microsoft.sqlserver.testframework.DBResultSet; import com.microsoft.sqlserver.testframework.DBStatement; import com.microsoft.sqlserver.testframework.DBTable; +import com.microsoft.sqlserver.testframework.Utils; /** * Utility class @@ -406,17 +407,17 @@ static void comapreSourceDest(int dataType, case java.sql.Types.VARCHAR: case java.sql.Types.NVARCHAR: - assertTrue((((String) expectedValue).equals((String) actualValue)), "Unexpected varchar/nvarchar value "); + assertTrue(((((String) expectedValue).trim()).equals(((String) actualValue).trim())), "Unexpected varchar/nvarchar value "); break; case java.sql.Types.CHAR: case java.sql.Types.NCHAR: - assertTrue((((String) expectedValue).equals((String) actualValue)), "Unexpected char/nchar value "); + assertTrue(((((String) expectedValue).trim()).equals(((String) actualValue).trim())), "Unexpected char/nchar value "); break; case java.sql.Types.BINARY: case java.sql.Types.VARBINARY: - assertTrue(Arrays.equals(((byte[]) expectedValue), ((byte[]) actualValue)), "Unexpected bianry/varbinary value "); + assertTrue(Utils.parseByte((byte[]) expectedValue, (byte[]) actualValue), "Unexpected bianry/varbinary value "); break; case java.sql.Types.TIMESTAMP: @@ -425,7 +426,7 @@ static void comapreSourceDest(int dataType, break; case java.sql.Types.DATE: - assertTrue((((Date) expectedValue).getTime() == (((Date) actualValue).getTime())), "Unexpected datetime value"); + assertTrue((((Date) expectedValue).getDate() == (((Date) actualValue).getDate())), "Unexpected datetime value"); break; case java.sql.Types.TIME: @@ -442,4 +443,78 @@ static void comapreSourceDest(int dataType, break; } } + + /** + * + * @param bulkWrapper + * @param srcData + * @param dstTable + */ + static void performBulkCopy(BulkCopyTestWrapper bulkWrapper, + ISQLServerBulkRecord srcData, + DBTable dstTable) { + SQLServerBulkCopy bc; + DBConnection con = new DBConnection(bulkWrapper.getConnectionString()); + DBStatement stmt = con.createStatement(); + try { + bc = new SQLServerBulkCopy(bulkWrapper.getConnectionString()); + bc.setDestinationTableName(dstTable.getEscapedTableName()); + bc.writeToServer(srcData); + bc.close(); + validateValues(con, srcData, dstTable); + } + catch (Exception e) { + fail(e.getMessage()); + } + finally { + con.close(); + } + } + + /** + * + * @param con + * @param srcData + * @param destinationTable + * @throws Exception + */ + static void validateValues( + DBConnection con, + ISQLServerBulkRecord srcData, + DBTable destinationTable) throws Exception { + + DBStatement dstStmt = con.createStatement(); + DBResultSet dstResultSet = dstStmt.executeQuery("SELECT * FROM " + destinationTable.getEscapedTableName() + ";"); + ResultSetMetaData destMeta = ((ResultSet) dstResultSet.product()).getMetaData(); + int totalColumns = destMeta.getColumnCount(); + + // reset the counter in ISQLServerBulkRecord, which was incremented during read by BulkCopy + java.lang.reflect.Method method = srcData.getClass().getMethod("reset"); + method.invoke(srcData); + + + // verify data from sourceType and resultSet + while (srcData.next() && dstResultSet.next()) + { + Object[] srcValues = srcData.getRowData(); + for (int i = 1; i <= totalColumns; i++) { + + Object srcValue, dstValue; + srcValue = srcValues[i-1]; + if(srcValue.getClass().getName().equalsIgnoreCase("java.lang.Double")){ + // in case of SQL Server type Float (ie java type double), in float(n) if n is <=24 ie precsion is <=7 SQL Server type Real is returned(ie java type float) + if(destMeta.getPrecision(i) <8) + srcValue = new Float(((Double)srcValue)); + } + dstValue = dstResultSet.getObject(i); + int dstType = destMeta.getColumnType(i); + if(java.sql.Types.TIMESTAMP != dstType + && java.sql.Types.TIME != dstType + && microsoft.sql.Types.DATETIMEOFFSET != dstType){ + // skip validation for temporal types due to rounding eg 7986-10-21 09:51:15.114 is rounded as 7986-10-21 09:51:15.113 in server + comapreSourceDest(dstType, srcValue, dstValue); + } + } + } + } } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java b/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java index 78bccaaf4..e391528dd 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java @@ -310,7 +310,7 @@ else if (metaData.getColumnTypeName(ordinal + 1).equalsIgnoreCase("smalldatetime break; case java.sql.Types.BINARY: - assertTrue(parseByte((byte[]) expectedData, (byte[]) retrieved), + assertTrue(Utils.parseByte((byte[]) expectedData, (byte[]) retrieved), " unexpected BINARY value, expected: " + expectedData + " ,received: " + retrieved); break; @@ -324,14 +324,6 @@ else if (metaData.getColumnTypeName(ordinal + 1).equalsIgnoreCase("smalldatetime } } - private boolean parseByte(byte[] expectedData, - byte[] retrieved) { - assertTrue(Arrays.equals(expectedData, Arrays.copyOf(retrieved, expectedData.length)), " unexpected BINARY value, expected"); - for (int i = expectedData.length; i < retrieved.length; i++) { - assertTrue(0 == retrieved[i], "unexpected data BINARY"); - } - return true; - } /** * diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java b/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java index f1e28bb44..3da9c72d9 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java @@ -213,6 +213,11 @@ else if (VariableLengthType.Scale == column.getSqlType().getVariableLengthType() sb.add("" + column.getSqlType().getScale()); sb.add(CLOSE_BRACKET); } + else if (VariableLengthType.ScaleOnly == column.getSqlType().getVariableLengthType()) { + sb.add(OPEN_BRACKET); + sb.add("" + column.getSqlType().getScale()); + sb.add(CLOSE_BRACKET); + } sb.add(COMMA); } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java index 1f959b8eb..8db5e96e5 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java @@ -9,12 +9,14 @@ package com.microsoft.sqlserver.testframework; import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.CharArrayReader; import java.net.URI; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; @@ -305,4 +307,14 @@ private static void dropObjectIfExists(String objectName, String objectProperty, bracketedObjectName); stmt.executeUpdate(sql); } + + public static boolean parseByte(byte[] expectedData, + byte[] retrieved) { + assertTrue(Arrays.equals(expectedData, Arrays.copyOf(retrieved, expectedData.length)), " unexpected BINARY value, expected"); + for (int i = expectedData.length; i < retrieved.length; i++) { + assertTrue(0 == retrieved[i], "unexpected data BINARY"); + } + return true; + } + } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime2.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime2.java index 2da5df99e..5050d68a2 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime2.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime2.java @@ -14,9 +14,9 @@ import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.time.format.ResolverStyle; import java.time.temporal.ChronoField; import java.util.concurrent.ThreadLocalRandom; @@ -42,15 +42,16 @@ public SqlDateTime2() { generatePrecision(); formatter = new DateTimeFormatterBuilder().appendPattern(basePattern).appendFraction(ChronoField.NANO_OF_SECOND, 0, this.precision, true) .toFormatter(); - + formatter = formatter.withResolverStyle(ResolverStyle.STRICT); } public Object createdata() { Timestamp temp = new Timestamp(ThreadLocalRandom.current().nextLong(((Timestamp) minvalue).getTime(), ((Timestamp) maxvalue).getTime())); temp.setNanos(0); String timeNano = temp.toString().substring(0, temp.toString().length() - 1) + RandomStringUtils.randomNumeric(this.precision); + return timeNano; // can pass string rather than converting to LocalDateTime, but leaving // it unchanged for now to handle prepared statements - return LocalDateTime.parse(timeNano, formatter); +// return LocalDateTime.parse(timeNano, formatter); } } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java index 86ee22d53..a2216d0e9 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java @@ -33,12 +33,11 @@ public SqlFloat() { } public Object createdata() { - // TODO: include max value - if (precision > 24) { + // for float in SQL Server, any precision <=24 is considered as real so the value must be within SqlTypeValue.REAL.minValue/maxValue + if (precision > 24) return Double.longBitsToDouble(ThreadLocalRandom.current().nextLong(((Double) minvalue).longValue(), ((Double) maxvalue).longValue())); - } else { - return new Float(ThreadLocalRandom.current().nextDouble(new Float(-3.4E38), new Float(+3.4E38))); + return ThreadLocalRandom.current().nextDouble((Float) SqlTypeValue.REAL.minValue, (Float) SqlTypeValue.REAL.maxValue); } } } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java index cbdd5db90..bea109696 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java @@ -9,6 +9,7 @@ package com.microsoft.sqlserver.testframework.sqlType; import java.sql.JDBCType; +import java.util.concurrent.ThreadLocalRandom; public class SqlReal extends SqlFloat { @@ -16,4 +17,9 @@ public SqlReal() { super("real", JDBCType.REAL, 24, SqlTypeValue.REAL.minValue, SqlTypeValue.REAL.maxValue, SqlTypeValue.REAL.nullValue, VariableLengthType.Fixed, Float.class); } + + @Override + public Object createdata() { + return new Float(ThreadLocalRandom.current().nextDouble((Float) minvalue, (Float) maxvalue)); + } } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallDateTime.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallDateTime.java index 392fbc2ca..c9ab21c3d 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallDateTime.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallDateTime.java @@ -35,6 +35,7 @@ public Object createdata() { ThreadLocalRandom.current().nextLong(((Timestamp) minvalue).getTime(), ((Timestamp) maxvalue).getTime())); // remove the random nanosecond value if any smallDateTime.setNanos(0); - return smallDateTime; + return smallDateTime.toString().substring(0,19);// ignore the nano second portion +// return smallDateTime; } } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java index 8cde8932e..52cc9bcb4 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java @@ -14,9 +14,9 @@ import java.sql.Time; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.time.format.ResolverStyle; import java.time.temporal.ChronoField; import java.util.concurrent.ThreadLocalRandom; @@ -38,19 +38,26 @@ public SqlTime() { catch (ParseException ex) { fail(ex.getMessage()); } - this.precision = 7; - this.variableLengthType = VariableLengthType.Precision; - generatePrecision(); - formatter = new DateTimeFormatterBuilder().appendPattern(basePattern).appendFraction(ChronoField.NANO_OF_SECOND, 0, this.precision, true) + this.scale = 7; + this.variableLengthType = VariableLengthType.ScaleOnly; + generateScale(); + + formatter = new DateTimeFormatterBuilder().appendPattern(basePattern).appendFraction(ChronoField.NANO_OF_SECOND, 0, this.scale, true) .toFormatter(); + formatter = formatter.withResolverStyle(ResolverStyle.STRICT); } public Object createdata() { Time temp = new Time(ThreadLocalRandom.current().nextLong(((Time) minvalue).getTime(), ((Time) maxvalue).getTime())); - String timeNano = temp.toString() + "." + RandomStringUtils.randomNumeric(this.precision); + String timeNano = temp.toString() + "." + RandomStringUtils.randomNumeric(this.scale); + return timeNano; + // can pass String rather than converting to loacTime, but leaving it // unchanged for now to handle prepared statements - return LocalTime.parse(timeNano, formatter); + /* + * converting string '20:53:44.9' to LocalTime results in 20:53:44.900, this extra scale causes failure + */ +// return LocalTime.parse(timeNano, formatter); } } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java index e70f5fd0b..36480f7ec 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java @@ -9,9 +9,6 @@ package com.microsoft.sqlserver.testframework.sqlType; import java.sql.JDBCType; -import java.sql.SQLTimeoutException; -import java.sql.SQLType; -import java.util.ArrayList; import java.util.BitSet; import java.util.concurrent.ThreadLocalRandom; @@ -19,7 +16,6 @@ import com.microsoft.sqlserver.testframework.DBCoercions; import com.microsoft.sqlserver.testframework.DBConnection; import com.microsoft.sqlserver.testframework.DBItems; -import com.microsoft.sqlserver.testframework.Utils; public abstract class SqlType extends DBItems { // TODO: add seed to generate random data -> will help to reproduce the @@ -225,7 +221,16 @@ void generatePrecision() { int maxPrecision = this.precision; this.precision = ThreadLocalRandom.current().nextInt(minPrecision, maxPrecision + 1); } - + + /** + * generates random precision for SQL types with scale + */ + void generateScale() { + int minScale = 1; + int maxScale = this.scale; + this.scale = ThreadLocalRandom.current().nextInt(minScale, maxScale + 1); + } + /** * @return */ diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java index 26d3de103..fb12fa1ae 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java @@ -15,5 +15,6 @@ public enum VariableLengthType { Fixed, // primitive types with fixed Length Precision, // variable length type that just has precision char/varchar Scale, // variable length type with scale and precision + ScaleOnly, // variable length type with just scale like Time Variable } From 11b82282365fc71266a8e2198dfbcc776d17c67f Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Fri, 21 Apr 2017 10:33:22 -0700 Subject: [PATCH 53/72] updating number of test rows --- .../jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java index f0cbf3e53..ba75329be 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyISQLServerBulkRecordTest.java @@ -54,7 +54,6 @@ static void setUpConnection() { @Test void testISQLServerBulkRecord() { dstTable = new DBTable(true); - dstTable.setTotalRows(1); stmt.createTable(dstTable); BulkData Bdata = new BulkData(); From d0e5f12416cbebe0f364d37105e4cbafed27729c Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Fri, 21 Apr 2017 11:24:55 -0700 Subject: [PATCH 54/72] added support for longvarchar types --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 6 + .../sqlserver/jdbc/SQLServerDataTable.java | 3 + .../sqlserver/jdbc/tvp/TVPTypes.java | 294 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypes.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 0289551e2..822b36250 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4684,6 +4684,9 @@ void writeTVPRows(TVP value) throws SQLServerException { case VARCHAR: case NCHAR: case NVARCHAR: + case LONGVARCHAR: + case LONGNVARCHAR: + case SQLXML: isShortValue = (2L * columnPair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; isNull = (null == currentColumnStringValue); dataLength = isNull ? 0 : currentColumnStringValue.length() * 2; @@ -4859,6 +4862,9 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException { case VARCHAR: case NCHAR: case NVARCHAR: + case LONGVARCHAR: + case LONGNVARCHAR: + case SQLXML: writeByte(TDSType.NVARCHAR.byteValue()); isShortValue = (2L * pair.getValue().precision) <= DataTypes.SHORT_VARTYPE_MAX_BYTES; // Use PLP encoding on Yukon and later with long values diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java index d0fb9b589..423592fd4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java @@ -230,6 +230,9 @@ else if (val instanceof OffsetTime) case VARCHAR: case NCHAR: case NVARCHAR: + case LONGVARCHAR: + case LONGNVARCHAR: + case SQLXML: bValueNull = (null == val); nValueLen = bValueNull ? 0 : (2 * ((String) val).length()); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypes.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypes.java new file mode 100644 index 000000000..b277bcce7 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypes.java @@ -0,0 +1,294 @@ +/* + * 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.tvp; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; +import com.microsoft.sqlserver.jdbc.SQLServerDataTable; +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.DBConnection; +import com.microsoft.sqlserver.testframework.DBResultSet; +import com.microsoft.sqlserver.testframework.DBStatement; + +@RunWith(JUnitPlatform.class) +public class TVPTypes extends AbstractTest { + + private static DBConnection conn = null; + static DBStatement stmt = null; + static DBResultSet rs = null; + static SQLServerDataTable tvp = null; + static String expectecValue1 = "hello"; + static String expectecValue2 = "world"; + static String expectecValue3 = "again"; + private static String tvpName = "numericTVP"; + private static String charTable = "tvpNumericTable"; + private static String procedureName = "procedureThatCallsTVP"; + + /** + * Test a longvarchar support + * + * @throws SQLException + */ + @Test + public void testLongVarchar() throws SQLException { + createTables("varchar(max)"); + createTVPS("varchar(max)"); + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 9000; i++) + buffer.append("a"); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); + tvp.addRow(buffer.toString()); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, tvp); + + pstmt.execute(); + + if (null != pstmt) { + pstmt.close(); + } + } + + /** + * Test longnvarchar + * + * @throws SQLException + */ + @Test + public void testLongNVarchar() throws SQLException { + createTables("nvarchar(max)"); + createTVPS("nvarchar(max)"); + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 8001; i++) + buffer.append("سس"); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); + tvp.addRow(buffer.toString()); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, tvp); + + pstmt.execute(); + + if (null != pstmt) { + pstmt.close(); + } + } + + /** + * Test xml support + * + * @throws SQLException + */ + @Test + public void testXML() throws SQLException { + createTables("xml"); + createTVPS("xml"); + String value = "Variable E" + "Variable F" + "API" + + "The following are Japanese chars." + + " Some UTF-8 encoded characters: �������"; + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.SQLXML); + tvp.addRow(value); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, tvp); + + pstmt.execute(); + + Connection con = DriverManager.getConnection(connectionString); + ResultSet rs = con.createStatement().executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), value); + + if (null != pstmt) { + pstmt.close(); + } + } + + /** + * LongVarchar with StoredProcedure + * + * @throws SQLException + */ + @Test + public void testTVPLongVarchar_StoredProcedure() throws SQLException { + createTables("varchar(max)"); + createTVPS("varchar(max)"); + createPreocedure(); + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 8001; i++) + buffer.append("a"); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); + tvp.addRow(buffer.toString()); + + final String sql = "{call " + procedureName + "(?)}"; + + SQLServerCallableStatement P_C_statement = (SQLServerCallableStatement) connection.prepareCall(sql); + P_C_statement.setStructured(1, tvpName, tvp); + P_C_statement.execute(); + + rs = stmt.executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), buffer.toString()); + + if (null != P_C_statement) { + P_C_statement.close(); + } + } + + /** + * LongNVarchar with StoredProcedure + * + * @throws SQLException + */ + @Test + public void testTVPLongNVarchar_StoredProcedure() throws SQLException { + createTables("nvarchar(max)"); + createTVPS("nvarchar(max)"); + createPreocedure(); + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 8001; i++) + buffer.append("سس"); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); + tvp.addRow(buffer.toString()); + + final String sql = "{call " + procedureName + "(?)}"; + + SQLServerCallableStatement P_C_statement = (SQLServerCallableStatement) connection.prepareCall(sql); + P_C_statement.setStructured(1, tvpName, tvp); + P_C_statement.execute(); + + rs = stmt.executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), buffer.toString()); + + if (null != P_C_statement) { + P_C_statement.close(); + } + } + + /** + * XML with StoredProcedure + * + * @throws SQLException + */ + @Test + public void testTVPXML_StoredProcedure() throws SQLException { + createTables("xml"); + createTVPS("xml"); + createPreocedure(); + + String value = "Variable E" + "Variable F" + "API" + + "The following are Japanese chars." + + " Some UTF-8 encoded characters: �������"; + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.SQLXML); + tvp.addRow(value); + + final String sql = "{call " + procedureName + "(?)}"; + + SQLServerCallableStatement P_C_statement = (SQLServerCallableStatement) connection.prepareCall(sql); + P_C_statement.setStructured(1, tvpName, tvp); + P_C_statement.execute(); + + rs = stmt.executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), value); + if (null != P_C_statement) { + P_C_statement.close(); + } + } + + @BeforeEach + private void testSetup() throws SQLException { + conn = new DBConnection(connectionString); + stmt = conn.createStatement(); + + dropProcedure(); + dropTables(); + dropTVPS(); + } + + private void dropProcedure() throws SQLException { + String sql = " IF EXISTS (select * from sysobjects where id = object_id(N'" + procedureName + "') and OBJECTPROPERTY(id, N'IsProcedure') = 1)" + + " DROP PROCEDURE " + procedureName; + stmt.execute(sql); + } + + private static void dropTables() throws SQLException { + stmt.executeUpdate("if object_id('" + charTable + "','U') is not null" + " drop table " + charTable); + } + + private static void dropTVPS() throws SQLException { + stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); + } + + private static void createPreocedure() throws SQLException { + String sql = "CREATE PROCEDURE " + procedureName + " @InputData " + tvpName + " READONLY " + " AS " + " BEGIN " + " INSERT INTO " + charTable + + " SELECT * FROM @InputData" + " END"; + + stmt.execute(sql); + } + + private void createTables(String colType) throws SQLException { + String sql = "create table " + charTable + " (c1 " + colType + " null);"; + stmt.execute(sql); + } + + private void createTVPS(String colType) throws SQLException { + String TVPCreateCmd = "CREATE TYPE " + tvpName + " as table (c1 " + colType + " null)"; + stmt.executeUpdate(TVPCreateCmd); + } + + @AfterEach + private void terminateVariation() throws SQLException { + if (null != conn) { + conn.close(); + } + if (null != stmt) { + stmt.close(); + } + if (null != rs) { + rs.close(); + } + if (null != tvp) { + tvp.clear(); + } + } + +} \ No newline at end of file From c5004e6e8618efb9442a36f1f271c84fc42e0298 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Fri, 21 Apr 2017 12:04:08 -0700 Subject: [PATCH 55/72] added test in the file name --- .../jdbc/tvp/{TVPTypes.java => TVPTypesTest.java} | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) rename src/test/java/com/microsoft/sqlserver/jdbc/tvp/{TVPTypes.java => TVPTypesTest.java} (96%) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypes.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java similarity index 96% rename from src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypes.java rename to src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java index b277bcce7..4d4fc6ff8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypes.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java @@ -14,6 +14,7 @@ import java.sql.ResultSet; import java.sql.SQLException; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +30,7 @@ import com.microsoft.sqlserver.testframework.DBStatement; @RunWith(JUnitPlatform.class) -public class TVPTypes extends AbstractTest { +public class TVPTypesTest extends AbstractTest { private static DBConnection conn = null; static DBStatement stmt = null; @@ -243,8 +244,18 @@ private void testSetup() throws SQLException { dropTables(); dropTVPS(); } + + @AfterAll + public static void terminate() throws SQLException { + conn = new DBConnection(connectionString); + stmt = conn.createStatement(); + + dropProcedure(); + dropTables(); + dropTVPS(); + } - private void dropProcedure() throws SQLException { + private static void dropProcedure() throws SQLException { String sql = " IF EXISTS (select * from sysobjects where id = object_id(N'" + procedureName + "') and OBJECTPROPERTY(id, N'IsProcedure') = 1)" + " DROP PROCEDURE " + procedureName; stmt.execute(sql); From 0c060b6b65ee07ff1ea1438cfc7f6fa6ac137da9 Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Fri, 21 Apr 2017 14:03:58 -0700 Subject: [PATCH 56/72] Handle ClassCastException --- .../sqlserver/jdbc/SQLServerBulkCopy.java | 687 +++++++++--------- 1 file changed, 347 insertions(+), 340 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index 93a6ef268..cdfa825fd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -2035,417 +2035,424 @@ else if (null != sourceBulkRecord) { } } - // 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); + try { + // 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); } - tdsWriter.writeInt((int) colValue); - } - break; - - case java.sql.Types.SMALLINT: - if (null == colValue) { - writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); - } - else { - if (bulkNullable) { - tdsWriter.writeByte((byte) 0x02); + else { + if (bulkNullable) { + tdsWriter.writeByte((byte) 0x04); + } + tdsWriter.writeInt((int) colValue); } - tdsWriter.writeShort(((Number) colValue).shortValue()); - } - break; + break; - case java.sql.Types.BIGINT: - if (null == colValue) { - writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); - } - else { - if (bulkNullable) { - tdsWriter.writeByte((byte) 0x08); + case java.sql.Types.SMALLINT: + if (null == colValue) { + writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); } - tdsWriter.writeLong((long) colValue); - } - break; + else { + if (bulkNullable) { + tdsWriter.writeByte((byte) 0x02); + } + tdsWriter.writeShort(((Number) colValue).shortValue()); + } + break; - case java.sql.Types.BIT: - if (null == colValue) { - writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); - } - else { - if (bulkNullable) { - tdsWriter.writeByte((byte) 0x01); + case java.sql.Types.BIGINT: + if (null == colValue) { + writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); } - tdsWriter.writeByte((byte) (((Boolean) colValue).booleanValue() ? 1 : 0)); - } - break; + else { + if (bulkNullable) { + tdsWriter.writeByte((byte) 0x08); + } + tdsWriter.writeLong((long) colValue); + } + break; - case java.sql.Types.TINYINT: - if (null == colValue) { - writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); - } - else { - if (bulkNullable) { - tdsWriter.writeByte((byte) 0x01); + case java.sql.Types.BIT: + if (null == colValue) { + writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); } - // 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 (bulkNullable) { + tdsWriter.writeByte((byte) 0x01); + } + tdsWriter.writeByte((byte) (((Boolean) colValue).booleanValue() ? 1 : 0)); + } + break; - } - 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)); - case java.sql.Types.DOUBLE: - if (null == colValue) { - writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); - } - else { - if (bulkNullable) { - tdsWriter.writeByte((byte) 0x08); } - tdsWriter.writeDouble((double) colValue); - } - break; + break; - case java.sql.Types.REAL: - if (null == colValue) { - writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); - } - else { - if (bulkNullable) { - tdsWriter.writeByte((byte) 0x04); + case java.sql.Types.DOUBLE: + if (null == colValue) { + writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); } - tdsWriter.writeReal((float) colValue); - } - break; + else { + if (bulkNullable) { + tdsWriter.writeByte((byte) 0x08); + } + tdsWriter.writeDouble((double) 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 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.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. + 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 { - // 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; + 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(); } - else { - reader = new StringReader(colValue.toString()); + catch (IOException e) { + throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e); } - - if ((SSType.BINARY == destSSType) || (SSType.VARBINARY == destSSType) || (SSType.VARBINARYMAX == destSSType) - || (SSType.IMAGE == destSSType)) { - tdsWriter.writeNonUnicodeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, true, null); + } + } + 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.writeNonUnicodeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, false, destCollation.getCharset()); + tdsWriter.writeBytes(colValueStr.getBytes(destColumnMetadata.get(destColOrdinal).collation.getCharset())); + } else { - tdsWriter.writeNonUnicodeReader(reader, DataTypes.UNKNOWN_STREAM_LENGTH, false, null); + tdsWriter.writeBytes(colValueStr.getBytes()); } } - reader.close(); - } - catch (IOException e) { - throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e); } } - } - else // Non-PLP - { - if (null == colValue) { - writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); + 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 { - String colValueStr = colValue.toString(); - if ((SSType.BINARY == destSSType) || (SSType.VARBINARY == destSSType)) { - byte[] bytes = null; + 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 { - bytes = ParameterUtils.HexToBin(colValueStr); + // 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 (SQLServerException e) { + catch (IOException e) { throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e); } - tdsWriter.writeShort((short) bytes.length); - tdsWriter.writeBytes(bytes); + } + } + else // Non-PLP + { + if (null == colValue) { + writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); } 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())); - + byte[] srcBytes; + if (colValue instanceof byte[]) { + srcBytes = (byte[]) colValue; } else { - tdsWriter.writeBytes(colValueStr.getBytes()); + 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; + 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. + case microsoft.sql.Types.DATETIME: + case microsoft.sql.Types.SMALLDATETIME: + case java.sql.Types.TIMESTAMP: 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); + 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(' '))); } } - } - else { + break; + + case java.sql.Types.DATE: 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()); + tdsWriter.writeByte((byte) 0x03); + tdsWriter.writeDate(colValue.toString()); } - } - break; + 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. + 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 { - // 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); - } + 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); } - } - else // Non-PLP - { + break; + + case 2013: // java.sql.Types.TIME_WITH_TIMEZONE 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); + 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; + 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(' '))); + case 2014: // java.sql.Types.TIMESTAMP_WITH_TIMEZONE + if (null == colValue) { + writeNullToTdsWriter(tdsWriter, bulkJdbcType, isStreaming); } - } - 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); + 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; - tdsWriter.writeDateTimeOffset(colValue, bulkScale, destSSType); - } - 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 + 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 + } + catch (ClassCastException ex) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue")); + Object[] msgArgs = {colValue.getClass().getSimpleName(), JDBCType.of(bulkJdbcType)}; + throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, ex); + } } private Object readColumnFromResultSet(int srcColOrdinal, From b5268663505422f18f766607c479241f77fa4240 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Fri, 21 Apr 2017 15:03:20 -0700 Subject: [PATCH 57/72] added longvarbinary for image/varbinary(max) types and the test for it --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 2 + .../sqlserver/jdbc/SQLServerDataTable.java | 1 + .../sqlserver/jdbc/tvp/TVPTypesTest.java | 237 ++++++++++++++++-- 3 files changed, 225 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index a713f566e..c5fcbaa72 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4699,6 +4699,7 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) case BINARY: case VARBINARY: + case LONGVARBINARY: // Handle conversions as done in other types. isShortValue = columnPair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES; isNull = (null == currentObject); @@ -4860,6 +4861,7 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException { case BINARY: case VARBINARY: + case LONGVARBINARY: writeByte(TDSType.BIGVARBINARY.byteValue()); isShortValue = pair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES; // Use PLP encoding on Yukon and later with long values diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java index 423592fd4..30085ed25 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java @@ -213,6 +213,7 @@ else if (val instanceof OffsetTime) case BINARY: case VARBINARY: + case LONGVARBINARY: bValueNull = (null == val); nValueLen = bValueNull ? 0 : ((byte[]) val).length; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java index 4d4fc6ff8..f0d8b3a75 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java @@ -8,11 +8,14 @@ package com.microsoft.sqlserver.jdbc.tvp; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -25,22 +28,16 @@ import com.microsoft.sqlserver.jdbc.SQLServerDataTable; import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.testframework.AbstractTest; -import com.microsoft.sqlserver.testframework.DBConnection; -import com.microsoft.sqlserver.testframework.DBResultSet; -import com.microsoft.sqlserver.testframework.DBStatement; @RunWith(JUnitPlatform.class) public class TVPTypesTest extends AbstractTest { - private static DBConnection conn = null; - static DBStatement stmt = null; - static DBResultSet rs = null; + private static Connection conn = null; + static Statement stmt = null; + static ResultSet rs = null; static SQLServerDataTable tvp = null; - static String expectecValue1 = "hello"; - static String expectecValue2 = "world"; - static String expectecValue3 = "again"; - private static String tvpName = "numericTVP"; - private static String charTable = "tvpNumericTable"; + private static String tvpName = "MaxTypesTVP"; + private static String charTable = "MaxTypesTVPTable"; private static String procedureName = "procedureThatCallsTVP"; /** @@ -134,6 +131,106 @@ public void testXML() throws SQLException { } } + /** + * Test ntext support + * + * @throws SQLException + */ + @Test + public void testnText() throws SQLException { + createTables("ntext"); + createTVPS("ntext"); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 9000; i++) + buffer.append("س"); + String value = buffer.toString(); + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); + tvp.addRow(value); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, tvp); + + pstmt.execute(); + + Connection con = DriverManager.getConnection(connectionString); + ResultSet rs = con.createStatement().executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), value); + + if (null != pstmt) { + pstmt.close(); + } + } + + /** + * Test text support + * + * @throws SQLException + */ + @Test + public void testText() throws SQLException { + createTables("text"); + createTVPS("text"); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 9000; i++) + buffer.append("a"); + String value = buffer.toString(); + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); + tvp.addRow(value); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, tvp); + + pstmt.execute(); + + Connection con = DriverManager.getConnection(connectionString); + ResultSet rs = con.createStatement().executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), value); + + if (null != pstmt) { + pstmt.close(); + } + } + + /** + * Test text support + * + * @throws SQLException + */ + @Test + public void testImage() throws SQLException { + createTables("varbinary(max)"); + createTVPS("varbinary(max)"); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 10000; i++) + buffer.append("a"); + String value = buffer.toString(); + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGVARBINARY); + tvp.addRow(value.getBytes()); + + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection + .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); + pstmt.setStructured(1, tvpName, tvp); + + pstmt.execute(); + + Connection con = DriverManager.getConnection(connectionString); + ResultSet rs = con.createStatement().executeQuery("select * from " + charTable); + + while (rs.next()) + assertTrue(parseByte(rs.getBytes(1), value.getBytes())); + + if (null != pstmt) { + pstmt.close(); + } + } + /** * LongVarchar with StoredProcedure * @@ -235,21 +332,122 @@ public void testTVPXML_StoredProcedure() throws SQLException { } } + /** + * Text with StoredProcedure + * + * @throws SQLException + */ + @Test + public void testTVPText_StoredProcedure() throws SQLException { + createTables("text"); + createTVPS("text"); + createPreocedure(); + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 9000; i++) + buffer.append("a"); + String value = buffer.toString(); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); + tvp.addRow(value); + + final String sql = "{call " + procedureName + "(?)}"; + + SQLServerCallableStatement P_C_statement = (SQLServerCallableStatement) connection.prepareCall(sql); + P_C_statement.setStructured(1, tvpName, tvp); + P_C_statement.execute(); + + rs = stmt.executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), value); + if (null != P_C_statement) { + P_C_statement.close(); + } + } + + /** + * Text with StoredProcedure + * + * @throws SQLException + */ + @Test + public void testTVPNText_StoredProcedure() throws SQLException { + createTables("ntext"); + createTVPS("ntext"); + createPreocedure(); + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 9000; i++) + buffer.append("س"); + String value = buffer.toString(); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); + tvp.addRow(value); + + final String sql = "{call " + procedureName + "(?)}"; + + SQLServerCallableStatement P_C_statement = (SQLServerCallableStatement) connection.prepareCall(sql); + P_C_statement.setStructured(1, tvpName, tvp); + P_C_statement.execute(); + + rs = stmt.executeQuery("select * from " + charTable); + while (rs.next()) + assertEquals(rs.getString(1), value); + if (null != P_C_statement) { + P_C_statement.close(); + } + } + + /** + * Image with StoredProcedure acts the same as varbinary(max) + * + * @throws SQLException + */ + @Test + public void testTVPImage_StoredProcedure() throws SQLException { + createTables("image"); + createTVPS("image"); + createPreocedure(); + + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < 9000; i++) + buffer.append("a"); + String value = buffer.toString(); + + tvp = new SQLServerDataTable(); + tvp.addColumnMetadata("c1", java.sql.Types.LONGVARBINARY); + tvp.addRow(value.getBytes()); + + final String sql = "{call " + procedureName + "(?)}"; + + SQLServerCallableStatement P_C_statement = (SQLServerCallableStatement) connection.prepareCall(sql); + P_C_statement.setStructured(1, tvpName, tvp); + P_C_statement.execute(); + + rs = stmt.executeQuery("select * from " + charTable); + while (rs.next()) + assertTrue(parseByte(rs.getBytes(1), value.getBytes())); + if (null != P_C_statement) { + P_C_statement.close(); + } + } + @BeforeEach private void testSetup() throws SQLException { - conn = new DBConnection(connectionString); + conn = DriverManager.getConnection(connectionString); stmt = conn.createStatement(); dropProcedure(); dropTables(); dropTVPS(); } - + @AfterAll public static void terminate() throws SQLException { - conn = new DBConnection(connectionString); + conn = DriverManager.getConnection(connectionString); stmt = conn.createStatement(); - dropProcedure(); dropTables(); dropTVPS(); @@ -286,6 +484,15 @@ private void createTVPS(String colType) throws SQLException { stmt.executeUpdate(TVPCreateCmd); } + private boolean parseByte(byte[] expectedData, + byte[] retrieved) { + assertTrue(Arrays.equals(expectedData, Arrays.copyOf(retrieved, expectedData.length)), " unexpected BINARY value, expected"); + for (int i = expectedData.length; i < retrieved.length; i++) { + assertTrue(0 == retrieved[i], "unexpected data BINARY"); + } + return true; + } + @AfterEach private void terminateVariation() throws SQLException { if (null != conn) { From da6701978fcc9c95b236fdf12e16c7138a5a6627 Mon Sep 17 00:00:00 2001 From: v-ahibr Date: Mon, 24 Apr 2017 16:55:03 -0700 Subject: [PATCH 58/72] Add temporal types to CSV --- src/test/resources/BulkCopyCSVTestInput.csv | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/resources/BulkCopyCSVTestInput.csv b/src/test/resources/BulkCopyCSVTestInput.csv index f43d20d14..2d28e77bc 100644 --- a/src/test/resources/BulkCopyCSVTestInput.csv +++ b/src/test/resources/BulkCopyCSVTestInput.csv @@ -1,6 +1,6 @@ -bit,tinyint,smallint,int,bigint,float(53),real,decimal(18-6),numeric(18-4),money(20-4),smallmoney(20-4),char(11),nchar(10),varchar(50),nvarchar(10),binary(5),varbinary(25) -1,2,-32768,0,0,-1.78E307,-3.4E38,22.335600,22.3356,-922337203685477.5808,-214748.3648,a5()b,௵ஷஇமண,test to test csv files,ࢨहश,6163686974,6163686974 -,,,,,,,,,,,,,,,, -0,5,32767,1,12,-2.23E-308,-1.18E-38,33.552695,33.5526,922337203685477.5807,0.0000,what!,ৡਐਲ,123 norma black street,Ӧ NӦ,5445535455,54455354 -0,255,0,-2147483648,-9223372036854775808,2.23E-308,0.0,33.503288,33.5032,0.0000,1.0011,no way,Ӧ NӦ,baker street Mr.Homls,àĂ,303C2D3988,303C2D39 -1,5,0,2147483647,9223372036854775807,12.0,3.4E38,33.000501,33.0005,1.0001,214748.3647,l l l l l |,Ȣʗʘ,test to test csv files,௵ஷஇமண,7E7D7A7B20,7E7D7A7B \ No newline at end of file +bit,tinyint,smallint,int,bigint,float(53),real,decimal(18-6),numeric(18-4),money(20-4),smallmoney(20-4),char(11),nchar(10),varchar(50),nvarchar(10),binary(5),varbinary(25),date,datetime,datetime2(7),smalldatetime,datetimeoffset(7),time(16-7) +1,2,-32768,0,0,-1.78E307,-3.4E38,22.335600,22.3356,-922337203685477.5808,-214748.3648,a5()b,௵ஷஇமண,test to test csv files,ࢨहश,6163686974,6163686974,1922-11-02,2004-05-23 14:25:10.487,2007-05-02 19:58:47.1234567,2004-05-23 14:25:00.0,2025-12-10 12:32:10.1234567 +01:00,12:23:48.1234567 +,,,,,,,,,,,,,,,,,,,,,, +0,5,32767,1,12,-2.23E-308,-1.18E-38,33.552695,33.5526,922337203685477.5807,0.0000,what!,ৡਐਲ,123 norma black street,Ӧ NӦ,5445535455,54455354,9999-12-31,9999-12-31 23:59:59.997,9999-12-31 23:59:59.9999999,2079-06-06 23:59:00.0,9999-12-31 23:59:00.0000000 +00:00,23:59:59.9990000 +0,255,0,-2147483648,-9223372036854775808,2.23E-308,0.0,33.503288,33.5032,0.0000,1.0011,no way,Ӧ NӦ,baker street Mr.Homls,àĂ,303C2D3988,303C2D39,0001-01-01,1973-01-01 00:00:00.0,0001-01-01 00:00:00.0000000,1900-01-01 00:00:00.0,0001-01-01 00:00:00.0000000 +00:00,00:00:00.0000000 +1,5,0,2147483647,9223372036854775807,12.0,3.4E38,33.000501,33.0005,1.0001,214748.3647,l l l l l |,Ȣʗʘ,test to test csv files,௵ஷஇமண,7E7D7A7B20,7E7D7A7B,2017-04-18,2014-10-11 20:13:12.123,2017-10-12 09:38:17.7654321,2014-10-11 20:13:00.0,2017-01-06 10:52:20.7654321 +03:00,18:02:16.7654321 From 98be43965041f570c498ed375c8e97d001f2fa1e Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Mon, 24 Apr 2017 17:44:22 -0700 Subject: [PATCH 59/72] update cached variables in synchronized way --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index efff14683..1e53e9e60 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4539,6 +4539,10 @@ void writeTVPRows(TVP value) throws SQLServerException { boolean tdsWritterCached = false; ByteBuffer cachedTVPHeaders = null; TDSCommand cachedCommand = null; + + boolean cachedRequestComplete = false; + boolean cachedInterruptsEnabled = false; + boolean cachedProcessedResponse = false; if (!value.isNull()) { @@ -4557,6 +4561,10 @@ void writeTVPRows(TVP value) throws SQLServerException { cachedCommand = this.command; + cachedRequestComplete = command.requestComplete; + cachedInterruptsEnabled = command.interruptsEnabled; + cachedProcessedResponse = command.processedResponse; + tdsWritterCached = true; if (sourceResultSet.isForwardOnly()) { @@ -4806,17 +4814,15 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) SQLServerException.makeFromDatabaseError(con, null, databaseError.getMessage(), databaseError, false); } - command.setInterruptsEnabled(true); - command.setRequestComplete(false); + command.interruptsEnabled = true; + command.requestComplete = false; } } } // reset command status which have been overwritten if (tdsWritterCached) { - command.setRequestComplete(false); - command.setInterruptsEnabled(true); - command.setProcessedResponse(false); + command.resetCachedFlags(cachedRequestComplete, cachedInterruptsEnabled, cachedProcessedResponse); } else { // TVP_END_TOKEN @@ -7021,11 +7027,7 @@ final void log(Level level, // 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; - - protected void setInterruptsEnabled(boolean interruptsEnabled) { - this.interruptsEnabled = interruptsEnabled; - } + protected volatile boolean interruptsEnabled = false; // Flag set to indicate that an interrupt has happened. private volatile boolean wasInterrupted = false; @@ -7041,11 +7043,7 @@ private boolean wasInterrupted() { // 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; - - protected void setRequestComplete(boolean requestComplete) { - this.requestComplete = requestComplete; - } + protected 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. @@ -7059,11 +7057,7 @@ boolean 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; - - protected void setProcessedResponse(boolean processedResponse) { - this.processedResponse = processedResponse; - } + protected 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 @@ -7522,6 +7516,16 @@ final TDSReader startResponse(boolean isAdaptive) throws SQLServerException { return tdsReader; } + + protected void resetCachedFlags(boolean cachedRequestComplete, + boolean cachedInterruptsEnabled, + boolean cachedProcessedResponse) { + synchronized (interruptLock) { + this.requestComplete = cachedRequestComplete; + this.interruptsEnabled = cachedInterruptsEnabled; + this.processedResponse = cachedProcessedResponse; + } + } } /** From c23e4997067014259e77ec82131e2458cbdfa937 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Tue, 25 Apr 2017 11:03:41 -0700 Subject: [PATCH 60/72] use setters and getters --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 60 +++++++++++++------ 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 1e53e9e60..439595e39 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -4561,9 +4561,9 @@ void writeTVPRows(TVP value) throws SQLServerException { cachedCommand = this.command; - cachedRequestComplete = command.requestComplete; - cachedInterruptsEnabled = command.interruptsEnabled; - cachedProcessedResponse = command.processedResponse; + cachedRequestComplete = command.getRequestComplete(); + cachedInterruptsEnabled = command.getInterruptsEnabled(); + cachedProcessedResponse = command.getProcessedResponse(); tdsWritterCached = true; @@ -4814,15 +4814,17 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) SQLServerException.makeFromDatabaseError(con, null, databaseError.getMessage(), databaseError, false); } - command.interruptsEnabled = true; - command.requestComplete = false; + command.setInterruptsEnabled(true); + command.setRequestComplete(false); } } } // reset command status which have been overwritten if (tdsWritterCached) { - command.resetCachedFlags(cachedRequestComplete, cachedInterruptsEnabled, cachedProcessedResponse); + command.setRequestComplete(cachedRequestComplete); + command.setInterruptsEnabled(cachedInterruptsEnabled); + command.setProcessedResponse(cachedProcessedResponse); } else { // TVP_END_TOKEN @@ -7027,7 +7029,17 @@ final void log(Level level, // 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. - protected volatile boolean interruptsEnabled = false; + private volatile boolean interruptsEnabled = false; + + protected boolean getInterruptsEnabled() { + return interruptsEnabled; + } + + protected void setInterruptsEnabled(boolean interruptsEnabled) { + synchronized (interruptLock) { + this.interruptsEnabled = interruptsEnabled; + } + } // Flag set to indicate that an interrupt has happened. private volatile boolean wasInterrupted = false; @@ -7043,7 +7055,17 @@ private boolean wasInterrupted() { // 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. - protected volatile boolean requestComplete; + private volatile boolean requestComplete; + + protected boolean getRequestComplete() { + return requestComplete; + } + + protected void setRequestComplete(boolean requestComplete) { + synchronized (interruptLock) { + this.requestComplete = 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. @@ -7057,7 +7079,17 @@ boolean 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. - protected volatile boolean processedResponse; + private volatile boolean processedResponse; + + protected boolean getProcessedResponse() { + return processedResponse; + } + + protected void setProcessedResponse(boolean processedResponse) { + synchronized (interruptLock) { + this.processedResponse = 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 @@ -7516,16 +7548,6 @@ final TDSReader startResponse(boolean isAdaptive) throws SQLServerException { return tdsReader; } - - protected void resetCachedFlags(boolean cachedRequestComplete, - boolean cachedInterruptsEnabled, - boolean cachedProcessedResponse) { - synchronized (interruptLock) { - this.requestComplete = cachedRequestComplete; - this.interruptsEnabled = cachedInterruptsEnabled; - this.processedResponse = cachedProcessedResponse; - } - } } /** From f9701c129b17b9498947513169e2350793a388ab Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Tue, 25 Apr 2017 16:39:29 -0700 Subject: [PATCH 61/72] adding connection property jaasConfigurationName --- .../sqlserver/jdbc/SQLServerDataSource.java | 21 +++++++++++++++++++ .../sqlserver/jdbc/SQLServerDriver.java | 1 + .../sqlserver/jdbc/SQLServerResource.java | 1 + 3 files changed, 23 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 54a65a28d..891fe3b90 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -722,6 +722,27 @@ public int getSocketTimeout() { return getIntProperty(connectionProps, SQLServerDriverIntProperty.SOCKET_TIMEOUT.toString(), defaultTimeOut); } + /** + * Sets the login configuration file for Kerberos authentication. This + * overrides the default configuration SQLJDBCDriver + * + * @param configurationName + */ + public void setJASSConfigurationName(String configurationName) { + setStringProperty(connectionProps, SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(), + configurationName); + } + + /** + * Retrieves the login configuration file for Kerberos authentication. + * + * @return + */ + public String getJASSConfigurationName() { + return getStringProperty(connectionProps, SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(), + SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue()); + } + // responseBuffering controls the driver's buffering of responses from SQL Server. // Possible values are: // diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index c5e3e4aea..646af582b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -380,6 +380,7 @@ public final class SQLServerDriver implements java.sql.Driver { new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.FIPS.toString(), Boolean.toString(SQLServerDriverBooleanProperty.FIPS.getDefaultValue()), false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.ENABLE_PREPARE_ON_FIRST_PREPARED_STATEMENT.toString(), Boolean.toString(SQLServerConnection.getDefaultEnablePrepareOnFirstPreparedStatementCall()), false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverIntProperty.SERVER_PREPARED_STATEMENT_DISCARD_THRESHOLD.toString(), Integer.toString(SQLServerConnection.getDefaultServerPreparedStatementDiscardThreshold()), false, null), + new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(), SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue(), false, null), }; // Properties that can only be set by using Properties. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 5d8c86d9b..ea5cd7074 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -382,5 +382,6 @@ protected Object[][] getContents() { {"R_serverPreparedStatementDiscardThreshold", "The serverPreparedStatementDiscardThreshold {0} is not valid."}, {"R_kerberosLoginFailedForUsername", "Cannot login with Kerberos principal {0}, check your credentials. {1}"}, {"R_kerberosLoginFailed", "Kerberos Login failed: {0} due to {1} ({2})"}, + {"R_jaasConfigurationNamePropertyDescription", "Login configuration file for Kerberos authentication."}, }; } From be47602a78756477897c4c34145ac4a084a5bb84 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Wed, 26 Apr 2017 14:13:43 -0700 Subject: [PATCH 62/72] added missing verifications for two methods --- .../sqlserver/jdbc/tvp/TVPTypesTest.java | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java index f0d8b3a75..27778be4c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPTypesTest.java @@ -39,6 +39,7 @@ public class TVPTypesTest extends AbstractTest { private static String tvpName = "MaxTypesTVP"; private static String charTable = "MaxTypesTVPTable"; private static String procedureName = "procedureThatCallsTVP"; + private String value = null; /** * Test a longvarchar support @@ -54,9 +55,10 @@ public void testLongVarchar() throws SQLException { for (int i = 0; i < 9000; i++) buffer.append("a"); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); - tvp.addRow(buffer.toString()); + tvp.addRow(value); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); @@ -64,6 +66,10 @@ public void testLongVarchar() throws SQLException { pstmt.execute(); + rs = conn.createStatement().executeQuery("select * from " + charTable); + while (rs.next()) { + assertEquals(rs.getString(1), value); + } if (null != pstmt) { pstmt.close(); } @@ -83,9 +89,10 @@ public void testLongNVarchar() throws SQLException { for (int i = 0; i < 8001; i++) buffer.append("سس"); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); - tvp.addRow(buffer.toString()); + tvp.addRow(value); SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection .prepareStatement("INSERT INTO " + charTable + " select * from ? ;"); @@ -93,6 +100,11 @@ public void testLongNVarchar() throws SQLException { pstmt.execute(); + rs = conn.createStatement().executeQuery("select * from " + charTable); + while (rs.next()) { + assertEquals(rs.getString(1), value); + } + if (null != pstmt) { pstmt.close(); } @@ -107,7 +119,7 @@ public void testLongNVarchar() throws SQLException { public void testXML() throws SQLException { createTables("xml"); createTVPS("xml"); - String value = "Variable E" + "Variable F" + "API" + value = "Variable E" + "Variable F" + "API" + "The following are Japanese chars." + " Some UTF-8 encoded characters: �������"; @@ -143,7 +155,7 @@ public void testnText() throws SQLException { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 9000; i++) buffer.append("س"); - String value = buffer.toString(); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); tvp.addRow(value); @@ -176,7 +188,7 @@ public void testText() throws SQLException { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 9000; i++) buffer.append("a"); - String value = buffer.toString(); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); tvp.addRow(value); @@ -209,7 +221,7 @@ public void testImage() throws SQLException { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 10000; i++) buffer.append("a"); - String value = buffer.toString(); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGVARBINARY); tvp.addRow(value.getBytes()); @@ -246,9 +258,10 @@ public void testTVPLongVarchar_StoredProcedure() throws SQLException { for (int i = 0; i < 8001; i++) buffer.append("a"); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); - tvp.addRow(buffer.toString()); + tvp.addRow(value); final String sql = "{call " + procedureName + "(?)}"; @@ -258,7 +271,7 @@ public void testTVPLongVarchar_StoredProcedure() throws SQLException { rs = stmt.executeQuery("select * from " + charTable); while (rs.next()) - assertEquals(rs.getString(1), buffer.toString()); + assertEquals(rs.getString(1), value); if (null != P_C_statement) { P_C_statement.close(); @@ -279,7 +292,7 @@ public void testTVPLongNVarchar_StoredProcedure() throws SQLException { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 8001; i++) buffer.append("سس"); - + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); tvp.addRow(buffer.toString()); @@ -292,7 +305,7 @@ public void testTVPLongNVarchar_StoredProcedure() throws SQLException { rs = stmt.executeQuery("select * from " + charTable); while (rs.next()) - assertEquals(rs.getString(1), buffer.toString()); + assertEquals(rs.getString(1), value); if (null != P_C_statement) { P_C_statement.close(); @@ -310,7 +323,7 @@ public void testTVPXML_StoredProcedure() throws SQLException { createTVPS("xml"); createPreocedure(); - String value = "Variable E" + "Variable F" + "API" + value = "Variable E" + "Variable F" + "API" + "The following are Japanese chars." + " Some UTF-8 encoded characters: �������"; @@ -346,7 +359,7 @@ public void testTVPText_StoredProcedure() throws SQLException { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 9000; i++) buffer.append("a"); - String value = buffer.toString(); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGVARCHAR); @@ -380,7 +393,7 @@ public void testTVPNText_StoredProcedure() throws SQLException { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 9000; i++) buffer.append("س"); - String value = buffer.toString(); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGNVARCHAR); @@ -414,7 +427,7 @@ public void testTVPImage_StoredProcedure() throws SQLException { StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 9000; i++) buffer.append("a"); - String value = buffer.toString(); + value = buffer.toString(); tvp = new SQLServerDataTable(); tvp.addColumnMetadata("c1", java.sql.Types.LONGVARBINARY); From 5bbc9706357fab5ee70c467fb4e2ea05c825092c Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 26 Apr 2017 15:05:41 -0700 Subject: [PATCH 63/72] throw exception if no metadata is retrieved for stored procedure --- .../sqlserver/jdbc/SQLServerParameterMetaData.java | 11 +++++++++++ .../microsoft/sqlserver/jdbc/SQLServerResource.java | 1 + 2 files changed, 12 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java index 79d4afa57..6a6dc2474 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java @@ -581,6 +581,17 @@ private void checkClosed() throws SQLServerException { rsProcedureMeta = s.executeQueryInternal("exec sp_sproc_columns_100 " + sProc + " @ODBCVer=3"); else rsProcedureMeta = s.executeQueryInternal("exec sp_sproc_columns " + sProc + " @ODBCVer=3"); + + // if rsProcedureMeta has no next row, it means the stored procedure is not found + if (!rsProcedureMeta.next()) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_StoredProcedureNotFound")); + Object[] msgArgs = {st.procedureName}; + SQLServerException.makeFromDriverError(con, rsProcedureMeta, form.format(msgArgs), null, false); + } + else { + rsProcedureMeta.beforeFirst(); + } + // Sixth is DATA_TYPE rsProcedureMeta.getColumn(6).setFilter(new DataTypeFilter()); if (con.isKatmaiOrLater()) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 980c30bbf..67766de09 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -381,5 +381,6 @@ protected Object[][] getContents() { {"R_serverPreparedStatementDiscardThreshold", "The serverPreparedStatementDiscardThreshold {0} is not valid."}, {"R_kerberosLoginFailedForUsername", "Cannot login with Kerberos principal {0}, check your credentials. {1}"}, {"R_kerberosLoginFailed", "Kerberos Login failed: {0} due to {1} ({2})"}, + {"R_StoredProcedureNotFound", "Could not find stored procedure ''{0}''."}, }; } From 3e3531cccdbe3f387cb80c6e329e2508e1601e35 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 26 Apr 2017 15:32:08 -0700 Subject: [PATCH 64/72] added tests --- .../ParameterMetaDataTest.java | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java index c4e8bd0d9..371e1185c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java @@ -16,11 +16,13 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; +import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Utils; @@ -29,7 +31,7 @@ @RunWith(JUnitPlatform.class) public class ParameterMetaDataTest extends AbstractTest { private static final String tableName = "[" + RandomUtil.getIdentifier("StatementParam") + "]"; - + /** * Test ParameterMetaData#isWrapperFor and ParameterMetaData#unwrap. * @@ -37,19 +39,19 @@ public class ParameterMetaDataTest extends AbstractTest { */ @Test public void testParameterMetaDataWrapper() throws SQLException { - try (Connection con = DriverManager.getConnection(connectionString); - Statement stmt = con.createStatement()) { + try (Connection con = DriverManager.getConnection(connectionString); Statement stmt = con.createStatement()) { stmt.executeUpdate("create table " + tableName + " (col1 int identity(1,1) primary key)"); try { String query = "SELECT * from " + tableName + " where col1 = ?"; - + try (PreparedStatement pstmt = con.prepareStatement(query)) { ParameterMetaData parameterMetaData = pstmt.getParameterMetaData(); assertTrue(parameterMetaData.isWrapperFor(ParameterMetaData.class)); assertSame(parameterMetaData, parameterMetaData.unwrap(ParameterMetaData.class)); } - } finally { + } + finally { Utils.dropTableIfExists(tableName, stmt); } @@ -73,4 +75,30 @@ public void testSQLServerExceptionNotWrapped() throws SQLException { "SQLServerException should not be wrapped by another SQLServerException."); } } + + /** + * Test exception when invalid stored procedure name is used. + * + * @throws Exception + */ + @Test + public void testExceptionWithInvalidStoredProcedureName() throws Exception { + String randomProcedureName = UUID.randomUUID().toString(); + final String sql = "{call [" + randomProcedureName + "] (?)}"; + + try (Connection con = DriverManager.getConnection(connectionString); + SQLServerCallableStatement Cstmt = (SQLServerCallableStatement) con.prepareCall(sql);) { + Cstmt.getParameterMetaData(); + + throw new Exception("Expected Exception for invalied stored procedure name is not thrown."); + } + catch (Exception e) { + if (e instanceof SQLServerException) { + assertTrue(e.getMessage().contains("Could not find stored procedure")); + } + else { + throw e; + } + } + } } From 6a8993667ef4830e6e57929f75ec4ed9c8a56c0c Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 26 Apr 2017 16:27:05 -0700 Subject: [PATCH 65/72] make it works for TVP only --- .../jdbc/SQLServerParameterMetaData.java | 13 +++++---- .../jdbc/SQLServerPreparedStatement.java | 7 +++++ .../ParameterMetaDataTest.java | 28 ------------------- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java index 6a6dc2474..c0e26d646 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java @@ -41,6 +41,8 @@ public final class SQLServerParameterMetaData implements ParameterMetaData { /* Used for callable statement meta data */ private Statement stmtCall; private SQLServerResultSet rsProcedureMeta; + + protected boolean procedureIsFound = false; static final private java.util.logging.Logger logger = java.util.logging.Logger .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerParameterMetaData"); @@ -582,15 +584,14 @@ private void checkClosed() throws SQLServerException { else rsProcedureMeta = s.executeQueryInternal("exec sp_sproc_columns " + sProc + " @ODBCVer=3"); - // if rsProcedureMeta has no next row, it means the stored procedure is not found - if (!rsProcedureMeta.next()) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_StoredProcedureNotFound")); - Object[] msgArgs = {st.procedureName}; - SQLServerException.makeFromDriverError(con, rsProcedureMeta, form.format(msgArgs), null, false); + // if rsProcedureMeta has next row, it means the stored procedure is found + if (rsProcedureMeta.next()) { + procedureIsFound = true; } else { - rsProcedureMeta.beforeFirst(); + procedureIsFound = false; } + rsProcedureMeta.beforeFirst(); // Sixth is DATA_TYPE rsProcedureMeta.getColumn(6).setFilter(new DataTypeFilter()); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index b1dff7c4d..ba7f093ac 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -2199,6 +2199,13 @@ String getTVPNameIfNull(int n, if(null != this.procedureName) { SQLServerParameterMetaData pmd = (SQLServerParameterMetaData) this.getParameterMetaData(); pmd.isTVP = true; + + if (!pmd.procedureIsFound) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_StoredProcedureNotFound")); + Object[] msgArgs = {this.procedureName}; + SQLServerException.makeFromDriverError(connection, pmd, form.format(msgArgs), null, false); + } + try { String tvpNameWithoutSchema = pmd.getParameterTypeName(n); String tvpSchema = pmd.getTVPSchemaFromStoredProcedure(n); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java index 371e1185c..bfc421571 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java @@ -16,13 +16,11 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; -import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; -import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.testframework.AbstractTest; import com.microsoft.sqlserver.testframework.Utils; @@ -75,30 +73,4 @@ public void testSQLServerExceptionNotWrapped() throws SQLException { "SQLServerException should not be wrapped by another SQLServerException."); } } - - /** - * Test exception when invalid stored procedure name is used. - * - * @throws Exception - */ - @Test - public void testExceptionWithInvalidStoredProcedureName() throws Exception { - String randomProcedureName = UUID.randomUUID().toString(); - final String sql = "{call [" + randomProcedureName + "] (?)}"; - - try (Connection con = DriverManager.getConnection(connectionString); - SQLServerCallableStatement Cstmt = (SQLServerCallableStatement) con.prepareCall(sql);) { - Cstmt.getParameterMetaData(); - - throw new Exception("Expected Exception for invalied stored procedure name is not thrown."); - } - catch (Exception e) { - if (e instanceof SQLServerException) { - assertTrue(e.getMessage().contains("Could not find stored procedure")); - } - else { - throw e; - } - } - } } From 0ff590042bdf95559427d26be7bd7f9338f09608 Mon Sep 17 00:00:00 2001 From: v-xiangs Date: Wed, 26 Apr 2017 16:44:34 -0700 Subject: [PATCH 66/72] added test in TVP test suite --- .../sqlserver/jdbc/tvp/TVPIssuesTest.java | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java index 6da95f6c3..78294fc80 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/tvp/TVPIssuesTest.java @@ -8,6 +8,7 @@ package com.microsoft.sqlserver.jdbc.tvp; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.sql.Connection; @@ -22,6 +23,8 @@ import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; +import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; +import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; import com.microsoft.sqlserver.jdbc.SQLServerStatement; import com.microsoft.sqlserver.testframework.AbstractTest; @@ -32,9 +35,10 @@ public class TVPIssuesTest extends AbstractTest { static Connection connection = null; static Statement stmt = null; - private static String tvpName = "tryTVP_RS_varcharMax_4001_Issue"; - private static String srcTable = "tryTVP_RS_varcharMax_4001_Issue_src"; - private static String desTable = "tryTVP_RS_varcharMax_4001_Issue_dest"; + private static String tvpName = "TVPIssuesTest_TVP"; + private static String procedureName = "TVPIssuesTest_SP"; + private static String srcTable = "TVPIssuesTest_src"; + private static String desTable = "TVPIssuesTest_dest"; @Test public void tryTVP_RS_varcharMax_4001_Issue() throws Exception { @@ -52,6 +56,34 @@ public void tryTVP_RS_varcharMax_4001_Issue() throws Exception { testDestinationTable(); } + /** + * Test exception when invalid stored procedure name is used. + * + * @throws Exception + */ + @Test + public void testExceptionWithInvalidStoredProcedureName() throws Exception { + SQLServerStatement st = (SQLServerStatement) connection.createStatement(); + ResultSet rs = st.executeQuery("select * from " + srcTable); + + dropProcedure(); + + final String sql = "{call " + procedureName + "(?)}"; + SQLServerCallableStatement Cstmt = (SQLServerCallableStatement) connection.prepareCall(sql); + try { + Cstmt.setObject(1, rs); + throw new Exception("Expected Exception for invalied stored procedure name is not thrown."); + } + catch (Exception e) { + if (e instanceof SQLServerException) { + assertTrue(e.getMessage().contains("Could not find stored procedure"), "Invalid Error Message."); + } + else { + throw e; + } + } + } + private void testDestinationTable() throws SQLException, IOException { ResultSet rs = connection.createStatement().executeQuery("select * from " + desTable); while (rs.next()) { @@ -83,6 +115,8 @@ public static void beforeAll() throws SQLException { connection = DriverManager.getConnection(connectionString); stmt = connection.createStatement(); + dropProcedure(); + stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); Utils.dropTableIfExists(srcTable, stmt); Utils.dropTableIfExists(desTable, stmt); @@ -96,11 +130,25 @@ public static void beforeAll() throws SQLException { String TVPCreateCmd = "CREATE TYPE " + tvpName + " as table (c1 varchar(max) null)"; stmt.executeUpdate(TVPCreateCmd); + createPreocedure(); + populateSourceTable(); } + private static void dropProcedure() throws SQLException { + Utils.dropProcedureIfExists(procedureName, stmt); + } + + private static void createPreocedure() throws SQLException { + String sql = "CREATE PROCEDURE " + procedureName + " @InputData " + tvpName + " READONLY " + " AS " + " BEGIN " + " INSERT INTO " + desTable + + " SELECT * FROM @InputData" + " END"; + + stmt.execute(sql); + } + @AfterAll public static void terminateVariation() throws SQLException { + dropProcedure(); stmt.executeUpdate("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName); Utils.dropTableIfExists(srcTable, stmt); Utils.dropTableIfExists(desTable, stmt); From 29b35efb7cf72c4c87e347cd690307a02a8cc97e Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Thu, 27 Apr 2017 15:36:48 -0700 Subject: [PATCH 67/72] version update --- CHANGELOG.md | 17 ++++++++++++++++- pom.xml | 2 +- .../sqlserver/jdbc/SQLJdbcVersion.java | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abcb36658..9aa431806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) -## [Unreleased] +## [6.1.7] ### Added +- Added support for data type LONGVARCHAR, LONGNVARCHAR, LONGVARBINARY and SQLXML in TVP [#259](https://github.com/Microsoft/mssql-jdbc/pull/259) +- Added new connection property to accept custom JASS configuration for Kerberos [#254](https://github.com/Microsoft/mssql-jdbc/pull/254) +- Added support for server cursor with TVP [#234](https://github.com/Microsoft/mssql-jdbc/pull/234) +- Added new connection property to support network timeout [#253](https://github.com/Microsoft/mssql-jdbc/pull/253) +- Added support to authenticate Kerberos with principal and password [#163](https://github.com/Microsoft/mssql-jdbc/pull/163) +- Added temporal types to BulkCopyCSVTestInput.csv [#262](https://github.com/Microsoft/mssql-jdbc/pull/262) +- Added automatic detection of REALM in SPN needed for Cross Domain authentication [#40](https://github.com/Microsoft/mssql-jdbc/pull/40) ### Changed +- Updated minor semantics [#232] (https://github.com/Microsoft/mssql-jdbc/pull/232) +- Cleaned up Azure Active Directory (AAD) Authentication methods [#256](https://github.com/Microsoft/mssql-jdbc/pull/256) +- Updated permission check before setting network timeout [#255] (https://github.com/Microsoft/mssql-jdbc/pull/255) ### Fixed Issues +- Turn TNIR (TransparentNetworkIPResolution) off for Azure Active Directory (AAD) Authentication and changed TNIR multipliers [#240] (https://github.com/Microsoft/mssql-jdbc/pull/240) +- Wrapped ClassCastException in BulkCopy with SQLServerException [#260] (https://github.com/Microsoft/mssql-jdbc/pull/260) +- Initialized the XA transaction manager for each XAResource [#257] (https://github.com/Microsoft/mssql-jdbc/pull/257) +- Fixed BigDecimal scale rounding issue in BulkCopy [#230] (https://github.com/Microsoft/mssql-jdbc/issues/230) +- Fixed the invalid exception thrown when stored procedure does not exist is used with TVP [#265] (https://github.com/Microsoft/mssql-jdbc/pull/265) ## [6.1.6] diff --git a/pom.xml b/pom.xml index 8dfd2d078..6e04bd903 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.microsoft.sqlserver mssql-jdbc - 6.1.7-SNAPSHOT + 6.1.7 jar diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java index c9b35aed5..495b2cc2d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLJdbcVersion.java @@ -11,6 +11,6 @@ final class SQLJdbcVersion { static final int major = 6; static final int minor = 1; - static final int patch = 6; + static final int patch = 7; static final int build = 0; } From de5363a94be3fc70c806cf0bc3162f8953997f7a Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Thu, 27 Apr 2017 16:40:41 -0700 Subject: [PATCH 68/72] correcting typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa431806..e504cd96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) ## [6.1.7] ### Added - Added support for data type LONGVARCHAR, LONGNVARCHAR, LONGVARBINARY and SQLXML in TVP [#259](https://github.com/Microsoft/mssql-jdbc/pull/259) -- Added new connection property to accept custom JASS configuration for Kerberos [#254](https://github.com/Microsoft/mssql-jdbc/pull/254) +- Added new connection property to accept custom JAAS configuration for Kerberos [#254](https://github.com/Microsoft/mssql-jdbc/pull/254) - Added support for server cursor with TVP [#234](https://github.com/Microsoft/mssql-jdbc/pull/234) - Added new connection property to support network timeout [#253](https://github.com/Microsoft/mssql-jdbc/pull/253) - Added support to authenticate Kerberos with principal and password [#163](https://github.com/Microsoft/mssql-jdbc/pull/163) From a21ece6722dab12881789d8f195b2222facc403b Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Fri, 28 Apr 2017 15:52:00 -0700 Subject: [PATCH 69/72] updates to CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e504cd96e..e0dd1aa27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) - Added support for data type LONGVARCHAR, LONGNVARCHAR, LONGVARBINARY and SQLXML in TVP [#259](https://github.com/Microsoft/mssql-jdbc/pull/259) - Added new connection property to accept custom JAAS configuration for Kerberos [#254](https://github.com/Microsoft/mssql-jdbc/pull/254) - Added support for server cursor with TVP [#234](https://github.com/Microsoft/mssql-jdbc/pull/234) -- Added new connection property to support network timeout [#253](https://github.com/Microsoft/mssql-jdbc/pull/253) +- Experimental Feature: Added new connection property to support network timeout [#253](https://github.com/Microsoft/mssql-jdbc/pull/253) - Added support to authenticate Kerberos with principal and password [#163](https://github.com/Microsoft/mssql-jdbc/pull/163) - Added temporal types to BulkCopyCSVTestInput.csv [#262](https://github.com/Microsoft/mssql-jdbc/pull/262) - Added automatic detection of REALM in SPN needed for Cross Domain authentication [#40](https://github.com/Microsoft/mssql-jdbc/pull/40) From 7d04cba53d3ac8fd64cfc00b06353cfc1cf95b3d Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Fri, 28 Apr 2017 16:07:35 -0700 Subject: [PATCH 70/72] updates to README --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index aac0e7c0f..dcf979486 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,11 @@ We hope you enjoy using the Microsoft JDBC Driver for SQL Server. SQL Server Team +## Take our survey + +Let us know how you think we're doing. + + ## Status of Most Recent Builds | AppVeyor (Windows) | Travis CI (Linux) | |--------------------------|--------------------------| @@ -30,13 +35,13 @@ What's coming next? We will look into adding a more comprehensive set of tests, ## Build ### Prerequisites * Java 8 -* [Maven](http://maven.apache.org/download.cgi) or [Gradle](https://gradle.org/gradle-download/) +* [Maven](http://maven.apache.org/download.cgi) * An instance of SQL Server or Azure SQL Database that you can connect to. ### Build the JAR files -Maven and Gradle builds automatically trigger a set of verification tests to run. For these tests to pass, you will first need to add an environment variable in your system called `mssql_jdbc_test_connection_properties` to provide the [correct connection properties](https://msdn.microsoft.com/en-us/library/ms378428(v=sql.110).aspx) for your SQL Server or Azure SQL Database instance. +Maven builds automatically trigger a set of verification tests to run. For these tests to pass, you will first need to add an environment variable in your system called `mssql_jdbc_test_connection_properties` to provide the [correct connection properties](https://msdn.microsoft.com/en-us/library/ms378428(v=sql.110).aspx) for your SQL Server or Azure SQL Database instance. -To build the jar files, you must use Java 8 with either Maven or Gradle. You can choose to build a JDBC 4.1 compliant jar file (for use with JRE 7) and/or a JDBC 4.2 compliant jar file (for use with JRE 8). +To build the jar files, you must use Java 8 with Maven. You can choose to build a JDBC 4.1 compliant jar file (for use with JRE 7) and/or a JDBC 4.2 compliant jar file (for use with JRE 8). * Maven: 1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance. @@ -44,6 +49,8 @@ To build the jar files, you must use Java 8 with either Maven or Gradle. You ca * Run `mvn install -Pbuild41`. This creates JDBC 4.1 compliant jar in \target directory * Run `mvn install -Pbuild42`. This creates JDBC 4.2 compliant jar in \target directory +**NOTE**: Beginning release v6.1.7, we will no longer be maintaining the existing [Gradle build script](build.gradle) and it will be left in the repository for reference. Please refer to issue [#62](https://github.com/Microsoft/mssql-jdbc/issues/62) for this decision. + * Gradle: 1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance. 2. Run one of the commands below to build a JDBC 4.1 compliant jar or JDBC 4.2 compliant jar in the \build\libs directory. @@ -65,18 +72,27 @@ For some features (e.g. Integrated Authentication and Distributed Transactions), Don't want to compile anything? We're now on the Maven Central Repository. Add the following to your POM file: - -``` +```xml com.microsoft.sqlserver mssql-jdbc 6.1.0.jre8 ``` +The driver can be downloaded from the [Microsoft Download Center](https://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=11774). + +To get the latest preview version of the driver, add the following to your POM file: +```xml + + com.microsoft.sqlserver + mssql-jdbc + 6.1.7.jre8-preview + +``` -The driver can be downloaded from the [Microsoft Download Center](https://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=11774) -##Dependencies + +## Dependencies This project has following dependencies: Compile Time: @@ -86,7 +102,7 @@ Compile Time: Test Time: - `junit:jar` : For Unit Test cases. -###Dependency Tree +### Dependency Tree One can see all dependencies including Transitive Dependency by executing following command. ``` mvn dependency:tree @@ -96,7 +112,7 @@ mvn dependency:tree Projects that require either of the two features need to explicitly declare the dependency in their pom file. ***For Example:*** If you are using *Azure Key Vault feature* then you need to redeclare *azure-keyvault* dependency in your project's pom file. Please see the following snippet: -``` +```xml com.microsoft.sqlserver mssql-jdbc @@ -122,7 +138,7 @@ We appreciate you taking the time to test the driver, provide feedback and repor - Report each issue as a new issue (but check first if it's already been reported) - Try to be detailed in your report. Useful information for good bug reports include: * What you are seeing and what the expected behaviour is - * Which jar file? + * Which jar file? * Environment details: e.g. Java version, client operating system? * Table schema (for some issues the data types make a big difference!) * Any other relevant information you want to share @@ -133,11 +149,25 @@ Thank you! ### Reporting security issues and security bugs Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) [secure@microsoft.com](mailto:secure@microsoft.com). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://technet.microsoft.com/en-us/security/ff852094.aspx). +## Contributors +Special thanks to everyone who has contributed to the project. -## License -The Microsoft JDBC Driver for SQL Server is licensed under the MIT license. See the [LICENSE](https://github.com/Microsoft/mssql-jdbc/blob/master/LICENSE) file for more details. +Up-to-date list of contributors: https://github.com/Microsoft/mssql-jdbc/graphs/contributors +- marschall (Philippe Marschall) +- pierresouchay (Pierre Souchay) +- gordthompson (Gord Thompson) +- gstojsic +- cosmofrit +- JamieMagee (Jamie Magee) +- mfriesen (Mike Friesen) +- tonytamwk +- sehrope (Sehrope Sarkuni) +- jacobovazquez +- brettwooldridge (Brett Wooldridge) +## License +The Microsoft JDBC Driver for SQL Server is licensed under the MIT license. See the [LICENSE](https://github.com/Microsoft/mssql-jdbc/blob/master/LICENSE) file for more details. ## Code of conduct This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. From bb1db47b036997c148e939cdb6e3a35df0ecef24 Mon Sep 17 00:00:00 2001 From: Andrea Lam Date: Fri, 28 Apr 2017 16:11:11 -0700 Subject: [PATCH 71/72] Fix URLs in CHANGELOG --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0dd1aa27..052c9b149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,16 +14,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) - Added automatic detection of REALM in SPN needed for Cross Domain authentication [#40](https://github.com/Microsoft/mssql-jdbc/pull/40) ### Changed -- Updated minor semantics [#232] (https://github.com/Microsoft/mssql-jdbc/pull/232) +- Updated minor semantics [#232](https://github.com/Microsoft/mssql-jdbc/pull/232) - Cleaned up Azure Active Directory (AAD) Authentication methods [#256](https://github.com/Microsoft/mssql-jdbc/pull/256) -- Updated permission check before setting network timeout [#255] (https://github.com/Microsoft/mssql-jdbc/pull/255) +- Updated permission check before setting network timeout [#255](https://github.com/Microsoft/mssql-jdbc/pull/255) ### Fixed Issues -- Turn TNIR (TransparentNetworkIPResolution) off for Azure Active Directory (AAD) Authentication and changed TNIR multipliers [#240] (https://github.com/Microsoft/mssql-jdbc/pull/240) -- Wrapped ClassCastException in BulkCopy with SQLServerException [#260] (https://github.com/Microsoft/mssql-jdbc/pull/260) -- Initialized the XA transaction manager for each XAResource [#257] (https://github.com/Microsoft/mssql-jdbc/pull/257) -- Fixed BigDecimal scale rounding issue in BulkCopy [#230] (https://github.com/Microsoft/mssql-jdbc/issues/230) -- Fixed the invalid exception thrown when stored procedure does not exist is used with TVP [#265] (https://github.com/Microsoft/mssql-jdbc/pull/265) +- Turn TNIR (TransparentNetworkIPResolution) off for Azure Active Directory (AAD) Authentication and changed TNIR multipliers [#240](https://github.com/Microsoft/mssql-jdbc/pull/240) +- Wrapped ClassCastException in BulkCopy with SQLServerException [#260](https://github.com/Microsoft/mssql-jdbc/pull/260) +- Initialized the XA transaction manager for each XAResource [#257](https://github.com/Microsoft/mssql-jdbc/pull/257) +- Fixed BigDecimal scale rounding issue in BulkCopy [#230](https://github.com/Microsoft/mssql-jdbc/issues/230) +- Fixed the invalid exception thrown when stored procedure does not exist is used with TVP [#265](https://github.com/Microsoft/mssql-jdbc/pull/265) ## [6.1.6] From f887dede7d635b946d1f10fa3d7bb91cdf1b477b Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Mon, 1 May 2017 11:16:04 -0700 Subject: [PATCH 72/72] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcf979486..927e1455f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ SQL Server Team ## Take our survey Let us know how you think we're doing. - + ## Status of Most Recent Builds | AppVeyor (Windows) | Travis CI (Linux) |