Skip to content

Commit

Permalink
Improved Sql bulk copy error message (#437)
Browse files Browse the repository at this point in the history
  • Loading branch information
DavoudEshtehari authored Feb 29, 2020
1 parent ba84cf0 commit 0dc60ad
Show file tree
Hide file tree
Showing 18 changed files with 508 additions and 31 deletions.
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:
case ValueSourceType.Unspecified:
default:
return -1;
}
return ++rowNo;
}
}

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);
}
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;
}
}

private TdsParser _parser;
private TdsParserStateObject _stateObj;
private List<_ColumnMapping> _sortedColumnMappings;
Expand Down Expand Up @@ -1626,11 +1650,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 @@ -1719,7 +1743,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 @@ -1736,7 +1760,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
Loading

0 comments on commit 0dc60ad

Please sign in to comment.