diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 55033f6cc2..99e1017f59 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -532,207 +532,219 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i string[] parts = MultipartIdentifier.ParseMultipartIdentifier(DestinationTableName, "[\"", "]\"", Strings.SQL_BulkCopyDestinationTableName, true); updateBulkCommandText.AppendFormat("insert bulk {0} (", ADP.BuildMultiPartName(parts)); - int nmatched = 0; // Number of columns that match and are accepted - int nrejected = 0; // Number of columns that match but were rejected - bool rejectColumn; // True if a column is rejected because of an excluded type - bool isInTransaction; - - isInTransaction = _connection.HasLocalTransaction; // Throw if there is a transaction but no flag is set - if (isInTransaction && _externalTransaction == null && _internalTransaction == null && (_connection.Parser != null && _connection.Parser.CurrentTransaction != null && _connection.Parser.CurrentTransaction.IsLocal)) + if (_connection.HasLocalTransaction + && _externalTransaction == null + && _internalTransaction == null + && _connection.Parser != null + && _connection.Parser.CurrentTransaction != null + && _connection.Parser.CurrentTransaction.IsLocal) { throw SQL.BulkLoadExistingTransaction(); } HashSet destColumnNames = new HashSet(); - Dictionary columnMappingStatusLookup = new Dictionary(); - // Loop over the metadata for each column + // Keep track of any result columns that we don't have a local + // mapping for. + HashSet unmatchedColumns = new(_localColumnMappings.Count); + + // Start by assuming all locally mapped Destination columns will be + // unmatched. + for (int i = 0; i < _localColumnMappings.Count; ++i) + { + unmatchedColumns.Add(_localColumnMappings[i].DestinationColumn); + } + + // Flag to remember whether or not we need to append a comma before + // the next column in the command text. + bool appendComma = false; + + // Loop over the metadata for each result column. _SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData; _sortedColumnMappings = new List<_ColumnMapping>(metaDataSet.Length); for (int i = 0; i < metaDataSet.Length; i++) { _SqlMetaData metadata = metaDataSet[i]; - rejectColumn = false; - // Check for excluded types - if ((metadata.type == SqlDbType.Timestamp) - || ((metadata.IsIdentity) && !IsCopyOption(SqlBulkCopyOptions.KeepIdentity))) + bool matched = false; + bool rejected = false; + + // Look for a local match for the remote column. + for (int j = 0; j < _localColumnMappings.Count; ++j) { - // Remove metadata for excluded columns - metaDataSet[i] = null; - rejectColumn = true; - // We still need to find a matching column association - } + var localColumn = _localColumnMappings[j]; - // Find out if this column is associated - int assocId; - for (assocId = 0; assocId < _localColumnMappings.Count; assocId++) - { - if (!columnMappingStatusLookup.ContainsKey(_localColumnMappings[assocId].DestinationColumn)) - { - columnMappingStatusLookup.Add(_localColumnMappings[assocId].DestinationColumn, false); - } + // Are we missing a mapping between the result column and + // this local column (by ordinal or name)? + if (localColumn._destinationColumnOrdinal != metadata.ordinal + && UnquotedName(localColumn._destinationColumnName) != metadata.column) + { + // Yes, so move on to the next local column. + continue; + } - if ((_localColumnMappings[assocId]._destinationColumnOrdinal == metadata.ordinal) || - (UnquotedName(_localColumnMappings[assocId]._destinationColumnName) == metadata.column)) + // Ok, we found a matching local column. + matched = true; + + // Remove it from our unmatched set. + unmatchedColumns.Remove(localColumn.DestinationColumn); + + // Check for column types that we refuse to bulk load, even + // though we found a match. + // + // We will not process timestamp or identity columns. + // + if (metadata.type == SqlDbType.Timestamp + || (metadata.IsIdentity && !IsCopyOption(SqlBulkCopyOptions.KeepIdentity))) { - columnMappingStatusLookup[_localColumnMappings[assocId].DestinationColumn] = true; + rejected = true; + break; + } - if (rejectColumn) - { - nrejected++; // Count matched columns only - break; - } + _sortedColumnMappings.Add(new _ColumnMapping(localColumn._internalSourceColumnOrdinal, metadata)); + destColumnNames.Add(metadata.column); - _sortedColumnMappings.Add(new _ColumnMapping(_localColumnMappings[assocId]._internalSourceColumnOrdinal, metadata)); - destColumnNames.Add(metadata.column); - nmatched++; + // Append a comma for each subsequent column. + if (appendComma) + { + updateBulkCommandText.Append(", "); + } + else + { + appendComma = true; + } - if (nmatched > 1) - { - updateBulkCommandText.Append(", "); // A leading comma for all but the first one - } + // Some datatypes need special handling ... + if (metadata.type == SqlDbType.Variant) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); + } + else if (metadata.type == SqlDbType.Udt) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); + } + else if (metadata.type == SqlDbTypeExtensions.Json) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "json"); + } + else + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, metadata.type.ToString()); + } - // Some datatypes need special handling ... - if (metadata.type == SqlDbType.Variant) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); - } - else if (metadata.type == SqlDbType.Udt) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); - } - else if (metadata.type == SqlDbTypeExtensions.Json) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "json"); - } - else + switch (metadata.metaType.NullableType) + { + case TdsEnums.SQLNUMERICN: + case TdsEnums.SQLDECIMALN: + // Decimal and numeric need to include precision and scale + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0},{1})", metadata.precision, metadata.scale); + break; + case TdsEnums.SQLUDT: { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, metadata.type.ToString()); + if (metadata.IsLargeUdt) + { + updateBulkCommandText.Append("(max)"); + } + else + { + int size = metadata.length; + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); + } + break; } - - switch (metadata.metaType.NullableType) + case TdsEnums.SQLTIME: + case TdsEnums.SQLDATETIME2: + case TdsEnums.SQLDATETIMEOFFSET: + // date, dateime2, and datetimeoffset need to include scale + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", metadata.scale); + break; + default: { - case TdsEnums.SQLNUMERICN: - case TdsEnums.SQLDECIMALN: - // Decimal and numeric need to include precision and scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0},{1})", metadata.precision, metadata.scale); - break; - case TdsEnums.SQLUDT: - { - if (metadata.IsLargeUdt) - { - updateBulkCommandText.Append("(max)"); - } - else - { - int size = metadata.length; - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - break; - } - case TdsEnums.SQLTIME: - case TdsEnums.SQLDATETIME2: - case TdsEnums.SQLDATETIMEOFFSET: - // date, dateime2, and datetimeoffset need to include scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", metadata.scale); - break; - default: + // For non-long non-fixed types we need to add the Size + if (!metadata.metaType.IsFixed && !metadata.metaType.IsLong) + { + int size = metadata.length; + switch (metadata.metaType.NullableType) { - // For non-long non-fixed types we need to add the Size - if (!metadata.metaType.IsFixed && !metadata.metaType.IsLong) - { - int size = metadata.length; - switch (metadata.metaType.NullableType) - { - case TdsEnums.SQLNCHAR: - case TdsEnums.SQLNVARCHAR: - case TdsEnums.SQLNTEXT: - size /= 2; - break; - default: - break; - } - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - else if (metadata.metaType.IsPlp && metadata.metaType.SqlDbType != SqlDbType.Xml && metadata.metaType.SqlDbType != SqlDbTypeExtensions.Json) - { - // Partial length column prefix (max) - updateBulkCommandText.Append("(max)"); - } - break; + case TdsEnums.SQLNCHAR: + case TdsEnums.SQLNVARCHAR: + case TdsEnums.SQLNTEXT: + size /= 2; + break; + default: + break; } + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); + } + else if (metadata.metaType.IsPlp && metadata.metaType.SqlDbType != SqlDbType.Xml && metadata.metaType.SqlDbType != SqlDbTypeExtensions.Json) + { + // Partial length column prefix (max) + updateBulkCommandText.Append("(max)"); + } + break; } + } - // Get collation for column i - Result rowset = internalResults[CollationResultId]; - object rowvalue = rowset[i][CollationId]; + // Get collation for column i + Result rowset = internalResults[CollationResultId]; + object rowvalue = rowset[i][CollationId]; - bool shouldSendCollation; - switch (metadata.type) - { - case SqlDbType.Char: - case SqlDbType.NChar: - case SqlDbType.VarChar: - case SqlDbType.NVarChar: - case SqlDbType.Text: - case SqlDbType.NText: - shouldSendCollation = true; - break; - - default: - shouldSendCollation = false; - break; - } + bool shouldSendCollation; + switch (metadata.type) + { + case SqlDbType.Char: + case SqlDbType.NChar: + case SqlDbType.VarChar: + case SqlDbType.NVarChar: + case SqlDbType.Text: + case SqlDbType.NText: + shouldSendCollation = true; + break; - if (rowvalue != null && shouldSendCollation) + default: + shouldSendCollation = false; + break; + } + + if (rowvalue != null && shouldSendCollation) + { + Debug.Assert(rowvalue is SqlString); + SqlString collation_name = (SqlString)rowvalue; + if (!collation_name.IsNull) { - Debug.Assert(rowvalue is SqlString); - SqlString collation_name = (SqlString)rowvalue; - if (!collation_name.IsNull) + updateBulkCommandText.Append(" COLLATE " + collation_name.Value); + // Compare collations only if the collation value was set on the metadata + if (_sqlDataReaderRowSource != null && metadata.collation != null) { - updateBulkCommandText.Append(" COLLATE " + collation_name.Value); - // Compare collations only if the collation value was set on the metadata - if (_sqlDataReaderRowSource != null && metadata.collation != null) + // On SqlDataReader we can verify the sourcecolumn collation! + int sourceColumnId = localColumn._internalSourceColumnOrdinal; + int destinationLcid = metadata.collation.LCID; + int sourceLcid = _sqlDataReaderRowSource.GetLocaleId(sourceColumnId); + if (sourceLcid != destinationLcid) { - // On SqlDataReader we can verify the sourcecolumn collation! - int sourceColumnId = _localColumnMappings[assocId]._internalSourceColumnOrdinal; - int destinationLcid = metadata.collation.LCID; - int sourceLcid = _sqlDataReaderRowSource.GetLocaleId(sourceColumnId); - if (sourceLcid != destinationLcid) - { - throw SQL.BulkLoadLcidMismatch(sourceLcid, _sqlDataReaderRowSource.GetName(sourceColumnId), destinationLcid, metadata.column); - } + throw SQL.BulkLoadLcidMismatch(sourceLcid, _sqlDataReaderRowSource.GetName(sourceColumnId), destinationLcid, metadata.column); } } } - break; } + + // We found a match, so no need to keep looking. + break; } - if (assocId == _localColumnMappings.Count) + // Remove metadata for unmatched and rejected columns. + if (! matched || rejected) { - // Remove metadata for unmatched columns metaDataSet[i] = null; } } - // All columnmappings should have matched up - if (nmatched + nrejected != _localColumnMappings.Count) + // Do we have any unmatched columns? + if (unmatchedColumns.Count > 0) { - List unmatchedColumns = new List(); - - foreach(KeyValuePair keyValuePair in columnMappingStatusLookup) - { - if (!keyValuePair.Value) - { - unmatchedColumns.Add(keyValuePair.Key); - } - } - - throw SQL.BulkLoadNonMatchingColumnName(unmatchedColumns); + throw SQL.BulkLoadNonMatchingColumnNames(unmatchedColumns); } updateBulkCommandText.Append(")"); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 810487f52d..512c57c402 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -538,210 +538,218 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i string[] parts = MultipartIdentifier.ParseMultipartIdentifier(DestinationTableName, "[\"", "]\"", Strings.SQL_BulkCopyDestinationTableName, true); updateBulkCommandText.AppendFormat("insert bulk {0} (", ADP.BuildMultiPartName(parts)); - int nmatched = 0; // Number of columns that match and are accepted - int nrejected = 0; // Number of columns that match but were rejected - bool rejectColumn; // True if a column is rejected because of an excluded type - bool isInTransaction; - - isInTransaction = _connection.HasLocalTransaction; // Throw if there is a transaction but no flag is set - if (isInTransaction && - _externalTransaction == null && - _internalTransaction == null && - (_connection.Parser != null && _connection.Parser.CurrentTransaction != null && _connection.Parser.CurrentTransaction.IsLocal)) + if (_connection.HasLocalTransaction + && _externalTransaction == null + && _internalTransaction == null + && _connection.Parser != null + && _connection.Parser.CurrentTransaction != null + && _connection.Parser.CurrentTransaction.IsLocal) { throw SQL.BulkLoadExistingTransaction(); } HashSet destColumnNames = new HashSet(); - Dictionary columnMappingStatusLookup = new Dictionary(); - // Loop over the metadata for each column + // Keep track of any result columns that we don't have a local + // mapping for. + HashSet unmatchedColumns = new(); + + // Start by assuming all locally mapped Destination columns will be + // unmatched. + for (int i = 0; i < _localColumnMappings.Count; ++i) + { + unmatchedColumns.Add(_localColumnMappings[i].DestinationColumn); + } + + // Flag to remember whether or not we need to append a comma before + // the next column in the command text. + bool appendComma = false; + + // Loop over the metadata for each result column. _SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData; _sortedColumnMappings = new List<_ColumnMapping>(metaDataSet.Length); for (int i = 0; i < metaDataSet.Length; i++) { _SqlMetaData metadata = metaDataSet[i]; - rejectColumn = false; - // Check for excluded types - if ((metadata.type == SqlDbType.Timestamp) - || ((metadata.IsIdentity) && !IsCopyOption(SqlBulkCopyOptions.KeepIdentity))) + bool matched = false; + bool rejected = false; + + // Look for a local match for the remote column. + for (int j = 0; j < _localColumnMappings.Count; ++j) { - // Remove metadata for excluded columns - metaDataSet[i] = null; - rejectColumn = true; - // We still need to find a matching column association - } - - // Find out if this column is associated - int assocId; - for (assocId = 0; assocId < _localColumnMappings.Count; assocId++) - { - if (!columnMappingStatusLookup.ContainsKey(_localColumnMappings[assocId].DestinationColumn)) - { - columnMappingStatusLookup.Add(_localColumnMappings[assocId].DestinationColumn, false); - } + var localColumn = _localColumnMappings[j]; - if ((_localColumnMappings[assocId]._destinationColumnOrdinal == metadata.ordinal) || - (UnquotedName(_localColumnMappings[assocId]._destinationColumnName) == metadata.column)) + // Are we missing a mapping between the result column and + // this local column (by ordinal or name)? + if (localColumn._destinationColumnOrdinal != metadata.ordinal + && UnquotedName(localColumn._destinationColumnName) != metadata.column) { - columnMappingStatusLookup[_localColumnMappings[assocId].DestinationColumn] = true; + // Yes, so move on to the next local column. + continue; + } - if (rejectColumn) - { - nrejected++; // Count matched columns only - break; - } + // Ok, we found a matching local column. + matched = true; + + // Remove it from our unmatched set. + unmatchedColumns.Remove(localColumn.DestinationColumn); + + // Check for column types that we refuse to bulk load, even + // though we found a match. + // + // We will not process timestamp or identity columns. + // + if (metadata.type == SqlDbType.Timestamp + || (metadata.IsIdentity && !IsCopyOption(SqlBulkCopyOptions.KeepIdentity))) + { + rejected = true; + break; + } - _sortedColumnMappings.Add(new _ColumnMapping(_localColumnMappings[assocId]._internalSourceColumnOrdinal, metadata)); - destColumnNames.Add(metadata.column); - nmatched++; + _sortedColumnMappings.Add(new _ColumnMapping(localColumn._internalSourceColumnOrdinal, metadata)); + destColumnNames.Add(metadata.column); - if (nmatched > 1) - { - updateBulkCommandText.Append(", "); // A leading comma for all but the first one - } + // Append a comma for each subsequent column. + if (appendComma) + { + updateBulkCommandText.Append(", "); + } + else + { + appendComma = true; + } - // Some datatypes need special handling ... - if (metadata.type == SqlDbType.Variant) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); - } - else if (metadata.type == SqlDbType.Udt) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); - } - else if (metadata.type == SqlDbTypeExtensions.Json) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "json"); - } - else - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, typeof(SqlDbType).GetEnumName(metadata.type)); - } + // Some datatypes need special handling ... + if (metadata.type == SqlDbType.Variant) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); + } + else if (metadata.type == SqlDbType.Udt) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); + } + else if (metadata.type == SqlDbTypeExtensions.Json) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "json"); + } + else + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, typeof(SqlDbType).GetEnumName(metadata.type)); + } - switch (metadata.metaType.NullableType) - { - case TdsEnums.SQLNUMERICN: - case TdsEnums.SQLDECIMALN: - // Decimal and numeric need to include precision and scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0},{1})", metadata.precision, metadata.scale); - break; - case TdsEnums.SQLUDT: + switch (metadata.metaType.NullableType) + { + case TdsEnums.SQLNUMERICN: + case TdsEnums.SQLDECIMALN: + // Decimal and numeric need to include precision and scale + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0},{1})", metadata.precision, metadata.scale); + break; + case TdsEnums.SQLUDT: + { + if (metadata.IsLargeUdt) { - if (metadata.IsLargeUdt) - { - updateBulkCommandText.Append("(max)"); - } - else - { - int size = metadata.length; - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - break; + updateBulkCommandText.Append("(max)"); + } + else + { + int size = metadata.length; + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); } - case TdsEnums.SQLTIME: - case TdsEnums.SQLDATETIME2: - case TdsEnums.SQLDATETIMEOFFSET: - // date, dateime2, and datetimeoffset need to include scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", metadata.scale); break; - default: + } + case TdsEnums.SQLTIME: + case TdsEnums.SQLDATETIME2: + case TdsEnums.SQLDATETIMEOFFSET: + // date, dateime2, and datetimeoffset need to include scale + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", metadata.scale); + break; + default: + { + // For non-long non-fixed types we need to add the Size + if (!metadata.metaType.IsFixed && !metadata.metaType.IsLong) { - // For non-long non-fixed types we need to add the Size - if (!metadata.metaType.IsFixed && !metadata.metaType.IsLong) - { - int size = metadata.length; - switch (metadata.metaType.NullableType) - { - case TdsEnums.SQLNCHAR: - case TdsEnums.SQLNVARCHAR: - case TdsEnums.SQLNTEXT: - size /= 2; - break; - default: - break; - } - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - else if (metadata.metaType.IsPlp && metadata.metaType.SqlDbType != SqlDbType.Xml && metadata.metaType.SqlDbType != SqlDbTypeExtensions.Json) + int size = metadata.length; + switch (metadata.metaType.NullableType) { - // Partial length column prefix (max) - updateBulkCommandText.Append("(max)"); + case TdsEnums.SQLNCHAR: + case TdsEnums.SQLNVARCHAR: + case TdsEnums.SQLNTEXT: + size /= 2; + break; + default: + break; } - break; + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); } - } + else if (metadata.metaType.IsPlp && metadata.metaType.SqlDbType != SqlDbType.Xml && metadata.metaType.SqlDbType != SqlDbTypeExtensions.Json) + { + // Partial length column prefix (max) + updateBulkCommandText.Append("(max)"); + } + break; + } + } - // Get collation for column i - Result rowset = internalResults[CollationResultId]; - object rowvalue = rowset[i][CollationId]; + // Get collation for column i + Result rowset = internalResults[CollationResultId]; + object rowvalue = rowset[i][CollationId]; - bool shouldSendCollation; - switch (metadata.type) - { - case SqlDbType.Char: - case SqlDbType.NChar: - case SqlDbType.VarChar: - case SqlDbType.NVarChar: - case SqlDbType.Text: - case SqlDbType.NText: - shouldSendCollation = true; - break; + bool shouldSendCollation; + switch (metadata.type) + { + case SqlDbType.Char: + case SqlDbType.NChar: + case SqlDbType.VarChar: + case SqlDbType.NVarChar: + case SqlDbType.Text: + case SqlDbType.NText: + shouldSendCollation = true; + break; - default: - shouldSendCollation = false; - break; - } + default: + shouldSendCollation = false; + break; + } - if (rowvalue != null && shouldSendCollation) - { - Debug.Assert(rowvalue is SqlString); - SqlString collation_name = (SqlString)rowvalue; + if (rowvalue != null && shouldSendCollation) + { + Debug.Assert(rowvalue is SqlString); + SqlString collation_name = (SqlString)rowvalue; - if (!collation_name.IsNull) + if (!collation_name.IsNull) + { + updateBulkCommandText.Append(" COLLATE " + collation_name.Value); + // VSTFDEVDIV 461426: compare collations only if the collation value was set on the metadata + if (_sqlDataReaderRowSource != null && metadata.collation != null) { - updateBulkCommandText.Append(" COLLATE " + collation_name.Value); - // VSTFDEVDIV 461426: compare collations only if the collation value was set on the metadata - if (_sqlDataReaderRowSource != null && metadata.collation != null) + // On SqlDataReader we can verify the sourcecolumn collation! + int sourceColumnId = localColumn._internalSourceColumnOrdinal; + int destinationLcid = metadata.collation.LCID; + int sourceLcid = _sqlDataReaderRowSource.GetLocaleId(sourceColumnId); + if (sourceLcid != destinationLcid) { - // On SqlDataReader we can verify the sourcecolumn collation! - int sourceColumnId = _localColumnMappings[assocId]._internalSourceColumnOrdinal; - int destinationLcid = metadata.collation.LCID; - int sourceLcid = _sqlDataReaderRowSource.GetLocaleId(sourceColumnId); - if (sourceLcid != destinationLcid) - { - throw SQL.BulkLoadLcidMismatch(sourceLcid, _sqlDataReaderRowSource.GetName(sourceColumnId), destinationLcid, metadata.column); - } + throw SQL.BulkLoadLcidMismatch(sourceLcid, _sqlDataReaderRowSource.GetName(sourceColumnId), destinationLcid, metadata.column); } } } - break; } + break; } - if (assocId == _localColumnMappings.Count) + + // Remove metadata for unmatched and rejected columns. + if (! matched || rejected) { - // Remove metadata for unmatched columns metaDataSet[i] = null; } } - // All columnmappings should have matched up - if (nmatched + nrejected != _localColumnMappings.Count) + // Do we have any unmatched columns? + if (unmatchedColumns.Count > 0) { - List unmatchedColumns = new List(); - - foreach(KeyValuePair keyValuePair in columnMappingStatusLookup) - { - if (!keyValuePair.Value) - { - unmatchedColumns.Add(keyValuePair.Key); - } - } - - throw SQL.BulkLoadNonMatchingColumnName(unmatchedColumns); + throw SQL.BulkLoadNonMatchingColumnNames(unmatchedColumns); } updateBulkCommandText.Append(")"); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs index b18d929a41..a7f2b3d3ea 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1257,9 +1257,9 @@ internal static Exception BulkLoadNonMatchingColumnName(string columnName) { return BulkLoadNonMatchingColumnName(columnName, null); } - internal static Exception BulkLoadNonMatchingColumnName(IEnumerable columns) + internal static Exception BulkLoadNonMatchingColumnNames(IEnumerable columnNames) { - return BulkLoadNonMatchingColumnName(String.Join(",", columns), null); + return BulkLoadNonMatchingColumnName(string.Join(",", columnNames), null); } internal static Exception BulkLoadNonMatchingColumnName(string columnName, Exception e) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs index 7a49860f45..c9f054a882 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs @@ -126,20 +126,25 @@ public void KillConnection() { object tdsParser = ConnectionHelper.GetParser(_internalConnection); object stateObject = TdsParserHelper.GetStateObject(tdsParser); - object sessionHandle = TdsParserStateObjectHelper.GetSessionHandle(stateObject); - Assembly systemDotData = Assembly.Load(new AssemblyName(typeof(SqlConnection).GetTypeInfo().Assembly.FullName)); - Type sniHandleType = systemDotData.GetType("Microsoft.Data.SqlClient.SNI.SNIHandle"); - MethodInfo killConn = sniHandleType.GetMethod("KillConnection"); + Assembly assembly = Assembly.Load(new AssemblyName(typeof(SqlConnection).GetTypeInfo().Assembly.FullName)); + Type sniHandleType = assembly.GetType("Microsoft.Data.SqlClient.SNI.SNIHandle"); - if (killConn != null) + MethodInfo killConn = null; + if (sniHandleType is not null) { - killConn.Invoke(sessionHandle, null); + killConn = sniHandleType.GetMethod("KillConnection"); } - else + + if (killConn is null) { throw new InvalidOperationException("Error: Could not find SNI KillConnection test hook. This operation is only supported in debug builds."); } + + killConn.Invoke( + TdsParserStateObjectHelper.GetSessionHandle(stateObject), + null); + // Ensure kill occurs outside of check connection window Thread.Sleep(100); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs index 9f484093b1..625f0e9d9b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs @@ -5,13 +5,12 @@ using System; using System.Diagnostics; using System.Reflection; +using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals { internal static class TdsParserStateObjectHelper { - private static readonly Assembly s_systemDotData; - private static readonly Type s_tdsParserStateObject; private static readonly FieldInfo s_forceAllPends; private static readonly FieldInfo s_skipSendAttention; private static readonly FieldInfo s_forceSyncOverAsyncAfterFirstPend; @@ -22,19 +21,46 @@ internal static class TdsParserStateObjectHelper static TdsParserStateObjectHelper() { - s_systemDotData = typeof(Microsoft.Data.SqlClient.SqlConnection).GetTypeInfo().Assembly; - s_tdsParserStateObject = s_systemDotData.GetType("Microsoft.Data.SqlClient.TdsParserStateObject"); - s_forceAllPends = s_tdsParserStateObject.GetField("s_forceAllPends", BindingFlags.Static | BindingFlags.NonPublic); - s_skipSendAttention = s_tdsParserStateObject.GetField("s_skipSendAttention", BindingFlags.Static | BindingFlags.NonPublic); - s_forceSyncOverAsyncAfterFirstPend = s_tdsParserStateObject.GetField("s_forceSyncOverAsyncAfterFirstPend", BindingFlags.Static | BindingFlags.NonPublic); - s_failAsyncPends = s_tdsParserStateObject.GetField("s_failAsyncPends", BindingFlags.Static | BindingFlags.NonPublic); - s_forcePendingReadsToWaitForUser = s_tdsParserStateObject.GetField("s_forcePendingReadsToWaitForUser", BindingFlags.Static | BindingFlags.NonPublic); - s_tdsParserStateObjectManaged = s_systemDotData.GetType("Microsoft.Data.SqlClient.SNI.TdsParserStateObjectManaged"); - if (s_tdsParserStateObjectManaged != null) + Assembly assembly = typeof(Microsoft.Data.SqlClient.SqlConnection).GetTypeInfo().Assembly; + Assert.True(assembly is not null, nameof(assembly)); + + Type tdsParserStateObject = assembly.GetType("Microsoft.Data.SqlClient.TdsParserStateObject"); + Assert.True(tdsParserStateObject is not null, nameof(tdsParserStateObject)); + + s_forceAllPends = tdsParserStateObject.GetField("s_forceAllPends", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_forceAllPends is not null, nameof(s_forceAllPends)); + + s_skipSendAttention = tdsParserStateObject.GetField("s_skipSendAttention", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_skipSendAttention is not null, nameof(s_skipSendAttention)); + + s_forceSyncOverAsyncAfterFirstPend = tdsParserStateObject.GetField("s_forceSyncOverAsyncAfterFirstPend", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_forceSyncOverAsyncAfterFirstPend is not null, nameof(s_forceSyncOverAsyncAfterFirstPend)); + + s_failAsyncPends = tdsParserStateObject.GetField("s_failAsyncPends", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_failAsyncPends is not null, nameof(s_failAsyncPends)); + + s_forcePendingReadsToWaitForUser = tdsParserStateObject.GetField("s_forcePendingReadsToWaitForUser", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_forcePendingReadsToWaitForUser is not null, nameof(s_forcePendingReadsToWaitForUser)); + + // These managed SNI handles are allowed to be null, since they + // won't exist in .NET Framework builds. + s_tdsParserStateObjectManaged = + assembly.GetType("Microsoft.Data.SqlClient.SNI.TdsParserStateObjectManaged"); + s_tdsParserStateObjectManagedSessionHandle = null; + if (s_tdsParserStateObjectManaged is not null) { - s_tdsParserStateObjectManagedSessionHandle = s_tdsParserStateObjectManaged.GetField("_sessionHandle", BindingFlags.Instance | BindingFlags.NonPublic); + s_tdsParserStateObjectManagedSessionHandle = + s_tdsParserStateObjectManaged.GetField( + "_sessionHandle", + BindingFlags.Instance | BindingFlags.NonPublic); + // If we have the managed SNI type, we must have the session + // handle field. + Assert.True( + s_tdsParserStateObjectManagedSessionHandle is not null, + nameof(s_tdsParserStateObjectManagedSessionHandle)); } } + internal static bool ForceAllPends { get { return (bool)s_forceAllPends.GetValue(null); } @@ -65,25 +91,20 @@ internal static bool FailAsyncPends set { s_failAsyncPends.SetValue(null, value); } } - private static void VerifyObjectIsTdsParserStateObject(object stateObject) + internal static object GetSessionHandle(object stateObject) { if (stateObject == null) { throw new ArgumentNullException(nameof(stateObject)); } - if (s_tdsParserStateObjectManaged == null) + if (s_tdsParserStateObjectManaged is null) { throw new ArgumentException("Library being tested does not implement TdsParserStateObjectManaged", nameof(stateObject)); } - if (!s_tdsParserStateObjectManaged.IsInstanceOfType(stateObject)) + if (! s_tdsParserStateObjectManaged.IsInstanceOfType(stateObject)) { throw new ArgumentException("Object provided was not a TdsParserStateObjectManaged", nameof(stateObject)); } - } - - internal static object GetSessionHandle(object stateObject) - { - VerifyObjectIsTdsParserStateObject(stateObject); return s_tdsParserStateObjectManagedSessionHandle.GetValue(stateObject); } }