From 420b5839dd294fda984aa68d3dd59db5e829b0b8 Mon Sep 17 00:00:00 2001 From: Ananya Garg Date: Tue, 17 Dec 2024 16:50:57 +0530 Subject: [PATCH 1/9] Updated getIndexInfo() to include Columnstore indexes by using custom query. --- .../jdbc/SQLServerDatabaseMetaData.java | 46 ++--- .../DatabaseMetadataGetIndexInfoTest.java | 174 ++++++++++++++++++ 2 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java index 12cc843b7..e80ddea1d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java @@ -1197,39 +1197,31 @@ private ResultSet executeSPFkeys(String[] procParams) throws SQLException { } } - private static final String[] getIndexInfoColumnNames = { /* 1 */ TABLE_CAT, /* 2 */ TABLE_SCHEM, - /* 3 */ TABLE_NAME, /* 4 */ NON_UNIQUE, /* 5 */ INDEX_QUALIFIER, /* 6 */ INDEX_NAME, /* 7 */ TYPE, - /* 8 */ ORDINAL_POSITION, /* 9 */ COLUMN_NAME, /* 10 */ ASC_OR_DESC, /* 11 */ CARDINALITY, /* 12 */ PAGES, - /* 13 */ FILTER_CONDITION}; - @Override public java.sql.ResultSet getIndexInfo(String cat, String schema, String table, boolean unique, - boolean approximate) throws SQLServerException, SQLTimeoutException { + boolean approximate) throws SQLServerException, SQLTimeoutException, SQLException { if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + ACTIVITY_ID + ActivityCorrelator.getCurrent().toString()); } checkClosed(); - /* - * sp_statistics [ @table_name = ] 'table_name' [ , [ @table_owner = ] 'owner' ] [ , [ @table_qualifier = ] - * 'qualifier' ] [ , [ @index_name = ] 'index_name' ] [ , [ @is_unique = ] 'is_unique' ] [ , [ @accuracy = ] - * 'accuracy' ] - */ - String[] arguments = new String[6]; - arguments[0] = table; - arguments[1] = schema; - arguments[2] = cat; - // use default for index name - arguments[3] = "%"; // index name % is default - if (unique) - arguments[4] = "Y"; // is_unique - else - arguments[4] = "N"; - if (approximate) - arguments[5] = "Q"; - else - arguments[5] = "E"; - return getResultSetWithProvidedColumnNames(cat, CallableHandles.SP_STATISTICS, arguments, - getIndexInfoColumnNames); + String query = "SELECT " + + "db_name() AS CatalogName, " + + "sch.name AS SchemaName, " + + "t.name AS TableName, " + + "i.name AS IndexName, " + + "i.type_desc AS IndexType, " + + "i.is_unique AS IsUnique, " + + "c.name AS ColumnName, " + + "ic.key_ordinal AS ColumnOrder " + + "FROM sys.indexes i " + + "INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id " + + "INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id " + + "INNER JOIN sys.tables t ON i.object_id = t.object_id " + + "INNER JOIN sys.schemas sch ON t.schema_id = sch.schema_id " + + "WHERE t.name = '" + table + "' " + + "AND sch.name = '" + schema + "' " + + "ORDER BY t.name, i.name, ic.key_ordinal"; + return getResultSetFromInternalQueries(cat, query); } @Override diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java new file mode 100644 index 000000000..cfa870036 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java @@ -0,0 +1,174 @@ +package com.microsoft.sqlserver.jdbc.databasemetadata; + +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.jdbc.TestResource; +import com.microsoft.sqlserver.jdbc.TestUtils; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; + +public class DatabaseMetadataGetIndexInfoTest extends AbstractTest { + + private static String tableName = AbstractSQLGenerator.escapeIdentifier("DBMetadataTestTable"); + private static String col1Name = AbstractSQLGenerator.escapeIdentifier("p1"); + private static String col2Name = AbstractSQLGenerator.escapeIdentifier("p2"); + private static String col3Name = AbstractSQLGenerator.escapeIdentifier( "p3"); + + @BeforeAll + public static void setupTests() throws Exception { + setConnection(); + } + + @BeforeEach + public void init() throws Exception { + try (Connection con = getConnection()) { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(tableName, stmt); + String createTableSQL = "CREATE TABLE " + tableName + " (" + + col1Name + " INT, " + + col2Name + " INT, " + + col3Name + " INT)"; + + stmt.executeUpdate(createTableSQL); + assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create table', at Connection."); + assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create table', at Statement."); + + String createClusteredIndexSQL = "CREATE CLUSTERED INDEX IDX_Clustered ON " + tableName + "(" + col1Name + ")"; + stmt.executeUpdate(createClusteredIndexSQL); + assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection."); + assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement."); + + String createNonClusteredIndexSQL = "CREATE NONCLUSTERED INDEX IDX_NonClustered ON " + tableName + "(" + col2Name + ")"; + stmt.executeUpdate(createNonClusteredIndexSQL); + assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection."); + assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement."); + + String createColumnstoreIndexSQL = "CREATE COLUMNSTORE INDEX IDX_Columnstore ON " + tableName + "(" + col3Name + ")"; + stmt.executeUpdate(createColumnstoreIndexSQL); + assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection."); + assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement."); + } + con.commit(); + } + } + + @AfterEach + public void terminate() throws Exception { + try (Connection con = getConnection(); Statement stmt = con.createStatement()) { + try { + TestUtils.dropTableIfExists(tableName, stmt); + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + } + + @Test + public void testGetIndexInfo() throws SQLException, SQLServerException { + ResultSet rs1, rs2 = null; + try (Connection connection = getConnection(); Statement stmt = connection.createStatement()) { + String catalog = connection.getCatalog(); + String schema = "dbo"; + String table = "DBMetadataTestTable"; + DatabaseMetaData dbMetadata = connection.getMetaData(); + rs1 = dbMetadata.getIndexInfo(catalog, schema, table, false, false); + + boolean hasClusteredIndex = false; + boolean hasNonClusteredIndex = false; + boolean hasColumnstoreIndex = false; + + String query = + "SELECT " + + " db_name() AS CatalogName, " + + " sch.name AS SchemaName, " + + " t.name AS TableName, " + + " i.name AS IndexName, " + + " i.type_desc AS IndexType, " + + " i.is_unique AS IsUnique, " + + " c.name AS ColumnName, " + + " ic.key_ordinal AS ColumnOrder " + + "FROM " + + " sys.indexes i " + + "INNER JOIN " + + " sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id " + + "INNER JOIN " + + " sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id " + + "INNER JOIN " + + " sys.tables t ON i.object_id = t.object_id " + + "INNER JOIN " + + " sys.schemas sch ON t.schema_id = sch.schema_id " + + + "WHERE t.name = '" + table + "' " + + "AND sch.name = '" + schema + "' " + + "ORDER BY " + + " t.name, i.name, ic.key_ordinal;"; + rs2 = stmt.executeQuery(query); + + while (rs1.next() && rs2.next()) { + String indexType = rs1.getString("IndexType"); + String indexName = rs1.getString("IndexName"); + + assertEquals(rs1.getString("CatalogName"), rs2.getString("CatalogName")); + assertEquals(rs1.getString("SchemaName"), rs2.getString("SchemaName")); + assertEquals(rs1.getString("TableName"), rs2.getString("TableName")); + assertEquals(indexName, rs2.getString("IndexName")); + assertEquals(indexType, rs2.getString("IndexType")); + assertEquals(rs1.getString("IsUnique"), rs2.getString("IsUnique")); + assertEquals(rs1.getString("ColumnName"), rs2.getString("ColumnName")); + assertEquals(rs1.getString("ColumnOrder"), rs2.getString("ColumnOrder")); + + if (indexType.contains("COLUMNSTORE")) { + hasColumnstoreIndex = true; + } else if (indexType.equals("CLUSTERED")) { + hasClusteredIndex = true; + } else if (indexType.equals("NONCLUSTERED")) { + hasNonClusteredIndex = true; + } + } + + assertTrue(hasColumnstoreIndex, "COLUMNSTORE index not found."); + assertTrue(hasClusteredIndex, "CLUSTERED index not found."); + assertTrue(hasNonClusteredIndex, "NONCLUSTERED index not found."); + } + } + + @Test + public void testGetIndexInfoCaseSensitivity() throws SQLException, SQLServerException { + ResultSet rs1, rs2 = null; + try (Connection connection = getConnection(); Statement stmt = connection.createStatement()) { + String catalog = connection.getCatalog(); + String schema = "dbo"; + String table = "DBMetadataTestTable"; + + DatabaseMetaData dbMetadata = connection.getMetaData(); + rs1 = dbMetadata.getIndexInfo(catalog, schema, table, false, false); + rs2 = dbMetadata.getIndexInfo(catalog, schema, table.toUpperCase(), false, false); + + while (rs1.next() && rs2.next()) { + String indexType = rs1.getString("IndexType"); + String indexName = rs1.getString("IndexName"); + + assertEquals(rs1.getString("CatalogName"), rs2.getString("CatalogName")); + assertEquals(rs1.getString("SchemaName"), rs2.getString("SchemaName")); + assertEquals(rs1.getString("TableName"), rs2.getString("TableName")); + assertEquals(indexName, rs2.getString("IndexName")); + assertEquals(indexType, rs2.getString("IndexType")); + assertEquals(rs1.getString("IsUnique"), rs2.getString("IsUnique")); + assertEquals(rs1.getString("ColumnName"), rs2.getString("ColumnName")); + assertEquals(rs1.getString("ColumnOrder"), rs2.getString("ColumnOrder")); + } + } + } +} From 162f3e011b209f49b1fadb0843bae27c8bb1a589 Mon Sep 17 00:00:00 2001 From: Ananya Garg Date: Fri, 20 Dec 2024 13:50:35 +0530 Subject: [PATCH 2/9] Updated changes as per review comments. --- .../jdbc/SQLServerDatabaseMetaData.java | 52 +++++++++----- .../sqlserver/jdbc/TestResource.java | 6 +- .../DatabaseMetadataGetIndexInfoTest.java | 70 ++++++++++++------- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java index e80ddea1d..8b60eccb8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java @@ -1199,28 +1199,44 @@ private ResultSet executeSPFkeys(String[] procParams) throws SQLException { @Override public java.sql.ResultSet getIndexInfo(String cat, String schema, String table, boolean unique, - boolean approximate) throws SQLServerException, SQLTimeoutException, SQLException { + boolean approximate) throws SQLException { if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) { loggerExternal.finer(toString() + ACTIVITY_ID + ActivityCorrelator.getCurrent().toString()); } checkClosed(); - String query = "SELECT " + - "db_name() AS CatalogName, " + - "sch.name AS SchemaName, " + - "t.name AS TableName, " + - "i.name AS IndexName, " + - "i.type_desc AS IndexType, " + - "i.is_unique AS IsUnique, " + - "c.name AS ColumnName, " + - "ic.key_ordinal AS ColumnOrder " + - "FROM sys.indexes i " + - "INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id " + - "INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id " + - "INNER JOIN sys.tables t ON i.object_id = t.object_id " + - "INNER JOIN sys.schemas sch ON t.schema_id = sch.schema_id " + - "WHERE t.name = '" + table + "' " + - "AND sch.name = '" + schema + "' " + - "ORDER BY t.name, i.name, ic.key_ordinal"; + /* + * Replaced the use of the sp_statistics stored procedure with a custom query to retrieve index information. + * + * Reason for change: + * The sp_statistics procedure was not returning Columnstore indexes, which was limiting the results. + * To address this issue and include all index types (Clustered, NonClustered, and Columnstore), a direct + * SQL query using sys.indexes, sys.index_columns, and related system views was implemented. + * + * This query ensures a complete set of index information, regardless of the index type, as a workaround for + * the limitations of sp_statistics. + * + * GitHub Issue: #2546 - Columnstore indexes were missing from sp_statistics results. + */ + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("SELECT ") + .append("db_name() AS CatalogName, ") + .append("sch.name AS SchemaName, ") + .append("t.name AS TableName, ") + .append("i.name AS IndexName, ") + .append("i.type_desc AS IndexType, ") + .append("i.is_unique AS IsUnique, ") + .append("c.name AS ColumnName, ") + .append("ic.key_ordinal AS ColumnOrder ") + .append("FROM sys.indexes i ") + .append("INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id ") + .append("INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id ") + .append("INNER JOIN sys.tables t ON i.object_id = t.object_id ") + .append("INNER JOIN sys.schemas sch ON t.schema_id = sch.schema_id ") + .append("WHERE t.name = '").append(table).append("' ") + .append("AND sch.name = '").append(schema).append("' ") + .append("ORDER BY t.name, i.name, ic.key_ordinal"); + + String query = queryBuilder.toString(); return getResultSetFromInternalQueries(cat, query); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java index 29a5e21dd..06ab713c8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java @@ -216,5 +216,9 @@ protected Object[][] getContents() { {"R_expectedClassDoesNotMatchActualClass", "Expected column class {0} does not match actual column class {1} for column {2}."}, {"R_loginFailedMI", "Login failed for user ''"}, - {"R_MInotAvailable", "Managed Identity authentication is not available"},}; + {"R_MInotAvailable", "Managed Identity authentication is not available"}, + {"R_noSQLWarningsCreateTableConnection", "Expecting NO SQLWarnings from 'create table', at Connection."}, + {"R_noSQLWarningsCreateTableStatement", "Expecting NO SQLWarnings from 'create table', at Statement."}, + {"R_noSQLWarningsCreateIndexConnection", "Expecting NO SQLWarnings from 'create index', at Connection."}, + {"R_noSQLWarningsCreateIndexStatement", "Expecting NO SQLWarnings from 'create index', at Statement."},}; } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java index cfa870036..e78590714 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java @@ -1,6 +1,9 @@ package com.microsoft.sqlserver.jdbc.databasemetadata; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -13,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.jdbc.TestUtils; @@ -23,7 +27,7 @@ public class DatabaseMetadataGetIndexInfoTest extends AbstractTest { private static String tableName = AbstractSQLGenerator.escapeIdentifier("DBMetadataTestTable"); private static String col1Name = AbstractSQLGenerator.escapeIdentifier("p1"); private static String col2Name = AbstractSQLGenerator.escapeIdentifier("p2"); - private static String col3Name = AbstractSQLGenerator.escapeIdentifier( "p3"); + private static String col3Name = AbstractSQLGenerator.escapeIdentifier("p3"); @BeforeAll public static void setupTests() throws Exception { @@ -31,7 +35,7 @@ public static void setupTests() throws Exception { } @BeforeEach - public void init() throws Exception { + public void init() throws SQLException { try (Connection con = getConnection()) { con.setAutoCommit(false); try (Statement stmt = con.createStatement()) { @@ -42,30 +46,30 @@ public void init() throws Exception { col3Name + " INT)"; stmt.executeUpdate(createTableSQL); - assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create table', at Connection."); - assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create table', at Statement."); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateTableConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateTableStatement")); String createClusteredIndexSQL = "CREATE CLUSTERED INDEX IDX_Clustered ON " + tableName + "(" + col1Name + ")"; stmt.executeUpdate(createClusteredIndexSQL); - assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection."); - assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement."); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); String createNonClusteredIndexSQL = "CREATE NONCLUSTERED INDEX IDX_NonClustered ON " + tableName + "(" + col2Name + ")"; stmt.executeUpdate(createNonClusteredIndexSQL); - assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection."); - assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement."); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); String createColumnstoreIndexSQL = "CREATE COLUMNSTORE INDEX IDX_Columnstore ON " + tableName + "(" + col3Name + ")"; stmt.executeUpdate(createColumnstoreIndexSQL); - assertNull(connection.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Connection."); - assertNull(stmt.getWarnings(), "Expecting NO SQLWarnings from 'create index', at Statement."); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); } con.commit(); } } @AfterEach - public void terminate() throws Exception { + public void terminate() throws SQLException { try (Connection con = getConnection(); Statement stmt = con.createStatement()) { try { TestUtils.dropTableIfExists(tableName, stmt); @@ -76,7 +80,7 @@ public void terminate() throws Exception { } @Test - public void testGetIndexInfo() throws SQLException, SQLServerException { + public void testGetIndexInfo() throws SQLException { ResultSet rs1, rs2 = null; try (Connection connection = getConnection(); Statement stmt = connection.createStatement()) { String catalog = connection.getCatalog(); @@ -119,15 +123,21 @@ public void testGetIndexInfo() throws SQLException, SQLServerException { while (rs1.next() && rs2.next()) { String indexType = rs1.getString("IndexType"); String indexName = rs1.getString("IndexName"); + String catalogName = rs1.getString("CatalogName"); + String schemaName = rs1.getString("SchemaName"); + String tableName = rs1.getString("TableName"); + boolean isUnique = rs1.getBoolean("IsUnique"); + String columnName = rs1.getString("ColumnName"); + int columnOrder = rs1.getInt("ColumnOrder"); - assertEquals(rs1.getString("CatalogName"), rs2.getString("CatalogName")); - assertEquals(rs1.getString("SchemaName"), rs2.getString("SchemaName")); - assertEquals(rs1.getString("TableName"), rs2.getString("TableName")); + assertEquals(catalogName, rs2.getString("CatalogName")); + assertEquals(schemaName, rs2.getString("SchemaName")); + assertEquals(tableName, rs2.getString("TableName")); assertEquals(indexName, rs2.getString("IndexName")); assertEquals(indexType, rs2.getString("IndexType")); - assertEquals(rs1.getString("IsUnique"), rs2.getString("IsUnique")); - assertEquals(rs1.getString("ColumnName"), rs2.getString("ColumnName")); - assertEquals(rs1.getString("ColumnOrder"), rs2.getString("ColumnOrder")); + assertEquals(isUnique, rs2.getBoolean("IsUnique")); + assertEquals(columnName, rs2.getString("ColumnName")); + assertEquals(columnOrder, rs2.getInt("ColumnOrder")); if (indexType.contains("COLUMNSTORE")) { hasColumnstoreIndex = true; @@ -145,9 +155,9 @@ public void testGetIndexInfo() throws SQLException, SQLServerException { } @Test - public void testGetIndexInfoCaseSensitivity() throws SQLException, SQLServerException { + public void testGetIndexInfoCaseSensitivity() throws SQLException { ResultSet rs1, rs2 = null; - try (Connection connection = getConnection(); Statement stmt = connection.createStatement()) { + try (Connection connection = getConnection()) { String catalog = connection.getCatalog(); String schema = "dbo"; String table = "DBMetadataTestTable"; @@ -159,15 +169,21 @@ public void testGetIndexInfoCaseSensitivity() throws SQLException, SQLServerExce while (rs1.next() && rs2.next()) { String indexType = rs1.getString("IndexType"); String indexName = rs1.getString("IndexName"); + String catalogName = rs1.getString("CatalogName"); + String schemaName = rs1.getString("SchemaName"); + String tableName = rs1.getString("TableName"); + boolean isUnique = rs1.getBoolean("IsUnique"); + String columnName = rs1.getString("ColumnName"); + int columnOrder = rs1.getInt("ColumnOrder"); - assertEquals(rs1.getString("CatalogName"), rs2.getString("CatalogName")); - assertEquals(rs1.getString("SchemaName"), rs2.getString("SchemaName")); - assertEquals(rs1.getString("TableName"), rs2.getString("TableName")); + assertEquals(catalogName, rs2.getString("CatalogName")); + assertEquals(schemaName, rs2.getString("SchemaName")); + assertEquals(tableName, rs2.getString("TableName")); assertEquals(indexName, rs2.getString("IndexName")); assertEquals(indexType, rs2.getString("IndexType")); - assertEquals(rs1.getString("IsUnique"), rs2.getString("IsUnique")); - assertEquals(rs1.getString("ColumnName"), rs2.getString("ColumnName")); - assertEquals(rs1.getString("ColumnOrder"), rs2.getString("ColumnOrder")); + assertEquals(isUnique, rs2.getBoolean("IsUnique")); + assertEquals(columnName, rs2.getString("ColumnName")); + assertEquals(columnOrder, rs2.getInt("ColumnOrder")); } } } From a3864380bd18885cb2358da6a6d36856cc921235 Mon Sep 17 00:00:00 2001 From: Ananya Garg Date: Fri, 20 Dec 2024 14:01:15 +0530 Subject: [PATCH 3/9] formatted code --- .../DatabaseMetadataGetIndexInfoTest.java | 295 +++++++++--------- 1 file changed, 141 insertions(+), 154 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java index e78590714..375758192 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java @@ -25,166 +25,153 @@ public class DatabaseMetadataGetIndexInfoTest extends AbstractTest { private static String tableName = AbstractSQLGenerator.escapeIdentifier("DBMetadataTestTable"); - private static String col1Name = AbstractSQLGenerator.escapeIdentifier("p1"); - private static String col2Name = AbstractSQLGenerator.escapeIdentifier("p2"); - private static String col3Name = AbstractSQLGenerator.escapeIdentifier("p3"); - - @BeforeAll - public static void setupTests() throws Exception { - setConnection(); - } - - @BeforeEach - public void init() throws SQLException { - try (Connection con = getConnection()) { - con.setAutoCommit(false); - try (Statement stmt = con.createStatement()) { - TestUtils.dropTableIfExists(tableName, stmt); - String createTableSQL = "CREATE TABLE " + tableName + " (" + - col1Name + " INT, " + - col2Name + " INT, " + - col3Name + " INT)"; - - stmt.executeUpdate(createTableSQL); - assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateTableConnection")); - assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateTableStatement")); - - String createClusteredIndexSQL = "CREATE CLUSTERED INDEX IDX_Clustered ON " + tableName + "(" + col1Name + ")"; - stmt.executeUpdate(createClusteredIndexSQL); - assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); - assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); - - String createNonClusteredIndexSQL = "CREATE NONCLUSTERED INDEX IDX_NonClustered ON " + tableName + "(" + col2Name + ")"; - stmt.executeUpdate(createNonClusteredIndexSQL); - assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); - assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); - - String createColumnstoreIndexSQL = "CREATE COLUMNSTORE INDEX IDX_Columnstore ON " + tableName + "(" + col3Name + ")"; - stmt.executeUpdate(createColumnstoreIndexSQL); - assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); - assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); - } - con.commit(); - } - } - - @AfterEach - public void terminate() throws SQLException { - try (Connection con = getConnection(); Statement stmt = con.createStatement()) { - try { - TestUtils.dropTableIfExists(tableName, stmt); - } catch (SQLException e) { - fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); - } - } - } - - @Test - public void testGetIndexInfo() throws SQLException { - ResultSet rs1, rs2 = null; - try (Connection connection = getConnection(); Statement stmt = connection.createStatement()) { - String catalog = connection.getCatalog(); - String schema = "dbo"; - String table = "DBMetadataTestTable"; - DatabaseMetaData dbMetadata = connection.getMetaData(); + private static String col1Name = AbstractSQLGenerator.escapeIdentifier("p1"); + private static String col2Name = AbstractSQLGenerator.escapeIdentifier("p2"); + private static String col3Name = AbstractSQLGenerator.escapeIdentifier("p3"); + + @BeforeAll + public static void setupTests() throws Exception { + setConnection(); + } + + @BeforeEach + public void init() throws SQLException { + try (Connection con = getConnection()) { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + TestUtils.dropTableIfExists(tableName, stmt); + String createTableSQL = "CREATE TABLE " + tableName + " (" + col1Name + " INT, " + col2Name + " INT, " + + col3Name + " INT)"; + + stmt.executeUpdate(createTableSQL); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateTableConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateTableStatement")); + + String createClusteredIndexSQL = "CREATE CLUSTERED INDEX IDX_Clustered ON " + tableName + "(" + col1Name + + ")"; + stmt.executeUpdate(createClusteredIndexSQL); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); + + String createNonClusteredIndexSQL = "CREATE NONCLUSTERED INDEX IDX_NonClustered ON " + tableName + "(" + + col2Name + ")"; + stmt.executeUpdate(createNonClusteredIndexSQL); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); + + String createColumnstoreIndexSQL = "CREATE COLUMNSTORE INDEX IDX_Columnstore ON " + tableName + "(" + + col3Name + ")"; + stmt.executeUpdate(createColumnstoreIndexSQL); + assertNull(connection.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexConnection")); + assertNull(stmt.getWarnings(), TestResource.getResource("R_noSQLWarningsCreateIndexStatement")); + } + con.commit(); + } + } + + @AfterEach + public void terminate() throws SQLException { + try (Connection con = getConnection(); Statement stmt = con.createStatement()) { + try { + TestUtils.dropTableIfExists(tableName, stmt); + } catch (SQLException e) { + fail(TestResource.getResource("R_unexpectedException") + e.getMessage()); + } + } + } + + @Test + public void testGetIndexInfo() throws SQLException { + ResultSet rs1, rs2 = null; + try (Connection connection = getConnection(); Statement stmt = connection.createStatement()) { + String catalog = connection.getCatalog(); + String schema = "dbo"; + String table = "DBMetadataTestTable"; + DatabaseMetaData dbMetadata = connection.getMetaData(); rs1 = dbMetadata.getIndexInfo(catalog, schema, table, false, false); boolean hasClusteredIndex = false; - boolean hasNonClusteredIndex = false; - boolean hasColumnstoreIndex = false; - - String query = - "SELECT " + - " db_name() AS CatalogName, " + - " sch.name AS SchemaName, " + - " t.name AS TableName, " + - " i.name AS IndexName, " + - " i.type_desc AS IndexType, " + - " i.is_unique AS IsUnique, " + - " c.name AS ColumnName, " + - " ic.key_ordinal AS ColumnOrder " + - "FROM " + - " sys.indexes i " + - "INNER JOIN " + - " sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id " + - "INNER JOIN " + - " sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id " + - "INNER JOIN " + - " sys.tables t ON i.object_id = t.object_id " + - "INNER JOIN " + - " sys.schemas sch ON t.schema_id = sch.schema_id " + - - "WHERE t.name = '" + table + "' " + - "AND sch.name = '" + schema + "' " + - "ORDER BY " + - " t.name, i.name, ic.key_ordinal;"; + boolean hasNonClusteredIndex = false; + boolean hasColumnstoreIndex = false; + + String query = "SELECT " + " db_name() AS CatalogName, " + " sch.name AS SchemaName, " + + " t.name AS TableName, " + " i.name AS IndexName, " + " i.type_desc AS IndexType, " + + " i.is_unique AS IsUnique, " + " c.name AS ColumnName, " + + " ic.key_ordinal AS ColumnOrder " + "FROM " + " sys.indexes i " + "INNER JOIN " + + " sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id " + + "INNER JOIN " + " sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id " + + "INNER JOIN " + " sys.tables t ON i.object_id = t.object_id " + "INNER JOIN " + + " sys.schemas sch ON t.schema_id = sch.schema_id " + + + "WHERE t.name = '" + table + "' " + "AND sch.name = '" + schema + "' " + "ORDER BY " + + " t.name, i.name, ic.key_ordinal;"; rs2 = stmt.executeQuery(query); - - while (rs1.next() && rs2.next()) { - String indexType = rs1.getString("IndexType"); - String indexName = rs1.getString("IndexName"); - String catalogName = rs1.getString("CatalogName"); - String schemaName = rs1.getString("SchemaName"); - String tableName = rs1.getString("TableName"); - boolean isUnique = rs1.getBoolean("IsUnique"); - String columnName = rs1.getString("ColumnName"); - int columnOrder = rs1.getInt("ColumnOrder"); - - assertEquals(catalogName, rs2.getString("CatalogName")); - assertEquals(schemaName, rs2.getString("SchemaName")); - assertEquals(tableName, rs2.getString("TableName")); - assertEquals(indexName, rs2.getString("IndexName")); - assertEquals(indexType, rs2.getString("IndexType")); - assertEquals(isUnique, rs2.getBoolean("IsUnique")); - assertEquals(columnName, rs2.getString("ColumnName")); - assertEquals(columnOrder, rs2.getInt("ColumnOrder")); - - if (indexType.contains("COLUMNSTORE")) { - hasColumnstoreIndex = true; - } else if (indexType.equals("CLUSTERED")) { - hasClusteredIndex = true; - } else if (indexType.equals("NONCLUSTERED")) { - hasNonClusteredIndex = true; - } - } - - assertTrue(hasColumnstoreIndex, "COLUMNSTORE index not found."); - assertTrue(hasClusteredIndex, "CLUSTERED index not found."); - assertTrue(hasNonClusteredIndex, "NONCLUSTERED index not found."); - } - } - - @Test - public void testGetIndexInfoCaseSensitivity() throws SQLException { - ResultSet rs1, rs2 = null; - try (Connection connection = getConnection()) { - String catalog = connection.getCatalog(); - String schema = "dbo"; - String table = "DBMetadataTestTable"; - - DatabaseMetaData dbMetadata = connection.getMetaData(); + + while (rs1.next() && rs2.next()) { + String indexType = rs1.getString("IndexType"); + String indexName = rs1.getString("IndexName"); + String catalogName = rs1.getString("CatalogName"); + String schemaName = rs1.getString("SchemaName"); + String tableName = rs1.getString("TableName"); + boolean isUnique = rs1.getBoolean("IsUnique"); + String columnName = rs1.getString("ColumnName"); + int columnOrder = rs1.getInt("ColumnOrder"); + + assertEquals(catalogName, rs2.getString("CatalogName")); + assertEquals(schemaName, rs2.getString("SchemaName")); + assertEquals(tableName, rs2.getString("TableName")); + assertEquals(indexName, rs2.getString("IndexName")); + assertEquals(indexType, rs2.getString("IndexType")); + assertEquals(isUnique, rs2.getBoolean("IsUnique")); + assertEquals(columnName, rs2.getString("ColumnName")); + assertEquals(columnOrder, rs2.getInt("ColumnOrder")); + + if (indexType.contains("COLUMNSTORE")) { + hasColumnstoreIndex = true; + } else if (indexType.equals("CLUSTERED")) { + hasClusteredIndex = true; + } else if (indexType.equals("NONCLUSTERED")) { + hasNonClusteredIndex = true; + } + } + + assertTrue(hasColumnstoreIndex, "COLUMNSTORE index not found."); + assertTrue(hasClusteredIndex, "CLUSTERED index not found."); + assertTrue(hasNonClusteredIndex, "NONCLUSTERED index not found."); + } + } + + @Test + public void testGetIndexInfoCaseSensitivity() throws SQLException { + ResultSet rs1, rs2 = null; + try (Connection connection = getConnection()) { + String catalog = connection.getCatalog(); + String schema = "dbo"; + String table = "DBMetadataTestTable"; + + DatabaseMetaData dbMetadata = connection.getMetaData(); rs1 = dbMetadata.getIndexInfo(catalog, schema, table, false, false); rs2 = dbMetadata.getIndexInfo(catalog, schema, table.toUpperCase(), false, false); while (rs1.next() && rs2.next()) { - String indexType = rs1.getString("IndexType"); - String indexName = rs1.getString("IndexName"); - String catalogName = rs1.getString("CatalogName"); - String schemaName = rs1.getString("SchemaName"); - String tableName = rs1.getString("TableName"); - boolean isUnique = rs1.getBoolean("IsUnique"); - String columnName = rs1.getString("ColumnName"); - int columnOrder = rs1.getInt("ColumnOrder"); - - assertEquals(catalogName, rs2.getString("CatalogName")); - assertEquals(schemaName, rs2.getString("SchemaName")); - assertEquals(tableName, rs2.getString("TableName")); - assertEquals(indexName, rs2.getString("IndexName")); - assertEquals(indexType, rs2.getString("IndexType")); - assertEquals(isUnique, rs2.getBoolean("IsUnique")); - assertEquals(columnName, rs2.getString("ColumnName")); - assertEquals(columnOrder, rs2.getInt("ColumnOrder")); - } - } - } + String indexType = rs1.getString("IndexType"); + String indexName = rs1.getString("IndexName"); + String catalogName = rs1.getString("CatalogName"); + String schemaName = rs1.getString("SchemaName"); + String tableName = rs1.getString("TableName"); + boolean isUnique = rs1.getBoolean("IsUnique"); + String columnName = rs1.getString("ColumnName"); + int columnOrder = rs1.getInt("ColumnOrder"); + + assertEquals(catalogName, rs2.getString("CatalogName")); + assertEquals(schemaName, rs2.getString("SchemaName")); + assertEquals(tableName, rs2.getString("TableName")); + assertEquals(indexName, rs2.getString("IndexName")); + assertEquals(indexType, rs2.getString("IndexType")); + assertEquals(isUnique, rs2.getBoolean("IsUnique")); + assertEquals(columnName, rs2.getString("ColumnName")); + assertEquals(columnOrder, rs2.getInt("ColumnOrder")); + } + } + } } From 57a9e34455d4f21c7ef019ca7ee0eac87265faef Mon Sep 17 00:00:00 2001 From: Ananya Garg Date: Tue, 7 Jan 2025 14:45:56 +0530 Subject: [PATCH 4/9] Added random identifier to the col names --- .../databasemetadata/DatabaseMetadataGetIndexInfoTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java index 375758192..794eb721a 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetadataGetIndexInfoTest.java @@ -25,9 +25,9 @@ public class DatabaseMetadataGetIndexInfoTest extends AbstractTest { private static String tableName = AbstractSQLGenerator.escapeIdentifier("DBMetadataTestTable"); - private static String col1Name = AbstractSQLGenerator.escapeIdentifier("p1"); - private static String col2Name = AbstractSQLGenerator.escapeIdentifier("p2"); - private static String col3Name = AbstractSQLGenerator.escapeIdentifier("p3"); + private static String col1Name = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("col1")); + private static String col2Name = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("col2")); + private static String col3Name = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("col3")); @BeforeAll public static void setupTests() throws Exception { From aeb5ce02bdaf75d67623c12453dded34da18d297 Mon Sep 17 00:00:00 2001 From: muskan124947 Date: Fri, 20 Dec 2024 13:03:12 +0530 Subject: [PATCH 5/9] Capture Client Guest OS and architecture in JDBC (#2561) * Capture Client Guest OS and architecture in JDBC * Added app name with format Microsoft JDBC - {os}, {platform} - {arch} * Adding log info and getAppNameWithProperties() * Updated the application name to be set dynamically * Added log level as FINE * Added test case to verify application name * Updated the SQLServerDriverPropertyInfo with default name * Added static block for initialization * Added test case for code coverage * Updated app Name --- .../sqlserver/jdbc/SQLServerConnection.java | 2 +- .../sqlserver/jdbc/SQLServerDriver.java | 31 ++++++++++++++- .../sqlserver/jdbc/SQLServerDriverTest.java | 38 +++++++++++++++++++ 3 files changed, 69 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 6874deab4..42ae6647d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -2446,7 +2446,7 @@ Connection connectInternal(Properties propsIn, if (null != sPropValue) validateMaxSQLLoginName(sPropKey, sPropValue); else - activeConnectionProperties.setProperty(sPropKey, SQLServerDriver.DEFAULT_APP_NAME); + activeConnectionProperties.setProperty(sPropKey, SQLServerDriver.constructedAppName); sPropKey = SQLServerDriverBooleanProperty.LAST_UPDATE_COUNT.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index e4b1d59ee..833368169 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -731,7 +731,32 @@ public final class SQLServerDriver implements java.sql.Driver { static final String AUTH_DLL_NAME = "mssql-jdbc_auth-" + SQLJdbcVersion.MAJOR + "." + SQLJdbcVersion.MINOR + "." + SQLJdbcVersion.PATCH + "." + Util.getJVMArchOnWindows() + SQLJdbcVersion.RELEASE_EXT; static final String DEFAULT_APP_NAME = "Microsoft JDBC Driver for SQL Server"; + static final String APP_NAME_TEMPLATE = "Microsoft JDBC - %s, %s - %s"; + static final String constructedAppName; + static { + constructedAppName = getAppName(); + } + /** + * Constructs the application name using system properties for OS, platform, and architecture. + * If any of the properties cannot be fetched, it falls back to the default application name. + * Format -> Microsoft JDBC - {OS}, {Platform} - {architecture} + * + * @return the constructed application name or the default application name if properties are not available + */ + static String getAppName() { + String osName = System.getProperty("os.name", ""); + String osArch = System.getProperty("os.arch", ""); + String javaVmName = System.getProperty("java.vm.name", ""); + String javaVmVersion = System.getProperty("java.vm.version", ""); + String platform = javaVmName.isEmpty() || javaVmVersion.isEmpty() ? "" : javaVmName + " " + javaVmVersion; + + if (osName.isEmpty() && platform.isEmpty() && osArch.isEmpty()) { + return DEFAULT_APP_NAME; + } + return String.format(APP_NAME_TEMPLATE, osName, platform, osArch); + } + private static final String[] TRUE_FALSE = {"true", "false"}; private static final SQLServerDriverPropertyInfo[] DRIVER_PROPERTIES = { @@ -741,7 +766,7 @@ public final class SQLServerDriver implements java.sql.Driver { SQLServerDriverStringProperty.APPLICATION_INTENT.getDefaultValue(), false, new String[] {ApplicationIntent.READ_ONLY.toString(), ApplicationIntent.READ_WRITE.toString()}), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.APPLICATION_NAME.toString(), - SQLServerDriverStringProperty.APPLICATION_NAME.getDefaultValue(), false, null), + SQLServerDriverStringProperty.APPLICATION_NAME.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.COLUMN_ENCRYPTION.toString(), SQLServerDriverStringProperty.COLUMN_ENCRYPTION.getDefaultValue(), false, new String[] {ColumnEncryptionSetting.DISABLED.toString(), @@ -1028,6 +1053,9 @@ String getClassNameLogging() { drLogger.finer("Error registering driver: " + e); } } + if (loggerExternal.isLoggable(Level.FINE)) { + loggerExternal.log(Level.FINE, "Application Name: " + SQLServerDriver.constructedAppName); + } } // Check for jdk.net.ExtendedSocketOptions to set TCP keep-alive options for idle connection resiliency @@ -1266,6 +1294,7 @@ public java.sql.Connection connect(String url, Properties suppliedProperties) th Properties connectProperties = parseAndMergeProperties(url, suppliedProperties); if (connectProperties != null) { result = DriverJDBCVersion.getSQLServerConnection(toString()); + connectProperties.setProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString(), SQLServerDriver.constructedAppName); result.connect(connectProperties, null); } loggerExternal.exiting(getClassNameLogging(), "connect", result); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java index 5309780ca..2d2fee89d 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java @@ -2,6 +2,8 @@ import static org.junit.Assert.fail; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.sql.Connection; @@ -190,4 +192,40 @@ public void testConnectionDriver() throws SQLException { } } } + + /** + * test application name + * + * @throws SQLException + */ + @Test + public void testApplicationName() throws SQLException { + try (Connection conn = DriverManager.getConnection(connectionString); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT program_name FROM sys.dm_exec_sessions WHERE session_id = @@SPID")) { + if (rs.next()) { + assertEquals(SQLServerDriver.constructedAppName, rs.getString("program_name")); + } + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * test application name when system properties are empty + * + */ + @Test + public void testGetAppName() { + String appName = SQLServerDriver.getAppName(); + assertNotNull(appName, "Application name should not be null"); + assertFalse(appName.isEmpty(), "Application name should not be empty"); + + System.setProperty("os.name", ""); + System.setProperty("os.arch", ""); + System.setProperty("java.vm.name", ""); + System.setProperty("java.vm.version", ""); + String defaultAppName = SQLServerDriver.getAppName(); + assertEquals(SQLServerDriver.DEFAULT_APP_NAME, defaultAppName, "Application name should be the default one"); + } } From 945cd1fcaa453aa46d4d54b774a8130d719722a6 Mon Sep 17 00:00:00 2001 From: Ananya Garg Date: Fri, 20 Dec 2024 13:28:08 +0530 Subject: [PATCH 6/9] Driver cuts out the question mark from columns labels (aliases) (#2569) * Driver cuts out the question mark from columns labels (aliases) * added SQLServerPreparedStatement --- .../jdbc/SQLServerPreparedStatement.java | 15 +++++++++--- .../unit/statement/PreparedStatementTest.java | 23 ++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java index 2128f9cef..244cfb696 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java @@ -1246,16 +1246,25 @@ public final java.sql.ResultSetMetaData getMetaData() throws SQLServerException, */ private SQLServerResultSet buildExecuteMetaData() throws SQLServerException, SQLTimeoutException { String fmtSQL = userSQL; - + SQLServerResultSet emptyResultSet = null; try { - fmtSQL = replaceMarkerWithNull(fmtSQL); internalStmt = (SQLServerStatement) connection.createStatement(); emptyResultSet = internalStmt.executeQueryInternal("set fmtonly on " + fmtSQL + "\nset fmtonly off"); } catch (SQLServerException sqle) { // Ignore empty result set errors, otherwise propagate the server error. if (!sqle.getMessage().equals(SQLServerException.getErrString("R_noResultset"))) { - throw sqle; + //try by replacing ? characters in case that was an issue + try { + fmtSQL = replaceMarkerWithNull(fmtSQL); + internalStmt = (SQLServerStatement) connection.createStatement(); + emptyResultSet = internalStmt.executeQueryInternal("set fmtonly on " + fmtSQL + "\nset fmtonly off"); + } catch (SQLServerException ex) { + // Ignore empty result set errors, otherwise propagate the server error. + if (!ex.getMessage().equals(SQLServerException.getErrString("R_noResultset"))) { + throw ex; + } + } } } return emptyResultSet; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java index 23c89071d..87e0994aa 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java @@ -13,8 +13,10 @@ import java.lang.reflect.Field; import java.sql.BatchUpdateException; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; @@ -124,6 +126,25 @@ public void testPreparedStatementWithSpPrepare() throws SQLException { } } } + + @Test + void testDatabaseQueryMetaData() throws SQLException { + try (Connection connection = getConnection()) { + try (SQLServerPreparedStatement stmt = (SQLServerPreparedStatement) connection.prepareStatement( + "select 1 as \"any questions ???\"")) { + ResultSetMetaData metaData = stmt.getMetaData(); + String actualLabel = metaData.getColumnLabel(1); + String actualName = metaData.getColumnName(1); + + String expected = "any questions ???"; + assertEquals(expected, actualLabel, "Column label should match the expected value"); + assertEquals(expected, actualName, "Column name should match the expected value"); + } + } catch (SQLException e) { + e.printStackTrace(); + fail("SQLException occurred during test: " + e.getMessage()); + } + } @Test public void testPreparedStatementParamNameSpacingWithMultipleParams() throws SQLException { @@ -927,5 +948,5 @@ private static void dropTables() throws Exception { TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName5), stmt); } } - + } From 0483bd4fc8e75a1d77846bcd6d03eb2b3909d3db Mon Sep 17 00:00:00 2001 From: Mahendra Chavan Date: Tue, 24 Dec 2024 10:45:32 +0530 Subject: [PATCH 7/9] Fix OffsetDateTime conversion for pre-Gregorian dates (#2568) * Fix OffsetDateTime conversion for pre-Gregorian dates * Formatting changes * Removed unused import --- .../java/microsoft/sql/DateTimeOffset.java | 33 +++++++++++++++---- .../jdbc/datatypes/DataTypesTest.java | 28 ++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/main/java/microsoft/sql/DateTimeOffset.java b/src/main/java/microsoft/sql/DateTimeOffset.java index bf9e95c7b..dd1de85b2 100644 --- a/src/main/java/microsoft/sql/DateTimeOffset.java +++ b/src/main/java/microsoft/sql/DateTimeOffset.java @@ -5,6 +5,8 @@ package microsoft.sql; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; @@ -190,7 +192,6 @@ public String toString() { .substring(2), // -> "123456" formattedOffset); } - return result; } @@ -257,12 +258,32 @@ public java.sql.Timestamp getTimestamp() { * @return OffsetDateTime equivalent to this DateTimeOffset object. */ public java.time.OffsetDateTime getOffsetDateTime() { - java.time.ZoneOffset zoneOffset = java.time.ZoneOffset.ofTotalSeconds(60 * minutesOffset); - java.time.LocalDateTime localDateTime = java.time.LocalDateTime.ofEpochSecond(utcMillis / 1000, nanos, - zoneOffset); - return java.time.OffsetDateTime.of(localDateTime, zoneOffset); + // Format the offset as +hh:mm or -hh:mm. Zero offset is formatted as +00:00. + String formattedOffset = (minutesOffset < 0) ? + String.format(Locale.US, "-%1$02d:%2$02d", -minutesOffset / 60, -minutesOffset % 60) : + String.format(Locale.US, "+%1$02d:%2$02d", minutesOffset / 60, minutesOffset % 60); + + // Create a Calendar instance with the time zone set to GMT plus the formatted offset + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT" + formattedOffset), Locale.US); + // Initialize the calendar with the UTC milliseconds value + calendar.setTimeInMillis(utcMillis); + + // Extract the date and time components from the calendar + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH is zero-based + int day = calendar.get(Calendar.DAY_OF_MONTH); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + int second = calendar.get(Calendar.SECOND); + + // Create the ZoneOffset from the minutesOffset + ZoneOffset offset = ZoneOffset.ofTotalSeconds(minutesOffset * 60); + + // Create and return the OffsetDateTime + return OffsetDateTime.of(year, month, day, hour, minute, second, nanos, offset); } - + + /** * Returns this DateTimeOffset object's offset value. * diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java index 445d23dd0..d1bb30a2c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java @@ -1945,6 +1945,34 @@ public void testDateTimeOffsetValueOfOffsetDateTime() throws Exception { assertEquals(expected, DateTimeOffset.valueOf(roundUp).getOffsetDateTime()); assertEquals(expected, DateTimeOffset.valueOf(roundDown).getOffsetDateTime()); } + + @Test + public void testPreGregorianDateTime() throws Exception { + try (Connection conn = getConnection(); + Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);) { + + conn.setAutoCommit(false); + TestUtils.dropTableIfExists(escapedTableName, stmt); + + stmt.executeUpdate("CREATE TABLE " + escapedTableName + " (dob datetimeoffset(7) null)"); + stmt.executeUpdate("INSERT INTO " + escapedTableName + " VALUES ('1500-12-16 00:00:00.0000000+08:00')"); + stmt.executeUpdate("INSERT INTO " + escapedTableName + " VALUES ('1400-09-27 09:30:00.0000000+08:00')"); + stmt.executeUpdate("INSERT INTO " + escapedTableName + " VALUES ('2024-12-16 23:40:00.0000000+08:00')"); + + try (ResultSet rs = stmt.executeQuery("select dob from " + escapedTableName + " order by dob")) { + while (rs.next()) { + String strDateTimeOffset = rs.getString(1).substring(0, 10); + DateTimeOffset objDateTimeOffset = (DateTimeOffset) rs.getObject(1); + OffsetDateTime objOffsetDateTime = objDateTimeOffset.getOffsetDateTime(); + + String strOffsetDateTime = objOffsetDateTime.toString().substring(0, 10); + assertEquals(strDateTimeOffset, strOffsetDateTime, "Mismatch found in DateTimeOffset : " + + objDateTimeOffset + " and OffsetDateTime : " + objOffsetDateTime); + } + } + TestUtils.dropTableIfExists(escapedTableName, stmt); + } + } static LocalDateTime getUnstorableValue() throws Exception { ZoneId systemTimezone = ZoneId.systemDefault(); From 8d4f2c66cddfc476c960a566198ca479a261990a Mon Sep 17 00:00:00 2001 From: muskan124947 Date: Thu, 2 Jan 2025 14:30:52 +0530 Subject: [PATCH 8/9] Handled failure in getApplicationName() (#2571) * Handled failure in getApplicationName() * Added test to validate appName using select app_name() query * Updated connect property * Updated connect property to add appname * Fixed checking of APPLICATION_NAME in connect properties * Added test case --------- Co-authored-by: machavan --- .../sqlserver/jdbc/SQLServerDataSource.java | 2 +- .../sqlserver/jdbc/SQLServerDriver.java | 4 +- .../sqlserver/jdbc/SQLServerDriverTest.java | 38 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index da7688e60..480f36ba3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -170,7 +170,7 @@ public void setApplicationName(String applicationName) { @Override public String getApplicationName() { return getStringProperty(connectionProps, SQLServerDriverStringProperty.APPLICATION_NAME.toString(), - SQLServerDriverStringProperty.APPLICATION_NAME.getDefaultValue()); + SQLServerDriver.constructedAppName); } /** diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 833368169..1ffffa6f0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -1294,7 +1294,9 @@ public java.sql.Connection connect(String url, Properties suppliedProperties) th Properties connectProperties = parseAndMergeProperties(url, suppliedProperties); if (connectProperties != null) { result = DriverJDBCVersion.getSQLServerConnection(toString()); - connectProperties.setProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString(), SQLServerDriver.constructedAppName); + if (connectProperties.getProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString()) == null) { + connectProperties.setProperty(SQLServerDriverStringProperty.APPLICATION_NAME.toString(), SQLServerDriver.constructedAppName); + } result.connect(connectProperties, null); } loggerExternal.exiting(getClassNameLogging(), "connect", result); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java index 2d2fee89d..6b8b95c29 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java @@ -211,6 +211,44 @@ public void testApplicationName() throws SQLException { } } + /** + * test application name by executing select app_name() + * + * @throws SQLException + */ + @Test + public void testApplicationNameUsingApp_Name() throws SQLException { + try (Connection conn = DriverManager.getConnection(connectionString); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT app_name()")) { + if (rs.next()) { + assertEquals(SQLServerDriver.constructedAppName, rs.getString(1)); + } + } catch (SQLException e) { + fail(e.getMessage()); + } + } + + /** + * test application name by executing select app_name() + * + * @throws SQLException + */ + @Test + public void testAppNameWithSpecifiedApplicationName() throws SQLException { + String url = connectionString + ";applicationName={0123456789012345678901234567890123456789012345678901234567890123456789012345678901234589012345678901234567890123456789012345678}"; + + try (Connection conn = DriverManager.getConnection(url); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT app_name()")) { + if (rs.next()) { + assertEquals(SQLServerDriver.constructedAppName, rs.getString(1)); + } + } catch (SQLException e) { + fail(e.getMessage()); + } + } + /** * test application name when system properties are empty * From 3f98b61dca973c2f7b07be027a4c26e3272aaefd Mon Sep 17 00:00:00 2001 From: muskan124947 Date: Sat, 4 Jan 2025 09:00:30 +0530 Subject: [PATCH 9/9] Fixed test failure (#2574) --- .../java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java index 6b8b95c29..646ad75e9 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerDriverTest.java @@ -242,7 +242,7 @@ public void testAppNameWithSpecifiedApplicationName() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT app_name()")) { if (rs.next()) { - assertEquals(SQLServerDriver.constructedAppName, rs.getString(1)); + assertEquals("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234589012345678901234567890123456789012345678", rs.getString(1)); } } catch (SQLException e) { fail(e.getMessage());