Skip to content

Commit b3c2905

Browse files
committed
Merge branch 'dev/divang/sybase-migration-dynamic-prepare' of https://github.com/microsoft/mssql-jdbc into dev/divang/sybase-migration-dynamic-prepare
2 parents c4fe035 + a35c878 commit b3c2905

28 files changed

+863
-65
lines changed

build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ dependencies {
209209
'org.mockito:mockito-inline:4.11.0',
210210
'net.bytebuddy:byte-buddy:1.15.11',
211211
'net.bytebuddy:byte-buddy-agent:1.15.11'
212+
} else if (hasProperty('buildProfile') && buildProfile == "jre25") {
213+
// JRE 25 profile
214+
testImplementation 'org.mockito:mockito-core:5.20.0',
215+
'org.mockito:mockito-inline:5.20.0',
216+
'net.bytebuddy:byte-buddy:1.18.2',
217+
'net.bytebuddy:byte-buddy-agent:1.18.2'
212218
} else {
213219
// JRE 11, 17, 21, 25 profiles (all use same versions)
214220
testImplementation 'org.mockito:mockito-core:5.14.2',

pom.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
xSQLv14 - - - - - - For tests not compatible with SQL Server 2016 - 2017
4040
xSQLv15 - - - - - - For tests not compatible with SQL Server 2019 - - - -
4141
xSQLv16 - - - - - - For tests not compatible with SQL Server 2022 - - - -
42+
xSQLv17 - - - - - - For tests not compatible with SQL Server 2025 - - - -
4243
xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - -
4344
xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse -
4445
xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance
@@ -496,19 +497,19 @@
496497
<dependency>
497498
<groupId>org.mockito</groupId>
498499
<artifactId>mockito-core</artifactId>
499-
<version>5.14.2</version>
500+
<version>5.20.0</version>
500501
<scope>test</scope>
501502
</dependency>
502503
<dependency>
503504
<groupId>net.bytebuddy</groupId>
504505
<artifactId>byte-buddy</artifactId>
505-
<version>1.17.5</version>
506+
<version>1.18.2</version>
506507
<scope>test</scope>
507508
</dependency>
508509
<dependency>
509510
<groupId>net.bytebuddy</groupId>
510511
<artifactId>byte-buddy-agent</artifactId>
511-
<version>1.17.5</version>
512+
<version>1.18.2</version>
512513
<scope>test</scope>
513514
</dependency>
514515
</dependencies>

src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ final class TDS {
178178
static final byte TDS_FEATURE_EXT_JSONSUPPORT = 0x0D;
179179
static final byte JSONSUPPORT_NOT_SUPPORTED = 0x00;
180180
static final byte MAX_JSONSUPPORT_VERSION = 0x01;
181+
// User agent telemetry support
182+
static final byte TDS_FEATURE_EXT_USERAGENT = 0x10;
181183

182184
static final int TDS_TVP = 0xF3;
183185
static final int TVP_ROW = 0x01;
@@ -251,7 +253,9 @@ static final String getTokenName(int tdsTokenType) {
251253
return "TDS_FEATURE_EXT_VECTORSUPPORT (0x0E)";
252254
case TDS_FEATURE_EXT_JSONSUPPORT:
253255
return "TDS_FEATURE_EXT_JSONSUPPORT (0x0D)";
254-
256+
case TDS_FEATURE_EXT_USERAGENT:
257+
return "TDS_FEATURE_EXT_USERAGENT (0x10)";
258+
255259
default:
256260
return "unknown token (0x" + Integer.toHexString(tdsTokenType).toUpperCase() + ")";
257261
}

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2092,7 +2092,7 @@ private void validateColumnMappings() throws SQLServerException {
20922092
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND,
20932093
DriverError.NOT_SET, null);
20942094
}
2095-
} else if (0 > cm.destinationColumnOrdinal || destColumnCount < cm.destinationColumnOrdinal) {
2095+
} else if (0 > cm.destinationColumnOrdinal || !destColumnMetadata.containsKey(cm.destinationColumnOrdinal)) {
20962096
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidColumn"));
20972097
Object[] msgArgs = {cm.destinationColumnOrdinal};
20982098
throw new SQLServerException(form.format(msgArgs), SQLState.COL_NOT_FOUND, DriverError.NOT_SET,

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,79 @@ public String toString() {
319319
**/
320320
private static final Lock sLock = new ReentrantLock();
321321

322+
static final String USER_AGENT_TEMPLATE = "%s|%s|%s|%s|%s|%s|%s";
323+
static final String USER_AGENT_EXT_VERSION_AND_DRIVER_NAME = "1|MS-JDBC";
324+
static final String userAgentStr;
325+
326+
static {
327+
userAgentStr = getUserAgent();
328+
}
329+
330+
static String getUserAgent() {
331+
try {
332+
return String.format(
333+
USER_AGENT_TEMPLATE,
334+
"1",
335+
"MS-JDBC",
336+
getJDBCVersion(),
337+
getArchitecture(),
338+
getOSType(),
339+
getOSDetails(),
340+
getRuntimeDetails()
341+
);
342+
} catch(Exception e) {
343+
return USER_AGENT_EXT_VERSION_AND_DRIVER_NAME;
344+
}
345+
}
346+
347+
static String getJDBCVersion() {
348+
return sanitizeField(SQLJdbcVersion.MAJOR + "." + SQLJdbcVersion.MINOR + "." + SQLJdbcVersion.PATCH + "." + SQLJdbcVersion.BUILD + SQLJdbcVersion.RELEASE_EXT, 24);
349+
}
350+
351+
static String getOSType() {
352+
String osName = System.getProperty("os.name", "Unknown").trim().toLowerCase();
353+
String osNameToReturn = "Unknown";
354+
if (osName.startsWith("windows")) {
355+
osNameToReturn = "Windows";
356+
} else if (osName.startsWith("linux")) {
357+
osNameToReturn = "Linux";
358+
} else if (osName.startsWith("mac")) {
359+
osNameToReturn = "macOS";
360+
} else if (osName.startsWith("freebsd")) {
361+
osNameToReturn = "FreeBSD";
362+
} else if (osName.startsWith("android")) {
363+
osNameToReturn = "Android";
364+
}
365+
return sanitizeField(osNameToReturn, 10);
366+
}
367+
368+
static String getArchitecture() {
369+
return sanitizeField(System.getProperty("os.arch", "Unknown").trim(), 10);
370+
}
371+
372+
static String getOSDetails() {
373+
String osName = System.getProperty("os.name", "").trim();
374+
String osVersion = System.getProperty("os.version", "").trim();
375+
if (osName.isEmpty() && osVersion.isEmpty()) {
376+
return "Unknown";
377+
}
378+
return sanitizeField(osName + " " + osVersion, 44);
379+
}
380+
381+
static String getRuntimeDetails() {
382+
String javaVmName = System.getProperty("java.vm.name", "").trim();
383+
String javaVmVersion = System.getProperty("java.vm.version", "").trim();
384+
if (javaVmName.isEmpty() && javaVmVersion.isEmpty()) {
385+
return "Unknown";
386+
}
387+
return sanitizeField(javaVmName + " " + javaVmVersion, 44);
388+
}
389+
390+
static String sanitizeField(String field, int maxLength) {
391+
String sanitized = field.replaceAll("[^A-Za-z0-9 .+_-]", "").trim();
392+
return (sanitized == null || sanitized.isEmpty()) ? "Unknown" : sanitized.substring(0, Math.min(sanitized.length(), maxLength));
393+
}
394+
322395
/**
323396
* Generate a 6 byte random array for netAddress
324397
* As per TDS spec this is a unique clientID (MAC address) used to identify the client.
@@ -5795,6 +5868,27 @@ int writeDNSCacheFeatureRequest(boolean write, /* if false just calculates the l
57955868
return len;
57965869
}
57975870

5871+
/**
5872+
* Writes the user agent telemetry feature request
5873+
* @param write
5874+
* If true, writes the feature request to the physical state object.
5875+
* @param tdsWriter
5876+
* @return
5877+
* The length of the feature request in bytes, or 0 if vectorTypeSupport is "off".
5878+
* @throws SQLServerException
5879+
*/
5880+
int writeUserAgentFeatureRequest(boolean write, /* if false just calculates the length */
5881+
TDSWriter tdsWriter) throws SQLServerException {
5882+
byte[] userAgentToSendBytes = toUCS16(userAgentStr);
5883+
int len = userAgentToSendBytes.length + 5; // 1byte = featureID, 1byte = version, 4byte = feature data length in bytes, remaining bytes: feature data
5884+
if (write) {
5885+
tdsWriter.writeByte(TDS.TDS_FEATURE_EXT_USERAGENT);
5886+
tdsWriter.writeInt(userAgentToSendBytes.length);
5887+
tdsWriter.writeBytes(userAgentToSendBytes);
5888+
}
5889+
return len;
5890+
}
5891+
57985892
/**
57995893
* Writes the Vector Support feature request to the physical state object,
58005894
* unless vectorTypeSupport is "off". The request includes the feature ID,
@@ -7049,6 +7143,14 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept
70497143
break;
70507144
}
70517145

7146+
case TDS.TDS_FEATURE_EXT_USERAGENT: {
7147+
if (connectionlogger.isLoggable(Level.FINER)) {
7148+
connectionlogger.fine(
7149+
toString() + " Received feature extension acknowledgement for User agent feature extension. Received byte: " + data[0]);
7150+
}
7151+
break;
7152+
}
7153+
70527154
default: {
70537155
// Unknown feature ack
70547156
throw new SQLServerException(SQLServerException.getErrString("R_UnknownFeatureAck"), null);
@@ -7336,6 +7438,9 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
73367438
}
73377439

73387440
int aeOffset = len;
7441+
7442+
len += writeUserAgentFeatureRequest(false, tdsWriter);
7443+
73397444
// AE is always ON
73407445
len += writeAEFeatureRequest(false, tdsWriter);
73417446
if (federatedAuthenticationInfoRequested || federatedAuthenticationRequested) {
@@ -7540,6 +7645,9 @@ final boolean complete(LogonCommand logonCommand, TDSReader tdsReader) throws SQ
75407645
tdsWriter.writeBytes(secBlob, 0, secBlob.length);
75417646
}
75427647

7648+
//Write user agent string
7649+
writeUserAgentFeatureRequest(true, tdsWriter);
7650+
75437651
// AE is always ON
75447652
writeAEFeatureRequest(true, tdsWriter);
75457653

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2943,19 +2943,24 @@ private ArrayList<String> parseUserSQLForValueListDWHelper(ArrayList<String> lis
29432943
continue;
29442944
}
29452945
if (localUserSQL.charAt(0) == ',' || localUserSQL.charAt(0) == ')') {
2946+
2947+
if (!"?".equals(sb.toString())) {
2948+
// throw IllegalArgumentException and fallback to original logic for batch insert
2949+
// Wildcards (?) are the only supported parameters for this functionality
2950+
// Does not support functions or literals (e.g. len(), 'string', 1, etc.)
2951+
throw new IllegalArgumentException(SQLServerException.getErrString("R_onlyFullParamAllowed"));
2952+
}
2953+
29462954
if (localUserSQL.charAt(0) == ',') {
29472955
localUserSQL = localUserSQL.substring(1);
2948-
if (!"?".equals(sb.toString())) {
2949-
// throw IllegalArgumentException and fallback to original logic for batch insert
2950-
throw new IllegalArgumentException(SQLServerException.getErrString("R_onlyFullParamAllowed"));
2951-
}
29522956
listOfValues.add(sb.toString());
29532957
sb.setLength(0);
29542958
} else {
29552959
localUserSQL = localUserSQL.substring(1);
29562960
listOfValues.add(sb.toString());
29572961
return listOfValues; // reached exit condition.
29582962
}
2963+
29592964
} else {
29602965
sb.append(localUserSQL.charAt(0));
29612966
localUserSQL = localUserSQL.substring(1);

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSpatialDatatype.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,11 @@ void readPointWkt() throws SQLServerException {
14841484

14851485
while (currentWktPos < wkt.length()
14861486
&& (Character.isDigit(wkt.charAt(currentWktPos)) || wkt.charAt(currentWktPos) == '.'
1487-
|| wkt.charAt(currentWktPos) == 'E' || wkt.charAt(currentWktPos) == 'e')) {
1487+
|| wkt.charAt(currentWktPos) == 'E' || wkt.charAt(currentWktPos) == 'e'
1488+
|| ((wkt.charAt(currentWktPos) == '-' || wkt.charAt(currentWktPos) == '+')
1489+
&& currentWktPos > startPos
1490+
&& (wkt.charAt(currentWktPos - 1) == 'E'
1491+
|| wkt.charAt(currentWktPos - 1) == 'e')))) {
14881492
currentWktPos++;
14891493
}
14901494

0 commit comments

Comments
 (0)