@@ -36,6 +36,13 @@ type TaskFixtureType = {
3636 task : MigrationJobTask ;
3737} ;
3838
39+ type InClauseFixtureType = {
40+ childTask : MigrationJobTask ;
41+ parentTask : MigrationJobTask ;
42+ productTask : MigrationJobTask ;
43+ relatedProductTask : MigrationJobTask ;
44+ } ;
45+
3946const createTempDir = ( ) : string => fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'sfdmu-task-' ) ) ;
4047
4148type FieldInitType = Partial < SFieldDescribe > & { name : string } ;
@@ -72,6 +79,70 @@ const createTaskFixture = (): TaskFixtureType => {
7279
7380const createMap = ( pairs : Array < [ string , string ] > ) : LookupIdMapType => new Map ( pairs ) ;
7481
82+ const hasFilteredInClause = ( queries : string [ ] , fieldName : string ) : boolean =>
83+ queries . some ( ( query ) => query . includes ( `WHERE ${ fieldName } IN (` ) || query . includes ( `AND ${ fieldName } IN (` ) ) ;
84+
85+ const createInClauseFixture = ( ) : InClauseFixtureType => {
86+ const script = new Script ( ) ;
87+ const job = new MigrationJob ( { script } ) ;
88+ const parentObject = new ScriptObject ( 'Parent_Offer__c' ) ;
89+ const productObject = new ScriptObject ( 'Product__c' ) ;
90+ const relatedProductObject = new ScriptObject ( 'Related_Product__c' ) ;
91+ const childObject = new ScriptObject ( 'Offer_Line_Item__c' ) ;
92+ childObject . query = 'SELECT Id, Parent_Offer__c, Product__c, Related_Product__c FROM Offer_Line_Item__c' ;
93+
94+ const parentTask = new MigrationJobTask ( { job, scriptObject : parentObject } ) ;
95+ const productTask = new MigrationJobTask ( { job, scriptObject : productObject } ) ;
96+ const relatedProductTask = new MigrationJobTask ( { job, scriptObject : relatedProductObject } ) ;
97+ const childTask = new MigrationJobTask ( { job, scriptObject : childObject } ) ;
98+
99+ job . tasks = [ childTask , parentTask , productTask , relatedProductTask ] ;
100+ parentTask . sourceData . idRecordsMap . set ( 'a01000000000001AAA' , { Id : 'a01000000000001AAA' } ) ;
101+ productTask . sourceData . idRecordsMap . set ( 'a02000000000001AAA' , { Id : 'a02000000000001AAA' } ) ;
102+ relatedProductTask . sourceData . idRecordsMap . set ( 'a03000000000001AAA' , { Id : 'a03000000000001AAA' } ) ;
103+
104+ childTask . data . fieldsInQueryMap = new Map ( [
105+ [
106+ 'Parent_Offer__c' ,
107+ new SFieldDescribe ( {
108+ name : 'Parent_Offer__c' ,
109+ objectName : childObject . name ,
110+ type : 'reference' ,
111+ lookup : true ,
112+ referencedObjectType : parentObject . name ,
113+ parentLookupObject : parentObject ,
114+ isDescribed : true ,
115+ } ) ,
116+ ] ,
117+ [
118+ 'Product__c' ,
119+ new SFieldDescribe ( {
120+ name : 'Product__c' ,
121+ objectName : childObject . name ,
122+ type : 'reference' ,
123+ lookup : true ,
124+ referencedObjectType : productObject . name ,
125+ parentLookupObject : productObject ,
126+ isDescribed : true ,
127+ } ) ,
128+ ] ,
129+ [
130+ 'Related_Product__c' ,
131+ new SFieldDescribe ( {
132+ name : 'Related_Product__c' ,
133+ objectName : childObject . name ,
134+ type : 'reference' ,
135+ lookup : true ,
136+ referencedObjectType : relatedProductObject . name ,
137+ parentLookupObject : relatedProductObject ,
138+ isDescribed : true ,
139+ } ) ,
140+ ] ,
141+ ] ) ;
142+
143+ return { childTask, parentTask, productTask, relatedProductTask } ;
144+ } ;
145+
75146describe ( 'MigrationJobTask relationship external id matching' , ( ) => {
76147 it ( 'links target records by nested relationship external id values' , ( ) => {
77148 const { task } = createTaskFixture ( ) ;
@@ -97,6 +168,55 @@ describe('MigrationJobTask relationship external id matching', () => {
97168 } ) ;
98169} ) ;
99170
171+ describe ( 'MigrationJobTask filtered IN-clause fields' , ( ) => {
172+ it ( 'uses every eligible lookup field when no IN-clause field filter is configured' , ( ) => {
173+ const { childTask } = createInClauseFixture ( ) ;
174+
175+ const queries = childTask . createFilteredQueries ( 'forwards' , false ) ;
176+
177+ assert . equal ( queries . length , 3 ) ;
178+ assert . ok ( hasFilteredInClause ( queries , 'Parent_Offer__c' ) ) ;
179+ assert . ok ( hasFilteredInClause ( queries , 'Product__c' ) ) ;
180+ assert . ok ( hasFilteredInClause ( queries , 'Related_Product__c' ) ) ;
181+ } ) ;
182+
183+ it ( 'uses only included lookup fields as filtered IN-clause pivots' , ( ) => {
184+ const { childTask } = createInClauseFixture ( ) ;
185+ childTask . scriptObject . includedInClauseFields = [ 'Parent_Offer__c' ] ;
186+
187+ const queries = childTask . createFilteredQueries ( 'forwards' , false ) ;
188+
189+ assert . equal ( queries . length , 1 ) ;
190+ assert . ok ( hasFilteredInClause ( queries , 'Parent_Offer__c' ) ) ;
191+ assert . ok ( ! hasFilteredInClause ( queries , 'Product__c' ) ) ;
192+ assert . ok ( ! hasFilteredInClause ( queries , 'Related_Product__c' ) ) ;
193+ } ) ;
194+
195+ it ( 'skips excluded lookup fields as filtered IN-clause pivots' , ( ) => {
196+ const { childTask } = createInClauseFixture ( ) ;
197+ childTask . scriptObject . excludedFromInClauseFields = [ 'Product__c' , 'Related_Product__c' ] ;
198+
199+ const queries = childTask . createFilteredQueries ( 'forwards' , false ) ;
200+
201+ assert . equal ( queries . length , 1 ) ;
202+ assert . ok ( hasFilteredInClause ( queries , 'Parent_Offer__c' ) ) ;
203+ assert . ok ( ! hasFilteredInClause ( queries , 'Product__c' ) ) ;
204+ assert . ok ( ! hasFilteredInClause ( queries , 'Related_Product__c' ) ) ;
205+ } ) ;
206+
207+ it ( 'subtracts excluded lookup fields from the IN-clause allow-list case-insensitively' , ( ) => {
208+ const { childTask } = createInClauseFixture ( ) ;
209+ childTask . scriptObject . includedInClauseFields = [ 'parent_offer__c' , 'PRODUCT__C' ] ;
210+ childTask . scriptObject . excludedFromInClauseFields = [ 'product__c' ] ;
211+
212+ const queries = childTask . createFilteredQueries ( 'forwards' , false ) ;
213+
214+ assert . equal ( queries . length , 1 ) ;
215+ assert . ok ( hasFilteredInClause ( queries , 'Parent_Offer__c' ) ) ;
216+ assert . ok ( ! hasFilteredInClause ( queries , 'Product__c' ) ) ;
217+ } ) ;
218+ } ) ;
219+
100220describe ( 'MigrationJobTask field capability warnings' , ( ) => {
101221 it ( 'warns only for explicit update query fields that are not updateable' , ( ) => {
102222 const originalLogger = Common . logger ;
0 commit comments