5353import org .apache .doris .nereids .CascadesContext ;
5454import org .apache .doris .nereids .analyzer .Scope ;
5555import org .apache .doris .nereids .analyzer .UnboundRelation ;
56+ import org .apache .doris .nereids .analyzer .UnboundSlot ;
5657import org .apache .doris .nereids .glue .translator .ExpressionTranslator ;
5758import org .apache .doris .nereids .glue .translator .PlanTranslatorContext ;
5859import org .apache .doris .nereids .jobs .executor .Analyzer ;
6263import org .apache .doris .nereids .rules .analysis .ExpressionAnalyzer ;
6364import org .apache .doris .nereids .rules .expression .ExpressionRewriteContext ;
6465import org .apache .doris .nereids .trees .expressions .Cast ;
66+ import org .apache .doris .nereids .trees .expressions .EqualTo ;
67+ import org .apache .doris .nereids .trees .expressions .ExprId ;
6568import org .apache .doris .nereids .trees .expressions .Expression ;
6669import org .apache .doris .nereids .trees .expressions .Slot ;
6770import org .apache .doris .nereids .trees .expressions .SlotReference ;
7073import org .apache .doris .nereids .trees .plans .algebra .OlapScan ;
7174import org .apache .doris .nereids .trees .plans .commands .LoadCommand ;
7275import org .apache .doris .nereids .trees .plans .logical .LogicalFilter ;
76+ import org .apache .doris .nereids .types .DataType ;
77+ import org .apache .doris .nereids .types .StringType ;
7378import org .apache .doris .nereids .util .Utils ;
7479import org .apache .doris .qe .ConnectContext ;
7580import org .apache .doris .qe .OriginStatement ;
@@ -239,8 +244,10 @@ public void doValidate(String user, String db, boolean checkAuth) throws Analysi
239244 }
240245 PlanTranslatorContext context = new PlanTranslatorContext (cascadesContext );
241246 List <Slot > slots = boundRelation .getOutput ();
242- Scope scope = new Scope (slots );
243- ExpressionAnalyzer analyzer = new ExpressionAnalyzer (null , scope , cascadesContext , false , false );
247+ CopyIntoFileSlots fileSlots = new CopyIntoFileSlots (slots , copyFromDesc .getFileColumns (),
248+ copyFromDesc .getColumnMappingList ());
249+ ExpressionAnalyzer analyzer = new ExpressionAnalyzer (null , new Scope (fileSlots .getScopeSlots ()),
250+ cascadesContext , false , false );
244251
245252 Map <SlotReference , SlotRef > translateMap = Maps .newHashMap ();
246253
@@ -257,13 +264,14 @@ public void doValidate(String user, String db, boolean checkAuth) throws Analysi
257264 if (copyFromDesc .getColumnMappingList () != null && !copyFromDesc .getColumnMappingList ().isEmpty ()) {
258265 legacyColumnMappingList = new ArrayList <>();
259266 for (Expression expression : copyFromDesc .getColumnMappingList ()) {
260- legacyColumnMappingList .add (translateToLegacyExpr (expression , analyzer , context , cascadesContext ));
267+ legacyColumnMappingList .add (translateToLegacyExpr (expression , analyzer , context , cascadesContext ,
268+ fileSlots ));
261269 }
262270 }
263271 Expr legacyFileFilterExpr = null ;
264272 if (copyFromDesc .getFileFilterExpr ().isPresent ()) {
265273 legacyFileFilterExpr = translateToLegacyExpr (copyFromDesc .getFileFilterExpr ().get (),
266- analyzer , context , cascadesContext );
274+ analyzer , context , cascadesContext , fileSlots );
267275 }
268276
269277 String compression = copyIntoProperties .getCompression ();
@@ -301,30 +309,32 @@ public void doValidate(String user, String db, boolean checkAuth) throws Analysi
301309 }
302310
303311 // translate copy from description to copy from param
304- legacyCopyFromParam = toLegacyParam (copyFromDesc , analyzer , context , cascadesContext );
312+ legacyCopyFromParam = toLegacyParam (copyFromDesc , analyzer , context , cascadesContext , fileSlots );
305313 }
306314
307315 private CopyFromParam toLegacyParam (CopyFromDesc copyFromDesc , ExpressionAnalyzer analyzer ,
308- PlanTranslatorContext context , CascadesContext cascadesContext ) {
316+ PlanTranslatorContext context , CascadesContext cascadesContext ,
317+ CopyIntoFileSlots fileSlots ) {
309318 StageAndPattern stageAndPattern = copyFromDesc .getStageAndPattern ();
310319 List <Expr > exprList = null ;
311320 if (copyFromDesc .getExprList () != null ) {
312321 exprList = new ArrayList <>();
313322 for (Expression expression : copyFromDesc .getExprList ()) {
314- exprList .add (translateToLegacyExpr (expression , analyzer , context , cascadesContext ));
323+ exprList .add (translateToLegacyExpr (expression , analyzer , context , cascadesContext , fileSlots ));
315324 }
316325 }
317326 Expr fileFilterExpr = null ;
318327 if (copyFromDesc .getFileFilterExpr ().isPresent ()) {
319328 fileFilterExpr = translateToLegacyExpr (copyFromDesc .getFileFilterExpr ().get (),
320- analyzer , context , cascadesContext );
329+ analyzer , context , cascadesContext , fileSlots );
321330 }
322331 List <String > fileColumns = copyFromDesc .getFileColumns ();
323332 List <Expr > columnMappingList = null ;
324333 if (copyFromDesc .getColumnMappingList () != null ) {
325334 columnMappingList = new ArrayList <>();
326335 for (Expression expression : copyFromDesc .getColumnMappingList ()) {
327- columnMappingList .add (translateToLegacyExpr (expression , analyzer , context , cascadesContext ));
336+ columnMappingList .add (translateToLegacyExpr (expression , analyzer , context , cascadesContext ,
337+ fileSlots ));
328338 }
329339 }
330340 List <String > targetColumns = copyFromDesc .getTargetColumns ();
@@ -333,7 +343,7 @@ private CopyFromParam toLegacyParam(CopyFromDesc copyFromDesc, ExpressionAnalyze
333343 }
334344
335345 private Expr translateToLegacyExpr (Expression expr , ExpressionAnalyzer analyzer , PlanTranslatorContext context ,
336- CascadesContext cascadesContext ) {
346+ CascadesContext cascadesContext , CopyIntoFileSlots fileSlots ) {
337347 Expression expression ;
338348 try {
339349 expression = analyzer .analyze (expr , new ExpressionRewriteContext (cascadesContext ));
@@ -342,11 +352,26 @@ private Expr translateToLegacyExpr(Expression expr, ExpressionAnalyzer analyzer,
342352 + expr .toSql () + "', "
343353 + Utils .convertFirstChar (e .getMessage ()));
344354 }
345- ExpressionToExpr translator = new ExpressionToExpr ();
355+ ExpressionToExpr translator = new ExpressionToExpr (fileSlots );
346356 return expression .accept (translator , context );
347357 }
348358
349359 private static class ExpressionToExpr extends ExpressionTranslator {
360+ private final CopyIntoFileSlots fileSlots ;
361+
362+ private ExpressionToExpr (CopyIntoFileSlots fileSlots ) {
363+ this .fileSlots = fileSlots ;
364+ }
365+
366+ @ Override
367+ public Expr visitSlotReference (SlotReference slotReference , PlanTranslatorContext context ) {
368+ String fileSlotName = fileSlots .getFileSlotName (slotReference .getExprId ());
369+ if (fileSlotName != null ) {
370+ return new SlotRef (null , fileSlotName );
371+ }
372+ return super .visitSlotReference (slotReference , context );
373+ }
374+
350375 @ Override
351376 public Expr visitCast (Cast cast , PlanTranslatorContext context ) {
352377 // left child of cast is target type, right child of cast is expression
@@ -355,6 +380,74 @@ public Expr visitCast(Cast cast, PlanTranslatorContext context) {
355380 }
356381 }
357382
383+ private static class CopyIntoFileSlots {
384+ private final List <Slot > scopeSlots ;
385+ private final Map <ExprId , String > fileSlotNames = Maps .newHashMap ();
386+
387+ private CopyIntoFileSlots (List <Slot > targetSlots , List <String > fileColumns ,
388+ List <Expression > columnMappingList ) {
389+ scopeSlots = new ArrayList <>(targetSlots );
390+ if (fileColumns == null ) {
391+ return ;
392+ }
393+ Map <String , DataType > targetColumnTypes = Maps .newTreeMap (String .CASE_INSENSITIVE_ORDER );
394+ for (Slot slot : targetSlots ) {
395+ targetColumnTypes .put (slot .getName (), slot .getDataType ());
396+ }
397+ Map <String , DataType > fileColumnTypes = inferFileColumnTypes (targetColumnTypes , columnMappingList );
398+ for (String fileColumn : fileColumns ) {
399+ if (!isFileColumnPlaceholder (fileColumn ) || fileSlotNames .containsValue (fileColumn )) {
400+ continue ;
401+ }
402+ SlotReference slot = new SlotReference (fileColumn ,
403+ fileColumnTypes .getOrDefault (fileColumn , StringType .INSTANCE ), true );
404+ scopeSlots .add (slot );
405+ fileSlotNames .put (slot .getExprId (), fileColumn );
406+ }
407+ }
408+
409+ private List <Slot > getScopeSlots () {
410+ return scopeSlots ;
411+ }
412+
413+ private String getFileSlotName (ExprId exprId ) {
414+ return fileSlotNames .get (exprId );
415+ }
416+
417+ private static boolean isFileColumnPlaceholder (String columnName ) {
418+ return columnName != null && columnName .startsWith ("$" );
419+ }
420+
421+ private static Map <String , DataType > inferFileColumnTypes (Map <String , DataType > targetColumnTypes ,
422+ List <Expression > columnMappingList ) {
423+ Map <String , DataType > fileColumnTypes = Maps .newHashMap ();
424+ if (columnMappingList == null ) {
425+ return fileColumnTypes ;
426+ }
427+ for (Expression expression : columnMappingList ) {
428+ if (!(expression instanceof EqualTo )) {
429+ continue ;
430+ }
431+ EqualTo columnMapping = (EqualTo ) expression ;
432+ if (!(columnMapping .left () instanceof UnboundSlot )) {
433+ continue ;
434+ }
435+ DataType targetType = targetColumnTypes .get (((UnboundSlot ) columnMapping .left ()).getName ());
436+ if (targetType == null ) {
437+ continue ;
438+ }
439+ for (UnboundSlot fileColumn : columnMapping .right ()
440+ .<UnboundSlot >collect (UnboundSlot .class ::isInstance )) {
441+ String fileColumnName = fileColumn .getName ();
442+ if (isFileColumnPlaceholder (fileColumnName )) {
443+ fileColumnTypes .putIfAbsent (fileColumnName , targetType );
444+ }
445+ }
446+ }
447+ return fileColumnTypes ;
448+ }
449+ }
450+
358451 // after validateStagePB, fileFormat and copyOption is not null
359452 private void validateStagePB (StagePB stagePB ) throws AnalysisException {
360453 stageType = stagePB .getType ();
0 commit comments