From 40458153b9ef6cfaed151f442f2bf05ec9d106d6 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Tue, 22 Dec 2020 17:05:44 -0500 Subject: [PATCH] Connection.getMetaData().getProcedures(String,String,String)) should return 9 columns but is missing the 9th column called SPECIFIC_NAME. The issues is that the underlying server sp_stored_procedures() returns 8 columns while the API expects 9 columns. The driver passes the result set as is to the caller. This patch allows the JDBC getProcedures() API to add the missing column. --- .../jdbc/SQLServerDatabaseMetaData.java | 25 +++++++---- .../sqlserver/jdbc/SQLServerResultSet.java | 10 ++++- .../DatabaseMetaDataTest.java | 44 +++++++++++++++++++ 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java index 951cee825d..57dbcb907b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java @@ -230,6 +230,7 @@ private void checkClosed() throws SQLServerException { private static final String PROCEDURE_NAME = "PROCEDURE_NAME"; private static final String PROCEDURE_SCHEM = "PROCEDURE_SCHEM"; private static final String PROCEDURE_TYPE = "PROCEDURE_TYPE"; + private static final String SPECIFIC_NAME = "SPECIFIC_NAME"; private static final String PSEUDO_COLUMN = "PSEUDO_COLUMN"; private static final String RADIX = "RADIX"; private static final String REMARKS = "REMARKS"; @@ -333,16 +334,14 @@ private CallableStatement getCallableStatementHandle(CallableHandles request, * to execute * @param arguments * for the stored procedure - * @return Resultset from the execution + * @return ResultSet from the execution * @throws SQLTimeoutException */ private SQLServerResultSet getResultSetFromStoredProc(String catalog, CallableHandles procedure, String[] arguments) throws SQLServerException, SQLTimeoutException { checkClosed(); assert null != arguments; - String orgCat = null; - orgCat = switchCatalogs(catalog); - SQLServerResultSet rs = null; + String orgCat = switchCatalogs(catalog); try { SQLServerCallableStatement call = (SQLServerCallableStatement) getCallableStatementHandle(procedure, catalog); @@ -351,13 +350,12 @@ private SQLServerResultSet getResultSetFromStoredProc(String catalog, CallableHa // note individual arguments can be null. call.setString(i, arguments[i - 1]); } - rs = (SQLServerResultSet) call.executeQueryInternal(); + return (SQLServerResultSet) call.executeQueryInternal(); } finally { if (null != orgCat) { connection.setCatalog(orgCat); } } - return rs; } private SQLServerResultSet getResultSetWithProvidedColumnNames(String catalog, CallableHandles procedure, @@ -1364,7 +1362,7 @@ public java.sql.ResultSet getProcedureColumns(String catalog, String schema, Str private static final String[] getProceduresColumnNames = { /* 1 */ PROCEDURE_CAT, /* 2 */ PROCEDURE_SCHEM, /* 3 */ PROCEDURE_NAME, /* 4 */ NUM_INPUT_PARAMS, /* 5 */ NUM_OUTPUT_PARAMS, /* 6 */ NUM_RESULT_SETS, - /* 7 */ REMARKS, /* 8 */ PROCEDURE_TYPE}; + /* 7 */ REMARKS, /* 8 */ PROCEDURE_TYPE /* Not provided by server: 9 SPECIFIC_NAME */ }; @Override public java.sql.ResultSet getProcedures(String catalog, String schema, @@ -1382,8 +1380,19 @@ public java.sql.ResultSet getProcedures(String catalog, String schema, arguments[0] = EscapeIDName(proc); arguments[1] = schema; arguments[2] = catalog; - return getResultSetWithProvidedColumnNames(catalog, CallableHandles.SP_STORED_PROCEDURES, arguments, + + final SQLServerResultSet rs = getResultSetWithProvidedColumnNames(catalog, CallableHandles.SP_STORED_PROCEDURES, arguments, getProceduresColumnNames); + if (rs.getColumnCount() == getProceduresColumnNames.length) { + // Since the 9th column is missing from the server, we add it here to follow the + // JDBC specification. We use the PROCEDURE_NAME column as the backing for + // SPECIFIC_NAME. + final Column baseColumn = rs.getColumn(3); // PROCEDURE_NAME + final Column column9 = new Column(baseColumn.getTypeInfo(), SPECIFIC_NAME, baseColumn.getTableName(), + baseColumn.getCryptoMetadata()); + rs.addColumn(column9); + } + return rs; } @Override diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 81460ae059..62f059fddd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -189,7 +189,7 @@ final void setDeletedCurrentRow(boolean rowDeleted) { private int rowCount; /** The current row's column values */ - private final Column[] columns; + private Column[] columns; // The CekTable retrieved from the COLMETADATA token for this resultset. private CekTable cekTable = null; @@ -5764,4 +5764,12 @@ public void updateObject(String columnName, Object obj, SQLType targetSqlType) t loggerExternal.exiting(getClassNameLogging(), "updateObject"); } + void addColumn(final Column newColumn) { + final int oldLen = columns.length; + Column[] temp = new Column[oldLen + 1]; + System.arraycopy(columns, 0, temp, 0, oldLen); + temp[oldLen] = newColumn; + columns = temp; + } + } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java index a16fc8152b..6096f78e01 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java @@ -510,6 +510,50 @@ public void testGetFunctions() throws SQLException { } } + /** + * Test {@link SQLServerDatabaseMetaData#getProcedures(String, String, String)} + * + * @throws SQLException + */ + @Test + public void testGetProceduresResultSetMetaData() throws SQLException { + // We specify a procedure name that does not exist since we only care to validate the metadata. + try (Connection conn = getConnection(); ResultSet rs = conn.getMetaData().getProcedures(null, null, "NOMATCH")) { + final ResultSetMetaData metaData = rs.getMetaData(); + // Column names 1 through 8. + assertEquals("PROCEDURE_CAT", metaData.getColumnName(1)); + assertEquals("PROCEDURE_SCHEM", metaData.getColumnName(2)); + assertEquals("PROCEDURE_NAME", metaData.getColumnName(3)); + // columns 4 is reserved for future use, just don't blow up + metaData.getColumnName(4); + // columns 5 is reserved for future use, just don't blow up + metaData.getColumnName(5); + // columns 6 is reserved for future use, just don't blow up + metaData.getColumnName(6); + assertEquals("REMARKS", metaData.getColumnName(7)); + assertEquals("PROCEDURE_TYPE", metaData.getColumnName(8)); + // + // All 9th column APIs should not blow up after PR 1491 + assertEquals("SPECIFIC_NAME", metaData.getColumnName(9)); + assertEquals(9, metaData.getColumnCount()); + // + assertEquals("", metaData.getCatalogName(9)); + assertEquals("", metaData.getSchemaName(9)); + assertEquals("", metaData.getTableName(9)); + assertEquals("java.lang.String", metaData.getColumnClassName(9)); + assertEquals("SPECIFIC_NAME", metaData.getColumnLabel(9)); + // Some kind of string: + assertEquals(Types.NVARCHAR, metaData.getColumnType(9)); + // Some kind of string: + assertEquals("nvarchar", metaData.getColumnTypeName(9)); + // does not blow up: + metaData.getColumnDisplaySize(9); + metaData.getPrecision(9); + metaData.getScale(9); + } + } + + /** * * @throws SQLException