Skip to content

Commit 0dc60ad

Browse files
Improved Sql bulk copy error message (#437)
1 parent ba84cf0 commit 0dc60ad

File tree

18 files changed

+508
-31
lines changed

18 files changed

+508
-31
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,30 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed)
206206
private DataRowState _rowStateToSkip;
207207
private IEnumerator _rowEnumerator;
208208

209+
private int RowNumber
210+
{
211+
get
212+
{
213+
int rowNo;
214+
215+
switch (_rowSourceType)
216+
{
217+
case ValueSourceType.RowArray:
218+
rowNo = ((DataTable)_dataTableSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
219+
break;
220+
case ValueSourceType.DataTable:
221+
rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
222+
break;
223+
case ValueSourceType.DbDataReader:
224+
case ValueSourceType.IDataReader:
225+
case ValueSourceType.Unspecified:
226+
default:
227+
return -1;
228+
}
229+
return ++rowNo;
230+
}
231+
}
232+
209233
private TdsParser _parser;
210234
private TdsParserStateObject _stateObj;
211235
private List<_ColumnMapping> _sortedColumnMappings;
@@ -1477,7 +1501,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
14771501
}
14781502
catch (SqlTruncateException)
14791503
{
1480-
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, ADP.ParameterValueOutOfRange(sqlValue));
1504+
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue));
14811505
}
14821506
}
14831507

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

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

15721596
if (typeChanged)
@@ -1583,7 +1607,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
15831607
{
15841608
throw;
15851609
}
1586-
throw SQL.BulkLoadCannotConvertValue(value.GetType(), metadata.metaType, e);
1610+
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e);
15871611
}
15881612
}
15891613

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -812,9 +812,21 @@ internal static Exception BulkLoadMappingsNamesOrOrdinalsOnly()
812812
{
813813
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadMappingsNamesOrOrdinalsOnly));
814814
}
815-
internal static Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, Exception e)
815+
internal static Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, int ordinal, int rowNumber, bool isEncrypted, string columnName, string value, Exception e)
816816
{
817-
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadCannotConvertValue, sourcetype.Name, metatype.TypeName), e);
817+
string quotedValue = string.Empty;
818+
if (!isEncrypted)
819+
{
820+
quotedValue = string.Format(" '{0}'", (value.Length > 100 ? value.Substring(0, 100) : value));
821+
}
822+
if (rowNumber == -1)
823+
{
824+
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadCannotConvertValueWithoutRowNo, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName), e);
825+
}
826+
else
827+
{
828+
return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_BulkLoadCannotConvertValue, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName, rowNumber), e);
829+
}
818830
}
819831
internal static Exception BulkLoadNonMatchingColumnMapping()
820832
{

src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@
625625
<value>Mappings must be either all name or all ordinal based.</value>
626626
</data>
627627
<data name="SQL_BulkLoadCannotConvertValue" xml:space="preserve">
628-
<value>The given value of type {0} from the data source cannot be converted to type {1} of the specified target column.</value>
628+
<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>
629629
</data>
630630
<data name="SQL_BulkLoadNonMatchingColumnMapping" xml:space="preserve">
631631
<value>The given ColumnMapping does not match up with any column in the source or destination.</value>
@@ -1860,4 +1860,7 @@
18601860
<data name="SQLUDT_InvalidSize" xml:space="preserve">
18611861
<value>UDT size must be less than {1}, size: {0}</value>
18621862
</data>
1863+
<data name="SQL_BulkLoadCannotConvertValueWithoutRowNo" xml:space="preserve">
1864+
<value>The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].</value>
1865+
</data>
18631866
</root>

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,30 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed)
266266
private DataRowState _rowStateToSkip;
267267
private IEnumerator _rowEnumerator;
268268

269+
private int RowNumber
270+
{
271+
get
272+
{
273+
int rowNo;
274+
275+
switch (_rowSourceType)
276+
{
277+
case ValueSourceType.RowArray:
278+
rowNo = ((DataTable)_dataTableSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
279+
break;
280+
case ValueSourceType.DataTable:
281+
rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow);
282+
break;
283+
case ValueSourceType.DbDataReader:
284+
case ValueSourceType.IDataReader:
285+
case ValueSourceType.Unspecified:
286+
default:
287+
return -1;
288+
}
289+
return ++rowNo;
290+
}
291+
}
292+
269293
private TdsParser _parser;
270294
private TdsParserStateObject _stateObj;
271295
private List<_ColumnMapping> _sortedColumnMappings;
@@ -1626,11 +1650,11 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
16261650
}
16271651
catch (SqlTruncateException)
16281652
{
1629-
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, ADP.ParameterValueOutOfRange(sqlValue));
1653+
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue));
16301654
}
16311655
catch (Exception e)
16321656
{
1633-
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, e);
1657+
throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e);
16341658
}
16351659
}
16361660

@@ -1719,7 +1743,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
17191743

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

17251749
if (typeChanged)
@@ -1736,7 +1760,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re
17361760
{
17371761
throw;
17381762
}
1739-
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, e);
1763+
throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e);
17401764
}
17411765
}
17421766

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -959,9 +959,21 @@ static internal Exception BulkLoadMappingsNamesOrOrdinalsOnly()
959959
{
960960
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadMappingsNamesOrOrdinalsOnly));
961961
}
962-
static internal Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, Exception e)
962+
static internal Exception BulkLoadCannotConvertValue(Type sourcetype, MetaType metatype, int ordinal, int rowNumber, bool isEncrypted, string columnName, string value, Exception e)
963963
{
964-
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadCannotConvertValue, sourcetype.Name, metatype.TypeName), e);
964+
string quotedValue = string.Empty;
965+
if (!isEncrypted)
966+
{
967+
quotedValue = string.Format(" '{0}'", (value.Length > 100 ? value.Substring(0, 100) : value));
968+
}
969+
if (rowNumber == -1)
970+
{
971+
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadCannotConvertValueWithoutRowNo, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName), e);
972+
}
973+
else
974+
{
975+
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadCannotConvertValue, quotedValue, sourcetype.Name, metatype.TypeName, ordinal, columnName, rowNumber), e);
976+
}
965977
}
966978
static internal Exception BulkLoadNonMatchingColumnMapping()
967979
{

src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2809,7 +2809,10 @@
28092809
<value>Mappings must be either all name or all ordinal based.</value>
28102810
</data>
28112811
<data name="SQL_BulkLoadCannotConvertValue" xml:space="preserve">
2812-
<value>The given value of type {0} from the data source cannot be converted to type {1} of the specified target column.</value>
2812+
<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>
2813+
</data>
2814+
<data name="SQL_BulkLoadCannotConvertValueWithoutRowNo" xml:space="preserve">
2815+
<value>The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].</value>
28132816
</data>
28142817
<data name="SQL_BulkLoadNonMatchingColumnMapping" xml:space="preserve">
28152818
<value>The given ColumnMapping does not match up with any column in the source or destination.</value>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Data;
7+
using Xunit;
8+
9+
namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
10+
{
11+
/// <summary>
12+
/// Always Encrypted public API Manual tests.
13+
/// 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.
14+
/// </summary>
15+
[PlatformSpecific(TestPlatforms.Windows)]
16+
public class BulkCopyAEErrorMessage : IClassFixture<SQLSetupStrategyCertStoreProvider>
17+
{
18+
private SQLSetupStrategyCertStoreProvider _fixture;
19+
20+
private readonly string _tableName;
21+
private readonly string _columnName;
22+
23+
public BulkCopyAEErrorMessage(SQLSetupStrategyCertStoreProvider fixture)
24+
{
25+
_fixture = fixture;
26+
_tableName = fixture.BulkCopyAEErrorMessageTestTable.Name;
27+
_columnName = "c1";
28+
}
29+
30+
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
31+
[ClassData(typeof(AEConnectionStringProvider))]
32+
public void TextToIntErrorMessageTest(string connectionString)
33+
{
34+
string value = "stringValue";
35+
DataTable dataTable = CreateDataTable(value);
36+
37+
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!");
38+
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!");
39+
Assert.True(StringToIntTest(connectionString, _tableName, dataTable.CreateDataReader(), value, -1),"Did not get any exceptions for DataReader when converting data from 'string' to 'int' datatype!");
40+
}
41+
42+
private DataTable CreateDataTable(string value)
43+
{
44+
var dataTable = new DataTable();
45+
dataTable.Columns.Add(_columnName, typeof(string));
46+
47+
var dataRow = dataTable.NewRow();
48+
dataRow[_columnName] = value;
49+
dataTable.Rows.Add(dataRow);
50+
dataTable.AcceptChanges();
51+
52+
return dataTable;
53+
}
54+
55+
private bool StringToIntTest(string connectionString, string targetTable, object dataSet, string value, int rowNo, string targetType = "int")
56+
{
57+
var encryptionEnabledConnectionString = new SqlConnectionStringBuilder(connectionString)
58+
{
59+
ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled
60+
}.ConnectionString;
61+
62+
bool hitException = false;
63+
try
64+
{
65+
using (var connection = new SqlConnection(encryptionEnabledConnectionString))
66+
using (var bulkCopy = new SqlBulkCopy(connection)
67+
{
68+
EnableStreaming = true,
69+
BatchSize = 1,
70+
DestinationTableName = "[" + _tableName + "]"
71+
})
72+
{
73+
connection.Open();
74+
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(0, 0));
75+
76+
if (dataSet as DataTable != null)
77+
{
78+
bulkCopy.WriteToServer((DataTable)dataSet);
79+
}
80+
if (dataSet as DataRow[] != null)
81+
{
82+
bulkCopy.WriteToServer((DataRow[])dataSet);
83+
}
84+
if (dataSet as IDataReader != null)
85+
{
86+
bulkCopy.WriteToServer((IDataReader)dataSet);
87+
}
88+
}
89+
}
90+
catch (Exception ex)
91+
{
92+
string pattern;
93+
object[] args =
94+
new object[] { string.Empty, value.GetType().Name, targetType, 0, _columnName, rowNo };
95+
if (rowNo == -1)
96+
{
97+
Array.Resize(ref args, args.Length - 1);
98+
pattern = SystemDataResourceManager.Instance.SQL_BulkLoadCannotConvertValueWithoutRowNo;
99+
}
100+
else
101+
{
102+
pattern = SystemDataResourceManager.Instance.SQL_BulkLoadCannotConvertValue;
103+
}
104+
105+
string expectedErrorMsg = string.Format(pattern, args);
106+
107+
Assert.True(ex.Message.Contains(expectedErrorMsg), "Unexpected error message: " + ex.Message);
108+
hitException = true;
109+
}
110+
return hitException;
111+
}
112+
}
113+
}

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class SQLSetupStrategy : IDisposable
1717
protected internal readonly X509Certificate2 certificate;
1818
public string keyPath { get; internal set; }
1919
public Table ApiTestTable { get; private set; }
20+
public Table BulkCopyAEErrorMessageTestTable { get; private set; }
2021
public Table BulkCopyAETestTable { get; private set; }
2122
public Table SqlParameterPropertiesTable { get; private set; }
2223
public Table End2EndSmokeTable { get; private set; }
@@ -112,6 +113,9 @@ protected List<Table> CreateTables(IList<ColumnEncryptionKey> columnEncryptionKe
112113
ApiTestTable = new ApiTestTable(GenerateUniqueName("ApiTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
113114
tables.Add(ApiTestTable);
114115

116+
BulkCopyAEErrorMessageTestTable = new BulkCopyAEErrorMessageTestTable(GenerateUniqueName("BulkCopyAEErrorMessageTestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
117+
tables.Add(BulkCopyAEErrorMessageTestTable);
118+
115119
BulkCopyAETestTable = new BulkCopyAETestTable(GenerateUniqueName("BulkCopyAETestTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]);
116120
tables.Add(BulkCopyAETestTable);
117121

0 commit comments

Comments
 (0)