@@ -532,207 +532,219 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i
532532
533533            string [ ]  parts  =  MultipartIdentifier . ParseMultipartIdentifier ( DestinationTableName ,  "[\" " ,  "]\" " ,  Strings . SQL_BulkCopyDestinationTableName ,  true ) ; 
534534            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 
538535
539-             bool  isInTransaction ; 
540- 
541-             isInTransaction  =  _connection . HasLocalTransaction ; 
542536            // 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 ) 
544543            { 
545544                throw  SQL . BulkLoadExistingTransaction ( ) ; 
546545            } 
547546
548547            HashSet < string >  destColumnNames  =  new  HashSet < string > ( ) ; 
549548
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  appendColumn  =  false ; 
563+ 
564+             // Loop over the metadata for each result column. 
552565            _SqlMetaDataSet  metaDataSet  =  internalResults [ MetaDataResultId ] . MetaData ; 
553566            _sortedColumnMappings  =  new  List < _ColumnMapping > ( metaDataSet . Length ) ; 
554567            for  ( int  i  =  0 ;  i  <  metaDataSet . Length ;  i ++ ) 
555568            { 
556569                _SqlMetaData  metadata  =  metaDataSet [ i ] ; 
557-                 rejectColumn  =  false ; 
558570
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 ) 
562576                { 
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 ] ; 
568578
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+                      } 
577587
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 ) ) ) 
580601                    { 
581-                         columnMappingStatusLookup [ _localColumnMappings [ assocId ] . DestinationColumn ]  =  true ; 
602+                         rejected  =  true ; 
603+                         break ; 
604+                     } 
582605
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 ) ; 
588608
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  ( appendColumn ) 
611+                     { 
612+                         updateBulkCommandText . Append ( ", " ) ; 
613+                     } 
614+                     else 
615+                     { 
616+                         appendColumn  =  true ; 
617+                     } 
592618
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+                     } 
597636
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 : 
612645                        { 
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 ; 
614656                        } 
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 : 
617664                        { 
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 ) 
643670                                { 
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 ; 
666678                                } 
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 ; 
667687                        } 
688+                     } 
668689
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 ] ; 
672693
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 ; 
689705
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 ) 
691716                        { 
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 ) 
695720                            { 
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 ) 
699726                                { 
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 ) ; 
708728                                } 
709729                            } 
710730                        } 
711-                         break ; 
712731                    } 
732+ 
733+                     // We found a match, so no need to keep looking. 
734+                     break ; 
713735                } 
714736
715-                 if  ( assocId  ==  _localColumnMappings . Count ) 
737+                 // Remove metadata for unmatched and rejected columns. 
738+                 if  ( !  matched  ||  rejected ) 
716739                { 
717-                     // Remove metadata for unmatched columns 
718740                    metaDataSet [ i ]  =  null ; 
719741                } 
720742            } 
721743
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 ) 
724746            { 
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 ) ; 
736748            } 
737749
738750            updateBulkCommandText . Append ( ")" ) ; 
0 commit comments