diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index 53f6c27c05..13d14465b2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -369,7 +369,7 @@ public void addColumnMapping(int sourceColumn, String destinationColumn) throws } else if (null == destinationColumn || destinationColumn.isEmpty()) { throwInvalidArgument("destinationColumn"); } - columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn.trim())); + columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn)); loggerExternal.exiting(loggerClassName, "addColumnMapping"); } @@ -396,7 +396,7 @@ public void addColumnMapping(String sourceColumn, int destinationColumn) throws } else if (null == sourceColumn || sourceColumn.isEmpty()) { throwInvalidArgument("sourceColumn"); } - columnMappings.add(new ColumnMapping(sourceColumn.trim(), destinationColumn)); + columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn)); loggerExternal.exiting(loggerClassName, "addColumnMapping"); } @@ -422,7 +422,7 @@ public void addColumnMapping(String sourceColumn, String destinationColumn) thro } else if (null == destinationColumn || destinationColumn.isEmpty()) { throwInvalidArgument("destinationColumn"); } - columnMappings.add(new ColumnMapping(sourceColumn.trim(), destinationColumn.trim())); + columnMappings.add(new ColumnMapping(sourceColumn, destinationColumn)); loggerExternal.exiting(loggerClassName, "addColumnMapping"); } @@ -929,11 +929,10 @@ private void writeTypeInfo(TDSWriter tdsWriter, int srcJdbcType, int srcScale, i case java.sql.Types.NUMERIC: case java.sql.Types.DECIMAL: /* - * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, - * but Azure DW only accepts money types for money column. - * To make the code compatible against both SQL Server and Azure DW, always send decimal and - * numeric as money/smallmoney if the destination column is money/smallmoney - * and the source is decimal/numeric. + * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, but + * Azure DW only accepts money types for money column. To make the code compatible against both SQL + * Server and Azure DW, always send decimal and numeric as money/smallmoney if the destination column is + * money/smallmoney and the source is decimal/numeric. */ if (destSSType == SSType.MONEY) { tdsWriter.writeByte(TDSType.MONEYN.byteValue()); @@ -944,7 +943,8 @@ private void writeTypeInfo(TDSWriter tdsWriter, int srcJdbcType, int srcScale, i tdsWriter.writeByte((byte) 4); break; } - byte byteType = (java.sql.Types.DECIMAL == srcJdbcType) ? TDSType.DECIMALN.byteValue() : TDSType.NUMERICN.byteValue(); + byte byteType = (java.sql.Types.DECIMAL == srcJdbcType) ? TDSType.DECIMALN.byteValue() + : TDSType.NUMERICN.byteValue(); tdsWriter.writeByte(byteType); tdsWriter.writeByte((byte) TDSWriter.BIGDECIMAL_MAX_LENGTH); // maximum length tdsWriter.writeByte((byte) srcPrecision); // unsigned byte @@ -1296,11 +1296,10 @@ private String getDestTypeFromSrcType(int srcColIndx, int destColIndx, return "smallmoney"; case java.sql.Types.DECIMAL: /* - * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, - * but Azure DW only accepts money types for money column. - * To make the code compatible against both SQL Server and Azure DW, always send decimal and - * numeric as money/smallmoney if the destination column is money/smallmoney - * and the source is decimal/numeric. + * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, but + * Azure DW only accepts money types for money column. To make the code compatible against both SQL + * Server and Azure DW, always send decimal and numeric as money/smallmoney if the destination column is + * money/smallmoney and the source is decimal/numeric. */ if (destSSType == SSType.MONEY) { return "money"; @@ -1842,8 +1841,7 @@ private void validateColumnMappings() throws SQLServerException { Set columnOrdinals = serverBulkData.getColumnOrdinals(); Iterator columnsIterator = columnOrdinals.iterator(); int j = 1; - outerWhileLoop: - while (columnsIterator.hasNext()) { + outerWhileLoop : while (columnsIterator.hasNext()) { int currentOrdinal = columnsIterator.next(); if (j != currentOrdinal) { /* @@ -2067,8 +2065,7 @@ else if (null != sourceCryptoMeta) { } else if (null != serverBulkData && connection.getSendTemporalDataTypesAsStringForBulkCopy()) { /* * Bulk copy from CSV and destination is not encrypted. In this case, we send the temporal types as varchar - * and - * SQL Server does the conversion. If destination is encrypted, then temporal types can not be sent as + * and SQL Server does the conversion. If destination is encrypted, then temporal types can not be sent as * varchar. */ switch (bulkJdbcType) { @@ -2219,10 +2216,9 @@ else if (null != sourceCryptoMeta) { } /* * SQL Server allows the insertion of decimal and numeric into a money (and smallmoney) column, - * but Azure DW only accepts money types for money column. - * To make the code compatible against both SQL Server and Azure DW, always send decimal and - * numeric as money/smallmoney if the destination column is money/smallmoney - * and the source is decimal/numeric. + * but Azure DW only accepts money types for money column. To make the code compatible against + * both SQL Server and Azure DW, always send decimal and numeric as money/smallmoney if the + * destination column is money/smallmoney and the source is decimal/numeric. */ if (destSSType == SSType.MONEY) { tdsWriter.writeMoney((BigDecimal) colValue, microsoft.sql.Types.MONEY); @@ -2490,14 +2486,12 @@ else if (4 >= bulkScale) tdsWriter.writeByte((byte) 0x05); if (colValue instanceof String) { /* - * if colValue is an instance of String, this means the data is coming from a CSV file. - * Time string is expected to come in with this pattern: hh:mm:ss[.nnnnnnn] - * First, look for the '.' character to determine if the String has the optional nanoseconds - * component. - * Next, create a java.sql.Time instance with the hh:mm:ss part we extracted, then set that - * time as the timestamp's time. - * Then, add the nanoseconds (optional, 0 if not provided) to the timestamp value. - * Finally, provide the timestamp value to writeTime method. + * if colValue is an instance of String, this means the data is coming from a CSV file. Time + * string is expected to come in with this pattern: hh:mm:ss[.nnnnnnn] First, look for the + * '.' character to determine if the String has the optional nanoseconds component. Next, + * create a java.sql.Time instance with the hh:mm:ss part we extracted, then set that time + * as the timestamp's time. Then, add the nanoseconds (optional, 0 if not provided) to the + * timestamp value. Finally, provide the timestamp value to writeTime method. */ java.sql.Timestamp ts = new java.sql.Timestamp(0); int nanos = 0; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java index 1ec4de537c..493eff7515 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyColumnMappingTest.java @@ -26,6 +26,7 @@ import com.microsoft.sqlserver.jdbc.ComparisonUtil; import com.microsoft.sqlserver.jdbc.RandomUtil; import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy; +import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions; import com.microsoft.sqlserver.jdbc.TestResource; import com.microsoft.sqlserver.jdbc.TestUtils; import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; @@ -377,6 +378,44 @@ public void testUnicodeCharToNchar() throws SQLException, ClassNotFoundException validateMapping("VARCHAR(5)", "NVARCHAR(max)", "фщыab"); } + @Tag(Constants.xAzureSQLDW) + @Test + @DisplayName("BulkCopy:test column name with trailing space") + public void testTrailingSpace() throws SQLException, ClassNotFoundException { + String sourceTable = TestUtils + .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("sourceTable"))); + String destTable = TestUtils + .escapeSingleQuotes(AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("destTable"))); + + try (Connection conn = DriverManager.getConnection(connectionString);) { + try (Statement sourceStmt = conn.createStatement(); Statement destStmt = conn.createStatement(); + SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) { + + sourceStmt.executeUpdate("CREATE TABLE " + sourceTable + " ([colTrailingSpace ] char(5) null);"); + sourceStmt.executeUpdate("INSERT INTO " + sourceTable + " VALUES('" + "data" + "');"); + destStmt.executeUpdate("CREATE TABLE " + destTable + " ([colTrailingSpace ] char(5) null);"); + ResultSet sourceRs = sourceStmt.executeQuery("SELECT * FROM " + sourceTable); + + ResultSet destRs = destStmt.executeQuery("SELECT * FROM " + destTable); + destRs.next(); + + ResultSetMetaData rsmd = sourceRs.getMetaData(); + String name = rsmd.getColumnName(1); + bulkCopy.addColumnMapping(name, name); + + bulkCopy.setDestinationTableName(destTable); + bulkCopy.writeToServer(sourceRs); + } catch (Exception e) { + fail(e.getMessage()); + } finally { + try (Statement stmt = conn.createStatement();) { + TestUtils.dropTableIfExists(destTable, stmt); + TestUtils.dropTableIfExists(sourceTable, stmt); + } + } + } + } + /** * Validate if same values are in both source and destination table taking into account 1 extra column in * destination which should be a copy of first column of source.