@@ -37,7 +37,7 @@ class OpenRowsetBulkNode : BaseDataNode
3737 /// </summary>
3838 [ Category ( "OpenRowset" ) ]
3939 [ Description ( "The format of the file to load" ) ]
40- public string Format { get ; set ; }
40+ public string DataFileFormat { get ; set ; }
4141
4242 /// <summary>
4343 /// Indicates if the file is to be read as a single value
@@ -101,6 +101,20 @@ class OpenRowsetBulkNode : BaseDataNode
101101 [ Description ( "Specifies a character that is used to escape the quote character in the CSV file" ) ]
102102 public string EscapeChar { get ; set ; } = "\" " ;
103103
104+ /// <summary>
105+ /// Specifies the maximum number of syntax errors or nonconforming rows, which can occur before OPENROWSET throws an exception
106+ /// </summary>
107+ [ Category ( "OpenRowset" ) ]
108+ [ Description ( "Specifies the maximum number of syntax errors or nonconforming rows, which can occur before OPENROWSET throws an exception" ) ]
109+ public int MaxErrors { get ; set ; } = 10 ;
110+
111+ /// <summary>
112+ /// Specifies the file used to collect rows that have formatting errors
113+ /// </summary>
114+ [ Category ( "OpenRowset" ) ]
115+ [ Description ( "Specifies the file used to collect rows that have formatting errors" ) ]
116+ public string ErrorFile { get ; set ; }
117+
104118 public override void AddRequiredColumns ( NodeCompilationContext context , IList < string > requiredColumns )
105119 {
106120 }
@@ -207,42 +221,67 @@ protected override IEnumerable<Entity> ExecuteInternal(NodeExecutionContext cont
207221 csvConfig . Quote = FieldQuote [ 0 ] ;
208222 csvConfig . Escape = EscapeChar [ 0 ] ;
209223
210- foreach ( var filename in EnumerateFiles ( dir , file ) )
224+ var errors = 0 ;
225+
226+ using ( var errorsWriter = OpenWriter ( ErrorFile ) )
227+ using ( var logWriter = OpenWriter ( String . IsNullOrEmpty ( ErrorFile ) ? ErrorFile : Path . ChangeExtension ( ErrorFile , ".ERROR.txt" ) ) )
211228 {
212- using ( var reader = new StreamReader ( OpenFile ( filename ) , GetEncoding ( ) ) )
213- using ( var csv = new CsvReader ( reader , csvConfig ) )
229+ foreach ( var filename in EnumerateFiles ( dir , file ) )
214230 {
215- var rowNum = 0 ;
216-
217- while ( csv . Read ( ) )
231+ using ( var reader = new StreamReader ( OpenFile ( filename ) , GetEncoding ( ) ) )
232+ using ( var csv = new CsvParser ( reader , csvConfig ) )
218233 {
219- rowNum ++ ;
234+ var rowNum = 0 ;
220235
221- if ( rowNum < FirstRow )
222- continue ;
236+ while ( csv . Read ( ) )
237+ {
238+ rowNum ++ ;
223239
224- if ( LastRow > 0 && rowNum > LastRow )
225- break ;
240+ if ( rowNum < FirstRow )
241+ continue ;
226242
227- var record = new Entity ( ) ;
243+ if ( LastRow > 0 && rowNum > LastRow )
244+ break ;
228245
229- for ( var i = 0 ; i < Schema . Count ; i ++ )
230- {
231- var col = Schema [ i ] ;
232- var escapedName = col . ColumnIdentifier . Value . EscapeIdentifier ( ) ;
233- var value = csv . GetField ( i ) ;
246+ var record = new Entity ( ) ;
247+ var valid = true ;
234248
235- // TODO: If we're using the RAW code page and loading into a string column, we need to
236- // re-interpret the bytes we've loaded into the string using the encoding for this column.
249+ try
250+ {
251+ for ( var i = 0 ; i < Schema . Count ; i ++ )
252+ {
253+ var col = Schema [ i ] ;
254+ var escapedName = col . ColumnIdentifier . Value . EscapeIdentifier ( ) ;
255+ var value = csv [ i ] ;
237256
238- // Convert the value to a SqlString, then to the target type
239- var sqlValue = SqlTypeConverter . NetToSqlType ( context . PrimaryDataSource , value , nvarcharmax ) ;
240- sqlValue = SqlTypeConverter . GetConversion ( nvarcharmax , col . DataType ) ( sqlValue , eec ) ;
257+ // TODO: If we're using the RAW code page and loading into a string column, we need to
258+ // re-interpret the bytes we've loaded into the string using the encoding for this column.
241259
242- record [ escapedAlias + "." + escapedName ] = sqlValue ;
243- }
260+ // Convert the value to a SqlString, then to the target type
261+ var sqlValue = SqlTypeConverter . NetToSqlType ( context . PrimaryDataSource , value , nvarcharmax ) ;
262+ sqlValue = SqlTypeConverter . GetConversion ( nvarcharmax , col . DataType ) ( sqlValue , eec ) ;
263+
264+ record [ escapedAlias + "." + escapedName ] = sqlValue ;
265+ }
266+ }
267+ catch ( Exception ex )
268+ {
269+ errors ++ ;
270+ valid = false ;
244271
245- yield return record ;
272+ if ( errorsWriter != null )
273+ errorsWriter . Write ( csv . RawRecord ) ;
274+
275+ if ( logWriter != null )
276+ logWriter . WriteLine ( $ "File: { filename } , Row: { rowNum } , Error: { ex . Message } ") ;
277+
278+ if ( errors == MaxErrors )
279+ throw new QueryExecutionException ( Sql4CdsError . OpenRowsetBulkMaxErrorsReached ( MaxErrors ) , ex ) ;
280+ }
281+
282+ if ( valid )
283+ yield return record ;
284+ }
246285 }
247286 }
248287 }
@@ -284,6 +323,22 @@ private Stream OpenFile(string filename)
284323 }
285324 }
286325
326+ private StreamWriter OpenWriter ( string filename )
327+ {
328+ if ( String . IsNullOrEmpty ( filename ) )
329+ return null ;
330+
331+ try
332+ {
333+ var stream = File . Open ( filename , FileMode . CreateNew , FileAccess . Write ) ;
334+ return new StreamWriter ( stream , Encoding . UTF8 ) ;
335+ }
336+ catch ( Exception ex )
337+ {
338+ throw new QueryExecutionException ( Sql4CdsError . OpenRowsetBulkErrorWritingFile ( filename , ex . Message ) , ex ) ;
339+ }
340+ }
341+
287342 private Encoding GetEncoding ( )
288343 {
289344 switch ( CodePage )
@@ -317,7 +372,7 @@ public override object Clone()
317372 var clone = new OpenRowsetBulkNode ( ) ;
318373 clone . Filename = Filename ;
319374 clone . Alias = Alias ;
320- clone . Format = Format ;
375+ clone . DataFileFormat = DataFileFormat ;
321376 clone . SingleOption = SingleOption ;
322377 clone . Schema = Schema ;
323378 clone . FirstRow = FirstRow ;
@@ -327,6 +382,8 @@ public override object Clone()
327382 clone . FieldTerminator = FieldTerminator ;
328383 clone . FieldQuote = FieldQuote ;
329384 clone . EscapeChar = EscapeChar ;
385+ clone . MaxErrors = MaxErrors ;
386+ clone . ErrorFile = ErrorFile ;
330387 return clone ;
331388 }
332389 }
0 commit comments