@@ -532,207 +532,219 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
532
532
533
533
string [ ] parts = MultipartIdentifier . ParseMultipartIdentifier ( DestinationTableName , "[\" " , "]\" " , Strings . SQL_BulkCopyDestinationTableName , true ) ;
534
534
updateBulkCommandText . AppendFormat ( "insert bulk {0} (" , ADP . BuildMultiPartName ( parts ) ) ;
535
- int nmatched = 0 ; // Number of columns that match and are accepted
536
- int nrejected = 0 ; // Number of columns that match but were rejected
537
- bool rejectColumn ; // True if a column is rejected because of an excluded type
538
535
539
- bool isInTransaction ;
540
-
541
- isInTransaction = _connection . HasLocalTransaction ;
542
536
// Throw if there is a transaction but no flag is set
543
- if ( isInTransaction && _externalTransaction == null && _internalTransaction == null && ( _connection . Parser != null && _connection . Parser . CurrentTransaction != null && _connection . Parser . CurrentTransaction . IsLocal ) )
537
+ if ( _connection . HasLocalTransaction
538
+ && _externalTransaction == null
539
+ && _internalTransaction == null
540
+ && _connection . Parser != null
541
+ && _connection . Parser . CurrentTransaction != null
542
+ && _connection . Parser . CurrentTransaction . IsLocal )
544
543
{
545
544
throw SQL . BulkLoadExistingTransaction ( ) ;
546
545
}
547
546
548
547
HashSet < string > destColumnNames = new HashSet < string > ( ) ;
549
548
550
- Dictionary < string , bool > columnMappingStatusLookup = new Dictionary < string , bool > ( ) ;
551
- // Loop over the metadata for each column
549
+ // Keep track of any result columns that we don't have a local
550
+ // mapping for.
551
+ HashSet < string > unmatchedColumns = new ( _localColumnMappings . Count ) ;
552
+
553
+ // Start by assuming all locally mapped Destination columns will be
554
+ // unmatched.
555
+ for ( int i = 0 ; i < _localColumnMappings . Count ; ++ i )
556
+ {
557
+ unmatchedColumns . Add ( _localColumnMappings [ i ] . DestinationColumn ) ;
558
+ }
559
+
560
+ // Flag to remember whether or not we need to append a comma before
561
+ // the next column in the command text.
562
+ bool appendComma = false ;
563
+
564
+ // Loop over the metadata for each result column.
552
565
_SqlMetaDataSet metaDataSet = internalResults [ MetaDataResultId ] . MetaData ;
553
566
_sortedColumnMappings = new List < _ColumnMapping > ( metaDataSet . Length ) ;
554
567
for ( int i = 0 ; i < metaDataSet . Length ; i ++ )
555
568
{
556
569
_SqlMetaData metadata = metaDataSet [ i ] ;
557
- rejectColumn = false ;
558
570
559
- // Check for excluded types
560
- if ( ( metadata . type == SqlDbType . Timestamp )
561
- || ( ( metadata . IsIdentity ) & & ! IsCopyOption ( SqlBulkCopyOptions . KeepIdentity ) ) )
571
+ bool matched = false ;
572
+ bool rejected = false ;
573
+
574
+ // Look for a local match for the remote column.
575
+ for ( int j = 0 ; j < _localColumnMappings . Count ; ++ j )
562
576
{
563
- // Remove metadata for excluded columns
564
- metaDataSet [ i ] = null ;
565
- rejectColumn = true ;
566
- // We still need to find a matching column association
567
- }
577
+ var localColumn = _localColumnMappings [ j ] ;
568
578
569
- // Find out if this column is associated
570
- int assocId ;
571
- for ( assocId = 0 ; assocId < _localColumnMappings . Count ; assocId ++ )
572
- {
573
- if ( ! columnMappingStatusLookup . ContainsKey ( _localColumnMappings [ assocId ] . DestinationColumn ) )
574
- {
575
- columnMappingStatusLookup . Add ( _localColumnMappings [ assocId ] . DestinationColumn , false ) ;
576
- }
579
+ // Are we missing a mapping between the result column and
580
+ // this local column (by ordinal or name)?
581
+ if ( localColumn . _destinationColumnOrdinal != metadata . ordinal
582
+ && UnquotedName ( localColumn . _destinationColumnName ) != metadata . column )
583
+ {
584
+ // Yes, so move on to the next local column.
585
+ continue ;
586
+ }
577
587
578
- if ( ( _localColumnMappings [ assocId ] . _destinationColumnOrdinal == metadata . ordinal ) ||
579
- ( UnquotedName ( _localColumnMappings [ assocId ] . _destinationColumnName ) == metadata . column ) )
588
+ // Ok, we found a matching local column.
589
+ matched = true ;
590
+
591
+ // Remove it from our unmatched set.
592
+ unmatchedColumns . Remove ( localColumn . DestinationColumn ) ;
593
+
594
+ // Check for column types that we refuse to bulk load, even
595
+ // though we found a match.
596
+ //
597
+ // We will not process timestamp or identity columns.
598
+ //
599
+ if ( metadata . type == SqlDbType . Timestamp
600
+ || ( metadata . IsIdentity && ! IsCopyOption ( SqlBulkCopyOptions . KeepIdentity ) ) )
580
601
{
581
- columnMappingStatusLookup [ _localColumnMappings [ assocId ] . DestinationColumn ] = true ;
602
+ rejected = true ;
603
+ break ;
604
+ }
582
605
583
- if ( rejectColumn )
584
- {
585
- nrejected ++ ; // Count matched columns only
586
- break ;
587
- }
606
+ _sortedColumnMappings . Add ( new _ColumnMapping ( localColumn . _internalSourceColumnOrdinal , metadata ) ) ;
607
+ destColumnNames . Add ( metadata . column ) ;
588
608
589
- _sortedColumnMappings . Add ( new _ColumnMapping ( _localColumnMappings [ assocId ] . _internalSourceColumnOrdinal , metadata ) ) ;
590
- destColumnNames . Add ( metadata . column ) ;
591
- nmatched ++ ;
609
+ // Append a comma for each subsequent column.
610
+ if ( appendComma )
611
+ {
612
+ updateBulkCommandText . Append ( ", " ) ;
613
+ }
614
+ else
615
+ {
616
+ appendComma = true ;
617
+ }
592
618
593
- if ( nmatched > 1 )
594
- {
595
- updateBulkCommandText . Append ( ", " ) ; // A leading comma for all but the first one
596
- }
619
+ // Some datatypes need special handling ...
620
+ if ( metadata . type == SqlDbType . Variant )
621
+ {
622
+ AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , "sql_variant" ) ;
623
+ }
624
+ else if ( metadata . type == SqlDbType . Udt )
625
+ {
626
+ AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , "varbinary" ) ;
627
+ }
628
+ else if ( metadata . type == SqlDbTypeExtensions . Json )
629
+ {
630
+ AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , "json" ) ;
631
+ }
632
+ else
633
+ {
634
+ AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , metadata . type . ToString ( ) ) ;
635
+ }
597
636
598
- // Some datatypes need special handling ...
599
- if ( metadata . type == SqlDbType . Variant )
600
- {
601
- AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , "sql_variant" ) ;
602
- }
603
- else if ( metadata . type == SqlDbType . Udt )
604
- {
605
- AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , "varbinary" ) ;
606
- }
607
- else if ( metadata . type == SqlDbTypeExtensions . Json )
608
- {
609
- AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , "json" ) ;
610
- }
611
- else
637
+ switch ( metadata . metaType . NullableType )
638
+ {
639
+ case TdsEnums . SQLNUMERICN :
640
+ case TdsEnums . SQLDECIMALN :
641
+ // Decimal and numeric need to include precision and scale
642
+ updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0},{1})" , metadata . precision , metadata . scale ) ;
643
+ break ;
644
+ case TdsEnums . SQLUDT :
612
645
{
613
- AppendColumnNameAndTypeName ( updateBulkCommandText , metadata . column , metadata . type . ToString ( ) ) ;
646
+ if ( metadata . IsLargeUdt )
647
+ {
648
+ updateBulkCommandText . Append ( "(max)" ) ;
649
+ }
650
+ else
651
+ {
652
+ int size = metadata . length ;
653
+ updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0})" , size ) ;
654
+ }
655
+ break ;
614
656
}
615
-
616
- switch ( metadata . metaType . NullableType )
657
+ case TdsEnums . SQLTIME :
658
+ case TdsEnums . SQLDATETIME2 :
659
+ case TdsEnums . SQLDATETIMEOFFSET :
660
+ // date, dateime2, and datetimeoffset need to include scale
661
+ updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0})" , metadata . scale ) ;
662
+ break ;
663
+ default :
617
664
{
618
- case TdsEnums . SQLNUMERICN :
619
- case TdsEnums . SQLDECIMALN :
620
- // Decimal and numeric need to include precision and scale
621
- updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0},{1})" , metadata . precision , metadata . scale ) ;
622
- break ;
623
- case TdsEnums . SQLUDT :
624
- {
625
- if ( metadata . IsLargeUdt )
626
- {
627
- updateBulkCommandText . Append ( "(max)" ) ;
628
- }
629
- else
630
- {
631
- int size = metadata . length ;
632
- updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0})" , size ) ;
633
- }
634
- break ;
635
- }
636
- case TdsEnums . SQLTIME :
637
- case TdsEnums . SQLDATETIME2 :
638
- case TdsEnums . SQLDATETIMEOFFSET :
639
- // date, dateime2, and datetimeoffset need to include scale
640
- updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0})" , metadata . scale ) ;
641
- break ;
642
- default :
665
+ // For non-long non-fixed types we need to add the Size
666
+ if ( ! metadata . metaType . IsFixed && ! metadata . metaType . IsLong )
667
+ {
668
+ int size = metadata . length ;
669
+ switch ( metadata . metaType . NullableType )
643
670
{
644
- // For non-long non-fixed types we need to add the Size
645
- if ( ! metadata . metaType . IsFixed && ! metadata . metaType . IsLong )
646
- {
647
- int size = metadata . length ;
648
- switch ( metadata . metaType . NullableType )
649
- {
650
- case TdsEnums . SQLNCHAR :
651
- case TdsEnums . SQLNVARCHAR :
652
- case TdsEnums . SQLNTEXT :
653
- size /= 2 ;
654
- break ;
655
- default :
656
- break ;
657
- }
658
- updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0})" , size ) ;
659
- }
660
- else if ( metadata . metaType . IsPlp && metadata . metaType . SqlDbType != SqlDbType . Xml && metadata . metaType . SqlDbType != SqlDbTypeExtensions . Json )
661
- {
662
- // Partial length column prefix (max)
663
- updateBulkCommandText . Append ( "(max)" ) ;
664
- }
665
- break ;
671
+ case TdsEnums . SQLNCHAR :
672
+ case TdsEnums . SQLNVARCHAR :
673
+ case TdsEnums . SQLNTEXT :
674
+ size /= 2 ;
675
+ break ;
676
+ default :
677
+ break ;
666
678
}
679
+ updateBulkCommandText . AppendFormat ( ( IFormatProvider ) null , "({0})" , size ) ;
680
+ }
681
+ else if ( metadata . metaType . IsPlp && metadata . metaType . SqlDbType != SqlDbType . Xml && metadata . metaType . SqlDbType != SqlDbTypeExtensions . Json )
682
+ {
683
+ // Partial length column prefix (max)
684
+ updateBulkCommandText . Append ( "(max)" ) ;
685
+ }
686
+ break ;
667
687
}
688
+ }
668
689
669
- // Get collation for column i
670
- Result rowset = internalResults [ CollationResultId ] ;
671
- object rowvalue = rowset [ i ] [ CollationId ] ;
690
+ // Get collation for column i
691
+ Result rowset = internalResults [ CollationResultId ] ;
692
+ object rowvalue = rowset [ i ] [ CollationId ] ;
672
693
673
- bool shouldSendCollation ;
674
- switch ( metadata . type )
675
- {
676
- case SqlDbType . Char :
677
- case SqlDbType . NChar :
678
- case SqlDbType . VarChar :
679
- case SqlDbType . NVarChar :
680
- case SqlDbType . Text :
681
- case SqlDbType . NText :
682
- shouldSendCollation = true ;
683
- break ;
684
-
685
- default :
686
- shouldSendCollation = false ;
687
- break ;
688
- }
694
+ bool shouldSendCollation ;
695
+ switch ( metadata . type )
696
+ {
697
+ case SqlDbType . Char :
698
+ case SqlDbType . NChar :
699
+ case SqlDbType . VarChar :
700
+ case SqlDbType . NVarChar :
701
+ case SqlDbType . Text :
702
+ case SqlDbType . NText :
703
+ shouldSendCollation = true ;
704
+ break ;
689
705
690
- if ( rowvalue != null && shouldSendCollation )
706
+ default :
707
+ shouldSendCollation = false ;
708
+ break ;
709
+ }
710
+
711
+ if ( rowvalue != null && shouldSendCollation )
712
+ {
713
+ Debug . Assert ( rowvalue is SqlString ) ;
714
+ SqlString collation_name = ( SqlString ) rowvalue ;
715
+ if ( ! collation_name . IsNull )
691
716
{
692
- Debug . Assert ( rowvalue is SqlString ) ;
693
- SqlString collation_name = ( SqlString ) rowvalue ;
694
- if ( ! collation_name . IsNull )
717
+ updateBulkCommandText . Append ( " COLLATE " + collation_name . Value ) ;
718
+ // Compare collations only if the collation value was set on the metadata
719
+ if ( _sqlDataReaderRowSource != null && metadata . collation != null )
695
720
{
696
- updateBulkCommandText . Append ( " COLLATE " + collation_name . Value ) ;
697
- // Compare collations only if the collation value was set on the metadata
698
- if ( _sqlDataReaderRowSource != null && metadata . collation != null )
721
+ // On SqlDataReader we can verify the sourcecolumn collation!
722
+ int sourceColumnId = localColumn . _internalSourceColumnOrdinal ;
723
+ int destinationLcid = metadata . collation . LCID ;
724
+ int sourceLcid = _sqlDataReaderRowSource . GetLocaleId ( sourceColumnId ) ;
725
+ if ( sourceLcid != destinationLcid )
699
726
{
700
- // On SqlDataReader we can verify the sourcecolumn collation!
701
- int sourceColumnId = _localColumnMappings [ assocId ] . _internalSourceColumnOrdinal ;
702
- int destinationLcid = metadata . collation . LCID ;
703
- int sourceLcid = _sqlDataReaderRowSource . GetLocaleId ( sourceColumnId ) ;
704
- if ( sourceLcid != destinationLcid )
705
- {
706
- throw SQL . BulkLoadLcidMismatch ( sourceLcid , _sqlDataReaderRowSource . GetName ( sourceColumnId ) , destinationLcid , metadata . column ) ;
707
- }
727
+ throw SQL . BulkLoadLcidMismatch ( sourceLcid , _sqlDataReaderRowSource . GetName ( sourceColumnId ) , destinationLcid , metadata . column ) ;
708
728
}
709
729
}
710
730
}
711
- break ;
712
731
}
732
+
733
+ // We found a match, so no need to keep looking.
734
+ break ;
713
735
}
714
736
715
- if ( assocId == _localColumnMappings . Count )
737
+ // Remove metadata for unmatched and rejected columns.
738
+ if ( ! matched || rejected )
716
739
{
717
- // Remove metadata for unmatched columns
718
740
metaDataSet [ i ] = null ;
719
741
}
720
742
}
721
743
722
- // All columnmappings should have matched up
723
- if ( nmatched + nrejected != _localColumnMappings . Count )
744
+ // Do we have any unmatched columns?
745
+ if ( unmatchedColumns . Count > 0 )
724
746
{
725
- List < string > unmatchedColumns = new List < string > ( ) ;
726
-
727
- foreach ( KeyValuePair < string , bool > keyValuePair in columnMappingStatusLookup )
728
- {
729
- if ( ! keyValuePair . Value )
730
- {
731
- unmatchedColumns . Add ( keyValuePair . Key ) ;
732
- }
733
- }
734
-
735
- throw SQL . BulkLoadNonMatchingColumnName ( unmatchedColumns ) ;
747
+ throw SQL . BulkLoadNonMatchingColumnNames ( unmatchedColumns ) ;
736
748
}
737
749
738
750
updateBulkCommandText . Append ( ")" ) ;
0 commit comments