public static void validate(TableConfig tableConfig, @Nullable Schema schema) {
    validate(tableConfig, schema, null);
  }
@Test
  public void testValidateFieldConfig() {
    Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
        .addDateTime(TIME_COLUMN, FieldSpec.DataType.LONG, "1:HOURS:EPOCH", "1:HOURS")
        .addSingleValueDimension("myCol1", FieldSpec.DataType.STRING)
        .addMultiValueDimension("myCol2", FieldSpec.DataType.INT)
        .addSingleValueDimension("intCol", FieldSpec.DataType.INT).build();
    TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol1")).build();

    try {
      FieldConfig fieldConfig = new FieldConfig("myCol1", FieldConfig.EncodingType.RAW, null, null, null, null, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.fail("all nullable fields set for fieldConfig should pass", e);
    }

    try {
      FieldConfig fieldConfig =
          new FieldConfig("myCol1", FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.FST, null, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail for with conflicting encoding type of myCol1");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "FieldConfig encoding type is different from indexingConfig for column: myCol1");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol1")).build();
    try {
      FieldConfig fieldConfig =
          new FieldConfig("myCol1", FieldConfig.EncodingType.RAW, FieldConfig.IndexType.FST, null, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail since FST index is enabled on RAW encoding type");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot create FST index on column: myCol1, it can only be applied to dictionary encoded single value "
              + "string columns");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build();
    try {
      FieldConfig fieldConfig =
          new FieldConfig("myCol2", FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.FST, null, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail since FST index is enabled on multi value column");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot create FST index on column: myCol2, it can only be applied to dictionary encoded single value "
              + "string columns");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build();
    try {
      FieldConfig fieldConfig =
          new FieldConfig("intCol", FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.FST, null, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail since FST index is enabled on non String column");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot create FST index on column: intCol, it can only be applied to dictionary encoded single value "
              + "string columns");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol2", "intCol")).build();
    try {
      FieldConfig fieldConfig =
          new FieldConfig("intCol", FieldConfig.EncodingType.RAW, FieldConfig.IndexType.TEXT, null, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail since TEXT index is enabled on non String column");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot create text index on column: intCol, it can only be applied to string columns");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol1")).build();
    try {
      FieldConfig fieldConfig =
          new FieldConfig("myCol21", FieldConfig.EncodingType.RAW, FieldConfig.IndexType.FST, null, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail since field name is not present in schema");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Column: myCol21 defined in field config list must be a valid column defined in the schema");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build();
    try {
      FieldConfig fieldConfig = new FieldConfig("intCol", FieldConfig.EncodingType.DICTIONARY, Collections.emptyList(),
          CompressionCodec.SNAPPY, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail since dictionary encoding does not support compression codec SNAPPY");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(), "Compression codec: SNAPPY is not applicable to dictionary encoded index");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build();
    try {
      FieldConfig fieldConfig = new FieldConfig("intCol", FieldConfig.EncodingType.RAW, Collections.emptyList(),
          CompressionCodec.MV_ENTRY_DICT, null);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail since raw encoding does not support compression codec MV_ENTRY_DICT");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(), "Compression codec: MV_ENTRY_DICT is not applicable to raw index");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol1")).build();
    try {
      // Enable forward index disabled flag for a raw column. This should succeed as though the forward index cannot
      // be rebuilt without a dictionary, the constraint to have a dictionary has been lifted.
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol1", FieldConfig.EncodingType.RAW, null, null, null, null, fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.fail("Validation should pass since forward index can be disabled for a column without a dictionary");
    }

    try {
      // Enable forward index disabled flag for a column without inverted index. This should succeed as though the
      // forward index cannot be rebuilt without an inverted index, the constraint to have an inverted index has been
      // lifted.
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol2", FieldConfig.EncodingType.DICTIONARY, null, null, null, null, fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.fail("Validation should pass since forward index can be disabled for a column without an inverted index");
    }

    try {
      // Enable forward index disabled flag for a column and verify that dictionary override options are not set.
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      tableConfig.getIndexingConfig().setOptimizeDictionaryForMetrics(true);
      tableConfig.getIndexingConfig().setOptimizeDictionaryForMetrics(true);
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(), "Dictionary override optimization options (OptimizeDictionary, "
          + "optimizeDictionaryForMetrics) not supported with forward index for column: myCol2, disabled");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol1")).setInvertedIndexColumns(Arrays.asList("myCol2")).build();
    try {
      // Enable forward index disabled flag for a column with inverted index
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol2", FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.INVERTED, null, null,
              null, fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.fail("Should not fail as myCol2 has forward index disabled but inverted index enabled");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol1")).setInvertedIndexColumns(Arrays.asList("myCol2"))
        .setSortedColumn("myCol2").build();
    try {
      // Enable forward index disabled flag for a column with inverted index and is sorted
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol2", FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.INVERTED, null, null,
              null, fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.fail("Should not fail for myCol2 with forward index disabled but is sorted, this is a no-op");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol1")).setInvertedIndexColumns(Arrays.asList("myCol2"))
        .setRangeIndexColumns(Arrays.asList("myCol2")).build();
    try {
      // Enable forward index disabled flag for a multi-value column with inverted index and range index
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol2", FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.INVERTED,
              Arrays.asList(FieldConfig.IndexType.INVERTED, FieldConfig.IndexType.RANGE), null, null,
              fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail for MV myCol2 with forward index disabled but has range and inverted index");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(), "Feature not supported for multi-value columns with range index. "
          + "Cannot disable forward index for column myCol2. Disable range index on this column to use this feature.");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setInvertedIndexColumns(Arrays.asList("myCol1")).setRangeIndexColumns(Arrays.asList("myCol1")).build();
    try {
      // Enable forward index disabled flag for a singe-value column with inverted index and range index v1
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol1", FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.INVERTED,
              Arrays.asList(FieldConfig.IndexType.INVERTED, FieldConfig.IndexType.RANGE), null, null,
              fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      tableConfig.getIndexingConfig().setRangeIndexVersion(1);
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should fail for SV myCol1 with forward index disabled but has range v1 and inverted index");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(), "Feature not supported for single-value columns with range index version "
          + "< 2. Cannot disable forward index for column myCol1. Either disable range index or create range index "
          + "with version >= 2 to use this feature.");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol2")).setInvertedIndexColumns(Arrays.asList("myCol2")).build();
    try {
      // Enable forward index disabled flag for a column with inverted index and disable dictionary
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol2", FieldConfig.EncodingType.RAW, FieldConfig.IndexType.INVERTED, null, null, null,
              fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should not be able to disable dictionary but keep inverted index");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot create an Inverted index on column myCol2 specified in the noDictionaryColumns config");
    }

    // Tests the case when the field-config list marks a column as raw (non-dictionary) and enables
    // inverted index on it
    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build();
    try {
      FieldConfig fieldConfig = new FieldConfig.Builder("myCol2")
          .withIndexTypes(Arrays.asList(FieldConfig.IndexType.INVERTED))
          .withEncodingType(FieldConfig.EncodingType.RAW).build();
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should not be able to disable dictionary but keep inverted index");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot create inverted index on column: myCol2, it can only be applied to dictionary encoded columns");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("myCol2")).build();
    try {
      // Enable forward index disabled flag for a column with FST index and disable dictionary
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("myCol2", FieldConfig.EncodingType.RAW, FieldConfig.IndexType.FST, null, null, null,
              fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
      Assert.fail("Should not be able to disable dictionary but keep inverted index");
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot create FST index on column: myCol2, it can only be applied to dictionary encoded single value "
              + "string columns");
    }

    tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME)
        .setNoDictionaryColumns(Arrays.asList("intCol")).setRangeIndexColumns(Arrays.asList("intCol")).build();
    try {
      // Enable forward index disabled flag for a column with FST index and disable dictionary
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("intCol", FieldConfig.EncodingType.RAW, FieldConfig.IndexType.RANGE, null, null, null,
              fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.fail("Range index with forward index disabled no dictionary column is allowed");
    }

    // Disabling forward index for realtime table will make the validation failed.
    Map<String, String> streamConfigs = getStreamConfigs();
    tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME).setTimeColumnName(TIME_COLUMN)
        .setNoDictionaryColumns(Arrays.asList("intCol")).setStreamConfigs(streamConfigs).build();
    try {
      // Enable forward index disabled flag for a column with inverted index index and disable dictionary
      Map<String, String> fieldConfigProperties = new HashMap<>();
      fieldConfigProperties.put(FieldConfig.FORWARD_INDEX_DISABLED, Boolean.TRUE.toString());
      FieldConfig fieldConfig =
          new FieldConfig("intCol", FieldConfig.EncodingType.RAW, FieldConfig.IndexType.INVERTED, null, null, null,
              fieldConfigProperties);
      tableConfig.setFieldConfigList(Arrays.asList(fieldConfig));
      TableConfigUtils.validate(tableConfig, schema);
    } catch (Exception e) {
      Assert.assertEquals(e.getMessage(),
          "Cannot disable forward index for column intCol, as the table type is REALTIME.");
    }
  }