/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.carbondata.spark.testsuite.alterTable

import java.io.{ByteArrayOutputStream, PrintStream}

import org.apache.spark.sql.{CarbonEnv, Row}
import org.apache.spark.sql.test.util.QueryTest
import org.scalatest.BeforeAndAfterAll

import org.apache.carbondata.core.constants.CarbonCommonConstants
import org.apache.carbondata.core.util.CarbonProperties
import org.apache.carbondata.core.util.path.CarbonTablePath
import org.apache.carbondata.tool.CarbonCli

class TestAlterTableSortColumnsProperty extends QueryTest with BeforeAndAfterAll {

  override def beforeAll(): Unit = {
    CarbonProperties.getInstance().addProperty(CarbonCommonConstants.CARBON_DATE_FORMAT,
      "yyyy-MM-dd")
    CarbonProperties.getInstance().addProperty(CarbonCommonConstants.CARBON_TIMESTAMP_FORMAT,
      "yyyy-MM-dd HH:mm:ss")
    CarbonProperties.getInstance()
      .addProperty(CarbonCommonConstants.ENABLE_QUERY_STATISTICS, "false")
    dropTable()
    prepareTable()
  }

  override def afterAll(): Unit = {
    defaultConfig()
    dropTable()
    CarbonProperties.getInstance().addProperty(CarbonCommonConstants.CARBON_DATE_FORMAT,
      CarbonCommonConstants.CARBON_DATE_DEFAULT_FORMAT)
    CarbonProperties.getInstance().addProperty(CarbonCommonConstants.CARBON_TIMESTAMP_FORMAT,
      CarbonCommonConstants.CARBON_TIMESTAMP_DEFAULT_FORMAT)
    CarbonProperties.getInstance()
      .addProperty(CarbonCommonConstants.ENABLE_QUERY_STATISTICS,
        CarbonCommonConstants.ENABLE_QUERY_STATISTICS_DEFAULT)
  }

  private def prepareTable(): Unit = {
    createTable(
      "alter_sc_base",
      Map("sort_scope"->"local_sort", "sort_columns"->"stringField")
    )
    createTable(
      "alter_sc_base_complex",
      Map("sort_scope"->"local_sort", "sort_columns"->"stringField"),
      true
    )
    createTable(
      "alter_sc_validate",
      Map.empty,
      true
    )
    createTable(
      "alter_sc_iud",
      Map.empty
    )
    createTable(
      "alter_sc_iud_complex",
      Map.empty,
      true
    )
    createTable(
      "alter_sc_long_string",
      Map("LONG_STRING_COLUMNS"->"stringField"),
      true
    )
    createTable(
      "alter_sc_insert",
      Map("sort_scope"->"local_sort", "sort_columns"->"stringField")
    )
    loadData("alter_sc_insert")
    createTable(
      "alter_sc_insert_complex",
      Map("sort_scope"->"local_sort", "sort_columns"->"stringField"),
      true
    )
    loadData("alter_sc_insert_complex")
    createTable(
      "alter_sc_range_column",
      Map("sort_scope" -> "local_sort",
        "sort_columns" -> "stringField",
        "range_column" -> "smallIntField")
    )
    createTable(
      "alter_sc_range_column_base",
      Map("sort_scope"->"local_sort", "sort_columns"->"stringField")
    )

    Array("alter_sc_add_column", "alter_sc_add_column_base").foreach { tableName =>
      sql(
        s"""create table $tableName(
           | smallIntField smallInt,
           | intField int,
           | bigIntField bigint,
           | floatField float,
           | doubleField double,
           | timestampField timestamp,
           | dateField date,
           | stringField string
           | )
           | stored as carbondata
      """.stripMargin)
    }
    // decimalField decimal(25, 4),

    createTable(
      "alter_sc_bloom",
      Map("sort_scope"->"local_sort", "sort_columns"->"stringField")
    )
    createBloomIndex("alter_sc_bloom", "alter_sc_bloom_dm1")
    createTable(
      "alter_sc_bloom_base",
      Map("sort_scope"->"local_sort", "sort_columns"->"stringField")
    )
    createBloomIndex("alter_sc_bloom_base", "alter_sc_bloom_base_dm1")
    createTable(
      "alter_sc_agg",
      Map("sort_scope"->"local_sort", "sort_columns"->"intField")
    )
    createTable(
      "alter_sc_agg_base",
      Map("sort_scope"->"local_sort", "sort_columns"->"intField")
    )

    createTable(
      "alter_sc_cli",
      Map.empty
    )
  }

  private def dropTable(): Unit = {
    sql(s"drop table if exists alter_sc_base")
    sql(s"drop table if exists alter_sc_base_complex")
    sql(s"drop table if exists alter_sc_validate")
    sql(s"drop table if exists alter_sc_iud")
    sql(s"drop table if exists alter_sc_iud_complex")
    sql(s"drop table if exists alter_sc_long_string")
    sql(s"drop table if exists alter_sc_insert")
    sql(s"drop table if exists alter_sc_insert_complex")
    sql(s"drop table if exists alter_sc_range_column")
    sql(s"drop table if exists alter_sc_range_column_base")
    sql(s"drop table if exists alter_sc_add_column")
    sql(s"drop table if exists alter_sc_add_column_base")
    sql(s"drop table if exists alter_sc_bloom")
    sql(s"drop table if exists alter_sc_bloom_base")
    sql(s"drop table if exists alter_sc_agg")
    sql(s"drop table if exists alter_sc_agg_base")
    sql(s"drop table if exists alter_sc_cli")
  }

  private def createTable(
      tableName: String,
      tblProperties: Map[String, String] = Map.empty,
      withComplex: Boolean = false
  ): Unit = {
    val complexSql =
      if (withComplex) {
        ", arrayField array<string>, structField struct<col1:string, col2:string, col3:string>"
      } else {
        ""
      }
    val tblPropertiesSql =
      if (tblProperties.isEmpty) {
        ""
      } else {
        val propertiesString =
          tblProperties
            .map { entry =>
              s"'${ entry._1 }'='${ entry._2 }'"
            }
            .mkString(",")
        s"tblproperties($propertiesString)"
      }

    sql(
      s"""create table $tableName(
         | smallIntField smallInt,
         | intField int,
         | bigIntField bigint,
         | floatField float,
         | doubleField double,
         | timestampField timestamp,
         | dateField date,
         | stringField string,
         | varcharField varchar(10),
         | charField char(10)
         | $complexSql
         | )
         | stored as carbondata
         | $tblPropertiesSql
      """.stripMargin)
    // decimalField decimal(25, 4),
  }

  private def createBloomIndex(tableName: String, indexName: String): Unit = {
    sql(
      s"""
         | CREATE INDEX $indexName
         | ON TABLE $tableName (smallIntField,floatField,timestampField,dateField,stringField)
         | AS 'bloomfilter'
         | PROPERTIES(
         | 'BLOOM_SIZE'='6400',
         | 'BLOOM_FPP'='0.001',
         | 'BLOOM_COMPRESS'='TRUE')
       """.stripMargin)
  }

  private def loadData(tableNames: String*): Unit = {
    tableNames.foreach { tableName =>
      sql(
        s"""load data local inpath '$resourcesPath/sort_columns'
           | into table $tableName
           | options ('global_sort_partitions'='2', 'COMPLEX_DELIMITER_LEVEL_1'='$$',
           |  'COMPLEX_DELIMITER_LEVEL_2'=':')
      """.stripMargin)
    }
  }

  private def insertData(insertTable: String, tableNames: String*): Unit = {
    tableNames.foreach { tableName =>
      sql(
        s"""insert into table $tableName select * from $insertTable
      """.stripMargin)
    }
  }

  test("validate sort_scope and sort_columns") {
    // invalid combination
    var ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_scope'='local_sort')")
    }
    assert(ex.getMessage.contains(
      "Cannot set SORT_SCOPE as local_sort when table has no SORT_COLUMNS"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_scope'='global_sort')")
    }
    assert(ex.getMessage.contains(
      "Cannot set SORT_SCOPE as global_sort when table has no SORT_COLUMNS"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate " +
          "set tblproperties('sort_scope'='local_sort', 'sort_columns'='')")
    }
    assert(ex.getMessage.contains(
      "Cannot set SORT_COLUMNS as empty when setting SORT_SCOPE as local_sort"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate " +
          "set tblproperties('sort_scope'='global_sort', 'sort_columns'=' ')")
    }
    assert(ex.getMessage.contains(
      "Cannot set SORT_COLUMNS as empty when setting SORT_SCOPE as global_sort"))

    sql("alter table alter_sc_validate " +
        "set tblproperties('sort_columns'='stringField', 'sort_scope'='local_sort')")
    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_columns'=' ')")
    }
    assert(ex.getMessage.contains("Cannot set SORT_COLUMNS as empty when SORT_SCOPE is LOCAL_SORT"))

    sql("alter table alter_sc_validate set tblproperties('sort_scope'='global_sort')")
    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_columns'='')")
    }
    assert(ex.getMessage.contains(
      "Cannot set SORT_COLUMNS as empty when SORT_SCOPE is GLOBAL_SORT"))

    // wrong/duplicate sort_columns
    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate " +
          "set tblproperties('sort_columns'=' stringField1 , intField')")
    }
    assert(ex.getMessage.contains("stringField1 does not exist in table"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate " +
          "set tblproperties('sort_columns'=' stringField1 , intField, stringField1')")
    }
    assert(ex.getMessage.contains(
      "SORT_COLUMNS Either contains duplicate columns : " +
      "stringfield1 or it contains an illegal argument"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate " +
          "set tblproperties('sort_columns'=' stringField , intField, stringField')")
    }
    assert(ex.getMessage.contains(
      "SORT_COLUMNS Either contains duplicate columns : " +
      "stringfield or it contains an illegal argument"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate " +
          "set tblproperties('sort_columns'=' stringField , intField, stringFIELD')")
    }
    assert(ex.getMessage.contains(
      "SORT_COLUMNS Either contains duplicate columns : " +
      "stringfield or it contains an illegal argument"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_columns'='doubleField')")
    }
    assert(ex.getMessage.contains(
      "sort_columns is unsupported for DOUBLE datatype column: doubleField"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_columns'='arrayField')")
    }
    assert(ex.getMessage.contains(
      "sort_columns is unsupported for ARRAY datatype column: arrayField"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_columns'='structField')")
    }
    assert(ex.getMessage.contains(
      "sort_columns is unsupported for STRUCT datatype column: structField"))

    ex = intercept[RuntimeException] {
      sql("alter table alter_sc_validate set tblproperties('sort_columns'='structField.col1')")
    }
    assert(ex.getMessage.contains("sort_columns: structField.col1 does not exist in table"))
  }

  test("long string column") {
    val ex = intercept[RuntimeException] {
      sql("alter table alter_sc_long_string " +
          "set tblproperties('sort_columns'='intField, stringField')")
    }
    assert(ex.getMessage.contains(
      "sort_columns is unsupported for long string datatype column: stringField"))
  }

  test("describe formatted") {
    // valid combination
    sql("alter table alter_sc_validate " +
        "set tblproperties('sort_scope'='no_sort', 'sort_columns'='')")
    checkExistence(sql("describe formatted alter_sc_validate"), true, "NO_SORT")

    sql("alter table alter_sc_validate " +
        "set tblproperties('sort_scope'='no_sort', 'sort_columns'='bigIntField,stringField')")
    checkExistence(sql("describe formatted alter_sc_validate"),
      true,
      "no_sort",
      "bigIntField, stringField".toLowerCase())

    sql("alter table alter_sc_validate " +
        "set tblproperties('sort_scope'='local_sort', 'sort_columns'='stringField,bigIntField')")
    checkExistence(sql("describe formatted alter_sc_validate"),
      true,
      "local_sort",
      "stringField, bigIntField".toLowerCase())

    // global dictionary or direct dictionary
    sql("alter table alter_sc_validate set tblproperties('sort_scope'='global_sort', " +
        "'sort_columns'=' charField , bigIntField , timestampField ')")
    checkExistence(sql("describe formatted alter_sc_validate"),
      true,
      "global_sort",
      "charField, bigIntField, timestampField".toLowerCase())

    // supported data type
    sql("alter table alter_sc_validate set tblproperties('sort_scope'='local_sort', " +
        "'sort_columns'='smallIntField, intField, bigIntField, timestampField, dateField, " +
        "stringField, varcharField, charField')")
    checkExistence(sql("describe formatted alter_sc_validate"),
      true,
      "local_sort",
      ("smallIntField, intField, bigIntField, timestampField, dateField, stringField, " +
       "varcharField, charField")
        .toLowerCase())
  }

  test("IUD and Query") {
    testIUDAndQuery("alter_sc_iud", "alter_sc_base", "alter_sc_insert")
  }

  test("IUD and Query with complex data type") {
    testIUDAndQuery("alter_sc_iud_complex", "alter_sc_base_complex", "alter_sc_insert_complex")
  }

  private def testIUDAndQuery(tableName: String,
      baseTableName: String,
      insertTableName: String): Unit = {
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // alter table to change SORT_SCOPE and SORT_COLUMNS
    sql(s"alter table $tableName " +
        s"set tblproperties('sort_scope'='global_sort', 'sort_columns'='charField')")
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // alter table to local_sort with new SORT_COLUMNS
    sql(s"alter table $tableName set tblproperties('sort_scope'='local_sort', " +
        s"'sort_columns'='timestampField, intField, stringField')")
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // alter table to revert SORT_COLUMNS
    sql(s"alter table $tableName " +
        s"set tblproperties('sort_columns'='stringField, intField, timestampField')")
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // alter table to change SORT_COLUMNS
    sql(s"alter table $tableName set tblproperties('sort_columns'='intField, stringField')")
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // alter table to change SORT_SCOPE
    sql(s"alter table $tableName " +
        s"set tblproperties('sort_scope'='local_sort', 'sort_columns'='charField, smallIntField')")
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // set input segments
    (0 to 5).foreach { segment =>
      sql(s"set carbon.input.segments.default.$tableName=$segment").collect()
      sql(s"set carbon.input.segments.default.$baseTableName=$segment").collect()
      checkAnswer(sql(s"select count(*) from $tableName"),
        sql(s"select count(*) from $baseTableName"))
      checkAnswer(sql(s"select * from $tableName order by floatField"),
        sql(s"select * from $baseTableName order by floatField"))
      checkAnswer(sql(s"select * from $tableName where smallIntField = 2 order by floatField"),
        sql(s"select * from $baseTableName where smallIntField = 2 order by floatField"))
      checkAnswer(sql(s"select * from $tableName where intField >= 2 order by floatField"),
        sql(s"select * from $baseTableName where intField >= 2 order by floatField"))
      checkAnswer(
        sql(s"select * from $tableName " +
            "where smallIntField = 2 or intField >= 2 order by floatField"),
        sql(s"select * from $baseTableName " +
            "where smallIntField = 2 or intField >= 2 order by floatField"))
    }
    sql(s"set carbon.input.segments.default.$tableName=*").collect()
    sql(s"set carbon.input.segments.default.$baseTableName=*").collect()
    sqlContext.sparkSession.conf.unset(s"carbon.input.segments.default.$tableName")
    sqlContext.sparkSession.conf.unset(s"carbon.input.segments.default.$baseTableName")

    // query
    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))
    checkAnswer(sql(s"select * from $tableName where smallIntField = 2 order by floatField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField"))
    checkAnswer(sql(s"select * from $tableName where intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName where intField >= 2 order by floatField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          "where smallIntField = 2 or intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName " +
          "where smallIntField = 2 or intField >= 2 order by floatField"))

    // delete
    sql(s"delete from $tableName where smallIntField = 2")
    sql(s"delete from $baseTableName where smallIntField = 2")
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // compaction for column drift
    sql(s"alter table $tableName set tblproperties('sort_scope'='local_sort', " +
        "'sort_columns'='charField, intField')")
    // scalastyle:off lineLength
    // [Segment info]:
    //   | sorted | dimension order(sort_columns is in [])                                                     | measure order
    // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    // 0 | false  | timestampField, dateField, stringField, varcharField, charField                            | smallIntField, intField, bigIntField, floatField, doubleField
    // 1 | true   | [charField], timestampField, dateField, stringField, varcharField                          | smallIntField, intField, bigIntField, floatField, doubleField
    // 2 | false  | [timestampField, intField, stringField], charField, dateField, varcharField                | smallIntField, bigIntField, floatField, doubleField
    // 3 | false  | [stringField, intField, timestampField], charField, dateField, varcharField                | smallIntField, bigIntField, floatField, doubleField
    // 4 | false  | [intField, stringField], timestampField, charField, dateField, varcharField                | smallIntField, bigIntField, floatField, doubleField
    // 5 | true   | [charField, smallIntField], intField, stringField, timestampField, dateField, varcharField | bigIntField, floatField, doubleField
    // Column drift happened, intField and smallIntField became dimension.
    // The order of columns also changed.
    //
    // [Table info]:
    //            | dimension order(sort_columns is in [])                                                     | measure order
    // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    // table      | [charField], smallIntField, intField, stringField, timestampField, dateField, varcharField | bigIntField, floatField, doubleField
    // scalastyle:on lineLength
    sql(s"alter table $tableName compact 'minor'")
    sql(s"alter table $baseTableName compact 'minor'")
    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))
    checkAnswer(sql(s"select * from $tableName where smallIntField = 2 order by floatField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField"))
    checkAnswer(sql(s"select * from $tableName where intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName where intField >= 2 order by floatField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          "where smallIntField = 2 or intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName " +
          "where smallIntField = 2 or intField >= 2 order by floatField"))

    sql(s"delete from $tableName")
    checkAnswer(sql(s"select count(*) from $tableName"), Seq(Row(0)))
    sql(s"delete from $baseTableName")
    checkAnswer(sql(s"select count(*) from $baseTableName"), Seq(Row(0)))

    // insert & load data
    sql(s"alter table $tableName " +
        s"set tblproperties('sort_scope'='global_sort', 'sort_columns'='timestampField')")
    insertData(insertTableName, tableName, baseTableName)
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    sql(s"alter table $tableName set tblproperties('sort_scope'='no_sort', 'sort_columns'='')")
    insertData(insertTableName, tableName, baseTableName)
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    sql(s"alter table $tableName set tblproperties('sort_scope'='local_sort', " +
        s"'sort_columns'='charField, bigIntField, smallIntField')")
    insertData(insertTableName, tableName, baseTableName)
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // update
    sql(s"update $tableName " +
        "set (smallIntField, intField, bigIntField, floatField, doubleField) = " +
        "(smallIntField + 3, intField + 3, bigIntField + 3, floatField + 3, doubleField + 3) " +
        "where smallIntField = 2").collect()
    sql(s"update $baseTableName " +
        s"set (smallIntField, intField, bigIntField, floatField, doubleField) = " +
        s"(smallIntField + 3, intField + 3, bigIntField + 3, floatField + 3, doubleField + 3) " +
        s"where smallIntField = 2").collect()
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))

    // query
    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName where smallIntField = 2 order by floatField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField"))
    checkAnswer(sql(s"select * from $tableName where intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName where intField >= 2 order by floatField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          "where smallIntField = 2 or intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName " +
          "where smallIntField = 2 or intField >= 2 order by floatField"))

    // set input segments
    (6 to 11).foreach { segment =>
      sql(s"set carbon.input.segments.default.$tableName=$segment").collect()
      sql(s"set carbon.input.segments.default.$baseTableName=$segment").collect()
      checkAnswer(sql(s"select * from $tableName where smallIntField = 2 order by floatField"),
        sql(s"select * from $baseTableName where smallIntField = 2 order by floatField"))
    }
    sql(s"set carbon.input.segments.default.$tableName=*").collect()
    sql(s"set carbon.input.segments.default.$baseTableName=*").collect()
    sqlContext.sparkSession.conf.unset(s"carbon.input.segments.default.$tableName")
    sqlContext.sparkSession.conf.unset(s"carbon.input.segments.default.$baseTableName")

    // no_sort compaction flow for column drift
    sql(s"alter table $tableName " +
        s"set tblproperties('sort_scope'='no_sort', 'sort_columns'='charField, intField')")
    // sort_scope become no_sort
    sql(s"alter table $tableName compact 'minor'")
    sql(s"alter table $baseTableName compact 'minor'")
    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))
    checkAnswer(sql(s"select * from $tableName where smallIntField = 2 order by floatField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField"))
    checkAnswer(sql(s"select * from $tableName where intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName where intField >= 2 order by floatField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          s"where smallIntField = 2 or intField >= 2 order by floatField"),
      sql(s"select * from $baseTableName " +
          s"where smallIntField = 2 or intField >= 2 order by floatField"))
  }

  test("range column") {
    val tableName = "alter_sc_range_column"
    val baseTableName = "alter_sc_range_column_base"
    loadData(tableName, baseTableName)
    sql(s"alter table $tableName " +
        s"set tblproperties('sort_scope'='local_sort', 'sort_columns'='smallIntField, charField')")
    loadData(tableName, baseTableName)

    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName order by floatField"),
      sql(s"select * from $baseTableName order by floatField"))
    checkAnswer(sql(s"select * from $tableName where smallIntField = 2 order by floatField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField"))
  }

  test("add/drop column for sort_columns") {
    val tableName = "alter_sc_add_column"
    val baseTableName = "alter_sc_add_column_base"
    loadData(tableName, baseTableName)
    sql(s"alter table $tableName set tblproperties('sort_scope'='local_sort', " +
        s"'sort_columns'='smallIntField, stringField')")
    loadData(tableName, baseTableName)
    // add column
    sql(s"alter table $tableName add columns( varcharField varchar(10), charField char(10))")
    sql(s"alter table $baseTableName add columns( varcharField varchar(10), charField char(10))")
    loadData(tableName, baseTableName)

    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName order by floatField, charField"),
      sql(s"select * from $baseTableName order by floatField, charField"))
    checkAnswer(
      sql(s"select * from $tableName where smallIntField = 2 order by floatField, charField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField, charField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          s"where smallIntField = 2 and charField is null order by floatField"),
      sql(s"select * from $baseTableName " +
          s"where smallIntField = 2 and charField is null order by floatField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          s"where smallIntField = 2 and charField is not null order by floatField"),
      sql(s"select * from $baseTableName " +
          s"where smallIntField = 2 and charField is not null order by floatField"))

    // add new column to sort_columns
    sql(s"alter table $tableName set tblproperties('sort_scope'='local_sort', " +
        s"'sort_columns'='smallIntField, charField')")
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName order by floatField, charField"),
      sql(s"select * from $baseTableName order by floatField, charField"))
    checkAnswer(
      sql(s"select * from $tableName where smallIntField = 2 order by floatField, charField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField, charField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          "where smallIntField = 2 and charField is null order by floatField"),
      sql(s"select * from $baseTableName " +
          "where smallIntField = 2 and charField is null order by floatField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          "where smallIntField = 2 and charField is not null order by floatField"),
      sql(s"select * from $baseTableName " +
          "where smallIntField = 2 and charField is not null order by floatField"))

    // drop column of old sort_columns
    sql(s"alter table $tableName drop columns(stringField)")
    sql(s"alter table $baseTableName drop columns(stringField)")
    loadData(tableName, baseTableName)
    checkAnswer(sql(s"select count(*) from $tableName"),
      sql(s"select count(*) from $baseTableName"))
    checkAnswer(sql(s"select * from $tableName order by floatField, charField"),
      sql(s"select * from $baseTableName order by floatField, charField"))
    checkAnswer(
      sql(s"select * from $tableName where smallIntField = 2 order by floatField, charField"),
      sql(s"select * from $baseTableName where smallIntField = 2 order by floatField, charField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          "where smallIntField = 2 and charField is null order by floatField"),
      sql(s"select * from $baseTableName " +
          "where smallIntField = 2 and charField is null order by floatField"))
    checkAnswer(
      sql(s"select * from $tableName " +
          "where smallIntField = 2 and charField is not null order by floatField"),
      sql(s"select * from $baseTableName " +
          "where smallIntField = 2 and charField is not null order by floatField"))
  }

  // Exclude when running with index server, as pruning info for explain command
  // not set with index server.
  test("bloom filter", true) {
    CarbonProperties.getInstance()
      .addProperty(CarbonCommonConstants.ENABLE_QUERY_STATISTICS, "true")
    val tableName = "alter_sc_bloom"
    val indexName = "alter_sc_bloom_dm1"
    val baseTableName = "alter_sc_bloom_base"
    loadData(tableName, baseTableName)
    checkExistence(sql(s"SHOW INDEXES ON TABLE $tableName"), true, "bloomfilter", indexName)
    checkExistence(sql(s"EXPLAIN SELECT * FROM $tableName WHERE smallIntField = 3"),
      true,
      "bloomfilter",
      indexName)
    checkAnswer(sql(s"select * from $tableName where smallIntField = 3 order by floatField"),
      sql(s"select * from $baseTableName where smallIntField = 3 order by floatField"))

    sql(s"alter table $tableName set tblproperties('sort_scope'='global_sort', " +
        s"'sort_columns'='smallIntField, charField')")
    loadData(tableName, baseTableName)
    checkExistence(sql(s"EXPLAIN SELECT * FROM $tableName WHERE smallIntField = 3"),
      true,
      "bloomfilter",
      indexName)
    checkAnswer(sql(s"select * from $tableName where smallIntField = 3 order by floatField"),
      sql(s"select * from $baseTableName where smallIntField = 3 order by floatField"))
  }

  test("carboncli -cmd sort_columns -p <segment folder>") {
    val tableName = "alter_sc_cli"
    // no_sort
    loadData(tableName)
    sql(s"alter table $tableName set tblproperties('sort_scope'='global_sort', " +
        s"'sort_columns'='charField, timestampField')")
    // global_sort
    loadData(tableName)
    sql(s"alter table $tableName set tblproperties('sort_scope'='local_sort', " +
        s"'sort_columns'='intField, stringField')")
    // local_sort
    loadData(tableName)
    // update table to generate one more index in each segment
    sql(s"update $tableName " +
        s"set (smallIntField, intField, bigIntField, floatField, doubleField) = " +
        s"(smallIntField + 3, intField + 3, bigIntField + 3, floatField + 3, doubleField + 3) " +
        s"where smallIntField = 2").collect()

    val table = CarbonEnv.getCarbonTable(Option("default"), tableName)(sqlContext.sparkSession)
    val tablePath = table.getTablePath
    (0 to 3).foreach { segmentId =>
      val segmentPath = CarbonTablePath.getSegmentPath(tablePath, segmentId.toString)
      val args: Array[String] = Array("-cmd", "sort_columns", "-p", segmentPath)
      val out: ByteArrayOutputStream = new ByteArrayOutputStream
      val stream: PrintStream = new PrintStream(out)
      CarbonCli.run(args, stream)
      val output: String = new String(out.toByteArray)
      if (segmentId == 3 || segmentId == 2) {
        assertResult(s"Input Folder: $segmentPath\nsorted by intfield,stringfield\n")(output)
      } else if ( segmentId == 1) {
        assertResult(s"Input Folder: $segmentPath\nsorted by charfield,timestampfield\n")(output)
      } else {
        assertResult(s"Input Folder: $segmentPath\nunsorted\n")(output)
      }
    }
  }
}
