docs: public void startProcessing(Database db, HashMap<Table, String> matViewMap, TreeSet<String> exportTableNames)
              throws VoltCompilerException {
          HashSet <String> viewTableNames = new HashSet<>();
          for (Entry<Table, String> entry : matViewMap.entrySet()) {
              viewTableNames.add(entry.getKey().getTypeName());
          }
  
          for (Entry<Table, String> entry : matViewMap.entrySet()) {
              Table destTable = entry.getKey();
              String query = entry.getValue();
  
              // get the xml for the query
              VoltXMLElement xmlquery = null;
              try {
                  xmlquery = m_hsql.getXMLCompiledStatement(query);
              }
              catch (HSQLParseException e) {
                  e.printStackTrace();
              }
              assert(xmlquery != null);
  
              // parse the xml like any other sql statement
              ParsedSelectStmt stmt = null;
              try {
                  stmt = (ParsedSelectStmt) AbstractParsedStmt.parse(null, query, xmlquery, null, db, null);
              }
              catch (Exception e) {
                  throw m_compiler.new VoltCompilerException(e.getMessage());
              }
              assert(stmt != null);
              // NOTE: it is hard to check for display columns at DDLCompiler time when we get from HSQL, so we
              // have to guard in here (the actual parsing is done in ParseSelectStmt)
              if (stmt.m_displayColumns.stream().anyMatch(col ->
                      col.m_expression.getExpressionType() == ExpressionType.AGGREGATE_COUNT &&
                              ((AggregateExpression) col.m_expression).isDistinct())) {
                  throw new PlanningErrorException(String.format("View does not support COUNT(DISTINCT) expression: \"%s\"",
                          stmt.m_sql));
              }
  
              String viewName = destTable.getTypeName();
              // throw an error if the view isn't within voltdb's limited world view
              checkViewMeetsSpec(viewName, stmt);
  
              // Allow only non-unique indexes other than the primary key index.
              // The primary key index is yet to be defined (below).
              for (Index destIndex : destTable.getIndexes()) {
                  if (destIndex.getUnique() || destIndex.getAssumeunique()) {
                      String msg = "A UNIQUE or ASSUMEUNIQUE index is not allowed on a materialized view. " +
                                   "Remove the qualifier from the index " + destIndex.getTypeName() +
                                   "defined on the materialized view \"" + viewName + "\".";
                      throw m_compiler.new VoltCompilerException(msg);
                  }
              }
  
              // A Materialized view cannot depend on another view.
              for (Table srcTable : stmt.m_tableList) {
                  if (viewTableNames.contains(srcTable.getTypeName())) {
                      String msg = String.format("A materialized view (%s) can not be defined on another view (%s).",
                              viewName, srcTable.getTypeName());
                      throw m_compiler.new VoltCompilerException(msg);
                  }
              }
  
              // The existing code base still need this materializer field to tell if a table
              // is a materialized view table. Leaving this for future refactoring.
              destTable.setMaterializer(stmt.m_tableList.get(0));
  
              List<Column> destColumnArray = CatalogUtil.getSortedCatalogItems(destTable.getColumns(), "index");
              List<AbstractExpression> groupbyExprs = null;
              if (stmt.hasComplexGroupby()) {
                  groupbyExprs = new ArrayList<>();
                  for (ParsedColInfo col: stmt.groupByColumns()) {
                      groupbyExprs.add(col.m_expression);
                  }
              }
  
              // Generate query XMLs for min/max recalculation (ENG-8641)
              boolean isMultiTableView = stmt.m_tableList.size() > 1;
              MatViewFallbackQueryXMLGenerator xmlGen = new MatViewFallbackQueryXMLGenerator(xmlquery, stmt.groupByColumns(), stmt.m_displayColumns, isMultiTableView);
              List<VoltXMLElement> fallbackQueryXMLs = xmlGen.getFallbackQueryXMLs();
  
              // create an index and constraint for the table
              // After ENG-7872 is fixed if there is no group by column then we will not create any
              // index or constraint in order to avoid error and crash.
              if (stmt.groupByColumns().size() != 0) {
                  Index pkIndex = destTable.getIndexes().add(HSQLInterface.AUTO_GEN_MATVIEW_IDX);
                  pkIndex.setType(IndexType.BALANCED_TREE.getValue());
                  pkIndex.setUnique(true);
                  // add the group by columns from the src table
                  // assume index 1 throuh #grpByCols + 1 are the cols
                  for (int i = 0; i < stmt.groupByColumns().size(); i++) {
                      ColumnRef c = pkIndex.getColumns().add(String.valueOf(i));
                      c.setColumn(destColumnArray.get(i));
                      c.setIndex(i);
                  }
                  Constraint pkConstraint = destTable.getConstraints().add(HSQLInterface.AUTO_GEN_MATVIEW_CONST);
                  pkConstraint.setType(ConstraintType.PRIMARY_KEY.getValue());
                  pkConstraint.setIndex(pkIndex);
              }
              // If we have an unsafe MV message, then
              // remember it here.  We don't really know how
              // to transfer the message through the catalog, but
              // we can transmit the existence of the message.
              boolean isSafeForDDL = (stmt.getUnsafeMVMessage() == null);
              // Here we will calculate the maximum allowed size for variable-length columns in the view.
              // The maximum row size is 2MB. We will first subtract the sum of
              // all fixed-size data type column sizes from this 2MB allowance then
              // divide it by the number of variable-length columns.
              int maximumColumnSize = DDLCompiler.MAX_ROW_SIZE;
              int varLengthColumnCount = stmt.m_displayColumns.size();
              for (int i = 0; i < stmt.m_displayColumns.size(); i++) {
                  ParsedColInfo col = stmt.m_displayColumns.get(i);
                  if ( ! col.m_expression.getValueType().isVariableLength()) {
                      varLengthColumnCount--;
                      maximumColumnSize -= col.m_expression.getValueSize();
                  }
              }
              if (varLengthColumnCount > 0) {
                  maximumColumnSize /= varLengthColumnCount;
              }
              // Note that the size of a single column cannot be larger than 1MB.
              if (maximumColumnSize > DDLCompiler.MAX_VALUE_LENGTH) {
                  maximumColumnSize = DDLCompiler.MAX_VALUE_LENGTH;
              }
  
              // Here the code path diverges for different kinds of views (single table view and joined table view)
              if (isMultiTableView) {
                  // Materialized view on joined tables
                  // Add mvHandlerInfo to the destTable:
                  MaterializedViewHandlerInfo mvHandlerInfo = destTable.getMvhandlerinfo().add("mvHandlerInfo");
                  mvHandlerInfo.setDesttable(destTable);
                  for (Table srcTable : stmt.m_tableList) {
                      // Now we do not support having a view on persistent tables joining streamed tables.
                      if (exportTableNames.contains(srcTable.getTypeName())) {
                          String msg = String.format("A materialized view (%s) on joined tables cannot have streamed table (%s) as its source.",
                                                     viewName, srcTable.getTypeName());
                          throw m_compiler.new VoltCompilerException(msg);
                      }
                      // The view table will need to keep a list of its source tables.
                      // The list is used to install / uninstall the view reference on the source tables when the
                      // view handler is constructed / destroyed.
                      TableRef tableRef = mvHandlerInfo.getSourcetables().add(srcTable.getTypeName());
                      tableRef.setTable(srcTable);
  
                      // Try to find a partition column for the view table.
                      // There could be more than one partition column candidate, but we will only use the first one we found.
                      if (destTable.getPartitioncolumn() == null && srcTable.getPartitioncolumn() != null) {
                          Column partitionColumn = srcTable.getPartitioncolumn();
                          String partitionColName = partitionColumn.getTypeName();
                          String srcTableName = srcTable.getTypeName();
                          destTable.setIsreplicated(false);
                          if (stmt.hasComplexGroupby()) {
                              for (int i = 0; i < groupbyExprs.size(); i++) {
                                  AbstractExpression groupbyExpr = groupbyExprs.get(i);
                                  if (groupbyExpr instanceof TupleValueExpression) {
                                      TupleValueExpression tve = (TupleValueExpression) groupbyExpr;
                                      if (tve.getTableName().equals(srcTableName) && tve.getColumnName().equals(partitionColName)) {
                                          // The partition column is set to destColumnArray.get(i), because we have the restriction
                                          // that the non-aggregate columns must come at the very begining, and must exactly match
                                          // the group-by columns.
                                          // If we are going to remove this restriction in the future, then we need to do more work
                                          // in order to find a proper partition column.
                                          destTable.setPartitioncolumn(destColumnArray.get(i));
                                          break;
                                      }
                                  }
                              }
                          }
                          else {
                              for (int i = 0; i < stmt.groupByColumns().size(); i++) {
                                  ParsedColInfo gbcol = stmt.groupByColumns().get(i);
                                  if (gbcol.m_tableName.equals(srcTableName) && gbcol.m_columnName.equals(partitionColName)) {
                                      destTable.setPartitioncolumn(destColumnArray.get(i));
                                      break;
                                  }
                              }
                          }
                      } // end find partition column
                  } // end for each source table
  
                  compileFallbackQueriesAndUpdateCatalog(db, query, fallbackQueryXMLs, mvHandlerInfo);
                  compileCreateQueryAndUpdateCatalog(db, query, xmlquery, mvHandlerInfo);
                  mvHandlerInfo.setGroupbycolumncount(stmt.groupByColumns().size());
  
                  for (int i=0; i<stmt.m_displayColumns.size(); i++) {
                      ParsedColInfo col = stmt.m_displayColumns.get(i);
                      Column destColumn = destColumnArray.get(i);
                      setTypeAttributesForColumn(destColumn, col.m_expression, maximumColumnSize);
  
                      // Set the expression type here to determine the behavior of the merge function.
                      destColumn.setAggregatetype(col.m_expression.getExpressionType().getValue());
                  }
                  mvHandlerInfo.setIssafewithnonemptysources(isSafeForDDL);
              }
              else { // =======================================================================================
                  // Materialized view on single table
                  // create the materializedviewinfo catalog node for the source table
                  Table srcTable = stmt.m_tableList.get(0);
                  MaterializedViewInfo matviewinfo = srcTable.getViews().add(viewName);
                  matviewinfo.setDest(destTable);
  
                  AbstractExpression where = stmt.getSingleTableFilterExpression();
                  if (where != null) {
                      String hex = Encoder.hexEncode(where.toJSONString());
                      matviewinfo.setPredicate(hex);
                  }
                  else {
                      matviewinfo.setPredicate("");
                  }
  
                  List<Column> srcColumnArray = CatalogUtil.getSortedCatalogItems(srcTable.getColumns(), "index");
                  if (stmt.hasComplexGroupby()) {
                      // Parse group by expressions to json string
                      String groupbyExprsJson = null;
                      try {
                          groupbyExprsJson = DDLCompiler.convertToJSONArray(groupbyExprs);
                      } catch (JSONException e) {
                          throw m_compiler.new VoltCompilerException ("Unexpected error serializing non-column " +
                                  "expressions for group by expressions: " + e.toString());
                      }
                      matviewinfo.setGroupbyexpressionsjson(groupbyExprsJson);
                  }
                  else {
                      // add the group by columns from the src table
                      for (int i = 0; i < stmt.groupByColumns().size(); i++) {
                          ParsedColInfo gbcol = stmt.groupByColumns().get(i);
                          Column srcCol = srcColumnArray.get(gbcol.m_index);
                          ColumnRef cref = matviewinfo.getGroupbycols().add(srcCol.getTypeName());
                          // groupByColumns is iterating in order of groups. Store that grouping order
                          // in the column ref index. When the catalog is serialized, it will, naturally,
                          // scramble this order like a two year playing dominos, presenting the data
                          // in a meaningless sequence.
                          cref.setIndex(i);           // the column offset in the view's grouping order
                          cref.setColumn(srcCol);     // the source column from the base (non-view) table
  
                          // parse out the group by columns into the dest table
                          ParsedColInfo col = stmt.m_displayColumns.get(i);
                          Column destColumn = destColumnArray.get(i);
                          processMaterializedViewColumn(srcTable, destColumn,
                                  ExpressionType.VALUE_TUPLE, (TupleValueExpression)col.m_expression);
                      }
                  }
  
                  // prepare info for aggregation columns and COUNT(*) column(s)
                  List<AbstractExpression> aggregationExprs = new ArrayList<>();
                  boolean hasAggregationExprs = false;
                  ArrayList<AbstractExpression> minMaxAggs = new ArrayList<>();
                  for (int i = stmt.groupByColumns().size(); i < stmt.m_displayColumns.size(); i++) {
                      ParsedColInfo col = stmt.m_displayColumns.get(i);
                      // skip COUNT(*)
                      if ( col.m_expression.getExpressionType() == ExpressionType.AGGREGATE_COUNT_STAR ) {
                          continue;
                      }
                      AbstractExpression aggExpr = col.m_expression.getLeft();
                      if (aggExpr.getExpressionType() != ExpressionType.VALUE_TUPLE) {
                          hasAggregationExprs = true;
                      }
                      aggregationExprs.add(aggExpr);
                      if (col.m_expression.getExpressionType() ==  ExpressionType.AGGREGATE_MIN ||
                              col.m_expression.getExpressionType() == ExpressionType.AGGREGATE_MAX) {
                          minMaxAggs.add(aggExpr);
                      }
                  }
  
                  compileFallbackQueriesAndUpdateCatalog(db, query, fallbackQueryXMLs, matviewinfo);
  
                  // set Aggregation Expressions.
                  if (hasAggregationExprs) {
                      String aggregationExprsJson = null;
                      try {
                          aggregationExprsJson = DDLCompiler.convertToJSONArray(aggregationExprs);
                      } catch (JSONException e) {
                          throw m_compiler.new VoltCompilerException ("Unexpected error serializing non-column " +
                                  "expressions for aggregation expressions: " + e.toString());
                      }
                      matviewinfo.setAggregationexpressionsjson(aggregationExprsJson);
                  }
  
                  // Find index for each min/max aggCol/aggExpr (ENG-6511 and ENG-8512)
                  for (Integer i = 0; i < minMaxAggs.size(); ++i) {
                      Index found = findBestMatchIndexForMatviewMinOrMax(matviewinfo, srcTable, groupbyExprs, minMaxAggs.get(i));
                      IndexRef refFound = matviewinfo.getIndexforminmax().add(i.toString());
                      if (found != null) {
                          refFound.setName(found.getTypeName());
                      } else {
                          refFound.setName("");
                      }
                  }
  
                  // This is to fix the data type mismatch of the group by columns (and potentially other columns).
                  // The COUNT(*) should return a BIGINT column, whereas we found here the COUNT(*) was assigned a INTEGER column is fixed below loop.
                  for (int i = 0; i < stmt.groupByColumns().size(); i++) {
                      ParsedColInfo col = stmt.m_displayColumns.get(i);
                      Column destColumn = destColumnArray.get(i);
                      setTypeAttributesForColumn(destColumn, col.m_expression, maximumColumnSize);
                  }
  
                  // parse out the aggregation columns into the dest table
                  for (int i = stmt.groupByColumns().size(); i < stmt.m_displayColumns.size(); i++) {
                      ParsedColInfo col = stmt.m_displayColumns.get(i);
                      Column destColumn = destColumnArray.get(i);
  
                      AbstractExpression colExpr = col.m_expression.getLeft();
                      TupleValueExpression tve = null;
  
                      if ( col.m_expression.getExpressionType() != ExpressionType.AGGREGATE_COUNT_STAR
                              && colExpr.getExpressionType() == ExpressionType.VALUE_TUPLE) {
                          tve = (TupleValueExpression)colExpr;
                      }
  
                      processMaterializedViewColumn(srcTable, destColumn,
                              col.m_expression.getExpressionType(), tve);
                      setTypeAttributesForColumn(destColumn, col.m_expression, maximumColumnSize);
                  }
  
                  if (srcTable.getPartitioncolumn() != null) {
                      // Set the partitioning of destination tables of associated views.
                      // If a view's source table is replicated, then a full scan of the
                      // associated view is single-sited. If the source is partitioned,
                      // a full scan of the view must be distributed, unless it is filtered
                      // by the original table's partitioning key, which, to be filtered,
                      // must also be a GROUP BY key.
                      destTable.setIsreplicated(false);
                      setGroupedTablePartitionColumn(matviewinfo, srcTable.getPartitioncolumn());
                  }
                  matviewinfo.setIssafewithnonemptysources(isSafeForDDL);
              } // end if single table view materialized view.
          }
      }