Skip to content

Commit 952e37e

Browse files
author
Divang Sharma
committed
Merge branch 'main' into user/divang/json-datatype-support
2 parents a8ebfd3 + 5bb3353 commit 952e37e

12 files changed

+216
-42
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
1313
- Changed MSAL logging from FINEST to FINER [#2489](https://github.com/microsoft/mssql-jdbc/pull/2489)
1414
- Updated project pom file to pull dependencies from public Azure Artifacts Feed [#2504](https://github.com/microsoft/mssql-jdbc/pull/2504)
1515
- Changed how Kerberos authentication acquires subject to provide compatibility for Kerberos with Java 23 and above [#2539](https://github.com/microsoft/mssql-jdbc/pull/2539)
16+
- Removed user and password check for AccessTokenCallback [#2549](https://github.com/microsoft/mssql-jdbc/pull/2549)
1617

1718
### Fixed issues
1819
- Changed driver behavior to allow prepared statement objects to be reused, preventing a "multiple queries are not allowed" error [#2482](https://github.com/microsoft/mssql-jdbc/pull/2482)
1920
- Adjusted DESTINATION_COL_METADATA_LOCK, in SQLServerBulkCopy, so that is properly released in all cases [#2484](https://github.com/microsoft/mssql-jdbc/pull/2484)
2021
- Fixed connection retry behavior when `connectRetryCount` is set to a value greater than 1 [#2513](https://github.com/microsoft/mssql-jdbc/pull/2513)
2122
- Resolved JavaDoc warnings that would appear during project build [#2521](https://github.com/microsoft/mssql-jdbc/pull/2521)
23+
- Fixed infinite loop when removing open statement [#2547](https://github.com/microsoft/mssql-jdbc/pull/2547)
2224

2325
## [12.8.0] Stable Release
2426
### Fixed issues

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313

1414
/**
15-
* Provides an interface to the {@link SQLServerConnection} and {@link SQLServerConnectionPoolProxy} classes.
15+
* Provides an interface to the {@link SQLServerConnection} class.
1616
*/
1717
public interface ISQLServerConnection extends java.sql.Connection {
1818

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

+8-11
Original file line numberDiff line numberDiff line change
@@ -2456,7 +2456,7 @@ Connection connectInternal(Properties propsIn,
24562456
if (null != sPropValue)
24572457
validateMaxSQLLoginName(sPropKey, sPropValue);
24582458
else
2459-
activeConnectionProperties.setProperty(sPropKey, SQLServerDriver.DEFAULT_APP_NAME);
2459+
activeConnectionProperties.setProperty(sPropKey, SQLServerDriver.constructedAppName);
24602460

24612461
sPropKey = SQLServerDriverBooleanProperty.LAST_UPDATE_COUNT.toString();
24622462
sPropValue = activeConnectionProperties.getProperty(sPropKey);
@@ -2812,13 +2812,6 @@ Connection connectInternal(Properties propsIn,
28122812
&& !activeConnectionProperties
28132813
.getProperty(SQLServerDriverStringProperty.ACCESS_TOKEN_CALLBACK_CLASS.toString())
28142814
.isEmpty();
2815-
if ((null != accessTokenCallback || hasAccessTokenCallbackClass) && (!activeConnectionProperties
2816-
.getProperty(SQLServerDriverStringProperty.USER.toString()).isEmpty()
2817-
|| !activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString())
2818-
.isEmpty())) {
2819-
throw new SQLServerException(
2820-
SQLServerException.getErrString("R_AccessTokenCallbackWithUserPassword"), null);
2821-
}
28222815

28232816
sPropKey = SQLServerDriverStringProperty.ACCESS_TOKEN_CALLBACK_CLASS.toString();
28242817
sPropValue = activeConnectionProperties.getProperty(sPropKey);
@@ -7641,7 +7634,7 @@ public <T> T unwrap(Class<T> iface) throws SQLException {
76417634
}
76427635

76437636
/** request started flag */
7644-
private boolean requestStarted = false;
7637+
private volatile boolean requestStarted = false;
76457638

76467639
/** original database autocommit mode */
76477640
private boolean originalDatabaseAutoCommitMode;
@@ -7783,9 +7776,13 @@ void endRequestInternal() throws SQLException {
77837776
sqlWarnings = originalSqlWarnings;
77847777
if (null != openStatements) {
77857778
while (!openStatements.isEmpty()) {
7786-
try (Statement st = openStatements.get(0)) {}
7779+
Statement st = openStatements.get(0);
7780+
try {
7781+
st.close();
7782+
} finally {
7783+
removeOpenStatement((SQLServerStatement) st);
7784+
}
77877785
}
7788-
openStatements.clear();
77897786
}
77907787
requestStarted = false;
77917788
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public void setApplicationName(String applicationName) {
170170
@Override
171171
public String getApplicationName() {
172172
return getStringProperty(connectionProps, SQLServerDriverStringProperty.APPLICATION_NAME.toString(),
173-
SQLServerDriverStringProperty.APPLICATION_NAME.getDefaultValue());
173+
SQLServerDriver.constructedAppName);
174174
}
175175

176176
/**

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

+32-1
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,32 @@ public final class SQLServerDriver implements java.sql.Driver {
731731
static final String AUTH_DLL_NAME = "mssql-jdbc_auth-" + SQLJdbcVersion.MAJOR + "." + SQLJdbcVersion.MINOR + "."
732732
+ SQLJdbcVersion.PATCH + "." + Util.getJVMArchOnWindows() + SQLJdbcVersion.RELEASE_EXT;
733733
static final String DEFAULT_APP_NAME = "Microsoft JDBC Driver for SQL Server";
734+
static final String APP_NAME_TEMPLATE = "Microsoft JDBC - %s, %s - %s";
735+
static final String constructedAppName;
736+
static {
737+
constructedAppName = getAppName();
738+
}
734739

740+
/**
741+
* Constructs the application name using system properties for OS, platform, and architecture.
742+
* If any of the properties cannot be fetched, it falls back to the default application name.
743+
* Format -> Microsoft JDBC - {OS}, {Platform} - {architecture}
744+
*
745+
* @return the constructed application name or the default application name if properties are not available
746+
*/
747+
static String getAppName() {
748+
String osName = System.getProperty("os.name", "");
749+
String osArch = System.getProperty("os.arch", "");
750+
String javaVmName = System.getProperty("java.vm.name", "");
751+
String javaVmVersion = System.getProperty("java.vm.version", "");
752+
String platform = javaVmName.isEmpty() || javaVmVersion.isEmpty() ? "" : javaVmName + " " + javaVmVersion;
753+
754+
if (osName.isEmpty() && platform.isEmpty() && osArch.isEmpty()) {
755+
return DEFAULT_APP_NAME;
756+
}
757+
return String.format(APP_NAME_TEMPLATE, osName, platform, osArch);
758+
}
759+
735760
private static final String[] TRUE_FALSE = {"true", "false"};
736761

737762
private static final SQLServerDriverPropertyInfo[] DRIVER_PROPERTIES = {
@@ -741,7 +766,7 @@ public final class SQLServerDriver implements java.sql.Driver {
741766
SQLServerDriverStringProperty.APPLICATION_INTENT.getDefaultValue(), false,
742767
new String[] {ApplicationIntent.READ_ONLY.toString(), ApplicationIntent.READ_WRITE.toString()}),
743768
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.APPLICATION_NAME.toString(),
744-
SQLServerDriverStringProperty.APPLICATION_NAME.getDefaultValue(), false, null),
769+
SQLServerDriverStringProperty.APPLICATION_NAME.getDefaultValue(), false, null),
745770
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.COLUMN_ENCRYPTION.toString(),
746771
SQLServerDriverStringProperty.COLUMN_ENCRYPTION.getDefaultValue(), false,
747772
new String[] {ColumnEncryptionSetting.DISABLED.toString(),
@@ -1028,6 +1053,9 @@ String getClassNameLogging() {
10281053
drLogger.finer("Error registering driver: " + e);
10291054
}
10301055
}
1056+
if (loggerExternal.isLoggable(Level.FINE)) {
1057+
loggerExternal.log(Level.FINE, "Application Name: " + SQLServerDriver.constructedAppName);
1058+
}
10311059
}
10321060

10331061
// Check for jdk.net.ExtendedSocketOptions to set TCP keep-alive options for idle connection resiliency
@@ -1266,6 +1294,9 @@ public java.sql.Connection connect(String url, Properties suppliedProperties) th
12661294
Properties connectProperties = parseAndMergeProperties(url, suppliedProperties);
12671295
if (connectProperties != null) {
12681296
result = DriverJDBCVersion.getSQLServerConnection(toString());
1297+
if (connectProperties.getProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString()) == null) {
1298+
connectProperties.setProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString(), SQLServerDriver.constructedAppName);
1299+
}
12691300
result.connect(connectProperties, null);
12701301
}
12711302
loggerExternal.exiting(getClassNameLogging(), "connect", result);

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -1246,16 +1246,25 @@ public final java.sql.ResultSetMetaData getMetaData() throws SQLServerException,
12461246
*/
12471247
private SQLServerResultSet buildExecuteMetaData() throws SQLServerException, SQLTimeoutException {
12481248
String fmtSQL = userSQL;
1249-
1249+
12501250
SQLServerResultSet emptyResultSet = null;
12511251
try {
1252-
fmtSQL = replaceMarkerWithNull(fmtSQL);
12531252
internalStmt = (SQLServerStatement) connection.createStatement();
12541253
emptyResultSet = internalStmt.executeQueryInternal("set fmtonly on " + fmtSQL + "\nset fmtonly off");
12551254
} catch (SQLServerException sqle) {
12561255
// Ignore empty result set errors, otherwise propagate the server error.
12571256
if (!sqle.getMessage().equals(SQLServerException.getErrString("R_noResultset"))) {
1258-
throw sqle;
1257+
//try by replacing ? characters in case that was an issue
1258+
try {
1259+
fmtSQL = replaceMarkerWithNull(fmtSQL);
1260+
internalStmt = (SQLServerStatement) connection.createStatement();
1261+
emptyResultSet = internalStmt.executeQueryInternal("set fmtonly on " + fmtSQL + "\nset fmtonly off");
1262+
} catch (SQLServerException ex) {
1263+
// Ignore empty result set errors, otherwise propagate the server error.
1264+
if (!ex.getMessage().equals(SQLServerException.getErrString("R_noResultset"))) {
1265+
throw ex;
1266+
}
1267+
}
12591268
}
12601269
}
12611270
return emptyResultSet;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ boolean onDone(TDSReader tdsReader) throws SQLServerException {
541541
/**
542542
* True is the statement is closed
543543
*/
544-
boolean bIsClosed;
544+
volatile boolean bIsClosed;
545545

546546
/**
547547
* True if the user requested to driver to generate insert keys

src/main/java/microsoft/sql/DateTimeOffset.java

+27-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package microsoft.sql;
77

8+
import java.time.OffsetDateTime;
9+
import java.time.ZoneOffset;
810
import java.util.Calendar;
911
import java.util.Locale;
1012
import java.util.TimeZone;
@@ -190,7 +192,6 @@ public String toString() {
190192
.substring(2), // -> "123456"
191193
formattedOffset);
192194
}
193-
194195
return result;
195196
}
196197

@@ -257,12 +258,32 @@ public java.sql.Timestamp getTimestamp() {
257258
* @return OffsetDateTime equivalent to this DateTimeOffset object.
258259
*/
259260
public java.time.OffsetDateTime getOffsetDateTime() {
260-
java.time.ZoneOffset zoneOffset = java.time.ZoneOffset.ofTotalSeconds(60 * minutesOffset);
261-
java.time.LocalDateTime localDateTime = java.time.LocalDateTime.ofEpochSecond(utcMillis / 1000, nanos,
262-
zoneOffset);
263-
return java.time.OffsetDateTime.of(localDateTime, zoneOffset);
261+
// Format the offset as +hh:mm or -hh:mm. Zero offset is formatted as +00:00.
262+
String formattedOffset = (minutesOffset < 0) ?
263+
String.format(Locale.US, "-%1$02d:%2$02d", -minutesOffset / 60, -minutesOffset % 60) :
264+
String.format(Locale.US, "+%1$02d:%2$02d", minutesOffset / 60, minutesOffset % 60);
265+
266+
// Create a Calendar instance with the time zone set to GMT plus the formatted offset
267+
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT" + formattedOffset), Locale.US);
268+
// Initialize the calendar with the UTC milliseconds value
269+
calendar.setTimeInMillis(utcMillis);
270+
271+
// Extract the date and time components from the calendar
272+
int year = calendar.get(Calendar.YEAR);
273+
int month = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH is zero-based
274+
int day = calendar.get(Calendar.DAY_OF_MONTH);
275+
int hour = calendar.get(Calendar.HOUR_OF_DAY);
276+
int minute = calendar.get(Calendar.MINUTE);
277+
int second = calendar.get(Calendar.SECOND);
278+
279+
// Create the ZoneOffset from the minutesOffset
280+
ZoneOffset offset = ZoneOffset.ofTotalSeconds(minutesOffset * 60);
281+
282+
// Create and return the OffsetDateTime
283+
return OffsetDateTime.of(year, month, day, hour, minute, second, nanos, offset);
264284
}
265-
285+
286+
266287
/**
267288
* Returns this DateTimeOffset object's offset value.
268289
*

src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java

+76
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static org.junit.Assert.fail;
44
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertFalse;
6+
import static org.junit.jupiter.api.Assertions.assertNotNull;
57
import static org.junit.jupiter.api.Assertions.assertTrue;
68

79
import java.sql.Connection;
@@ -190,4 +192,78 @@ public void testConnectionDriver() throws SQLException {
190192
}
191193
}
192194
}
195+
196+
/**
197+
* test application name
198+
*
199+
* @throws SQLException
200+
*/
201+
@Test
202+
public void testApplicationName() throws SQLException {
203+
try (Connection conn = DriverManager.getConnection(connectionString);
204+
Statement stmt = conn.createStatement();
205+
ResultSet rs = stmt.executeQuery("SELECT program_name FROM sys.dm_exec_sessions WHERE session_id = @@SPID")) {
206+
if (rs.next()) {
207+
assertEquals(SQLServerDriver.constructedAppName, rs.getString("program_name"));
208+
}
209+
} catch (SQLException e) {
210+
fail(e.getMessage());
211+
}
212+
}
213+
214+
/**
215+
* test application name by executing select app_name()
216+
*
217+
* @throws SQLException
218+
*/
219+
@Test
220+
public void testApplicationNameUsingApp_Name() throws SQLException {
221+
try (Connection conn = DriverManager.getConnection(connectionString);
222+
Statement stmt = conn.createStatement();
223+
ResultSet rs = stmt.executeQuery("SELECT app_name()")) {
224+
if (rs.next()) {
225+
assertEquals(SQLServerDriver.constructedAppName, rs.getString(1));
226+
}
227+
} catch (SQLException e) {
228+
fail(e.getMessage());
229+
}
230+
}
231+
232+
/**
233+
* test application name by executing select app_name()
234+
*
235+
* @throws SQLException
236+
*/
237+
@Test
238+
public void testAppNameWithSpecifiedApplicationName() throws SQLException {
239+
String url = connectionString + ";applicationName={0123456789012345678901234567890123456789012345678901234567890123456789012345678901234589012345678901234567890123456789012345678}";
240+
241+
try (Connection conn = DriverManager.getConnection(url);
242+
Statement stmt = conn.createStatement();
243+
ResultSet rs = stmt.executeQuery("SELECT app_name()")) {
244+
if (rs.next()) {
245+
assertEquals("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234589012345678901234567890123456789012345678", rs.getString(1));
246+
}
247+
} catch (SQLException e) {
248+
fail(e.getMessage());
249+
}
250+
}
251+
252+
/**
253+
* test application name when system properties are empty
254+
*
255+
*/
256+
@Test
257+
public void testGetAppName() {
258+
String appName = SQLServerDriver.getAppName();
259+
assertNotNull(appName, "Application name should not be null");
260+
assertFalse(appName.isEmpty(), "Application name should not be empty");
261+
262+
System.setProperty("os.name", "");
263+
System.setProperty("os.arch", "");
264+
System.setProperty("java.vm.name", "");
265+
System.setProperty("java.vm.version", "");
266+
String defaultAppName = SQLServerDriver.getAppName();
267+
assertEquals(SQLServerDriver.DEFAULT_APP_NAME, defaultAppName, "Application name should be the default one");
268+
}
193269
}

src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -1945,6 +1945,34 @@ public void testDateTimeOffsetValueOfOffsetDateTime() throws Exception {
19451945
assertEquals(expected, DateTimeOffset.valueOf(roundUp).getOffsetDateTime());
19461946
assertEquals(expected, DateTimeOffset.valueOf(roundDown).getOffsetDateTime());
19471947
}
1948+
1949+
@Test
1950+
public void testPreGregorianDateTime() throws Exception {
1951+
try (Connection conn = getConnection();
1952+
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);) {
1953+
1954+
conn.setAutoCommit(false);
1955+
TestUtils.dropTableIfExists(escapedTableName, stmt);
1956+
1957+
stmt.executeUpdate("CREATE TABLE " + escapedTableName + " (dob datetimeoffset(7) null)");
1958+
stmt.executeUpdate("INSERT INTO " + escapedTableName + " VALUES ('1500-12-16 00:00:00.0000000+08:00')");
1959+
stmt.executeUpdate("INSERT INTO " + escapedTableName + " VALUES ('1400-09-27 09:30:00.0000000+08:00')");
1960+
stmt.executeUpdate("INSERT INTO " + escapedTableName + " VALUES ('2024-12-16 23:40:00.0000000+08:00')");
1961+
1962+
try (ResultSet rs = stmt.executeQuery("select dob from " + escapedTableName + " order by dob")) {
1963+
while (rs.next()) {
1964+
String strDateTimeOffset = rs.getString(1).substring(0, 10);
1965+
DateTimeOffset objDateTimeOffset = (DateTimeOffset) rs.getObject(1);
1966+
OffsetDateTime objOffsetDateTime = objDateTimeOffset.getOffsetDateTime();
1967+
1968+
String strOffsetDateTime = objOffsetDateTime.toString().substring(0, 10);
1969+
assertEquals(strDateTimeOffset, strOffsetDateTime, "Mismatch found in DateTimeOffset : "
1970+
+ objDateTimeOffset + " and OffsetDateTime : " + objOffsetDateTime);
1971+
}
1972+
}
1973+
TestUtils.dropTableIfExists(escapedTableName, stmt);
1974+
}
1975+
}
19481976

19491977
static LocalDateTime getUnstorableValue() throws Exception {
19501978
ZoneId systemTimezone = ZoneId.systemDefault();

src/test/java/com/microsoft/sqlserver/jdbc/fedauth/PooledConnectionTest.java

+6-17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static org.junit.Assert.assertEquals;
99
import static org.junit.Assert.assertTrue;
1010
import static org.junit.jupiter.api.Assertions.fail;
11+
import static org.junit.jupiter.api.Assertions.assertNotNull;
1112

1213
import java.lang.reflect.Field;
1314
import java.sql.Connection;
@@ -435,27 +436,15 @@ public void testDSPooledConnectionAccessTokenCallbackClassExceptions() throws Ex
435436

436437
// User/password is not required for access token callback
437438
AbstractTest.updateDataSource(accessTokenCallbackConnectionString, ds);
439+
438440
ds.setAccessTokenCallbackClass(AccessTokenCallbackClass.class.getName());
439441
ds.setUser("user");
440-
SQLServerPooledConnection pc;
441-
442-
// Should fail with user set
443-
try {
444-
pc = (SQLServerPooledConnection) ds.getPooledConnection();
445-
fail(TestResource.getResource("R_expectedFailPassed"));
446-
} catch (SQLServerException e) {
447-
assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_AccessTokenCallbackWithUserPassword")));
448-
}
449-
450-
ds.setUser("");
451442
ds.setPassword(UUID.randomUUID().toString());
443+
SQLServerPooledConnection pc;
452444

453-
// Should fail with password set
454-
try {
455-
pc = (SQLServerPooledConnection) ds.getPooledConnection();
456-
fail(TestResource.getResource("R_expectedFailPassed"));
457-
} catch (SQLServerException e) {
458-
assertTrue(e.getMessage().matches(TestUtils.formatErrorMsg("R_AccessTokenCallbackWithUserPassword")));
445+
pc = (SQLServerPooledConnection) ds.getPooledConnection();
446+
try (Connection conn1 = pc.getConnection()) {
447+
assertNotNull(conn1);
459448
}
460449

461450
// Should fail with invalid accessTokenCallbackClass value

0 commit comments

Comments
 (0)