1717
1818package org .apache .doris .nereids .rules .rewrite ;
1919
20+ import org .apache .doris .analysis .IndexDef .IndexType ;
21+ import org .apache .doris .catalog .AggregateType ;
22+ import org .apache .doris .catalog .Column ;
23+ import org .apache .doris .catalog .Index ;
24+ import org .apache .doris .catalog .KeysType ;
25+ import org .apache .doris .catalog .OlapTable ;
26+ import org .apache .doris .catalog .PartitionInfo ;
27+ import org .apache .doris .catalog .TableIndexes ;
28+ import org .apache .doris .catalog .Type ;
2029import org .apache .doris .nereids .exceptions .AnalysisException ;
2130import org .apache .doris .nereids .rules .Rule ;
2231import org .apache .doris .nereids .trees .expressions .Expression ;
2332import org .apache .doris .nereids .trees .expressions .SearchExpression ;
2433import org .apache .doris .nereids .trees .expressions .SlotReference ;
34+ import org .apache .doris .nereids .trees .expressions .functions .scalar .ElementAt ;
2535import org .apache .doris .nereids .trees .expressions .functions .scalar .Search ;
2636import org .apache .doris .nereids .trees .expressions .functions .scalar .SearchDslParser ;
2737import org .apache .doris .nereids .trees .expressions .literal .StringLiteral ;
2838import org .apache .doris .nereids .trees .plans .logical .LogicalOlapScan ;
2939import org .apache .doris .nereids .types .StringType ;
3040import org .apache .doris .nereids .util .PlanConstructor ;
41+ import org .apache .doris .thrift .TStorageType ;
3142
3243import com .google .common .collect .ImmutableList ;
3344import org .junit .jupiter .api .Assertions ;
@@ -229,7 +240,7 @@ public void testSlotReferenceConsistency() {
229240 @ Test
230241 public void testRewriteSearchHandlesCaseInsensitiveField () throws Exception {
231242 LogicalOlapScan scan = new LogicalOlapScan (PlanConstructor .getNextRelationId (),
232- PlanConstructor . student , ImmutableList .of ("db" ));
243+ buildStudentWithInvertedIndexOnName ( 100L ) , ImmutableList .of ("db" ));
233244 Search searchFunc = new Search (new StringLiteral ("NAME:alice" ));
234245
235246 Method rewriteMethod = RewriteSearchToSlots .class .getDeclaredMethod (
@@ -250,6 +261,31 @@ public void testRewriteSearchHandlesCaseInsensitiveField() throws Exception {
250261 Assertions .assertEquals ("name" , normalizedPlan .getRoot ().getField ());
251262 }
252263
264+ @ Test
265+ public void testRewriteSearchHandlesCaseInsensitiveVariantParentField () throws Exception {
266+ LogicalOlapScan scan = new LogicalOlapScan (PlanConstructor .getNextRelationId (),
267+ buildVariantTableWithInvertedIndex (102L ), ImmutableList .of ("db" ));
268+ Search searchFunc = new Search (new StringLiteral ("V.foo:bar" ));
269+
270+ Method rewriteMethod = RewriteSearchToSlots .class .getDeclaredMethod (
271+ "rewriteSearch" , Search .class , LogicalOlapScan .class );
272+ rewriteMethod .setAccessible (true );
273+
274+ Object rewritten = rewriteMethod .invoke (rewriteRule , searchFunc , scan );
275+ Assertions .assertInstanceOf (SearchExpression .class , rewritten );
276+
277+ SearchExpression searchExpression = (SearchExpression ) rewritten ;
278+ Assertions .assertEquals (1 , searchExpression .getSlotChildren ().size ());
279+ Assertions .assertTrue (searchExpression .getSlotChildren ().get (0 ) instanceof ElementAt );
280+ ElementAt elementAt = (ElementAt ) searchExpression .getSlotChildren ().get (0 );
281+ Assertions .assertTrue (elementAt .child (0 ) instanceof SlotReference );
282+ Assertions .assertEquals ("v" , ((SlotReference ) elementAt .child (0 )).getName ());
283+
284+ SearchDslParser .QsPlan normalizedPlan = searchExpression .getQsPlan ();
285+ Assertions .assertEquals ("v.foo" , normalizedPlan .getFieldBindings ().get (0 ).getFieldName ());
286+ Assertions .assertEquals ("v.foo" , normalizedPlan .getRoot ().getField ());
287+ }
288+
253289 @ Test
254290 public void testRewriteSearchThrowsWhenFieldMissing () throws Exception {
255291 LogicalOlapScan scan = new LogicalOlapScan (PlanConstructor .getNextRelationId (),
@@ -266,4 +302,75 @@ public void testRewriteSearchThrowsWhenFieldMissing() throws Exception {
266302 Assertions .assertInstanceOf (AnalysisException .class , thrown .getCause ());
267303 Assertions .assertTrue (thrown .getCause ().getMessage ().contains ("unknown_field" ));
268304 }
305+
306+ @ Test
307+ public void testRewriteSearchThrowsWhenColumnHasNoInvertedIndex () throws Exception {
308+ // PlanConstructor.student has the 'name' column but no inverted index on it. The rewrite
309+ // must surface a clear error instead of letting BE silently return an empty bitmap.
310+ LogicalOlapScan scan = new LogicalOlapScan (PlanConstructor .getNextRelationId (),
311+ PlanConstructor .student , ImmutableList .of ("db" ));
312+ Search searchFunc = new Search (new StringLiteral ("name:alice" ));
313+
314+ Method rewriteMethod = RewriteSearchToSlots .class .getDeclaredMethod (
315+ "rewriteSearch" , Search .class , LogicalOlapScan .class );
316+ rewriteMethod .setAccessible (true );
317+
318+ InvocationTargetException thrown = Assertions .assertThrows (InvocationTargetException .class ,
319+ () -> rewriteMethod .invoke (rewriteRule , searchFunc , scan ));
320+ Assertions .assertNotNull (thrown .getCause ());
321+ Assertions .assertInstanceOf (AnalysisException .class , thrown .getCause ());
322+ Assertions .assertTrue (thrown .getCause ().getMessage ().contains ("inverted index" ),
323+ "Error message should mention inverted index, got: " + thrown .getCause ().getMessage ());
324+ Assertions .assertTrue (thrown .getCause ().getMessage ().contains ("name" ));
325+ }
326+
327+ @ Test
328+ public void testRewriteSearchSucceedsWhenColumnHasInvertedIndex () throws Exception {
329+ LogicalOlapScan scan = new LogicalOlapScan (PlanConstructor .getNextRelationId (),
330+ buildStudentWithInvertedIndexOnName (101L ), ImmutableList .of ("db" ));
331+ Search searchFunc = new Search (new StringLiteral ("name:alice" ));
332+
333+ Method rewriteMethod = RewriteSearchToSlots .class .getDeclaredMethod (
334+ "rewriteSearch" , Search .class , LogicalOlapScan .class );
335+ rewriteMethod .setAccessible (true );
336+
337+ Object rewritten = rewriteMethod .invoke (rewriteRule , searchFunc , scan );
338+ Assertions .assertInstanceOf (SearchExpression .class , rewritten );
339+
340+ SearchExpression searchExpression = (SearchExpression ) rewritten ;
341+ Assertions .assertEquals (1 , searchExpression .getSlotChildren ().size ());
342+ Assertions .assertTrue (searchExpression .getSlotChildren ().get (0 ) instanceof SlotReference );
343+ Assertions .assertEquals ("name" ,
344+ ((SlotReference ) searchExpression .getSlotChildren ().get (0 )).getName ());
345+ }
346+
347+ private static OlapTable buildStudentWithInvertedIndexOnName (long tableId ) {
348+ List <Column > columns = ImmutableList .of (
349+ new Column ("id" , Type .INT , true , AggregateType .NONE , "0" , "" ),
350+ new Column ("gender" , Type .INT , false , AggregateType .NONE , "0" , "" ),
351+ new Column ("name" , Type .STRING , true , AggregateType .NONE , "" , "" ),
352+ new Column ("age" , Type .INT , true , AggregateType .NONE , "" , "" ));
353+ Index invertedOnName = new Index (1L , "idx_name" , ImmutableList .of ("name" ),
354+ IndexType .INVERTED , null , "" );
355+ OlapTable table = new OlapTable (tableId , "student_with_inverted_index" , false , columns ,
356+ KeysType .PRIMARY_KEYS , new PartitionInfo (), null ,
357+ new TableIndexes (ImmutableList .of (invertedOnName )));
358+ table .setIndexMeta (-1 , "student_with_inverted_index" , table .getFullSchema (),
359+ 0 , 0 , (short ) 0 , TStorageType .COLUMN , KeysType .PRIMARY_KEYS );
360+ return table ;
361+ }
362+
363+ private static OlapTable buildVariantTableWithInvertedIndex (long tableId ) {
364+ List <Column > columns = ImmutableList .of (
365+ new Column ("id" , Type .INT , true , AggregateType .NONE , "0" , "" ),
366+ new Column ("v" , Type .VARIANT , false , AggregateType .NONE , "" , "" ));
367+ Index invertedOnVariant = new Index (2L , "idx_v" , ImmutableList .of ("v" ),
368+ IndexType .INVERTED , null , "" );
369+ OlapTable table = new OlapTable (tableId , "variant_with_inverted_index" , false , columns ,
370+ KeysType .PRIMARY_KEYS , new PartitionInfo (), null ,
371+ new TableIndexes (ImmutableList .of (invertedOnVariant )));
372+ table .setIndexMeta (-1 , "variant_with_inverted_index" , table .getFullSchema (),
373+ 0 , 0 , (short ) 0 , TStorageType .COLUMN , KeysType .PRIMARY_KEYS );
374+ return table ;
375+ }
269376}
0 commit comments