[improvement](mtmv) Add id to statistics map in statement context for cost estimation later (#35436)

Add id to statistics map in statement context for cost estimation later
this helps to improve the probability to use materialized view when
query a single table with aggregate and many filter
This commit is contained in:
seawinde
2024-05-28 11:11:56 +08:00
committed by yiguolei
parent d2df392994
commit 8599e8ee64
10 changed files with 241 additions and 72 deletions

View File

@ -34,6 +34,7 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalResultSink;
import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanRewriter;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.OriginStatement;
import org.apache.doris.statistics.Statistics;
import com.google.common.collect.ImmutableList;
@ -47,10 +48,12 @@ public class MTMVCache {
private final Plan logicalPlan;
// The original plan of mv def sql
private final Plan originalPlan;
private final Statistics statistics;
public MTMVCache(Plan logicalPlan, Plan originalPlan) {
public MTMVCache(Plan logicalPlan, Plan originalPlan, Statistics statistics) {
this.logicalPlan = logicalPlan;
this.originalPlan = originalPlan;
this.statistics = statistics;
}
public Plan getLogicalPlan() {
@ -61,6 +64,10 @@ public class MTMVCache {
return originalPlan;
}
public Statistics getStatistics() {
return statistics;
}
public static MTMVCache from(MTMV mtmv, ConnectContext connectContext) {
LogicalPlan unboundMvPlan = new NereidsParser().parseSingle(mtmv.getQuerySql());
StatementContext mvSqlStatementContext = new StatementContext(connectContext,
@ -71,7 +78,8 @@ public class MTMVCache {
}
// Can not convert to table sink, because use the same column from different table when self join
// the out slot is wrong
Plan originPlan = planner.plan(unboundMvPlan, PhysicalProperties.ANY, ExplainLevel.REWRITTEN_PLAN);
planner.plan(unboundMvPlan, PhysicalProperties.ANY, ExplainLevel.ALL_PLAN);
Plan originPlan = planner.getCascadesContext().getRewritePlan();
// Eliminate result sink because sink operator is useless in query rewrite by materialized view
// and the top sort can also be removed
Plan mvPlan = originPlan.accept(new DefaultPlanRewriter<Object>() {
@ -88,6 +96,6 @@ public class MTMVCache {
ImmutableList.of(Rewriter.custom(RuleType.ELIMINATE_SORT, EliminateSort::new))).execute();
return childContext.getRewritePlan();
}, mvPlan, originPlan);
return new MTMVCache(mvPlan, originPlan);
return new MTMVCache(mvPlan, originPlan, planner.getCascadesContext().getMemo().getRoot().getStatistics());
}
}

View File

@ -20,6 +20,7 @@ package org.apache.doris.nereids;
import org.apache.doris.analysis.StatementBase;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.catalog.constraint.TableIdentifier;
import org.apache.doris.common.Id;
import org.apache.doris.common.IdGenerator;
import org.apache.doris.common.Pair;
import org.apache.doris.nereids.hint.Hint;
@ -41,7 +42,9 @@ import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.OriginStatement;
import org.apache.doris.qe.SessionVariable;
import org.apache.doris.qe.cache.CacheAnalyzer;
import org.apache.doris.statistics.Statistics;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
@ -146,6 +149,10 @@ public class StatementContext implements Closeable {
// Record table id mapping, the key is the hash code of union catalogId, databaseId, tableId
// the value is the auto-increment id in the cascades context
private final Map<TableIdentifier, TableId> tableIdMapping = new LinkedHashMap<>();
// Record the materialization statistics by id which is used for cost estimation.
// Maybe return null, which means the id according statistics should calc normally rather than getting
// form this map
private final Map<RelationId, Statistics> relationIdToStatisticsMap = new LinkedHashMap<>();
public StatementContext() {
this(ConnectContext.get(), null, 0);
@ -412,6 +419,24 @@ public class StatementContext implements Closeable {
indexInSqlToString.put(pair, replacement);
}
public void addStatistics(Id id, Statistics statistics) {
if (id instanceof RelationId) {
this.relationIdToStatisticsMap.put((RelationId) id, statistics);
}
}
public Optional<Statistics> getStatistics(Id id) {
if (id instanceof RelationId) {
return Optional.ofNullable(this.relationIdToStatisticsMap.get((RelationId) id));
}
return Optional.empty();
}
@VisibleForTesting
public Map<RelationId, Statistics> getRelationIdToStatisticsMap() {
return relationIdToStatisticsMap;
}
/** addTableReadLock */
public synchronized void addTableReadLock(TableIf tableIf) {
if (!tableIf.needReadLockWhenPlan()) {

View File

@ -97,7 +97,7 @@ public abstract class AbstractMaterializedViewAggregateRule extends AbstractMate
viewStructInfo)) {
List<Expression> rewrittenQueryExpressions = rewriteExpression(queryTopPlan.getOutput(),
queryTopPlan,
materializationContext.getMvExprToMvScanExprMapping(),
materializationContext.getExprToScanExprMapping(),
viewToQuerySlotMapping,
true,
queryStructInfo.getTableBitSet());
@ -121,7 +121,7 @@ public abstract class AbstractMaterializedViewAggregateRule extends AbstractMate
() -> String.format("expressionToWrite = %s,\n mvExprToMvScanExprMapping = %s,\n"
+ "viewToQuerySlotMapping = %s",
queryTopPlan.getOutput(),
materializationContext.getMvExprToMvScanExprMapping(),
materializationContext.getExprToScanExprMapping(),
viewToQuerySlotMapping));
}
// if view is scalar aggregate but query is not. Or if query is scalar aggregate but view is not
@ -150,7 +150,7 @@ public abstract class AbstractMaterializedViewAggregateRule extends AbstractMate
List<? extends Expression> queryExpressions = queryTopPlan.getOutput();
// permute the mv expr mapping to query based
Map<Expression, Expression> mvExprToMvScanExprQueryBased =
materializationContext.getMvExprToMvScanExprMapping().keyPermute(viewToQuerySlotMapping)
materializationContext.getExprToScanExprMapping().keyPermute(viewToQuerySlotMapping)
.flattenMap().get(0);
for (Expression topExpression : queryExpressions) {
// if agg function, try to roll up and rewrite

View File

@ -46,7 +46,7 @@ public abstract class AbstractMaterializedViewJoinRule extends AbstractMateriali
List<Expression> expressionsRewritten = rewriteExpression(
queryStructInfo.getExpressions(),
queryStructInfo.getTopPlan(),
materializationContext.getMvExprToMvScanExprMapping(),
materializationContext.getExprToScanExprMapping(),
targetToSourceMapping,
true,
queryStructInfo.getTableBitSet()
@ -57,7 +57,7 @@ public abstract class AbstractMaterializedViewJoinRule extends AbstractMateriali
"Rewrite expressions by view in join fail",
() -> String.format("expressionToRewritten is %s,\n mvExprToMvScanExprMapping is %s,\n"
+ "targetToSourceMapping = %s", queryStructInfo.getExpressions(),
materializationContext.getMvExprToMvScanExprMapping(),
materializationContext.getExprToScanExprMapping(),
targetToSourceMapping));
return null;
}

View File

@ -24,6 +24,7 @@ import org.apache.doris.catalog.PartitionInfo;
import org.apache.doris.catalog.PartitionItem;
import org.apache.doris.catalog.PartitionType;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.common.Id;
import org.apache.doris.common.Pair;
import org.apache.doris.mtmv.BaseTableInfo;
import org.apache.doris.mtmv.MTMVPartitionInfo;
@ -59,6 +60,7 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
import org.apache.doris.nereids.util.ExpressionUtils;
import org.apache.doris.nereids.util.TypeUtils;
import org.apache.doris.statistics.Statistics;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
@ -77,6 +79,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -226,21 +229,21 @@ public abstract class AbstractMaterializedViewRule implements ExplorationRuleFac
continue;
}
Plan rewrittenPlan;
Plan mvScan = materializationContext.getMvScanPlan();
Plan mvScan = materializationContext.getScanPlan();
Plan queryPlan = queryStructInfo.getTopPlan();
if (compensatePredicates.isAlwaysTrue()) {
rewrittenPlan = mvScan;
} else {
// Try to rewrite compensate predicates by using mv scan
List<Expression> rewriteCompensatePredicates = rewriteExpression(compensatePredicates.toList(),
queryPlan, materializationContext.getMvExprToMvScanExprMapping(),
queryPlan, materializationContext.getExprToScanExprMapping(),
viewToQuerySlotMapping, true, queryStructInfo.getTableBitSet());
if (rewriteCompensatePredicates.isEmpty()) {
materializationContext.recordFailReason(queryStructInfo,
"Rewrite compensate predicate by view fail",
() -> String.format("compensatePredicates = %s,\n mvExprToMvScanExprMapping = %s,\n"
+ "viewToQuerySlotMapping = %s",
compensatePredicates, materializationContext.getMvExprToMvScanExprMapping(),
compensatePredicates, materializationContext.getExprToScanExprMapping(),
viewToQuerySlotMapping));
continue;
}
@ -334,9 +337,15 @@ public abstract class AbstractMaterializedViewRule implements ExplorationRuleFac
continue;
}
recordIfRewritten(queryStructInfo.getOriginalPlan(), materializationContext);
Optional<Pair<Id, Statistics>> materializationPlanStatistics =
materializationContext.getPlanStatistics(cascadesContext);
if (materializationPlanStatistics.isPresent() && materializationPlanStatistics.get().key() != null) {
cascadesContext.getStatementContext().addStatistics(
materializationPlanStatistics.get().key(), materializationPlanStatistics.get().value());
}
rewriteResults.add(rewrittenPlan);
// if rewrite successfully, try to regenerate mv scan because it maybe used again
materializationContext.tryReGenerateMvScanPlan(cascadesContext);
materializationContext.tryReGenerateScanPlan(cascadesContext);
}
return rewriteResults;
}

View File

@ -19,14 +19,20 @@ package org.apache.doris.nereids.rules.exploration.mv;
import org.apache.doris.catalog.MTMV;
import org.apache.doris.catalog.Table;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.Id;
import org.apache.doris.common.Pair;
import org.apache.doris.mtmv.MTMVCache;
import org.apache.doris.nereids.CascadesContext;
import org.apache.doris.nereids.rules.exploration.mv.mapping.ExpressionMapping;
import org.apache.doris.nereids.trees.plans.ObjectId;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.RelationId;
import org.apache.doris.nereids.trees.plans.algebra.Relation;
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalCatalogRelation;
import org.apache.doris.nereids.util.Utils;
import org.apache.doris.statistics.Statistics;
import com.google.common.collect.Multimap;
import org.apache.logging.log4j.LogManager;
@ -35,6 +41,7 @@ import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Async context for query rewrite by materialized view
@ -58,7 +65,7 @@ public class AsyncMaterializationContext extends MaterializationContext {
}
@Override
Plan doGenerateMvPlan(CascadesContext cascadesContext) {
Plan doGenerateScanPlan(CascadesContext cascadesContext) {
return MaterializedViewUtils.generateMvScanPlan(this.mtmv, cascadesContext);
}
@ -85,6 +92,24 @@ public class AsyncMaterializationContext extends MaterializationContext {
"failReason", failReasonBuilder.toString());
}
@Override
public Optional<Pair<Id, Statistics>> getPlanStatistics(CascadesContext cascadesContext) {
MTMVCache mtmvCache;
try {
mtmvCache = mtmv.getOrGenerateCache(cascadesContext.getConnectContext());
} catch (AnalysisException e) {
LOG.warn(String.format("get mv plan statistics fail, materialization qualifier is %s",
getMaterializationQualifier()), e);
return Optional.empty();
}
RelationId relationId = null;
List<Object> scanObjs = this.getPlan().collectFirst(plan -> plan instanceof LogicalOlapScan);
if (scanObjs != null && !scanObjs.isEmpty()) {
relationId = ((LogicalOlapScan) scanObjs.get(0)).getRelationId();
}
return Optional.of(Pair.of(relationId, mtmvCache.getStatistics()));
}
@Override
boolean isFinalChosen(Relation relation) {
if (!(relation instanceof PhysicalCatalogRelation)) {
@ -93,8 +118,8 @@ public class AsyncMaterializationContext extends MaterializationContext {
return ((PhysicalCatalogRelation) relation).getTable() instanceof MTMV;
}
public Plan getMvScanPlan() {
return mvScanPlan;
public Plan getScanPlan() {
return scanPlan;
}
public List<Table> getBaseTables() {
@ -105,16 +130,16 @@ public class AsyncMaterializationContext extends MaterializationContext {
return baseViews;
}
public ExpressionMapping getMvExprToMvScanExprMapping() {
return mvExprToMvScanExprMapping;
public ExpressionMapping getExprToScanExprMapping() {
return exprToScanExprMapping;
}
public boolean isAvailable() {
return available;
}
public Plan getMvPlan() {
return mvPlan;
public Plan getPlan() {
return plan;
}
public Multimap<ObjectId, Pair<String, String>> getFailReason() {

View File

@ -19,6 +19,7 @@ package org.apache.doris.nereids.rules.exploration.mv;
import org.apache.doris.analysis.StatementBase;
import org.apache.doris.catalog.Table;
import org.apache.doris.common.Id;
import org.apache.doris.common.Pair;
import org.apache.doris.nereids.CascadesContext;
import org.apache.doris.nereids.memo.GroupId;
@ -34,6 +35,7 @@ import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalRelation;
import org.apache.doris.nereids.trees.plans.visitor.DefaultPlanVisitor;
import org.apache.doris.nereids.util.ExpressionUtils;
import org.apache.doris.statistics.Statistics;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
@ -47,6 +49,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -56,69 +59,75 @@ import java.util.stream.Collectors;
*/
public abstract class MaterializationContext {
private static final Logger LOG = LogManager.getLogger(MaterializationContext.class);
public final Map<RelationMapping, SlotMapping> queryToMvSlotMappingCache = new HashMap<>();
public final Map<RelationMapping, SlotMapping> queryToMaterializationSlotMappingCache = new HashMap<>();
protected List<Table> baseTables;
protected List<Table> baseViews;
// The plan of mv def sql
protected Plan mvPlan;
// The original plan of mv def sql
protected Plan originalMvPlan;
// The plan of materialization def sql
protected Plan plan;
// The original plan of materialization sql
protected Plan originalPlan;
// Should regenerate when materialization is already rewritten successfully because one query may hit repeatedly
// make sure output is different in multi using
protected Plan mvScanPlan;
// The mvPlan output shuttled expression, this is used by generate field mvExprToMvScanExprMapping
protected List<? extends Expression> mvPlanOutputShuttledExpressions;
// Generated mapping from mv plan out shuttled expr to mv scan plan out slot mapping, this is used for later used
protected ExpressionMapping mvExprToMvScanExprMapping;
protected Plan scanPlan;
// The materialization plan output shuttled expression, this is used by generate field
// exprToScanExprMapping
protected List<? extends Expression> planOutputShuttledExpressions;
// Generated mapping from materialization plan out shuttled expr to materialization scan plan out slot mapping,
// this is used for later used
protected ExpressionMapping exprToScanExprMapping;
// This mark the materialization context is available or not,
// will not be used in query transparent rewritten if false
protected boolean available = true;
// Mark the mv plan in the context is already rewritten successfully or not
// Mark the materialization plan in the context is already rewritten successfully or not
protected boolean success = false;
// Mark enable record failure detail info or not, because record failure detail info is performance-depleting
protected final boolean enableRecordFailureDetail;
// The mv plan struct info
// The materialization plan struct info
protected final StructInfo structInfo;
// Group id set that are rewritten unsuccessfully by this mv for reducing rewrite times
// Group id set that are rewritten unsuccessfully by this materialization for reducing rewrite times
protected final Set<GroupId> matchedFailGroups = new HashSet<>();
// Group id set that are rewritten successfully by this mv for reducing rewrite times
// Group id set that are rewritten successfully by this materialization for reducing rewrite times
protected final Set<GroupId> matchedSuccessGroups = new HashSet<>();
// Record the reason, if rewrite by mv fail. The failReason should be empty if success.
// Record the reason, if rewrite by materialization fail. The failReason should be empty if success.
// The key is the query belonged group expression objectId, the value is the fail reasons because
// for one materialization query may be multi when nested materialized view.
protected final Multimap<ObjectId, Pair<String, String>> failReason = HashMultimap.create();
/**
* MaterializationContext, this contains necessary info for query rewriting by mv
* MaterializationContext, this contains necessary info for query rewriting by materialization
*/
public MaterializationContext(Plan mvPlan, Plan originalMvPlan, Plan mvScanPlan, CascadesContext cascadesContext) {
this.mvPlan = mvPlan;
this.originalMvPlan = originalMvPlan;
this.mvScanPlan = mvScanPlan;
public MaterializationContext(Plan plan, Plan originalPlan,
Plan scanPlan, CascadesContext cascadesContext) {
this.plan = plan;
this.originalPlan = originalPlan;
this.scanPlan = scanPlan;
StatementBase parsedStatement = cascadesContext.getStatementContext().getParsedStatement();
this.enableRecordFailureDetail = parsedStatement != null && parsedStatement.isExplain()
&& ExplainLevel.MEMO_PLAN == parsedStatement.getExplainOptions().getExplainLevel();
this.mvPlanOutputShuttledExpressions = ExpressionUtils.shuttleExpressionWithLineage(
originalMvPlan.getOutput(),
originalMvPlan,
this.planOutputShuttledExpressions = ExpressionUtils.shuttleExpressionWithLineage(
originalPlan.getOutput(),
originalPlan,
new BitSet());
// mv output expression shuttle, this will be used to expression rewrite
this.mvExprToMvScanExprMapping = ExpressionMapping.generate(this.mvPlanOutputShuttledExpressions,
this.mvScanPlan.getOutput());
// Construct mv struct info, catch exception which may cause planner roll back
// materialization output expression shuttle, this will be used to expression rewrite
this.exprToScanExprMapping = ExpressionMapping.generate(
this.planOutputShuttledExpressions,
this.scanPlan.getOutput());
// Construct materialization struct info, catch exception which may cause planner roll back
List<StructInfo> viewStructInfos;
try {
viewStructInfos = MaterializedViewUtils.extractStructInfo(mvPlan, cascadesContext, new BitSet());
viewStructInfos = MaterializedViewUtils.extractStructInfo(plan, cascadesContext, new BitSet());
if (viewStructInfos.size() > 1) {
// view struct info should only have one, log error and use the first struct info
LOG.warn(String.format("view strut info is more than one, mv scan plan is %s, mv plan is %s",
mvScanPlan.treeString(), mvPlan.treeString()));
LOG.warn(String.format("view strut info is more than one, materialization scan plan is %s, "
+ "materialization plan is %s",
scanPlan.treeString(), plan.treeString()));
}
} catch (Exception exception) {
LOG.warn(String.format("construct mv struct info fail, mv scan plan is %s, mv plan is %s",
mvScanPlan.treeString(), mvPlan.treeString()), exception);
LOG.warn(String.format("construct materialization struct info fail, materialization scan plan is %s, "
+ "materialization plan is %s",
scanPlan.treeString(), plan.treeString()), exception);
this.available = false;
this.structInfo = null;
return;
@ -141,32 +150,33 @@ public abstract class MaterializationContext {
/**
* Try to generate scan plan for materialization
* if MaterializationContext is already rewritten successfully, then should generate new scan plan in later
* query rewrite, because one plan may hit the materialized view repeatedly and the mv scan output
* query rewrite, because one plan may hit the materialized view repeatedly and the materialization scan output
* should be different.
* This method should be called when query rewrite successfully
*/
public void tryReGenerateMvScanPlan(CascadesContext cascadesContext) {
this.mvScanPlan = doGenerateMvPlan(cascadesContext);
// mv output expression shuttle, this will be used to expression rewrite
this.mvExprToMvScanExprMapping = ExpressionMapping.generate(this.mvPlanOutputShuttledExpressions,
this.mvScanPlan.getOutput());
public void tryReGenerateScanPlan(CascadesContext cascadesContext) {
this.scanPlan = doGenerateScanPlan(cascadesContext);
// materialization output expression shuttle, this will be used to expression rewrite
this.exprToScanExprMapping = ExpressionMapping.generate(
this.planOutputShuttledExpressions,
this.scanPlan.getOutput());
}
public void addSlotMappingToCache(RelationMapping relationMapping, SlotMapping slotMapping) {
queryToMvSlotMappingCache.put(relationMapping, slotMapping);
queryToMaterializationSlotMappingCache.put(relationMapping, slotMapping);
}
public SlotMapping getSlotMappingFromCache(RelationMapping relationMapping) {
return queryToMvSlotMappingCache.get(relationMapping);
return queryToMaterializationSlotMappingCache.get(relationMapping);
}
/**
* Try to generate scan plan for materialization
* if MaterializationContext is already rewritten successfully, then should generate new scan plan in later
* query rewrite, because one plan may hit the materialized view repeatedly and the mv scan output
* query rewrite, because one plan may hit the materialized view repeatedly and the materialization scan output
* should be different
*/
abstract Plan doGenerateMvPlan(CascadesContext cascadesContext);
abstract Plan doGenerateScanPlan(CascadesContext cascadesContext);
/**
* Get materialization unique qualifier which identify it
@ -178,21 +188,28 @@ public abstract class MaterializationContext {
*/
abstract String getStringInfo();
/**
* Get materialization plan statistics, the key is the identifier of statistics
* the value is Statistics.
* the statistics is used by cost estimation when the materialization is used
*/
abstract Optional<Pair<Id, Statistics>> getPlanStatistics(CascadesContext cascadesContext);
/**
* Calc the relation is chosen finally or not
*/
abstract boolean isFinalChosen(Relation relation);
public Plan getMvPlan() {
return mvPlan;
public Plan getPlan() {
return plan;
}
public Plan getOriginalMvPlan() {
return originalMvPlan;
public Plan getOriginalPlan() {
return originalPlan;
}
public Plan getMvScanPlan() {
return mvScanPlan;
public Plan getScanPlan() {
return scanPlan;
}
public List<Table> getBaseTables() {
@ -203,8 +220,8 @@ public abstract class MaterializationContext {
return baseViews;
}
public ExpressionMapping getMvExprToMvScanExprMapping() {
return mvExprToMvScanExprMapping;
public ExpressionMapping getExprToScanExprMapping() {
return exprToScanExprMapping;
}
public boolean isAvailable() {

View File

@ -47,7 +47,7 @@ public abstract class MaterializedViewScanRule extends AbstractMaterializedViewR
List<Expression> expressionsRewritten = rewriteExpression(
queryStructInfo.getExpressions(),
queryStructInfo.getTopPlan(),
materializationContext.getMvExprToMvScanExprMapping(),
materializationContext.getExprToScanExprMapping(),
targetToSourceMapping,
true,
queryStructInfo.getTableBitSet()
@ -58,7 +58,7 @@ public abstract class MaterializedViewScanRule extends AbstractMaterializedViewR
"Rewrite expressions by view in scan fail",
() -> String.format("expressionToRewritten is %s,\n mvExprToMvScanExprMapping is %s,\n"
+ "targetToSourceMapping = %s", queryStructInfo.getExpressions(),
materializationContext.getMvExprToMvScanExprMapping(),
materializationContext.getExprToScanExprMapping(),
targetToSourceMapping));
return null;
}