Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Sql bulk copy error message #437

Merged
merged 12 commits into from
Feb 29, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,30 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed)
private DataRowState _rowStateToSkip;
private IEnumerator _rowEnumerator;

private int RowNumber
{
get
{
int rowNo;

switch (_rowSourceType)
{
case ValueSourceType.RowArray:
rowNo = ((DataTable)_dataTableSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
break;
case ValueSourceType.DataTable:
rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
break;
case ValueSourceType.DbDataReader:
case ValueSourceType.IDataReader:
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
case ValueSourceType.Unspecified:
default:
return -1;
}
return ++rowNo;
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
}
}

private TdsParser _parser;
private TdsParserStateObject _stateObj;
private List<_ColumnMapping> _sortedColumnMappings;
Expand Down Expand Up @@ -1477,7 +1501,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
}
catch (SqlTruncateException)
{
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, ADP.ParameterValueOutOfRange(sqlValue));
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue));
}
}

Expand Down Expand Up @@ -1566,7 +1590,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re

default:
Debug.Fail("Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null));
throw SQL.BulkLoadCannotConvertValue(value.GetType(), metadata.metaType, null);
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), null);
}

if (typeChanged)
Expand All @@ -1583,7 +1607,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
{
throw;
}
throw SQL.BulkLoadCannotConvertValue(value.GetType(), metadata.metaType, e);
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,9 +812,21 @@ internal static Exception BulkLoadMappingsNamesOrOrdinalsOnly()
{
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadMappingsNamesOrOrdinalsOnly));
}
internal static Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, Exception e)
internal static Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, int ordinal, int rowNumber, bool isEncrypted, string columnName, string value, Exception e)
{
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadCannotConvertValue, sourcetype.Name, metatype.TypeName), e);
string quotedValue = string.Empty;
if (!isEncrypted)
{
quotedValue = string.Format(" '{0}'", (value.Length > 100 ? value.Substring(0, 100) : value));
}
if (rowNumber == -1)
{
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadCannotConvertValueWithoutRowNo, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName), e);
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadCannotConvertValue, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName, rowNumber), e);
}
}
internal static Exception BulkLoadNonMatchingColumnMapping()
{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@
<value>Mappings must be either all name or all ordinal based.</value>
</data>
<data name="SQL_BulkLoadCannotConvertValue" xml:space="preserve">
<value>The given value of type {0} from the data source cannot be converted to type {1} of the specified target column.</value>
<value>The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}] Row {5}.</value>
</data>
<data name="SQL_BulkLoadNonMatchingColumnMapping" xml:space="preserve">
<value>The given ColumnMapping does not match up with any column in the source or destination.</value>
Expand Down Expand Up @@ -1860,4 +1860,7 @@
<data name="SQLUDT_InvalidSize" xml:space="preserve">
<value>UDT size must be less than {1}, size: {0}</value>
</data>
<data name="SQL_BulkLoadCannotConvertValueWithoutRowNo" xml:space="preserve">
<value>The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,30 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed)
private DataRowState _rowStateToSkip;
private IEnumerator _rowEnumerator;

private int RowNumber
{
get
{
int rowNo;

switch (_rowSourceType)
{
case ValueSourceType.RowArray:
rowNo = ((DataTable)_dataTableSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
break;
case ValueSourceType.DataTable:
rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
break;
case ValueSourceType.DbDataReader:
case ValueSourceType.IDataReader:
case ValueSourceType.Unspecified:
default:
return -1;
}
return ++rowNo;
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
}
}

private TdsParser _parser;
private TdsParserStateObject _stateObj;
private List<_ColumnMapping> _sortedColumnMappings;
Expand Down Expand Up @@ -1629,11 +1653,11 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
}
catch (SqlTruncateException)
{
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, ADP.ParameterValueOutOfRange(sqlValue));
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue));
}
catch (Exception e)
{
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, e);
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e);
}
}

Expand Down Expand Up @@ -1722,7 +1746,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re

default:
Debug.Assert(false, "Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null));
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, null);
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), null);
}

if (typeChanged)
Expand All @@ -1739,7 +1763,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
{
throw;
}
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, e);
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -959,9 +959,21 @@ static internal Exception BulkLoadMappingsNamesOrOrdinalsOnly()
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadMappingsNamesOrOrdinalsOnly));
}
static internal Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, Exception e)
static internal Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, int ordinal, int rowNumber, bool isEncrypted, string columnName, string value, Exception e)
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadCannotConvertValue, sourcetype.Name, metatype.TypeName), e);
string quotedValue = string.Empty;
if (!isEncrypted)
{
quotedValue = string.Format(" '{0}'", (value.Length > 100 ? value.Substring(0, 100) : value));
}
if (rowNumber == -1)
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadCannotConvertValueWithoutRowNo, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName), e);
}
else
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadCannotConvertValue, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName, rowNumber), e);
}
}
static internal Exception BulkLoadNonMatchingColumnMapping()
{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2809,7 +2809,10 @@
<value>Mappings must be either all name or all ordinal based.</value>
</data>
<data name="SQL_BulkLoadCannotConvertValue" xml:space="preserve">
<value>The given value of type {0} from the data source cannot be converted to type {1} of the specified target column.</value>
<value>The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}] Row {5}.</value>
</data>
<data name="SQL_BulkLoadCannotConvertValueWithoutRowNo" xml:space="preserve">
<value>The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].</value>
</data>
<data name="SQL_BulkLoadNonMatchingColumnMapping" xml:space="preserve">
<value>The given ColumnMapping does not match up with any column in the source or destination.</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Data;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
{
/// <summary>
/// Always Encrypted public API Manual tests.
/// TODO: These tests are marked as Windows only for now but should be run for all platforms once the Master Key is accessible to this app from Azure Key Vault.
/// </summary>
[PlatformSpecific(TestPlatforms.Windows)]
public class BulkCopyAEErrorMessage : IClassFixture<SQLSetupStrategyCertStoreProvider>
{
private SQLSetupStrategyCertStoreProvider _fixture;

private readonly string _tableName;
private readonly string _columnName;

public BulkCopyAEErrorMessage(SQLSetupStrategyCertStoreProvider fixture)
{
_fixture = fixture;
_tableName = fixture.BulkCopyAEErrorMessageTestTable.Name;
_columnName = "c1";
}

[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
[ClassData(typeof(AEConnectionStringProvider))]
public void TextToIntErrorMessageTest(string connectionString)
{
string value = "stringValue";
DataTable dataTable = CreateDataTable(value);

Assert.True(StringToIntTest(connectionString, _tableName, dataTable, value, dataTable.Rows.Count), "Did not get any exceptions for DataTable when converting data from 'string' to 'int' datatype!");
Assert.True(StringToIntTest(connectionString, _tableName, dataTable.Select(), value, dataTable.Rows.Count),"Did not get any exceptions for DataRow[] when converting data from 'string' to 'int' datatype!");
Assert.True(StringToIntTest(connectionString, _tableName, dataTable.CreateDataReader(), value, -1),"Did not get any exceptions for DataReader when converting data from 'string' to 'int' datatype!");
}

private DataTable CreateDataTable(string value)
{
var dataTable = new DataTable();
dataTable.Columns.Add(_columnName, typeof(string));

var dataRow = dataTable.NewRow();
dataRow[_columnName] = value;
dataTable.Rows.Add(dataRow);
dataTable.AcceptChanges();

return dataTable;
}

private bool StringToIntTest(string connectionString, string targetTable, object dataSet, string value, int rowNo, string targetType = "int")
{
var encryptionEnabledConnectionString = new SqlConnectionStringBuilder(connectionString)
{
ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled
}.ConnectionString;

bool hitException = false;
try
{
using (var connection = new SqlConnection(encryptionEnabledConnectionString))
using (var bulkCopy = new SqlBulkCopy(connection)
{
EnableStreaming = true,
BatchSize = 1,
DestinationTableName = "[" + _tableName + "]"
})
{
connection.Open();
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(0, 0));

if (dataSet as DataTable != null)
{
bulkCopy.WriteToServer((DataTable)dataSet);
}
if (dataSet as DataRow[] != null)
{
bulkCopy.WriteToServer((DataRow[])dataSet);
}
if (dataSet as IDataReader != null)
{
bulkCopy.WriteToServer((IDataReader)dataSet);
}
}
}
catch (Exception ex)
{
string pattern;
object[] args =
new object[] { string.Empty, value.GetType().Name, targetType, 0, _columnName, rowNo };
if (rowNo == -1)
{
Array.Resize(ref args, args.Length - 1);
pattern = SystemDataResourceManager.Instance.SQL_BulkLoadCannotConvertValueWithoutRowNo;
}
else
{
pattern = SystemDataResourceManager.Instance.SQL_BulkLoadCannotConvertValue;
}

string expectedErrorMsg = string.Format(pattern, args);

Assert.True(ex.Message.Contains(expectedErrorMsg), "Unexpected error message: " + ex.Message);
hitException = true;
}
return hitException;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class SQLSetupStrategy : IDisposable
protected internal readonly X509Certificate2 certificate;
public string keyPath { get; internal set; }
public Table ApiTestTable { get; private set; }
public Table BulkCopyAEErrorMessageTestTable { get; private set; }
public Table BulkCopyAETestTable { get; private set; }
public Table SqlParameterPropertiesTable { get; private set; }
public Table End2EndSmokeTable { get; private set; }
Expand Down Expand Up @@ -112,6 +113,9 @@ protected List<Table> CreateTables(IList<ColumnEncryptionKey> columnEncryptionKe
ApiTestTable = new ApiTestTable(GenerateUniqueName("ApiTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
tables.Add(ApiTestTable);

BulkCopyAEErrorMessageTestTable = new BulkCopyAEErrorMessageTestTable(GenerateUniqueName("BulkCopyAEErrorMessageTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
tables.Add(BulkCopyAEErrorMessageTestTable);

BulkCopyAETestTable = new BulkCopyAETestTable(GenerateUniqueName("BulkCopyAETestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
tables.Add(BulkCopyAETestTable);

Expand Down