docs: protected String checkModifyWhitelist(final CatalogType suspect,
                                          final CatalogType prevType,
                                          final String field)
      {
          // should generate this from spec.txt
  
          if (suspect instanceof Systemsettings &&
                  (field.equals("elasticduration") || field.equals("elasticthroughput")
                          || field.equals("querytimeout"))) {
              return null;
          } else {
              m_canOccurWithElasticRebalance = false;
          }
  
          // Support any modification of these
          // I added Statement and PlanFragment for the need of materialized view recalculation plan updates.
          // ENG-8641, yzhang.
          if (suspect instanceof User ||
              suspect instanceof Group ||
              suspect instanceof Procedure ||
              suspect instanceof SnapshotSchedule ||
              suspect instanceof UserRef ||
              suspect instanceof GroupRef ||
              suspect instanceof ColumnRef ||
              suspect instanceof Statement ||
              suspect instanceof PlanFragment) {
              return null;
          }
  
          if (suspect instanceof TimeToLive) {
              TimeToLive current = (TimeToLive)suspect;
              if (prevType != null) {
                  TimeToLive previous = (TimeToLive)prevType;
                  if (previous.getMigrationtarget() == null && current.getMigrationtarget() != null) {
                      m_requiresNewExportGeneration= true;
                  }
              }
              return null;
          }
  
          // Allow functions to add dependers.
          if (suspect instanceof Function && field.equals("stmtDependers")) {
              return null;
          }
          // Support modification of these specific fields
          if (suspect instanceof Database && field.equals("schema")) {
              return null;
          }
          if (suspect instanceof Database && "securityprovider".equals(field)) {
              return null;
          }
          if (suspect instanceof Cluster && field.equals("securityEnabled")) {
              return null;
          }
          if (suspect instanceof Cluster && field.equals("adminstartup")) {
              return null;
          }
          if (suspect instanceof Cluster && field.equals("heartbeatTimeout")) {
              return null;
          }
          if (suspect instanceof Cluster && field.equals("drProducerEnabled")) {
              return null;
          }
          if (suspect instanceof Cluster && field.equals("drConsumerEnabled")) {
              return null;
          }
          if (suspect instanceof Cluster && field.equals("preferredSource")) {
              return null;
          }
          if (suspect instanceof Connector && "enabled".equals(field)) {
              m_requiresNewExportGeneration = true;
              return null;
          }
          if (suspect instanceof Connector && "loaderclass".equals(field)) {
              m_requiresNewExportGeneration = true;
              return null;
          }
          // ENG-6511 Allow materialized views to change the index they use dynamically.
          if (suspect instanceof IndexRef && field.equals("name")) {
              return null;
          }
  
          // Avoid over-generalization when describing limitations that are dependent on particular
          // cases of BEFORE and AFTER values by listing the offending values.
          String restrictionQualifier = "";
  
          if (suspect instanceof Cluster) {
              if (field.equals("drFlushInterval")) {
                  return null;
              } else if (field.equals("drProducerPort")) {
                  // Don't allow changes to ClusterId or ProducerPort while not transitioning to or from Disabled
                  if ((Boolean)prevType.getField("drProducerEnabled") && (Boolean)suspect.getField("drProducerEnabled")) {
                      restrictionQualifier = " while DR is enabled";
                  }
                  else {
                      return null;
                  }
              } else if (field.equals("drMasterHost")) {
                  String source = (String)suspect.getField("drMasterHost");
                  if (source.isEmpty() && (Boolean)suspect.getField("drConsumerEnabled")) {
                      restrictionQualifier = " while DR is enabled";
                  }
                  else {
                      return null;
                  }
              } else if (field.equals("drRole")) {
                  final String prevRole = (String) prevType.getField("drRole");
                  final String newRole = (String) suspect.getField("drRole");
                  // Promote from replica to master
                  if (prevRole.equals(DrRoleType.REPLICA.value()) && newRole.equals(DrRoleType.MASTER.value())) {
                      return null;
                  }
                  // Everything else is illegal
                  else {
                      restrictionQualifier = " from " + prevRole + " to " + newRole;
                  }
              } else if (field.equals("drConsumerSslPropertyFile")) {
                  return null;
              }
          }
  
          if (suspect instanceof Constraint && field.equals("index"))
              return null;
          if (suspect instanceof Table) {
              if (field.equals("signature") ||
                  field.equals("tuplelimit") || field.equals("tableType"))
                  return null;
  
              // Always allow disabling DR on table
              if (field.equalsIgnoreCase("isdred")) {
                  Boolean isDRed = (Boolean) suspect.getField(field);
                  assert isDRed != null;
                  if (!isDRed) return null;
              }
          }
  
          // whitelist certain column changes
          if (suspect instanceof Column) {
              CatalogType parent = suspect.getParent();
              // can change statements
              if (parent instanceof Statement) {
                  return null;
              }
  
              // all table column changes require snapshot isolation for now
              m_requiresSnapshotIsolation = true;
  
              // now assume parent is a Table
              Table table = (Table) parent;
              if (CatalogUtil.isTableExportOnly((Database)table.getParent(), table)) {
                  m_requiresNewExportGeneration = true;
                  return null;
              }
  
              if (field.equals("index")) {
                  return null;
              }
              if (field.equals("defaultvalue")) {
                  return null;
              }
              if (field.equals("defaulttype")) {
                  return null;
              }
              if (field.equals("nullable")) {
                  Boolean nullable = (Boolean) suspect.getField(field);
                  assert(nullable != null);
                  if (nullable) return null;
                  restrictionQualifier = " from nullable to non-nullable";
              }
              else if (field.equals("type") || field.equals("size") || field.equals("inbytes")) {
                  int oldTypeInt = (Integer) prevType.getField("type");
                  int newTypeInt = (Integer) suspect.getField("type");
                  int oldSize = (Integer) prevType.getField("size");
                  int newSize = (Integer) suspect.getField("size");
  
                  VoltType oldType = VoltType.get((byte) oldTypeInt);
                  VoltType newType = VoltType.get((byte) newTypeInt);
  
                  boolean oldInBytes = false, newInBytes = false;
                  if (oldType == VoltType.STRING) {
                      oldInBytes = (Boolean) prevType.getField("inbytes");
                  }
                  if (newType == VoltType.STRING) {
                      newInBytes = (Boolean) suspect.getField("inbytes");
                  }
  
                  if (checkIfColumnTypeChangeIsSupported(oldType, oldSize, newType, newSize,
                          oldInBytes, newInBytes)) {
                      return null;
                  }
                  if (oldTypeInt == newTypeInt) {
                      if (oldType == VoltType.STRING && oldInBytes == false && newInBytes == true) {
                          restrictionQualifier = " narrowing from " + oldSize + "CHARACTERS to "
                      + newSize * CatalogSizing.MAX_BYTES_PER_UTF8_CHARACTER + " BYTES";
                      } else {
                          restrictionQualifier = " narrowing from " + oldSize + " to " + newSize;
                      }
                  }
                  else {
                      // ENG-13094 If the data type already changed, we do not throw more errors about the size.
                      if ( ! field.equals("type")) {
                          return null;
                      }
                      restrictionQualifier = " from " + oldType.toSQLString() +
                                             " to " + newType.toSQLString();
                  }
              }
          }
  
          else if (suspect instanceof MaterializedViewInfo) {
              if ( ! m_inStrictMatViewDiffMode) {
                  // Ignore differences to json fields that only reflect other underlying
                  // changes that are presumably checked and accepted/rejected separately.
                  if (field.equals("groupbyExpressionsJson") ||
                          field.equals("aggregationExpressionsJson")) {
                      if (AbstractExpression.areOverloadedJSONExpressionLists((String)prevType.getField(field),
                              (String)suspect.getField(field))) {
                          return null;
                      }
                  }
              }
          }
  
          else if (isTableLimitDeleteStmt(suspect)) {
              return null;
          }
  
          // Also allow any field changes (that haven't triggered an early return already)
          // if they are found anywhere in these sub-trees.
  
          //TODO: There's a "salmon of doubt" about all this upstream checking in the middle of a
          // downward recursion.
          // In effect, each sub-element of these certain parent object types has been forced to
          // successfully "run the gnutella" of qualifiers above.
          // Having survived, they are only now paternity tested
          //  -- which repeatedly revisits once per changed field, per (recursive) child,
          // each of the parents that were seen on the way down --
          // to possibly decide "nevermind, this change is grand-fathered in after all".
          // A better general approach would be for the parent object types,
          // as they are recursed into, to set one or more state mode flags on the CatalogDiffEngine.
          // These would be somewhat like m_inStrictMatViewDiffMode
          // -- but with a loosening rather than restricting effect on recursive tests.
          // This would provide flexibility in the future for the grand-fathered elements
          // to bypass as many or as few checks as desired.
  
          for (CatalogType parent = suspect.getParent(); parent != null; parent = parent.getParent()) {
              if (parent instanceof Procedure || parent instanceof ColumnRef) {
                  if (m_triggeredVerbosity) {
                      System.out.println("DEBUG VERBOSE diffRecursively field change to " +
                                         "'" + field + "' of schema object '" + suspect + "'" +
                                         restrictionQualifier +
                                         " rescued by context '" + parent + "'");
                  }
                  return null;
              }
              // allow export connector property changes
              if (parent instanceof Connector && suspect instanceof ConnectorProperty) {
                  m_requiresNewExportGeneration = true;
                  return null;
              }
  
              if (isTableLimitDeleteStmt(parent)) {
                  return null;
              }
          }
  
          return "May not dynamically modify field '" + field +
                          "' of schema object '" + suspect + "'" + restrictionQualifier;
      }